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