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,142 @@
1
+ require 'flex_columns'
2
+ require 'flex_columns/helpers/exception_helpers'
3
+
4
+ describe FlexColumns::Definition::FieldSet do
5
+ include FlexColumns::Helpers::ExceptionHelpers
6
+
7
+ def klass
8
+ FlexColumns::Definition::FieldSet
9
+ end
10
+
11
+ before :each do
12
+ @model_class = double("model_class")
13
+ allow(@model_class).to receive(:name).with().and_return("modname")
14
+
15
+ @flex_column_class = double("flex_column_class")
16
+ allow(@flex_column_class).to receive(:model_class).with().and_return(@model_class)
17
+ allow(@flex_column_class).to receive(:column_name).with().and_return("colname")
18
+
19
+ @instance = klass.new(@flex_column_class)
20
+ end
21
+
22
+ it "should allow defining fields and returning them" do
23
+ @field_foo = double("field_foo")
24
+ allow(@field_foo).to receive(:json_storage_name).with().and_return(:foo_storage)
25
+
26
+ @field_bar = double("field_bar")
27
+ allow(@field_bar).to receive(:json_storage_name).with().and_return(:storage_bar)
28
+
29
+ expect(FlexColumns::Definition::FieldDefinition).to receive(:new).once.with(@flex_column_class, :foo, [ ], { }).and_return(@field_foo)
30
+ @instance.field(' fOo ')
31
+
32
+ expect(FlexColumns::Definition::FieldDefinition).to receive(:new).once.with(@flex_column_class, :bar, [ ], { }).and_return(@field_bar)
33
+ @instance.field(:bar)
34
+
35
+ @instance.all_field_names.sort_by(&:to_s).should == [ :foo, :bar ].sort_by(&:to_s)
36
+
37
+ @instance.field_named(:foo).should be(@field_foo)
38
+ @instance.field_named(' foO ').should be(@field_foo)
39
+
40
+ @instance.field_named(:bar).should be(@field_bar)
41
+ @instance.field_named('BAr ').should be(@field_bar)
42
+
43
+ @instance.field_with_json_storage_name(:foo).should_not be
44
+ @instance.field_with_json_storage_name(' fOo ').should_not be
45
+ @instance.field_with_json_storage_name(:foo_storage).should be(@field_foo)
46
+
47
+ @instance.field_with_json_storage_name(:bar_storage).should_not be
48
+ @instance.field_with_json_storage_name(:bar).should_not be
49
+ @instance.field_with_json_storage_name(:storage_bar).should be(@field_bar)
50
+ end
51
+
52
+ it "should raise if there's a duplicate JSON storage name" do
53
+ @field_foo = double("field_foo")
54
+ allow(@field_foo).to receive(:json_storage_name).with().and_return(:foo_storage)
55
+ allow(@field_foo).to receive(:field_name).with().and_return(:foo)
56
+
57
+ @field_bar = double("field_bar")
58
+ allow(@field_bar).to receive(:json_storage_name).with().and_return(:foo_storage)
59
+ allow(@field_bar).to receive(:field_name).with().and_return(:bar)
60
+
61
+ expect(FlexColumns::Definition::FieldDefinition).to receive(:new).once.with(@flex_column_class, :foo, [ ], { }).and_return(@field_foo)
62
+ @instance.field(' fOo ')
63
+
64
+ expect(FlexColumns::Definition::FieldDefinition).to receive(:new).once.with(@flex_column_class, :bar, [ ], { }).and_return(@field_bar)
65
+
66
+ e = capture_exception(FlexColumns::Errors::ConflictingJsonStorageNameError) do
67
+ @instance.field(:bar)
68
+ end
69
+
70
+ e.model_class.should be(@model_class)
71
+ e.column_name.should == "colname"
72
+ e.new_field_name.should == :bar
73
+ e.existing_field_name.should == :foo
74
+ e.json_storage_name.should == :foo_storage
75
+ end
76
+
77
+ it "should allow redefining the same field, and the new field should win" do
78
+ @field_foo_1 = double("field_foo_1")
79
+ allow(@field_foo_1).to receive(:json_storage_name).with().and_return(:foo_storage)
80
+ allow(@field_foo_1).to receive(:field_name).with().and_return(:foo)
81
+
82
+ @field_foo_2 = double("field_foo_2")
83
+ allow(@field_foo_2).to receive(:json_storage_name).with().and_return(:foo_storage)
84
+ allow(@field_foo_2).to receive(:field_name).with().and_return(:foo)
85
+
86
+ expect(FlexColumns::Definition::FieldDefinition).to receive(:new).once.with(@flex_column_class, :foo, [ ], { }).and_return(@field_foo_1)
87
+ @instance.field(' fOo ')
88
+
89
+ expect(FlexColumns::Definition::FieldDefinition).to receive(:new).once.with(@flex_column_class, :foo, [ ], { :null => false }).and_return(@field_foo_2)
90
+ @instance.field(:foo, :null => false)
91
+
92
+ @instance.all_field_names.should == [ :foo ]
93
+ @instance.field_named(:foo).should be(@field_foo_2)
94
+ end
95
+
96
+ it "should call through to the fields on #add_delegated_methods!" do
97
+ @field_foo = double("field_foo")
98
+ allow(@field_foo).to receive(:json_storage_name).with().and_return(:foo)
99
+
100
+ @field_bar = double("field_bar")
101
+ allow(@field_bar).to receive(:json_storage_name).with().and_return(:bar)
102
+
103
+ expect(FlexColumns::Definition::FieldDefinition).to receive(:new).once.with(@flex_column_class, :foo, [ ], { }).and_return(@field_foo)
104
+ @instance.field(:foo)
105
+
106
+ expect(FlexColumns::Definition::FieldDefinition).to receive(:new).once.with(@flex_column_class, :bar, [ ], { }).and_return(@field_bar)
107
+ @instance.field(:bar)
108
+
109
+ flex_column_dmm = double("flex_column_dmm")
110
+ model_dmm = double("model_dmm")
111
+
112
+ expect(@field_foo).to receive(:add_methods_to_flex_column_class!).once.with(flex_column_dmm)
113
+ expect(@field_foo).to receive(:add_methods_to_model_class!).once.with(model_dmm, @model_class)
114
+ expect(@field_bar).to receive(:add_methods_to_flex_column_class!).once.with(flex_column_dmm)
115
+ expect(@field_bar).to receive(:add_methods_to_model_class!).once.with(model_dmm, @model_class)
116
+
117
+ @instance.add_delegated_methods!(flex_column_dmm, model_dmm, @model_class)
118
+ end
119
+
120
+ it "should call through to the fields on #include_fields_into!" do
121
+ @field_foo = double("field_foo")
122
+ allow(@field_foo).to receive(:json_storage_name).with().and_return(:foo)
123
+
124
+ @field_bar = double("field_bar")
125
+ allow(@field_bar).to receive(:json_storage_name).with().and_return(:bar)
126
+
127
+ expect(FlexColumns::Definition::FieldDefinition).to receive(:new).once.with(@flex_column_class, :foo, [ ], { }).and_return(@field_foo)
128
+ @instance.field(:foo)
129
+
130
+ expect(FlexColumns::Definition::FieldDefinition).to receive(:new).once.with(@flex_column_class, :bar, [ ], { }).and_return(@field_bar)
131
+ @instance.field(:bar)
132
+
133
+ dmm = double("dmm")
134
+ target_class = double("target_class")
135
+ options = double("options")
136
+
137
+ expect(@field_foo).to receive(:add_methods_to_included_class!).once.with(dmm, :assocname, target_class, options)
138
+ expect(@field_bar).to receive(:add_methods_to_included_class!).once.with(dmm, :assocname, target_class, options)
139
+
140
+ @instance.include_fields_into(dmm, :assocname, target_class, options)
141
+ end
142
+ end
@@ -0,0 +1,733 @@
1
+ require 'flex_columns'
2
+ require 'flex_columns/definition/flex_column_contents_class'
3
+ require 'flex_columns/helpers/exception_helpers'
4
+
5
+ describe FlexColumns::Definition::FlexColumnContentsClass do
6
+ include FlexColumns::Helpers::ExceptionHelpers
7
+
8
+ before :each do
9
+ @klass = Class.new
10
+ @klass.send(:extend, FlexColumns::Definition::FlexColumnContentsClass)
11
+
12
+ @model_class = double("model_class")
13
+ allow(@model_class).to receive(:kind_of?).with(Class).and_return(true)
14
+ allow(@model_class).to receive(:has_any_flex_columns?).with().and_return(true)
15
+ allow(@model_class).to receive(:name).with().and_return(:mcname)
16
+
17
+ @column_foo = double("column_foo")
18
+ allow(@column_foo).to receive(:name).with().and_return(:foo)
19
+ allow(@column_foo).to receive(:type).with().and_return(:text)
20
+ allow(@column_foo).to receive(:text?).with().and_return(true)
21
+ allow(@column_foo).to receive(:null).with().and_return(true)
22
+ allow(@column_foo).to receive(:sql_type).with().and_return('clob')
23
+
24
+ @column_bar = double("column_bar")
25
+ allow(@column_bar).to receive(:name).with().and_return(:bar)
26
+ allow(@column_bar).to receive(:type).with().and_return(:binary)
27
+ allow(@column_bar).to receive(:text?).with().and_return(false)
28
+ allow(@column_bar).to receive(:null).with().and_return(true)
29
+ allow(@column_bar).to receive(:sql_type).with().and_return('blob')
30
+
31
+ @column_baz = double("column_baz")
32
+ allow(@column_baz).to receive(:name).with().and_return(:baz)
33
+ allow(@column_baz).to receive(:type).with().and_return(:integer)
34
+ allow(@column_baz).to receive(:text?).with().and_return(false)
35
+ allow(@column_baz).to receive(:null).with().and_return(true)
36
+ allow(@column_baz).to receive(:sql_type).with().and_return('integer')
37
+
38
+ @column_quux = double("column_quux")
39
+ allow(@column_quux).to receive(:name).with().and_return(:quux)
40
+ allow(@column_quux).to receive(:type).with().and_return(:string)
41
+ allow(@column_quux).to receive(:text?).with().and_return(true)
42
+ allow(@column_quux).to receive(:null).with().and_return(true)
43
+ allow(@column_quux).to receive(:sql_type).with().and_return('varchar')
44
+
45
+ @column_ajson = double("column_ajson")
46
+ allow(@column_ajson).to receive(:name).with().and_return(:ajson)
47
+ allow(@column_ajson).to receive(:type).with().and_return(:json)
48
+ allow(@column_ajson).to receive(:text?).with().and_return(false)
49
+ allow(@column_ajson).to receive(:null).with().and_return(true)
50
+ allow(@column_ajson).to receive(:sql_type).with().and_return('json')
51
+
52
+ columns = [ @column_foo, @column_bar, @column_baz, @column_quux, @column_ajson ]
53
+ allow(@model_class).to receive(:columns).with().and_return(columns)
54
+
55
+ allow(@model_class).to receive(:const_defined?).and_return(false)
56
+ allow(@model_class).to receive(:const_set)
57
+
58
+ @field_set = double("field_set")
59
+ allow(FlexColumns::Definition::FieldSet).to receive(:new).and_return(@field_set)
60
+ end
61
+
62
+ describe "setup!" do
63
+ describe "pre-setup errors" do
64
+ def should_raise_if_not_set_up(&block)
65
+ block.should raise_error(/setup!/i)
66
+ end
67
+
68
+ it "should raise an error if any other method is called" do
69
+ should_raise_if_not_set_up { @klass._flex_columns_create_column_data(double("storage_string"), double("data_source")) }
70
+ should_raise_if_not_set_up { @klass.field(:foo) }
71
+ should_raise_if_not_set_up { @klass.field_named(:foo) }
72
+ should_raise_if_not_set_up { @klass.field_with_json_storage_name(:foo) }
73
+ should_raise_if_not_set_up { @klass.include_fields_into(double("dynamic_methods_module"),
74
+ double("association_name"), double("target_class"), { }) }
75
+ should_raise_if_not_set_up { @klass.object_for(double("model_instance")) }
76
+ should_raise_if_not_set_up { @klass.delegation_prefix }
77
+ should_raise_if_not_set_up { @klass.delegation_type }
78
+ should_raise_if_not_set_up { @klass.column_name }
79
+ should_raise_if_not_set_up { @klass.fields_are_private_by_default? }
80
+ end
81
+ end
82
+
83
+ it "should work with no options" do
84
+ @klass.setup!(@model_class, :foo) { }
85
+ end
86
+
87
+ it "should not allow itself to be called twice" do
88
+ @klass.setup!(@model_class, :foo) { }
89
+
90
+ lambda { @klass.setup!(@model_class, :foo) { } }.should raise_error(ArgumentError)
91
+ end
92
+
93
+ it "should require a model class that's a class" do
94
+ mc = double("model_class")
95
+ expect(mc).to receive(:kind_of?).with(Class).and_return(false)
96
+ lambda { @klass.setup!(mc, :foo) { } }.should raise_error(ArgumentError)
97
+ end
98
+
99
+ it "should require a model class that's an AR class" do
100
+ allow(@model_class).to receive(:has_any_flex_columns?).with().and_return(false)
101
+ lambda { @klass.setup!(@model_class, :foo) { } }.should raise_error(ArgumentError)
102
+ end
103
+
104
+ it "should require a column name that's a Symbol" do
105
+ lambda { @klass.setup!(@model_class, 'foo') { } }.should raise_error(ArgumentError)
106
+ end
107
+
108
+ it "should raise a nice error if passed something that isn't a column on the model" do
109
+ e = capture_exception(FlexColumns::Errors::NoSuchColumnError) { @klass.setup!(@model_class, :unknowncolumn) { } }
110
+ e.message.should match(/quux/i)
111
+ e.message.should match(/foo/i)
112
+ e.message.should match(/bar/i)
113
+ e.message.should match(/baz/i)
114
+ e.message.should match(/mcname/i)
115
+ end
116
+
117
+ it "should raise a nice error if passed a column of the wrong type" do
118
+ e = capture_exception(FlexColumns::Errors::InvalidColumnTypeError) { @klass.setup!(@model_class, :baz) { } }
119
+ e.message.should match(/mcname/i)
120
+ e.message.should match(/baz/i)
121
+ e.message.should match(/integer/i)
122
+ end
123
+
124
+ it "should work on a text column" do
125
+ @klass.setup!(@model_class, :foo) { }
126
+ end
127
+
128
+ it "should work on a binary column" do
129
+ @klass.setup!(@model_class, :bar) { }
130
+ end
131
+
132
+ it "should work on a JSON column" do
133
+ @klass.setup!(@model_class, :ajson) { }
134
+ end
135
+
136
+ it "should work on a string column" do
137
+ @klass.setup!(@model_class, :quux) { }
138
+ end
139
+
140
+ it "should create a new field set and name itself properly" do
141
+ expect(FlexColumns::Definition::FieldSet).to receive(:new).once.with(@klass).and_return(@field_set)
142
+
143
+ expect(@model_class).to receive(:const_defined?).with(:FooFlexContents).and_return(false)
144
+ expect(@model_class).to receive(:const_set).once.with(:FooFlexContents, @klass)
145
+
146
+ @klass.setup!(@model_class, :foo) { }
147
+ end
148
+
149
+ it "should run the block it's passed" do
150
+ expect(@klass).to receive(:foobar).once.with(:foo, :bar, :baz).and_return(:quux)
151
+
152
+ @klass.setup!(@model_class, :foo) do
153
+ foobar(:foo, :bar, :baz)
154
+ end.should == :quux
155
+ end
156
+
157
+ describe "options validation" do
158
+ def should_reject(options)
159
+ lambda { @klass.setup!(@model_class, :foo, options) { } }.should raise_error(ArgumentError)
160
+ end
161
+
162
+ it "should require a Hash" do
163
+ should_reject(123)
164
+ end
165
+
166
+ it "should reject unknown keys" do
167
+ should_reject(:foo => 123)
168
+ end
169
+
170
+ it "should require a valid option for :visibility" do
171
+ should_reject(:visibility => :foo)
172
+ should_reject(:visibility => true)
173
+ end
174
+
175
+ it "should require a valid option for :unknown_fields" do
176
+ should_reject(:unknown_fields => :foo)
177
+ should_reject(:unknown_fields => false)
178
+ end
179
+
180
+ it "should require a valid option for :compress" do
181
+ should_reject(:compress => :foo)
182
+ should_reject(:compress => "foo")
183
+ end
184
+
185
+ it "should require a valid option for :header" do
186
+ should_reject(:header => :foo)
187
+ should_reject(:header => "foo")
188
+ should_reject(:header => 123)
189
+ end
190
+
191
+ it "should require a valid option for :prefix" do
192
+ should_reject(:prefix => true)
193
+ should_reject(:prefix => 123)
194
+ end
195
+
196
+ it "should require a valid option for :delegate" do
197
+ should_reject(:delegate => :foo)
198
+ should_reject(:delegate => 123)
199
+ end
200
+
201
+ it "should reject incompatible :visibility and :delegate options" do
202
+ should_reject(:visibility => :private, :delegate => :public)
203
+ end
204
+ end
205
+ end
206
+
207
+ describe "_flex_columns_create_column_data" do
208
+ def expect_options_transform(class_options, length_limit, resulting_options, column_name = :foo, storage_string = double("storage_string"))
209
+ @klass.setup!(@model_class, column_name, class_options)
210
+
211
+ data_source = double("data_source")
212
+
213
+ allow(instance_variable_get("@column_#{column_name}")).to receive(:limit).with().and_return(length_limit)
214
+
215
+ resulting_options = { :storage_string => storage_string, :data_source => data_source }.merge(resulting_options)
216
+
217
+ expect(FlexColumns::Contents::ColumnData).to receive(:new).once.with(@field_set, resulting_options)
218
+
219
+ @klass._flex_columns_create_column_data(storage_string, data_source)
220
+ end
221
+
222
+ it "should create a new ColumnData object with correct default options" do
223
+ expect_options_transform({ }, nil, {
224
+ :unknown_fields => :preserve,
225
+ :length_limit => nil,
226
+ :storage => :text,
227
+ :binary_header => true,
228
+ :compress_if_over_length => 200,
229
+ :null => true
230
+ })
231
+ end
232
+
233
+ it "should allow a nil storage string" do
234
+ expect_options_transform({ }, nil, {
235
+ :unknown_fields => :preserve,
236
+ :length_limit => nil,
237
+ :storage => :text,
238
+ :binary_header => true,
239
+ :compress_if_over_length => 200,
240
+ :storage_string => nil,
241
+ :null => true
242
+ }, :foo, nil)
243
+ end
244
+
245
+ it "should pass through :unknown_fields correctly" do
246
+ expect_options_transform({ :unknown_fields => :delete }, nil, {
247
+ :unknown_fields => :delete,
248
+ :length_limit => nil,
249
+ :storage => :text,
250
+ :binary_header => true,
251
+ :compress_if_over_length => 200,
252
+ :null => true
253
+ })
254
+ end
255
+
256
+ it "should pass through the column limit correctly" do
257
+ expect_options_transform({ }, 123, {
258
+ :unknown_fields => :preserve,
259
+ :length_limit => 123,
260
+ :storage => :text,
261
+ :binary_header => true,
262
+ :compress_if_over_length => 200,
263
+ :null => true
264
+ })
265
+ end
266
+
267
+ it "should pass through the column type correctly for a binary column" do
268
+ expect_options_transform({ }, 123, {
269
+ :unknown_fields => :preserve,
270
+ :length_limit => 123,
271
+ :storage => :binary,
272
+ :binary_header => true,
273
+ :compress_if_over_length => 200,
274
+ :null => true
275
+ }, :bar)
276
+ end
277
+
278
+ it "should pass through the nullability setting correctly" do
279
+ allow(@column_foo).to receive(:null).with().and_return(false)
280
+ expect_options_transform({ }, 123, {
281
+ :unknown_fields => :preserve,
282
+ :length_limit => 123,
283
+ :storage => :text,
284
+ :binary_header => true,
285
+ :compress_if_over_length => 200,
286
+ :null => false
287
+ }, :foo)
288
+ end
289
+
290
+ it "should pass through the column type correctly for a JSON column" do
291
+ expect_options_transform({ }, 123, {
292
+ :unknown_fields => :preserve,
293
+ :length_limit => 123,
294
+ :storage => :json,
295
+ :binary_header => true,
296
+ :compress_if_over_length => 200,
297
+ :null => true
298
+ }, :ajson)
299
+ end
300
+
301
+ it "should pass through disabled compression correctly" do
302
+ expect_options_transform({ :compress => false }, nil, {
303
+ :unknown_fields => :preserve,
304
+ :length_limit => nil,
305
+ :storage => :text,
306
+ :binary_header => true,
307
+ :null => true
308
+ })
309
+ end
310
+
311
+ it "should pass through a compression setting correctly" do
312
+ expect_options_transform({ :compress => 234 }, nil, {
313
+ :unknown_fields => :preserve,
314
+ :length_limit => nil,
315
+ :storage => :text,
316
+ :binary_header => true,
317
+ :compress_if_over_length => 234,
318
+ :null => true
319
+ })
320
+ end
321
+
322
+ it "should pass through a no-binary-header setting correctly" do
323
+ expect_options_transform({ :header => false }, nil, {
324
+ :unknown_fields => :preserve,
325
+ :length_limit => nil,
326
+ :storage => :text,
327
+ :binary_header => false,
328
+ :compress_if_over_length => 200,
329
+ :null => true
330
+ })
331
+ end
332
+ end
333
+
334
+ describe "#delegation_prefix" do
335
+ it "should not return a prefix if there isn't one" do
336
+ @klass.setup!(@model_class, :foo) { }
337
+ @klass.delegation_prefix.should_not be
338
+ end
339
+
340
+ it "should return a prefix if there is one" do
341
+ @klass.setup!(@model_class, :foo, :prefix => :baz) { }
342
+ @klass.delegation_prefix.should == "baz"
343
+ end
344
+ end
345
+
346
+ describe "#delegation_type" do
347
+ it "should return :public by default" do
348
+ @klass.setup!(@model_class, :foo) { }
349
+ @klass.delegation_type.should == :public
350
+ end
351
+
352
+ it "should return nil if none by default" do
353
+ @klass.setup!(@model_class, :foo, :delegate => false) { }
354
+ @klass.delegation_type.should == nil
355
+ end
356
+
357
+ it "should return :public if explicitly public" do
358
+ @klass.setup!(@model_class, :foo, :delegate => true) { }
359
+ @klass.delegation_type.should == :public
360
+ end
361
+
362
+ it "should return :private if private" do
363
+ @klass.setup!(@model_class, :foo, :delegate => :private) { }
364
+ @klass.delegation_type.should == :private
365
+ end
366
+ end
367
+
368
+ describe "#fields_are_private_by_default?" do
369
+ it "should be false by default" do
370
+ @klass.setup!(@model_class, :foo) { }
371
+ @klass.fields_are_private_by_default?.should_not be
372
+ end
373
+
374
+ it "should be true if specified" do
375
+ @klass.setup!(@model_class, :foo, :visibility => :private) { }
376
+ @klass.fields_are_private_by_default?.should be
377
+ end
378
+ end
379
+
380
+ describe "#all_field_names" do
381
+ it "should delegate to the field set" do
382
+ @klass.setup!(@model_class, :foo) { }
383
+ expect(@field_set).to receive(:all_field_names).once.with().and_return([ :a, :x, :z, :q ])
384
+ @klass.all_field_names.should == [ :a, :x, :z, :q ]
385
+ end
386
+ end
387
+
388
+ describe "#sync_methods!" do
389
+ it "should create a dynamic-methods module and delegate to the field set" do
390
+ @klass.setup!(@model_class, :foo) { }
391
+
392
+ dmm = double("dmm")
393
+ expect(FlexColumns::Util::DynamicMethodsModule).to receive(:new).once.with(@klass, :FlexFieldsDynamicMethods).and_return(dmm)
394
+ expect(dmm).to receive(:remove_all_methods!).with().once
395
+
396
+ mc_dmm = double("mc_dmm")
397
+ allow(@model_class).to receive(:_flex_column_dynamic_methods_module).with().and_return(mc_dmm)
398
+ expect(@field_set).to receive(:add_delegated_methods!).once.with(dmm, mc_dmm, @model_class)
399
+
400
+ @klass.sync_methods!
401
+ end
402
+
403
+ it "should reuse the dynamic-methods module" do
404
+ @klass.setup!(@model_class, :foo) { }
405
+
406
+ dmm = double("dmm")
407
+ expect(FlexColumns::Util::DynamicMethodsModule).to receive(:new).once.with(@klass, :FlexFieldsDynamicMethods).and_return(dmm)
408
+ expect(dmm).to receive(:remove_all_methods!).with().once
409
+
410
+ mc_dmm = double("mc_dmm")
411
+ allow(@model_class).to receive(:_flex_column_dynamic_methods_module).with().and_return(mc_dmm)
412
+ expect(@field_set).to receive(:add_delegated_methods!).once.with(dmm, mc_dmm, @model_class)
413
+
414
+ @klass.sync_methods!
415
+
416
+ expect(dmm).to receive(:remove_all_methods!).with().once
417
+ expect(@field_set).to receive(:add_delegated_methods!).once.with(dmm, mc_dmm, @model_class)
418
+
419
+ @klass.sync_methods!
420
+ end
421
+
422
+ it "should add custom methods, with :visibility specified correctly" do
423
+ @klass.setup!(@model_class, :foo, :delegate => :private) do
424
+ def cm1(*args, &block)
425
+ "cm1!"
426
+ end
427
+ end
428
+
429
+ dmm = double("dmm")
430
+ expect(FlexColumns::Util::DynamicMethodsModule).to receive(:new).once.with(@klass, :FlexFieldsDynamicMethods).and_return(dmm)
431
+ expect(dmm).to receive(:remove_all_methods!).with().once
432
+
433
+ mc_dmm = Class.new
434
+ mc_dmm.class_eval do
435
+ class << self
436
+ public :define_method, :private
437
+ end
438
+ end
439
+
440
+ allow(@model_class).to receive(:_flex_column_dynamic_methods_module).with().and_return(mc_dmm)
441
+ expect(@field_set).to receive(:add_delegated_methods!).once.with(dmm, mc_dmm, @model_class)
442
+
443
+ allow(@model_class).to receive(:_flex_columns_safe_to_define_method?).with("cm1").and_return(true)
444
+
445
+ @klass.sync_methods!
446
+
447
+ fco = Object.new
448
+ class << fco
449
+ def cm1(*args, &block)
450
+ "cm1: #{args.join(", ")}: #{block.call(*args)}"
451
+ end
452
+ end
453
+
454
+ o = mc_dmm.new
455
+ expect(o).to receive(:_flex_column_object_for).with(:foo).and_return(fco)
456
+
457
+ lambda { o.cm1 }.should raise_error(NoMethodError)
458
+ result = o.send(:cm1, :foo, :bar) { |*args| args.join("X") }
459
+ result.should == "cm1: foo, bar: fooXbar"
460
+ end
461
+ end
462
+
463
+ it "should return the column name from #column_name" do
464
+ @klass.setup!(@model_class, :foo) { }
465
+ @klass.column_name.should == :foo
466
+ end
467
+
468
+ context "with a set-up class" do
469
+ before :each do
470
+ @klass.setup!(@model_class, :foo) { }
471
+ end
472
+
473
+ it "should pass through :field to the field set" do
474
+ expect(@field_set).to receive(:field).once.with(:foo, :bar, :baz => :quux)
475
+ @klass.field(:foo, :bar, :baz => :quux)
476
+ end
477
+
478
+ it "should pass through :field_named to the field set" do
479
+ expect(@field_set).to receive(:field_named).once.with(:quux).and_return(:bar)
480
+ @klass.field_named(:quux).should == :bar
481
+ end
482
+
483
+ it "should pass through :field_with_json_storage_name to the field set" do
484
+ expect(@field_set).to receive(:field_with_json_storage_name).once.with(:quux).and_return(:bar)
485
+ @klass.field_with_json_storage_name(:quux).should == :bar
486
+ end
487
+
488
+ it "should be a flex-column class" do
489
+ @klass.is_flex_column_class?.should be
490
+ end
491
+
492
+ describe "#requires_serialization_on_save?" do
493
+ it "should be true if there's an object and it has been touched" do
494
+ model = double("model")
495
+ fco = double("fco")
496
+ allow(model).to receive(:_flex_column_object_for).with(:foo, false).and_return(fco)
497
+ allow(fco).to receive(:touched?).with().and_return(true)
498
+ @klass.requires_serialization_on_save?(model).should be
499
+ end
500
+
501
+ it "should be false if there's an object but it hasn't been touched" do
502
+ model = double("model")
503
+ fco = double("fco")
504
+ allow(model).to receive(:_flex_column_object_for).with(:foo, false).and_return(fco)
505
+ allow(fco).to receive(:touched?).with().and_return(false)
506
+ @klass.requires_serialization_on_save?(model).should_not be
507
+ end
508
+
509
+ it "should be false if there's no object and the column is NULLable" do
510
+ model = double("model")
511
+ fco = double("fco")
512
+ allow(model).to receive(:_flex_column_object_for).with(:foo, false).and_return(nil)
513
+ allow(@column_foo).to receive(:null).with().and_return(true)
514
+ @klass.requires_serialization_on_save?(model).should_not be
515
+ end
516
+
517
+ it "should be false if there's no object and the column is not null, but there's data" do
518
+ model = double("model")
519
+ fco = double("fco")
520
+ allow(model).to receive(:_flex_column_object_for).with(:foo, false).and_return(nil)
521
+ allow(@column_foo).to receive(:null).with().and_return(false)
522
+ allow(model).to receive(:[]).with(:foo).and_return("some data")
523
+ @klass.requires_serialization_on_save?(model).should_not be
524
+ end
525
+
526
+ it "should be true if there's no object and the column is not null, and there's no data" do
527
+ model = double("model")
528
+ fco = double("fco")
529
+ allow(model).to receive(:_flex_column_object_for).with(:foo, false).and_return(nil)
530
+ allow(@column_foo).to receive(:null).with().and_return(false)
531
+ allow(model).to receive(:[]).with(:foo).and_return(nil)
532
+ @klass.requires_serialization_on_save?(model).should be
533
+ end
534
+ end
535
+
536
+ describe "#include_fields_into" do
537
+ before :each do
538
+ @dmm = Class.new
539
+ @dmm.class_eval do
540
+ def bar_return=(x)
541
+ @bar_return = x
542
+ end
543
+
544
+ def bar
545
+ @bar_return
546
+ end
547
+
548
+ def build_bar_return=(x)
549
+ @build_bar_return = x
550
+ end
551
+
552
+ def build_bar
553
+ @build_bar_return
554
+ end
555
+
556
+ def set_flex_column_object_for!(x, y)
557
+ @_flex_column_objects_for ||= { }
558
+ @_flex_column_objects_for[x] = y
559
+ end
560
+
561
+ def _flex_column_object_for(x)
562
+ @_flex_column_objects_for[x]
563
+ end
564
+
565
+ class << self
566
+ public :define_method, :private
567
+ end
568
+ end
569
+
570
+ @target_class = double("target_class")
571
+ @associated_object = double("associated_object")
572
+ end
573
+
574
+ it "should define a method that's safe to define, and nothing else, if :delegate => false" do
575
+ expect(@target_class).to receive(:_flex_columns_safe_to_define_method?).with("foo").and_return(true)
576
+
577
+ @klass.include_fields_into(@dmm, :bar, @target_class, { :delegate => false })
578
+
579
+ expect(@associated_object).to receive(:foo).once.and_return(:quux)
580
+ instance = @dmm.new
581
+ instance.bar_return = @associated_object
582
+
583
+ instance.foo.should == :quux
584
+ end
585
+
586
+ it "should not define a method that's not safe to define" do
587
+ expect(@target_class).to receive(:_flex_columns_safe_to_define_method?).with("foo").and_return(false)
588
+
589
+ @klass.include_fields_into(@dmm, :bar, @target_class, { :delegate => false })
590
+
591
+ instance = @dmm.new
592
+ lambda { instance.send(:foo) }.should raise_error(NoMethodError)
593
+ end
594
+
595
+ it "should define a method that falls back to build_<x>" do
596
+ expect(@target_class).to receive(:_flex_columns_safe_to_define_method?).with("foo").and_return(true)
597
+
598
+ @klass.include_fields_into(@dmm, :bar, @target_class, { :delegate => false })
599
+
600
+ expect(@associated_object).to receive(:foo).once.and_return(:quux)
601
+ instance = @dmm.new
602
+ instance.build_bar_return = @associated_object
603
+ instance.foo.should == :quux
604
+ end
605
+
606
+ it "should define a method that's private, if requested" do
607
+ defined_block = nil
608
+
609
+ expect(@target_class).to receive(:_flex_columns_safe_to_define_method?).with("foo").and_return(true)
610
+
611
+ @klass.include_fields_into(@dmm, :bar, @target_class, { :delegate => false, :visibility => :private })
612
+
613
+ expect(@associated_object).to receive(:foo).once.and_return(:quux)
614
+ instance = @dmm.new
615
+ instance.bar_return = @associated_object
616
+
617
+ lambda { instance.foo }.should raise_error(NoMethodError)
618
+ instance.send(:foo).should == :quux
619
+ end
620
+
621
+ it "should prefix the method name, if requested" do
622
+ defined_block = nil
623
+
624
+ expect(@target_class).to receive(:_flex_columns_safe_to_define_method?).with("baz_foo").and_return(true)
625
+
626
+ @klass.include_fields_into(@dmm, :bar, @target_class, { :delegate => false, :prefix => "baz" })
627
+
628
+ expect(@associated_object).to receive(:foo).once.and_return(:quux)
629
+ instance = @dmm.new
630
+ instance.bar_return = @associated_object
631
+
632
+ instance.baz_foo.should == :quux
633
+ end
634
+
635
+ it "should add custom methods, and prefix them if needed" do
636
+ @klass = Class.new
637
+ @klass.send(:extend, FlexColumns::Definition::FlexColumnContentsClass)
638
+ @klass.setup!(@model_class, :foo) { def cm1(*args); "cm1!: #{args.join(", ")}: #{yield *args}"; end }
639
+
640
+ defined_block = nil
641
+ cm_defined_block = nil
642
+
643
+ expect(@target_class).to receive(:_flex_columns_safe_to_define_method?).with("baz_foo").and_return(true)
644
+ expect(@target_class).to receive(:_flex_columns_safe_to_define_method?).with("baz_cm1").and_return(true)
645
+
646
+ expect(@field_set).to receive(:include_fields_into).once.with(@dmm, :bar, @target_class, { :prefix => "baz" })
647
+
648
+ @klass.include_fields_into(@dmm, :bar, @target_class, { :prefix => "baz" })
649
+
650
+ expect(@associated_object).to receive(:foo).once.and_return(:quux)
651
+ instance = @dmm.new
652
+ instance.bar_return = @associated_object
653
+
654
+ instance.baz_foo.should == :quux
655
+
656
+ flex_object = Object.new
657
+ class << flex_object
658
+ def cm1(*args, &b)
659
+ "cm1 - #{args.join(", ")} - #{b.call(*args)}"
660
+ end
661
+ end
662
+
663
+ instance.set_flex_column_object_for!(:foo, flex_object)
664
+ result = instance.baz_cm1(:bar, :baz) { |*args| args.join("X") }
665
+ result.should == "cm1 - bar, baz - barXbaz"
666
+ end
667
+
668
+ it "should not add custom methods if they aren't safe" do
669
+ @klass = Class.new
670
+ @klass.send(:extend, FlexColumns::Definition::FlexColumnContentsClass)
671
+ @klass.setup!(@model_class, :foo) { def cm1(*args); "cm1!: #{args.join(", ")}: #{yield *args}"; end }
672
+
673
+ defined_block = nil
674
+ cm_defined_block = nil
675
+
676
+ expect(@target_class).to receive(:_flex_columns_safe_to_define_method?).with("foo").and_return(true)
677
+ expect(@target_class).to receive(:_flex_columns_safe_to_define_method?).with("cm1").and_return(false)
678
+
679
+ expect(@field_set).to receive(:include_fields_into).once.with(@dmm, :bar, @target_class, { })
680
+
681
+ @klass.include_fields_into(@dmm, :bar, @target_class, { })
682
+
683
+ expect(@associated_object).to receive(:foo).once.and_return(:quux)
684
+ instance = @dmm.new
685
+ instance.bar_return = @associated_object
686
+
687
+ instance.foo.should == :quux
688
+
689
+ lambda { instance.send(:cm1) }.should raise_error(NoMethodError)
690
+ end
691
+
692
+ it "should make custom methods private if requested" do
693
+ @klass = Class.new
694
+ @klass.send(:extend, FlexColumns::Definition::FlexColumnContentsClass)
695
+ @klass.setup!(@model_class, :foo) { def cm1(*args); "cm1!: #{args.join(", ")}: #{yield *args}"; end }
696
+
697
+ defined_block = nil
698
+ cm_defined_block = nil
699
+
700
+ expect(@target_class).to receive(:_flex_columns_safe_to_define_method?).with("baz_foo").and_return(true)
701
+ expect(@target_class).to receive(:_flex_columns_safe_to_define_method?).with("baz_cm1").and_return(true)
702
+
703
+ expect(@field_set).to receive(:include_fields_into).once.with(@dmm, :bar, @target_class, { :prefix => "baz" })
704
+
705
+ @klass.include_fields_into(@dmm, :bar, @target_class, { :prefix => "baz" })
706
+
707
+ expect(@associated_object).to receive(:foo).once.and_return(:quux)
708
+ instance = @dmm.new
709
+ instance.bar_return = @associated_object
710
+
711
+ instance.baz_foo.should == :quux
712
+
713
+ flex_object = Object.new
714
+ class << flex_object
715
+ def cm1(*args, &b)
716
+ "cm1 - #{args.join(", ")} - #{b.call(*args)}"
717
+ end
718
+ end
719
+
720
+ instance.set_flex_column_object_for!(:foo, flex_object)
721
+ lambda { instance.baz_cm1 }.should raise_error(NoMethodError)
722
+ result = instance.send(:baz_cm1, :bar, :baz) { |*args| args.join("X") }
723
+ result.should == "cm1 - bar, baz - barXbaz"
724
+ end
725
+ end
726
+
727
+ it "should delegate to the model instance on #object_for" do
728
+ model_instance = double("model_instance")
729
+ expect(model_instance).to receive(:_flex_column_object_for).once.with(:foo).and_return(:quux)
730
+ @klass.object_for(model_instance).should == :quux
731
+ end
732
+ end
733
+ end