flex_columns 1.0.0

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.
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