flex_columns 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.travis.yml +38 -0
  4. data/Gemfile +17 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +124 -0
  7. data/Rakefile +6 -0
  8. data/flex_columns.gemspec +72 -0
  9. data/lib/flex_columns.rb +15 -0
  10. data/lib/flex_columns/active_record/base.rb +57 -0
  11. data/lib/flex_columns/contents/column_data.rb +376 -0
  12. data/lib/flex_columns/contents/flex_column_contents_base.rb +188 -0
  13. data/lib/flex_columns/definition/field_definition.rb +316 -0
  14. data/lib/flex_columns/definition/field_set.rb +89 -0
  15. data/lib/flex_columns/definition/flex_column_contents_class.rb +327 -0
  16. data/lib/flex_columns/errors.rb +236 -0
  17. data/lib/flex_columns/has_flex_columns.rb +187 -0
  18. data/lib/flex_columns/including/include_flex_columns.rb +179 -0
  19. data/lib/flex_columns/util/dynamic_methods_module.rb +86 -0
  20. data/lib/flex_columns/util/string_utils.rb +31 -0
  21. data/lib/flex_columns/version.rb +4 -0
  22. data/spec/flex_columns/helpers/database_helper.rb +174 -0
  23. data/spec/flex_columns/helpers/exception_helpers.rb +20 -0
  24. data/spec/flex_columns/helpers/system_helpers.rb +47 -0
  25. data/spec/flex_columns/system/basic_system_spec.rb +245 -0
  26. data/spec/flex_columns/system/bulk_system_spec.rb +153 -0
  27. data/spec/flex_columns/system/compression_system_spec.rb +218 -0
  28. data/spec/flex_columns/system/custom_methods_system_spec.rb +120 -0
  29. data/spec/flex_columns/system/delegation_system_spec.rb +175 -0
  30. data/spec/flex_columns/system/dynamism_system_spec.rb +158 -0
  31. data/spec/flex_columns/system/error_handling_system_spec.rb +117 -0
  32. data/spec/flex_columns/system/including_system_spec.rb +285 -0
  33. data/spec/flex_columns/system/json_alias_system_spec.rb +171 -0
  34. data/spec/flex_columns/system/performance_system_spec.rb +218 -0
  35. data/spec/flex_columns/system/postgres_json_column_type_system_spec.rb +85 -0
  36. data/spec/flex_columns/system/types_system_spec.rb +93 -0
  37. data/spec/flex_columns/system/unknown_fields_system_spec.rb +126 -0
  38. data/spec/flex_columns/system/validations_system_spec.rb +111 -0
  39. data/spec/flex_columns/unit/active_record/base_spec.rb +32 -0
  40. data/spec/flex_columns/unit/contents/column_data_spec.rb +520 -0
  41. data/spec/flex_columns/unit/contents/flex_column_contents_base_spec.rb +253 -0
  42. data/spec/flex_columns/unit/definition/field_definition_spec.rb +617 -0
  43. data/spec/flex_columns/unit/definition/field_set_spec.rb +142 -0
  44. data/spec/flex_columns/unit/definition/flex_column_contents_class_spec.rb +733 -0
  45. data/spec/flex_columns/unit/errors_spec.rb +297 -0
  46. data/spec/flex_columns/unit/has_flex_columns_spec.rb +365 -0
  47. data/spec/flex_columns/unit/including/include_flex_columns_spec.rb +144 -0
  48. data/spec/flex_columns/unit/util/dynamic_methods_module_spec.rb +105 -0
  49. data/spec/flex_columns/unit/util/string_utils_spec.rb +23 -0
  50. metadata +286 -0
@@ -0,0 +1,253 @@
1
+ require 'flex_columns'
2
+ require 'flex_columns/helpers/exception_helpers'
3
+
4
+ describe FlexColumns::Contents::FlexColumnContentsBase do
5
+ include FlexColumns::Helpers::ExceptionHelpers
6
+
7
+ before :each do
8
+ @klass = Class.new(FlexColumns::Contents::FlexColumnContentsBase)
9
+ end
10
+
11
+ it "should include ActiveModel::Validations" do
12
+ @klass.included_modules.map(&:name).include?("ActiveModel::Validations").should be
13
+ end
14
+
15
+ it "should extend FlexColumnContentsClass" do
16
+ @klass.respond_to?(:is_flex_column_class?).should be
17
+ @klass.is_flex_column_class?.should be
18
+ end
19
+
20
+ context "with a set-up class" do
21
+ before :each do
22
+ @model_class = Class.new
23
+ allow(@klass).to receive(:model_class).with().and_return(@model_class)
24
+ allow(@klass).to receive(:column_name).with().and_return(:fcn)
25
+
26
+ @json_string = '{"foo":"bar","bar":"baz"}'
27
+
28
+ @model_instance = @model_class.new
29
+ allow(@model_instance).to receive(:[]).with(:fcn).and_return(@json_string)
30
+
31
+ @column_data = double("column_data")
32
+ end
33
+
34
+ def expect_column_data_creation(input)
35
+ expect(@klass).to receive(:_flex_columns_create_column_data).once do |*args|
36
+ args.length.should == 2
37
+ args[0].should == input
38
+ args[1].class.should be(@klass)
39
+
40
+ @column_data
41
+ end
42
+ end
43
+
44
+ describe "#initialize" do
45
+ it "should accept nil" do
46
+ expect_column_data_creation(nil)
47
+ @klass.new(nil)
48
+ end
49
+
50
+ it "should accept a String" do
51
+ source_string = " foo "
52
+ expect_column_data_creation(source_string)
53
+ @klass.new(source_string)
54
+ end
55
+
56
+ it "should accept a model of the right class" do
57
+ expect_column_data_creation(@json_string)
58
+ @klass.new(@model_instance)
59
+ end
60
+
61
+ it "should not accept a model of the wrong class" do
62
+ wrong_model_class = Class.new
63
+ allow(@model_class).to receive(:name).and_return("bongo")
64
+ wrong_model_instance = wrong_model_class.new
65
+ class << wrong_model_instance
66
+ def to_s
67
+ "whatevs"
68
+ end
69
+ def inspect
70
+ "whatevs"
71
+ end
72
+ end
73
+ e = capture_exception(ArgumentError) { @klass.new(wrong_model_instance) }
74
+ e.message.should match(/bongo/i)
75
+ e.message.should match(/whatevs/i)
76
+ end
77
+ end
78
+
79
+ describe "#describe_flex_column_data_source" do
80
+ it "should work with a model instance" do
81
+ expect_column_data_creation(@json_string)
82
+ @instance = @klass.new(@model_instance)
83
+
84
+ allow(@model_class).to receive(:name).with().and_return("mcname")
85
+ allow(@model_instance).to receive(:id).with().and_return("mcid")
86
+ s = @instance.describe_flex_column_data_source
87
+
88
+ s.should match(/mcname/)
89
+ s.should match(/mcid/)
90
+ s.should match(/fcn/)
91
+ end
92
+
93
+ it "should work with a raw string" do
94
+ expect_column_data_creation(@json_string)
95
+ @instance = @klass.new(@json_string)
96
+
97
+ allow(@model_class).to receive(:name).with().and_return("mcname")
98
+ s = @instance.describe_flex_column_data_source
99
+ s.should match(/mcname/)
100
+ s.should match(/fcn/)
101
+ end
102
+ end
103
+
104
+ describe "#notification_hash_for_flex_column_data_source" do
105
+ it "should work with a model instance" do
106
+ expect_column_data_creation(@json_string)
107
+ @instance = @klass.new(@model_instance)
108
+
109
+ h = @instance.notification_hash_for_flex_column_data_source
110
+ h.keys.sort_by(&:to_s).should == [ :model_class, :column_name, :model ].sort_by(&:to_s)
111
+ h[:model_class].should be(@model_class)
112
+ h[:column_name].should == :fcn
113
+ h[:model].should be(@model_instance)
114
+ end
115
+
116
+ it "should work with a raw string" do
117
+ expect_column_data_creation(@json_string)
118
+ @instance = @klass.new(@json_string)
119
+
120
+ h = @instance.notification_hash_for_flex_column_data_source
121
+ h.keys.sort_by(&:to_s).should == [ :model_class, :column_name, :source ].sort_by(&:to_s)
122
+ h[:model_class].should be(@model_class)
123
+ h[:column_name].should == :fcn
124
+ h[:source].should == @json_string
125
+ end
126
+ end
127
+
128
+ describe "#before_validation!" do
129
+ it "should do nothing if created with a raw string" do
130
+ expect_column_data_creation(@json_string)
131
+ @instance = @klass.new(@json_string)
132
+ @instance.before_validation!
133
+ end
134
+
135
+ it "should do nothing if the instance is valid" do
136
+ expect_column_data_creation(@json_string)
137
+ @instance = @klass.new(@model_instance)
138
+
139
+ expect(@instance).to receive(:valid?).once.with().and_return(true)
140
+ @instance.before_validation!
141
+ end
142
+
143
+ it "should copy errors over if the instance is invalid" do
144
+ expect_column_data_creation(@json_string)
145
+ @instance = @klass.new(@model_instance)
146
+
147
+ expect(@instance).to receive(:valid?).once.with().and_return(false)
148
+ allow(@instance).to receive(:errors).with().and_return({ :e1 => :m1, :e2 => :m2 })
149
+
150
+ errors = Object.new
151
+ class << errors
152
+ def add(k, v)
153
+ @_errors ||= [ ]
154
+ @_errors << [ k, v ]
155
+ end
156
+
157
+ def all_errors
158
+ @_errors
159
+ end
160
+ end
161
+
162
+ allow(@model_instance).to receive(:errors).with().and_return(errors)
163
+
164
+ @instance.before_validation!
165
+ all_errors = errors.all_errors
166
+ all_errors.length.should == 2
167
+
168
+ e1 = all_errors.detect { |e| e[0] == "fcn.e1" }
169
+ e1[1].should == :m1
170
+
171
+ e2 = all_errors.detect { |e| e[0] == "fcn.e2" }
172
+ e2[1].should == :m2
173
+ end
174
+ end
175
+
176
+ describe "#before_save!" do
177
+ it "should do nothing if created with a raw string" do
178
+ expect_column_data_creation(@json_string)
179
+ @instance = @klass.new(@json_string)
180
+
181
+ @instance.before_save!
182
+ end
183
+
184
+ context "with a valid instance" do
185
+ before :each do
186
+ expect_column_data_creation(@json_string)
187
+ @instance = @klass.new(@model_instance)
188
+ allow(@model_instance).to receive(:_flex_column_object_for).with(:fcn, false).and_return(@instance)
189
+ end
190
+
191
+ it "should tell you if it's been touched" do
192
+ expect(@column_data).to receive(:touched?).once.with().and_return(true)
193
+ @instance.touched?.should be
194
+ expect(@column_data).to receive(:touched?).once.with().and_return(false)
195
+ @instance.touched?.should_not be
196
+ end
197
+
198
+ it "should save if the class tells it to" do
199
+ expect(@klass).to receive(:requires_serialization_on_save?).once.with(@model_instance).and_return(true)
200
+ expect(@column_data).to receive(:to_stored_data).once.with().and_return("somestoreddata")
201
+ expect(@model_instance).to receive(:[]=).once.with(:fcn, "somestoreddata")
202
+ @instance.before_save!
203
+ end
204
+
205
+ it "should not save if the class tells it not to" do
206
+ expect(@klass).to receive(:requires_serialization_on_save?).once.with(@model_instance).and_return(false)
207
+ @instance.before_save!
208
+ end
209
+ end
210
+ end
211
+
212
+ context "with a valid instance" do
213
+ before :each do
214
+ expect_column_data_creation(@json_string)
215
+ @instance = @klass.new(@model_instance)
216
+ end
217
+
218
+ it "should return itself for #to_model" do
219
+ @instance.to_model.should be(@instance)
220
+ end
221
+
222
+ it "should delegate to the column data on []" do
223
+ expect(@column_data).to receive(:[]).once.with(:xxx).and_return(:yyy)
224
+ @instance[:xxx].should == :yyy
225
+ end
226
+
227
+ it "should delegate to the column data on []=" do
228
+ expect(@column_data).to receive(:[]=).once.with(:xxx, :yyy).and_return(:zzz)
229
+ (@instance[:xxx] = :yyy).should == :yyy
230
+ end
231
+
232
+ it "should delegate to the column data on #touch!" do
233
+ expect(@column_data).to receive(:touch!).once.with()
234
+ @instance.touch!
235
+ end
236
+
237
+ it "should delegate to the column data on #to_json" do
238
+ expect(@column_data).to receive(:to_json).once.with().and_return("somejsondata")
239
+ @instance.to_json.should == "somejsondata"
240
+ end
241
+
242
+ it "should delegate to the column data on #to_stored_data" do
243
+ expect(@column_data).to receive(:to_stored_data).once.with().and_return("somestoreddata")
244
+ @instance.to_stored_data.should == "somestoreddata"
245
+ end
246
+
247
+ it "should delegate to the column data on #keys" do
248
+ expect(@column_data).to receive(:keys).once.with().and_return([ :a, :z, :q ])
249
+ @instance.keys.should == [ :a, :z, :q ]
250
+ end
251
+ end
252
+ end
253
+ end
@@ -0,0 +1,617 @@
1
+ require 'flex_columns'
2
+ require 'flex_columns/helpers/exception_helpers'
3
+
4
+ describe FlexColumns::Definition::FieldDefinition do
5
+ include FlexColumns::Helpers::ExceptionHelpers
6
+
7
+ def klass
8
+ FlexColumns::Definition::FieldDefinition
9
+ end
10
+
11
+ it "should normalize names properly" do
12
+ lambda { klass.normalize_name(nil) }.should raise_error(ArgumentError)
13
+ lambda { klass.normalize_name(123) }.should raise_error(ArgumentError)
14
+ klass.normalize_name(" FoO ").should == :foo
15
+ klass.normalize_name(:foo).should == :foo
16
+ end
17
+
18
+ before :each do
19
+ @flex_column_class = double("flex_column_class")
20
+ allow(@flex_column_class).to receive(:is_flex_column_class?).with().and_return(true)
21
+ end
22
+
23
+ describe "#initialize" do
24
+ it "should validate its arguments" do
25
+ non_fcc = double("flex_column_class")
26
+ lambda { klass.new(non_fcc, :foo, [ ], { }) }.should raise_error(ArgumentError)
27
+
28
+ non_fcc = double("flex_column_class")
29
+ allow(non_fcc).to receive(:is_flex_column_class?).with().and_return(false)
30
+ lambda { klass.new(non_fcc, :foo, [ ], { }) }.should raise_error(ArgumentError)
31
+
32
+ lambda { klass.new(@flex_column_class, :foo, [ ], { :foo => :bar }) }.should raise_error(ArgumentError)
33
+
34
+ lambda { klass.new(@flex_column_class, :foo, [ ], { :visibility => 123 }) }.should raise_error(ArgumentError)
35
+ lambda { klass.new(@flex_column_class, :foo, [ ], { :visibility => true }) }.should raise_error(ArgumentError)
36
+
37
+ lambda { klass.new(@flex_column_class, :foo, [ ], { :json => 123 }) }.should raise_error(ArgumentError)
38
+
39
+ lambda { klass.new(@flex_column_class, :foo, [ ], { :null => 123 }) }.should raise_error(ArgumentError)
40
+ end
41
+
42
+ it "should raise an error if there are additional arguments" do
43
+ expect(@flex_column_class).to receive(:validates).once.with(:foo, { :numericality => { :only_integer => true }})
44
+ lambda { klass.new(@flex_column_class, :foo, [ :integer, :bar ], { }) }.should raise_error(ArgumentError)
45
+ end
46
+
47
+ describe "types support" do
48
+ it "should raise an error if given an invalid type" do
49
+ lambda { klass.new(@flex_column_class, :foo, [ :bar ], { }) }.should raise_error(ArgumentError)
50
+ end
51
+
52
+ it "should force a value to be present if :null => false" do
53
+ expect(@flex_column_class).to receive(:validates).once.with(:foo, { :presence => true })
54
+ klass.new(@flex_column_class, :foo, [ ], { :null => false })
55
+ end
56
+
57
+ it "should force a value to be in an :enum list" do
58
+ expect(@flex_column_class).to receive(:validates).once.with(:foo, { :inclusion => { :in => %w{a b c} } })
59
+ klass.new(@flex_column_class, :foo, [ ], { :enum => %w{a b c} })
60
+ end
61
+
62
+ it "should force a value to be of a maximum length" do
63
+ expect(@flex_column_class).to receive(:validates).once.with(:foo, { :length => { :maximum => 123 } })
64
+ klass.new(@flex_column_class, :foo, [ ], { :limit => 123 })
65
+ end
66
+
67
+ def expect_validation(type, arguments)
68
+ expect(@flex_column_class).to receive(:validates).once.with(:foo, arguments)
69
+ klass.new(@flex_column_class, :foo, [ type ], { })
70
+ end
71
+
72
+ it "should validate integers properly" do
73
+ expect_validation(:integer, { :numericality => { :only_integer => true } })
74
+ end
75
+
76
+ it "should validate floats properly" do
77
+ expect_validation(:float, { :numericality => true, :allow_nil => true })
78
+ end
79
+
80
+ it "should validate decimals properly" do
81
+ expect_validation(:decimal, { :numericality => true, :allow_nil => true })
82
+ end
83
+
84
+ it "should validate booleans properly" do
85
+ expect_validation(:boolean, { :inclusion => { :in => [ true, false, nil ] }})
86
+ end
87
+
88
+ def check_validation_block(type, input, expected_error)
89
+ validation_block = nil
90
+ expect(@flex_column_class).to receive(:validates_each).once.with(:foo) do |&block|
91
+ validation_block = block
92
+ end
93
+
94
+ klass.new(@flex_column_class, :foo, [ type.to_sym ], { })
95
+
96
+ record = double("record")
97
+ errors = double("errors")
98
+
99
+ allow(record).to receive(:errors).with().and_return(errors)
100
+
101
+ if expected_error
102
+ expect(errors).to receive(:add).once.with(:foo, expected_error)
103
+ end
104
+
105
+ validation_block.call(record, :foo, input)
106
+ end
107
+
108
+ %w{string text}.each do |type|
109
+ it "should validate fields of type #{type} properly" do
110
+ check_validation_block(type, "aaa", nil)
111
+ check_validation_block(type, :aaa, nil)
112
+ check_validation_block(type, nil, nil)
113
+ check_validation_block(type, 123, "must be a String")
114
+ end
115
+ end
116
+
117
+ it "should validate dates correctly" do
118
+ check_validation_block(:date, nil, nil)
119
+ check_validation_block(:date, Date.today, nil)
120
+ check_validation_block(:date, Time.now, "must be a Date")
121
+ check_validation_block(:date, DateTime.now, nil)
122
+ check_validation_block(:date, 123, "must be a Date")
123
+ end
124
+
125
+ it "should validate times correctly" do
126
+ check_validation_block(:time, nil, nil)
127
+ check_validation_block(:time, Date.today, "must be a Time")
128
+ check_validation_block(:time, Time.now, nil)
129
+ check_validation_block(:time, DateTime.now, "must be a Time")
130
+ check_validation_block(:time, 123, "must be a Time")
131
+ end
132
+
133
+ %w{timestamp datetime}.each do |type|
134
+ it "should validate fields of type #{type} properly" do
135
+ check_validation_block(type, nil, nil)
136
+ check_validation_block(type, Time.now, nil)
137
+
138
+ check_validation_block(type, DateTime.now, nil)
139
+
140
+ check_validation_block(type, 123, "must be a Time or DateTime")
141
+ check_validation_block(type, Date.today, "must be a Time or DateTime")
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ it "should return the JSON storage name from #json_storage_name" do
148
+ klass.new(@flex_column_class, :foo, [ ], { }).json_storage_name.should == :foo
149
+ klass.new(@flex_column_class, :foo, [ ], { :json => :bar }).json_storage_name.should == :bar
150
+ end
151
+
152
+ describe "#add_methods_to_flex_column_class!" do
153
+ before :each do
154
+ @dmm = Class.new do
155
+ class << self; public :define_method, :private; end
156
+
157
+ def initialize(h)
158
+ @h = h
159
+ end
160
+
161
+ def [](x)
162
+ @h[x]
163
+ end
164
+
165
+ def []=(x, y)
166
+ @h[x] = y
167
+ end
168
+ end
169
+ end
170
+
171
+ it "should define methods on the flex-column class" do
172
+ allow(@flex_column_class).to receive(:fields_are_private_by_default?).with().and_return(false)
173
+ klass.new(@flex_column_class, :foo, [ ], { :json => :bar }).add_methods_to_flex_column_class!(@dmm)
174
+
175
+ instance = @dmm.new(:foo => 123)
176
+ instance.foo.should == 123
177
+ instance.foo = 234
178
+ instance.foo.should == 234
179
+ end
180
+
181
+ it "should define methods on the flex-column class privately if the flex-column says to" do
182
+ allow(@flex_column_class).to receive(:fields_are_private_by_default?).with().and_return(true)
183
+ klass.new(@flex_column_class, :foo, [ ], { :json => :bar }).add_methods_to_flex_column_class!(@dmm)
184
+
185
+ instance = @dmm.new(:foo => 123)
186
+
187
+ lambda { instance.foo }.should raise_error(NoMethodError)
188
+ lambda { instance.foo = 123 }.should raise_error(NoMethodError)
189
+
190
+ instance.send(:foo).should == 123
191
+ instance.send(:foo=, 234)
192
+ instance.send(:foo).should == 234
193
+ end
194
+
195
+ it "should define methods on the flex-column class privately if the field says to" do
196
+ allow(@flex_column_class).to receive(:fields_are_private_by_default?).with().and_return(false)
197
+ klass.new(@flex_column_class, :foo, [ ], { :json => :bar, :visibility => :private }).add_methods_to_flex_column_class!(@dmm)
198
+
199
+ instance = @dmm.new(:foo => 123)
200
+
201
+ lambda { instance.foo }.should raise_error(NoMethodError)
202
+ lambda { instance.foo = 123 }.should raise_error(NoMethodError)
203
+
204
+ instance.send(:foo).should == 123
205
+ instance.send(:foo=, 234)
206
+ instance.send(:foo).should == 234
207
+ end
208
+
209
+ it "should define methods on the flex-column class publicly if the field says to" do
210
+ allow(@flex_column_class).to receive(:fields_are_private_by_default?).with().and_return(true)
211
+ klass.new(@flex_column_class, :foo, [ ], { :json => :bar, :visibility => :public }).add_methods_to_flex_column_class!(@dmm)
212
+
213
+ instance = @dmm.new(:foo => 123)
214
+ instance.foo.should == 123
215
+ instance.foo = 234
216
+ instance.foo.should == 234
217
+ end
218
+ end
219
+
220
+ describe "#add_methods_to_model_class!" do
221
+ before :each do
222
+ @dmm = Class.new do
223
+ class << self; public :define_method, :private; end
224
+ end
225
+
226
+ @model_class = double("model_class")
227
+ end
228
+
229
+ def check_add_methods_to_model_class(options)
230
+ create_options = options[:create_options]
231
+ fields_are_private_by_default = options[:fields_are_private_by_default] || false
232
+ safe_to_define = if options.has_key?(:safe_to_define) then options[:safe_to_define] else true end
233
+ delegation_type = options[:delegation_type]
234
+ delegation_prefix = options[:delegation_prefix]
235
+ method_name = options[:method_name]
236
+ should_be_private = options[:should_be_private]
237
+ method_should_exist = if options.has_key?(:method_should_exist) then options[:method_should_exist] else true end
238
+
239
+ flex_instance = { }
240
+ field = klass.new(@flex_column_class, :foo, [ ], create_options)
241
+ allow(@flex_column_class).to receive(:fields_are_private_by_default?).with().and_return(fields_are_private_by_default)
242
+
243
+ allow(@model_class).to receive(:_flex_columns_safe_to_define_method?).with(method_name.to_sym).and_return(safe_to_define)
244
+ allow(@model_class).to receive(:_flex_columns_safe_to_define_method?).with("#{method_name}=".to_sym).and_return(safe_to_define)
245
+ allow(@flex_column_class).to receive(:delegation_type).with().and_return(delegation_type)
246
+ allow(@flex_column_class).to receive(:delegation_prefix).with().and_return(delegation_prefix)
247
+
248
+ field.add_methods_to_model_class!(@dmm, @model_class)
249
+
250
+ instance = @dmm.new
251
+ allow(@flex_column_class).to receive(:object_for).with(instance).and_return(flex_instance)
252
+
253
+ if (! method_should_exist)
254
+ lambda { instance.send(method_name) }.should raise_error(NoMethodError)
255
+ lambda { instance.send("#{method_name}=", 123) }.should raise_error(NoMethodError)
256
+ elsif should_be_private
257
+ lambda { eval("instance.#{method_name}") }.should raise_error(NoMethodError)
258
+ lambda { eval("instance.#{method_name} = 123") }.should raise_error(NoMethodError)
259
+ instance.send(method_name).should be_nil
260
+ instance.send("#{method_name}=", 123).should == 123
261
+ instance.send(method_name).should == 123
262
+ else
263
+ eval("instance.#{method_name}").should be_nil
264
+ eval("instance.#{method_name} = 123").should == 123
265
+ eval("instance.#{method_name}").should == 123
266
+ end
267
+ end
268
+
269
+ it "should define a very standard public method just fine" do
270
+ check_add_methods_to_model_class(
271
+ :create_options => { },
272
+ :fields_are_private_by_default => false,
273
+ :safe_to_define => true,
274
+ :delegation_type => :public,
275
+ :delegation_prefix => nil,
276
+ :method_name => :foo,
277
+ :should_be_private => false
278
+ )
279
+ end
280
+
281
+ it "should define a private method if asked to on the field" do
282
+ check_add_methods_to_model_class(
283
+ :create_options => { :visibility => :private },
284
+ :fields_are_private_by_default => false,
285
+ :safe_to_define => true,
286
+ :delegation_type => :public,
287
+ :delegation_prefix => nil,
288
+ :method_name => :foo,
289
+ :should_be_private => true
290
+ )
291
+ end
292
+
293
+ it "should define a private method if fields are private by default" do
294
+ check_add_methods_to_model_class(
295
+ :create_options => { },
296
+ :fields_are_private_by_default => true,
297
+ :safe_to_define => true,
298
+ :delegation_type => :public,
299
+ :delegation_prefix => nil,
300
+ :method_name => :foo,
301
+ :should_be_private => true
302
+ )
303
+ end
304
+
305
+ it "should define a private method if fields are private by default, but the specific field is public" do
306
+ check_add_methods_to_model_class(
307
+ :create_options => { :visibility => :public },
308
+ :fields_are_private_by_default => true,
309
+ :safe_to_define => true,
310
+ :delegation_type => :public,
311
+ :delegation_prefix => nil,
312
+ :method_name => :foo,
313
+ :should_be_private => false
314
+ )
315
+ end
316
+
317
+ it "should define a private method if delegation is private" do
318
+ check_add_methods_to_model_class(
319
+ :create_options => { },
320
+ :fields_are_private_by_default => false,
321
+ :safe_to_define => true,
322
+ :delegation_type => :private,
323
+ :delegation_prefix => nil,
324
+ :method_name => :foo,
325
+ :should_be_private => true
326
+ )
327
+ end
328
+
329
+ it "should not define a method if delegation is off" do
330
+ check_add_methods_to_model_class(
331
+ :create_options => { },
332
+ :fields_are_private_by_default => false,
333
+ :safe_to_define => true,
334
+ :delegation_type => nil,
335
+ :delegation_prefix => nil,
336
+ :method_name => :foo,
337
+ :should_be_private => false,
338
+ :method_should_exist => false
339
+ )
340
+ end
341
+
342
+ it "should not define a method if it's not safe to do so" do
343
+ check_add_methods_to_model_class(
344
+ :create_options => { },
345
+ :fields_are_private_by_default => false,
346
+ :safe_to_define => false,
347
+ :delegation_type => :public,
348
+ :delegation_prefix => nil,
349
+ :method_name => :foo,
350
+ :should_be_private => false,
351
+ :method_should_exist => false
352
+ )
353
+ end
354
+
355
+ it "should prefix the method if requested" do
356
+ check_add_methods_to_model_class(
357
+ :create_options => { },
358
+ :fields_are_private_by_default => false,
359
+ :safe_to_define => true,
360
+ :delegation_type => :public,
361
+ :delegation_prefix => :aaa,
362
+ :method_name => :aaa_foo,
363
+ :should_be_private => false,
364
+ :method_should_exist => true
365
+ )
366
+ end
367
+ end
368
+
369
+ describe "#add_methods_to_included_class!" do
370
+ def check_add_methods_to_included_class(options)
371
+ delegation_type = options[:delegation_type]
372
+ delegation_prefix = options[:delegation_prefix]
373
+ create_options = options[:create_options]
374
+ fields_are_private_by_default = options[:fields_are_private_by_default] || false
375
+ method_name = options[:method_name]
376
+ safe_to_define = options[:safe_to_define]
377
+ include_options = options[:include_options]
378
+ method_should_exist = options[:method_should_exist]
379
+ should_be_private = options[:should_be_private]
380
+
381
+ allow(@flex_column_class).to receive(:delegation_type).with().and_return(delegation_type)
382
+ allow(@flex_column_class).to receive(:delegation_prefix).with().and_return(delegation_prefix)
383
+ allow(@flex_column_class).to receive(:fields_are_private_by_default?).with().and_return(fields_are_private_by_default)
384
+
385
+ @model_class = double("model_class")
386
+ allow(@model_class).to receive(:name).with().and_return("mcname")
387
+ allow(@flex_column_class).to receive(:model_class).with().and_return(@model_class)
388
+ allow(@flex_column_class).to receive(:column_name).with().and_return(:colname)
389
+
390
+ target_class = double("target_class")
391
+ allow(target_class).to receive(:_flex_columns_safe_to_define_method?).with(method_name.to_sym).and_return(safe_to_define)
392
+ allow(target_class).to receive(:_flex_columns_safe_to_define_method?).with("#{method_name}=".to_sym).and_return(safe_to_define)
393
+
394
+ associated_object = double("associated_object")
395
+
396
+ @dmm = Class.new do
397
+ def ao=(x)
398
+ @associated_object = x
399
+ end
400
+
401
+ def ao
402
+ @associated_object
403
+ end
404
+
405
+ def bao=(x)
406
+ @build_associated_object = x
407
+ end
408
+
409
+ def build_ao
410
+ @build_associated_object
411
+ end
412
+
413
+ class << self; public :define_method, :private; end
414
+ end
415
+
416
+ field = klass.new(@flex_column_class, :foo, [ ], create_options)
417
+ field.add_methods_to_included_class!(@dmm, :ao, target_class, include_options)
418
+
419
+ instance = @dmm.new
420
+
421
+ if options[:use_build]
422
+ instance.bao = associated_object
423
+ else
424
+ instance.ao = associated_object
425
+ end
426
+
427
+ flex_instance = Object.new
428
+ class << flex_instance
429
+ def foo=(x)
430
+ @foo = x
431
+ end
432
+
433
+ def foo
434
+ @foo
435
+ end
436
+ end
437
+
438
+ allow(associated_object).to receive(:colname).and_return(flex_instance)
439
+
440
+ if (! method_should_exist)
441
+ lambda { instance.send(method_name) }.should raise_error(NoMethodError)
442
+ lambda { instance.send("#{method_name}=", 123) }.should raise_error(NoMethodError)
443
+ elsif should_be_private
444
+ lambda { eval("instance.#{method_name}") }.should raise_error(NoMethodError)
445
+ lambda { eval("instance.#{method_name} = 123") }.should raise_error(NoMethodError)
446
+ instance.send(method_name).should be_nil
447
+ instance.send("#{method_name}=", 123).should == 123
448
+ instance.send(method_name).should == 123
449
+ else
450
+ eval("instance.#{method_name}").should be_nil
451
+ eval("instance.#{method_name} = 123").should == 123
452
+ eval("instance.#{method_name}").should == 123
453
+ end
454
+ end
455
+
456
+ it "should define a very standard public method just fine" do
457
+ check_add_methods_to_included_class(
458
+ :delegation_type => :public,
459
+ :delegation_prefix => nil,
460
+ :create_options => { },
461
+ :fields_are_private_by_default => false,
462
+ :method_name => :foo,
463
+ :safe_to_define => true,
464
+ :include_options => { },
465
+ :method_should_exist => true,
466
+ :should_be_private => false
467
+ )
468
+ end
469
+
470
+ it "should define a private method if the field is private" do
471
+ check_add_methods_to_included_class(
472
+ :delegation_type => :public,
473
+ :delegation_prefix => nil,
474
+ :create_options => { :visibility => :private },
475
+ :fields_are_private_by_default => false,
476
+ :method_name => :foo,
477
+ :safe_to_define => true,
478
+ :include_options => { },
479
+ :method_should_exist => true,
480
+ :should_be_private => true
481
+ )
482
+ end
483
+
484
+ it "should define a private method if the flex column is private" do
485
+ check_add_methods_to_included_class(
486
+ :delegation_type => :public,
487
+ :delegation_prefix => nil,
488
+ :create_options => { },
489
+ :fields_are_private_by_default => true,
490
+ :method_name => :foo,
491
+ :safe_to_define => true,
492
+ :include_options => { },
493
+ :method_should_exist => true,
494
+ :should_be_private => true
495
+ )
496
+ end
497
+
498
+ it "should define a public method if the flex column is private but the field is public" do
499
+ check_add_methods_to_included_class(
500
+ :delegation_type => :public,
501
+ :delegation_prefix => nil,
502
+ :create_options => { :visibility => :public },
503
+ :fields_are_private_by_default => true,
504
+ :method_name => :foo,
505
+ :safe_to_define => true,
506
+ :include_options => { },
507
+ :method_should_exist => true,
508
+ :should_be_private => false
509
+ )
510
+ end
511
+
512
+ it "should default to the flex-column prefix" do
513
+ check_add_methods_to_included_class(
514
+ :delegation_type => :public,
515
+ :delegation_prefix => :aaa,
516
+ :create_options => { },
517
+ :fields_are_private_by_default => false,
518
+ :method_name => :aaa_foo,
519
+ :safe_to_define => true,
520
+ :include_options => { },
521
+ :method_should_exist => true,
522
+ :should_be_private => false
523
+ )
524
+ end
525
+
526
+ it "should override to the flex-column prefix if requested" do
527
+ check_add_methods_to_included_class(
528
+ :delegation_type => :public,
529
+ :delegation_prefix => :aaa,
530
+ :create_options => { },
531
+ :fields_are_private_by_default => false,
532
+ :method_name => :bbb_foo,
533
+ :safe_to_define => true,
534
+ :include_options => { :prefix => :bbb },
535
+ :method_should_exist => true,
536
+ :should_be_private => false
537
+ )
538
+ end
539
+
540
+ it "should override to the flex-column prefix with no prefix if requested" do
541
+ check_add_methods_to_included_class(
542
+ :delegation_type => :public,
543
+ :delegation_prefix => :aaa,
544
+ :create_options => { },
545
+ :fields_are_private_by_default => false,
546
+ :method_name => :foo,
547
+ :safe_to_define => true,
548
+ :include_options => { :prefix => nil },
549
+ :method_should_exist => true,
550
+ :should_be_private => false
551
+ )
552
+ end
553
+
554
+ it "should raise if you ask for public methods from a private field" do
555
+ e = capture_exception(ArgumentError) do
556
+ check_add_methods_to_included_class(
557
+ :delegation_type => :public,
558
+ :delegation_prefix => nil,
559
+ :create_options => { },
560
+ :fields_are_private_by_default => true,
561
+ :method_name => :foo,
562
+ :safe_to_define => true,
563
+ :include_options => { :visibility => :public },
564
+ :method_should_exist => true,
565
+ :should_be_private => true
566
+ )
567
+ end
568
+
569
+ e.message.should match(/ao/i)
570
+ e.message.should match(/mcname/i)
571
+ e.message.should match(/colname/i)
572
+ end
573
+
574
+ it "should skip defining methods if there's no delegation" do
575
+ check_add_methods_to_included_class(
576
+ :delegation_type => nil,
577
+ :delegation_prefix => nil,
578
+ :create_options => { },
579
+ :fields_are_private_by_default => false,
580
+ :method_name => :foo,
581
+ :safe_to_define => true,
582
+ :include_options => { },
583
+ :method_should_exist => false,
584
+ :should_be_private => false
585
+ )
586
+ end
587
+
588
+ it "should skip defining methods if it's not safe" do
589
+ check_add_methods_to_included_class(
590
+ :delegation_type => :public,
591
+ :delegation_prefix => nil,
592
+ :create_options => { },
593
+ :fields_are_private_by_default => false,
594
+ :method_name => :foo,
595
+ :safe_to_define => false,
596
+ :include_options => { },
597
+ :method_should_exist => false,
598
+ :should_be_private => false
599
+ )
600
+ end
601
+
602
+ it "should build the association if needed" do
603
+ check_add_methods_to_included_class(
604
+ :delegation_type => :public,
605
+ :delegation_prefix => nil,
606
+ :create_options => { },
607
+ :fields_are_private_by_default => false,
608
+ :method_name => :foo,
609
+ :safe_to_define => true,
610
+ :include_options => { },
611
+ :method_should_exist => true,
612
+ :should_be_private => false,
613
+ :use_build => true
614
+ )
615
+ end
616
+ end
617
+ end