degu 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,61 @@
1
+ require 'degu/renum/enumerated_value'
2
+
3
+ module Degu
4
+ module Renum
5
+ module EnumeratedValueTypeFactory
6
+ class << self
7
+ def create nest, type_name, values, &block
8
+ klass = create_class nest, type_name
9
+ create_values klass, values, &block
10
+ end
11
+
12
+ def create_class nest, type_name
13
+ klass = Class.new EnumeratedValue
14
+ nest.const_set(type_name, klass)
15
+ klass
16
+ end
17
+
18
+ def create_values klass, values, &block
19
+ setup_for_definition_in_block(klass) if values == :defined_in_block
20
+ klass.class_eval &block if block_given?
21
+ if values == :defined_in_block
22
+ begin
23
+ klass.block_defined_values.each do |value_name, init_args, instance_block|
24
+ value = klass.new(value_name)
25
+ klass.const_set(value_name, value)
26
+ value.instance_eval &instance_block if instance_block
27
+ value.init *init_args if value.respond_to? :init
28
+ end
29
+ ensure
30
+ teardown_from_definition_in_block(klass)
31
+ end
32
+ else
33
+ values.each do |name|
34
+ klass.const_set(name, klass.new(name))
35
+ end
36
+ end
37
+ klass.values.freeze
38
+ end
39
+
40
+ def setup_for_definition_in_block klass
41
+ klass.class_eval do
42
+ def self.block_defined_values
43
+ @block_defined_values ||= []
44
+ end
45
+
46
+ def self.method_missing value_name, *init_args, &instance_block
47
+ block_defined_values << [value_name, init_args, instance_block]
48
+ end
49
+ end
50
+ end
51
+
52
+ def teardown_from_definition_in_block klass
53
+ class << klass
54
+ remove_method :block_defined_values
55
+ remove_method :method_missing
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
data/lib/degu/renum.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'degu/renum/enumerated_value_type_factory'
2
+
3
+ module Degu
4
+ # Requiring 'renum' mixes the Renum module into both the main Object and
5
+ # Module, so it can be called from anywhere that you might reasonably
6
+ # define an enumeration with an implicit receiver.
7
+ module Renum
8
+
9
+ # Figures out whether the new enumeration will live in Object or the
10
+ # receiving Module, then delegates to EnumeratedValueTypeFactory#create for
11
+ # all the real work.
12
+ def enum(type_name, values = :defined_in_block, &block)
13
+ nest = self.is_a?(Module) ? self : Object
14
+ EnumeratedValueTypeFactory.create(nest, type_name, values, &block)
15
+ end
16
+ end
17
+ end
data/lib/degu/rude.rb ADDED
@@ -0,0 +1,12 @@
1
+ module Degu
2
+ end
3
+ require 'degu/polite'
4
+ class Object
5
+ include Degu::Renum
6
+ end
7
+ if defined?(ActiveRecord::Base)
8
+ class ActiveRecord::Base
9
+ include Degu::HasEnum
10
+ include Degu::HasSet
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+ module Degu
2
+ # Degu version
3
+ VERSION = '0.0.4'
4
+ VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc:
5
+ VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
+ VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
7
+ VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
8
+ end
data/lib/degu.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Degu
2
+ end
3
+ require 'degu/rude'
@@ -0,0 +1,281 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ enum :Status, [ :NOT_STARTED, :IN_PROGRESS, :COMPLETE ]
4
+
5
+ enum :Fuzzy, [ :FooBar, :BarFoo ]
6
+
7
+ describe "basic enum" do
8
+
9
+ it "creates a class for the value type" do
10
+ Status.should be_an_instance_of(Class)
11
+ end
12
+
13
+ it "makes each value an instance of the value type" do
14
+ Status::NOT_STARTED.should be_an_instance_of(Status)
15
+ end
16
+
17
+ it "exposes array of values" do
18
+ Status.values.should == [Status::NOT_STARTED, Status::IN_PROGRESS, Status::COMPLETE]
19
+ Status.all.should == [Status::NOT_STARTED, Status::IN_PROGRESS, Status::COMPLETE]
20
+ end
21
+
22
+ it "groks first and last to retreive value" do
23
+ Status.first.should == Status::NOT_STARTED
24
+ Status.last.should == Status::COMPLETE
25
+ end
26
+
27
+ it "enumerates over values" do
28
+ Status.map {|s| s.name}.should == %w[NOT_STARTED IN_PROGRESS COMPLETE]
29
+ end
30
+
31
+ it "indexes values" do
32
+ Status[2].should == Status::COMPLETE
33
+ Color[0].should == Color::RED
34
+ Status['2'].should == Status::COMPLETE
35
+ Color['0'].should == Color::RED
36
+ end
37
+
38
+ it "provides index lookup on values" do
39
+ Status::IN_PROGRESS.index.should == 1
40
+ Color::GREEN.index.should == 1
41
+ end
42
+
43
+ it "provides an id on values" do
44
+ Status::IN_PROGRESS.id.should == 1
45
+ Color::GREEN.id.should == 1
46
+ end
47
+
48
+
49
+ it "provides name lookup on values" do
50
+ Status.with_name('IN_PROGRESS').should == Status::IN_PROGRESS
51
+ Color.with_name('GREEN').should == Color::GREEN
52
+ Color.with_name('IN_PROGRESS').should be_nil
53
+ end
54
+
55
+ it "provides fuzzy name lookup on values" do
56
+ Fuzzy[0].should == Fuzzy::FooBar
57
+ Fuzzy[1].should == Fuzzy::BarFoo
58
+ Fuzzy[:FooBar].should == Fuzzy::FooBar
59
+ Fuzzy[:BarFoo].should == Fuzzy::BarFoo
60
+ Fuzzy['FooBar'].should == Fuzzy::FooBar
61
+ Fuzzy['BarFoo'].should == Fuzzy::BarFoo
62
+ Fuzzy[:foo_bar].should == Fuzzy::FooBar
63
+ Fuzzy[:bar_foo].should == Fuzzy::BarFoo
64
+ Fuzzy['foo_bar'].should == Fuzzy::FooBar
65
+ Fuzzy['bar_foo'].should == Fuzzy::BarFoo
66
+ Fuzzy[Fuzzy::FooBar].should == Fuzzy::FooBar
67
+ Fuzzy[Fuzzy::BarFoo].should == Fuzzy::BarFoo
68
+ end
69
+
70
+ it "provides a reasonable to_s for values" do
71
+ Status::NOT_STARTED.to_s.should == "Status::NOT_STARTED"
72
+ end
73
+
74
+ it "makes values comparable" do
75
+ Color::RED.should < Color::GREEN
76
+ end
77
+ end
78
+
79
+ module MyNamespace
80
+ enum :FooValue, %w( Bar Baz Bat )
81
+ end
82
+
83
+ describe "nested enum" do
84
+ it "is namespaced in the containing module or class" do
85
+ MyNamespace::FooValue::Bar.class.should == MyNamespace::FooValue
86
+ end
87
+ end
88
+
89
+ enum :Color, [ :RED, :GREEN, :BLUE ] do
90
+ def abbr
91
+ name[0..0]
92
+ end
93
+ end
94
+
95
+ describe "enum with a block" do
96
+ it "can define additional instance methods" do
97
+ Color::RED.abbr.should == "R"
98
+ end
99
+ end
100
+
101
+ enum :Size do
102
+ Small("Really really tiny")
103
+ Medium("Sort of in the middle")
104
+ Large("Quite big")
105
+ Unknown()
106
+
107
+ attr_reader :description
108
+
109
+ def init description = nil
110
+ @description = description || "NO DESCRIPTION GIVEN"
111
+ end
112
+ end
113
+
114
+ enum :HairColor do
115
+ BLONDE()
116
+ BRUNETTE()
117
+ RED()
118
+ end
119
+
120
+ describe "enum with no values array and values declared in the block" do
121
+ it "provides another way to declare values where an init method can take extra params" do
122
+ Size::Small.description.should == "Really really tiny"
123
+ end
124
+
125
+ it "works the same as the basic form with respect to ordering" do
126
+ Size.values.should == [Size::Small, Size::Medium, Size::Large, Size::Unknown]
127
+ end
128
+
129
+ it "responds as expected to arbitrary method calls, in spite of using method_missing for value definition" do
130
+ lambda { Size.ExtraLarge() }.should raise_error(NoMethodError)
131
+ end
132
+
133
+ it "supports there being no extra data and no init() method defined, if you don't need them" do
134
+ HairColor::BLONDE.name.should == "BLONDE"
135
+ end
136
+
137
+ it "calls the init method even if no arguments are provided" do
138
+ Size::Unknown.description.should == "NO DESCRIPTION GIVEN"
139
+ end
140
+ end
141
+
142
+ enum :Rating do
143
+ NotRated()
144
+
145
+ ThumbsDown do
146
+ def description
147
+ "real real bad"
148
+ end
149
+ end
150
+
151
+ ThumbsUp do
152
+ def description
153
+ "so so good"
154
+ end
155
+
156
+ def thumbs_up_only_method
157
+ "this method is only defined on ThumbsUp"
158
+ end
159
+ end
160
+
161
+ def description
162
+ raise NotImplementedError
163
+ end
164
+ end
165
+
166
+ describe "an enum with instance-specific method definitions" do
167
+ it "allows each instance to have its own behavior" do
168
+ Rating::ThumbsDown.description.should == "real real bad"
169
+ Rating::ThumbsUp.description.should == "so so good"
170
+ end
171
+
172
+ it "uses the implementation given at the top level if no alternate definition is given for an instance" do
173
+ lambda { Rating::NotRated.description }.should raise_error(NotImplementedError)
174
+ end
175
+
176
+ it "allows definition of a method on just one instance" do
177
+ Rating::ThumbsUp.thumbs_up_only_method.should == "this method is only defined on ThumbsUp"
178
+ lambda { Rating::NotRated.thumbs_up_only_method }.should raise_error(NoMethodError)
179
+ end
180
+ end
181
+
182
+ describe "<=> comparison issue that at one time was causing segfaults on MRI" do
183
+ it "doesn't cause the ruby process to bomb!" do
184
+ Color::RED.should < Color::GREEN
185
+ Color::RED.should_not > Color::GREEN
186
+ Color::RED.should < Color::BLUE
187
+ end
188
+ end
189
+
190
+ modify_frozen_error =
191
+ begin
192
+ [].freeze << true
193
+ rescue => e
194
+ e.class
195
+ end
196
+
197
+ describe "prevention of subtle and annoying bugs" do
198
+ it "prevents you modifying the values array" do
199
+ lambda { Color.values << 'some crazy value' }.should raise_error(modify_frozen_error, /can't modify frozen/)
200
+ end
201
+
202
+ it "prevents you modifying the name hash" do
203
+ lambda { Color.values_by_name['MAGENTA'] = 'some crazy value' }.should raise_error(modify_frozen_error, /can't modify frozen/)
204
+ end
205
+
206
+ it "prevents you modifying the name of a value" do
207
+ lambda { Color::RED.name << 'dish-Brown' }.should raise_error(modify_frozen_error, /can't modify frozen/)
208
+ end
209
+ end
210
+
211
+ enum :Foo1 do
212
+ field :foo
213
+ field :bar, :default => 'my bar'
214
+ field :baz do |obj|
215
+ obj.__id__
216
+ end
217
+
218
+ Baz(
219
+ :foo => 'my foo'
220
+ )
221
+ end
222
+
223
+ enum :Foo2 do
224
+ field :foo
225
+ field :bar, :default => 'my bar'
226
+ field :baz do |obj|
227
+ obj.__id__
228
+ end
229
+
230
+
231
+ Baz(
232
+ :foo => 'my foo'
233
+ )
234
+
235
+ def init(opts = {})
236
+ super
237
+ end
238
+ end
239
+
240
+ describe "use field method to specify methods and defaults" do
241
+ it "should define methods with defaults for fields" do
242
+ Foo1::Baz.foo.should == "my foo"
243
+ Foo2::Baz.foo.should == "my foo"
244
+ Foo1::Baz.bar.should == "my bar"
245
+ Foo2::Baz.bar.should == "my bar"
246
+ Foo1::Baz.baz.should == Foo1::Baz.__id__
247
+ Foo2::Baz.baz.should == Foo2::Baz.__id__
248
+ end
249
+ end
250
+
251
+ describe "serialize and deserialize via Marshal" do
252
+ it "should define methods with defaults for fields" do
253
+ Marshal.load(Marshal.dump(Status::NOT_STARTED)).should == Status::NOT_STARTED
254
+ end
255
+ end
256
+
257
+ if defined?(::JSON)
258
+ describe "serialize and deserialize via JSON" do
259
+ it "should define methods with defaults for fields" do
260
+ JSON(JSON(Status::NOT_STARTED)).should == Status::NOT_STARTED
261
+ end
262
+
263
+ it "should not serialize fields by default" do
264
+ foo1_json = JSON(Foo1::Baz)
265
+ foo1_hash = JSON.parse(foo1_json, :create_additions => false)
266
+ foo1_hash.keys.sort.should == %w[name json_class].sort
267
+ end
268
+
269
+ it "should serialize all fields if desired" do
270
+ foo1_json = Foo1::Baz.to_json(:fields => true)
271
+ foo1_hash = JSON.parse(foo1_json, :create_additions => false)
272
+ foo1_hash.keys.sort.should == %w[name json_class bar baz foo].sort
273
+ end
274
+
275
+ it "should serialize requested fields" do
276
+ foo1_json = Foo1::Baz.to_json(:fields => [ :bar, 'baz' ])
277
+ foo1_hash = JSON.parse(foo1_json, :create_additions => false)
278
+ foo1_hash.keys.sort.should == %w[name json_class bar baz].sort
279
+ end
280
+ end
281
+ end
@@ -0,0 +1,3 @@
1
+ require 'rspec'
2
+
3
+ require 'degu'
@@ -0,0 +1,184 @@
1
+ require 'test_helper'
2
+
3
+ class HasEnumTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ setup_db
7
+ end
8
+
9
+ def teardown
10
+ teardown_db
11
+ end
12
+
13
+ def test_should_have_enum
14
+ assert ClassWithEnum.respond_to?(:has_enum)
15
+ product_enum = ClassWithEnum.new(:product => Product::Silver)
16
+ assert_equal Product::Silver, product_enum.product
17
+ end
18
+
19
+ def test_should_have_enum_column_value_set_to_enum_name
20
+ product_enum = ClassWithEnum.new
21
+ product_enum.product = Product::Gold
22
+ assert_equal Product::Gold.name, product_enum.product_type
23
+ end
24
+
25
+ def test_should_be_nullable
26
+ product_enum = ClassWithEnum.new
27
+ product_enum.product = Product::Silver
28
+ product_enum.reset_enum_changed
29
+ assert_equal false, product_enum.product_has_changed?
30
+ product_enum.product = nil
31
+ assert_nil product_enum.product
32
+ assert_equal true, product_enum.product_has_changed?
33
+ end
34
+
35
+ def test_should_be_nil_if_spaces_string
36
+ product_enum = ClassWithEnum.new
37
+ product_enum.product = Product::Silver
38
+ product_enum.reset_enum_changed
39
+ assert_equal false, product_enum.product_has_changed?
40
+ product_enum.product = " "
41
+ assert_nil product_enum.product
42
+ assert_equal true, product_enum.product_has_changed?
43
+ end
44
+
45
+ def test_should_be_setable_by_string
46
+ product_enum = ClassWithEnum.new
47
+ product_enum.product = Product::Silver
48
+ product_enum.reset_enum_changed
49
+ assert_equal false, product_enum.product_has_changed?
50
+ product_enum.product = 'Gold'
51
+ assert_equal Product::Gold, product_enum.product
52
+ assert_equal true, product_enum.product_has_changed?
53
+ end
54
+
55
+ def test_should_be_setable_by_symbol
56
+ product_enum = ClassWithEnum.new
57
+ product_enum.product = Product::Silver
58
+ product_enum.reset_enum_changed
59
+ assert_equal false, product_enum.product_has_changed?
60
+ product_enum.product = :gold
61
+ assert_equal Product::Gold, product_enum.product
62
+ assert_equal true, product_enum.product_has_changed?
63
+ end
64
+
65
+ def test_should_be_setable_by_number
66
+ product_enum = ClassWithEnum.new
67
+ product_enum.product = Product::Silver
68
+ product_enum.reset_enum_changed
69
+ assert_equal false, product_enum.product_has_changed?
70
+ product_enum.product = 1
71
+ assert_equal Product::Gold, product_enum.product
72
+ assert_equal true, product_enum.product_has_changed?
73
+ end
74
+
75
+ def test_should_accept_only_defined_enums
76
+ product_enum = ClassWithEnum.new
77
+
78
+ assert_raise(ArgumentError) { product_enum.product = Fakes::NOT_DEFINIED }
79
+ assert_raise(ArgumentError) { product_enum.product = "Product::Titanium" }
80
+ assert_raise(ArgumentError) { product_enum.product = Product }
81
+ assert_raise(ArgumentError) { product_enum.product = :symbol }
82
+ end
83
+
84
+ def test_should_not_set_enum_in_setter_if_new_enum_is_equal_to_current_enum
85
+ product_enum = ClassWithEnum.new(:product => Product::Silver)
86
+ assert product_enum.product=Product::Gold
87
+ end
88
+
89
+ def test_should_have_custom_column_name
90
+ enum_with_custom_column_name = ClassWithCustomNameEnum.new
91
+ enum_with_custom_column_name.product = Product::Gold
92
+ assert_equal Product::Gold, enum_with_custom_column_name.product
93
+ end
94
+
95
+ def test_should_mark_enum_as_changed_if_enum_column_was_set_directly
96
+ product_enum = ClassWithEnum.new
97
+ assert !product_enum.product_has_changed?
98
+ product_enum.product_type = "Gold"
99
+ assert product_enum.product_has_changed?
100
+ end
101
+
102
+ def test_should_only_change_enum_column_if_other_value_is_set
103
+ product_enum = ClassWithEnum.new
104
+ product_enum.product_type = "Gold"
105
+ product_enum.instance_variable_set("@enum_changed", false)
106
+ product_enum.product_type = "Gold"
107
+ assert !product_enum.product_has_changed?
108
+ end
109
+
110
+ def test_should_have_custom_column_value_set_to_enum_name
111
+ enum_with_custom_column_name = ClassWithCustomNameEnum.new
112
+ enum_with_custom_column_name.product = Product::Gold
113
+ assert_equal Product::Gold.name, enum_with_custom_column_name.product_enum
114
+ end
115
+
116
+ def test_should_raise_class_not_found_exception_if_enum_class_not_found
117
+ assert_raise(NameError) { ClassWithoutEnum.has_enum :foo }
118
+ end
119
+
120
+ def test_should_raise_argument_error_if_enum_is_no_renum_enum
121
+ assert_raise(ArgumentError) { ClassWithoutEnum.has_enum :array }
122
+ end
123
+
124
+ def test_should_know_if_enum_has_changed
125
+ product_enum = ClassWithEnum.new(:product => Product::Silver)
126
+ assert product_enum.product_has_changed?
127
+ product_enum.save
128
+ product_enum.reload
129
+ product_enum.product = Product::Gold
130
+ assert product_enum.product_has_changed?
131
+ end
132
+
133
+ def test_should_not_fail_if_no_enum_was_set_yet
134
+ enum_mixin = ClassWithEnum.new
135
+ assert_nothing_raised(TypeError) { enum_mixin.product }
136
+ end
137
+
138
+ def test_should_not_change_if_same_enum_was_assigned
139
+ enum_mixin = ClassWithEnum.new(:product => Product::Silver)
140
+ enum_mixin.save
141
+ enum_mixin.reload
142
+ enum_mixin.product = Product::Silver
143
+ assert !enum_mixin.product_has_changed?
144
+ end
145
+
146
+ def test_should_have_reset_changed_state_after_save
147
+ enum_mixin = ClassWithEnum.new(:product => Product::Silver)
148
+ enum_mixin.save
149
+ enum_mixin.reload
150
+ assert !enum_mixin.product_has_changed?
151
+ end
152
+
153
+ def test_should_not_be_able_to_set_invalid_enum
154
+ enum_mixin = ClassWithEnum.new
155
+ assert_raise(NameError) { enum_mixin.product = Product::Platin }
156
+ end
157
+
158
+ def test_should_not_be_able_to_set_invalid_enum_2
159
+ enum_mixin = ClassWithEnum.new
160
+ enum_mixin[:product_type] = "Platin"
161
+ enum_mixin.valid?
162
+ assert enum_mixin.errors[:product_type]
163
+ end
164
+
165
+ def test_should_not_return_validation_error_if_enum_is_nil
166
+ enum_mixin = ClassWithEnum.new
167
+ enum_mixin.valid?
168
+ assert_equal [], enum_mixin.errors[:product_type]
169
+ end
170
+
171
+ def test_should_return_nil_if_enum_is_of_wrong_type
172
+ enum_mixin = ClassWithEnum.new
173
+ enum_mixin[:product_type] = "Platin"
174
+ assert_nil enum_mixin.product
175
+ end
176
+
177
+ def test_should_execute_all_callbacks
178
+ enum_mixin = ClassWithEnum.new
179
+ enum_mixin.product = Product::Gold
180
+ assert enum_mixin.save
181
+ assert enum_mixin.callback1_executed
182
+ end
183
+
184
+ end