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,297 @@
1
+ require 'flex_columns'
2
+
3
+ describe FlexColumns::Errors do
4
+ def should_be_a_subclass(klass, expected_superclass)
5
+ current = klass
6
+
7
+ while current != expected_superclass && current != Object
8
+ current = current.superclass
9
+ end
10
+
11
+ if current == Object
12
+ raise "Expected #{klass} to have #{expected_superclass} as a superclass, but it doesn't"
13
+ end
14
+ end
15
+
16
+ before :each do
17
+ @data_source = double("data_source")
18
+ allow(@data_source).to receive(:describe_flex_column_data_source).with().and_return("dfcds")
19
+ end
20
+
21
+ describe FlexColumns::Errors::FieldError do
22
+ it "should inherit from Base" do
23
+ should_be_a_subclass(FlexColumns::Errors::FieldError, FlexColumns::Errors::Base)
24
+ end
25
+ end
26
+
27
+ describe FlexColumns::Errors::NoSuchFieldError do
28
+ it "should inherit from FieldError" do
29
+ should_be_a_subclass(FlexColumns::Errors::NoSuchFieldError, FlexColumns::Errors::FieldError)
30
+ end
31
+
32
+ it "should take data_source, data_name, and all_field_names, and use them in its message" do
33
+ instance = FlexColumns::Errors::NoSuchFieldError.new(@data_source, :foo, [ :bar, :baz, :quux ])
34
+ instance.message.should match(/dfcds/i)
35
+ instance.message.should match(/foo/i)
36
+ instance.message.should match(/bar.*baz.*quux/i)
37
+
38
+ instance.data_source.should be(@data_source)
39
+ instance.field_name.should == :foo
40
+ instance.all_field_names.should == [ :bar, :baz, :quux ]
41
+ end
42
+ end
43
+
44
+ describe FlexColumns::Errors::ConflictingJsonStorageNameError do
45
+ it "should inherit from FieldError" do
46
+ should_be_a_subclass(FlexColumns::Errors::ConflictingJsonStorageNameError, FlexColumns::Errors::FieldError)
47
+ end
48
+
49
+ it "should take model_class, column_name, new_field_name, existing_field_name, and json_storage_name" do
50
+ model_class = double("model_class")
51
+ allow(model_class).to receive(:name).with().and_return("mcname")
52
+
53
+ instance = FlexColumns::Errors::ConflictingJsonStorageNameError.new(model_class, :foo, :bar, :baz, :quux)
54
+ instance.model_class.should be(model_class)
55
+ instance.column_name.should == :foo
56
+ instance.new_field_name.should == :bar
57
+ instance.existing_field_name.should == :baz
58
+ instance.json_storage_name.should == :quux
59
+
60
+ instance.message.should match(/mcname/i)
61
+ instance.message.should match(/foo/i)
62
+ instance.message.should match(/bar/i)
63
+ instance.message.should match(/baz/i)
64
+ instance.message.should match(/quux/i)
65
+ end
66
+ end
67
+
68
+ describe FlexColumns::Errors::DefinitionError do
69
+ it "should inherit from Base" do
70
+ should_be_a_subclass(FlexColumns::Errors::DefinitionError, FlexColumns::Errors::Base)
71
+ end
72
+ end
73
+
74
+ describe FlexColumns::Errors::NoSuchColumnError do
75
+ it "should inherit from DefinitionError" do
76
+ should_be_a_subclass(FlexColumns::Errors::NoSuchColumnError, FlexColumns::Errors::DefinitionError)
77
+ end
78
+ end
79
+
80
+ describe FlexColumns::Errors::InvalidColumnTypeError do
81
+ it "should inherit from DefinitionError" do
82
+ should_be_a_subclass(FlexColumns::Errors::InvalidColumnTypeError, FlexColumns::Errors::DefinitionError)
83
+ end
84
+ end
85
+
86
+ describe FlexColumns::Errors::DataError do
87
+ it "should inherit from Base" do
88
+ should_be_a_subclass(FlexColumns::Errors::DataError, FlexColumns::Errors::Base)
89
+ end
90
+ end
91
+
92
+ describe FlexColumns::Errors::JsonTooLongError do
93
+ it "should inherit from DataError" do
94
+ should_be_a_subclass(FlexColumns::Errors::JsonTooLongError, FlexColumns::Errors::DataError)
95
+ end
96
+
97
+ it "should take data_source, limit, and json_string" do
98
+ instance = FlexColumns::Errors::JsonTooLongError.new(@data_source, 123, "a" * 10_000)
99
+ instance.data_source.should be(@data_source)
100
+ instance.limit.should == 123
101
+ instance.json_string.should == "a" * 10_000
102
+
103
+ instance.message.length.should < 1_000
104
+ instance.message.should match(/123/)
105
+ instance.message.should match(/dfcds/)
106
+ instance.message.should match("a" * 30)
107
+ end
108
+ end
109
+
110
+ describe FlexColumns::Errors::InvalidDataInDatabaseError do
111
+ it "should inherit from DataError" do
112
+ should_be_a_subclass(FlexColumns::Errors::InvalidDataInDatabaseError, FlexColumns::Errors::DataError)
113
+ end
114
+
115
+ it "should take data_source, raw_string, and additional_message" do
116
+ instance = FlexColumns::Errors::InvalidDataInDatabaseError.new(@data_source, "aaa" * 1_000, "holy hell")
117
+ instance.data_source.should be(@data_source)
118
+ instance.raw_string.should == "aaa" * 1_000
119
+ instance.additional_message.should == "holy hell"
120
+
121
+ instance.message.length.should < 1_000
122
+ instance.message.should match(/dfcds/)
123
+ instance.message.should match("a" * 20)
124
+ instance.message.should match(/holy hell/)
125
+ end
126
+ end
127
+
128
+ describe FlexColumns::Errors::InvalidCompressedDataInDatabaseError do
129
+ it "should inherit from InvalidDataInDatabaseError" do
130
+ should_be_a_subclass(FlexColumns::Errors::InvalidCompressedDataInDatabaseError, FlexColumns::Errors::InvalidDataInDatabaseError)
131
+ end
132
+
133
+ it "should take data_source, raw_string, and source_exception" do
134
+ source_exception = double("source_exception")
135
+ source_exception_class = double("source_exception_class")
136
+ allow(source_exception_class).to receive(:name).with().and_return("secname")
137
+ allow(source_exception).to receive(:class).with().and_return(source_exception_class)
138
+ allow(source_exception).to receive(:to_s).with().and_return("seto_s")
139
+
140
+ instance = FlexColumns::Errors::InvalidCompressedDataInDatabaseError.new(@data_source, "a" * 1_000, source_exception)
141
+ instance.data_source.should be(@data_source)
142
+ instance.raw_string.should == "a" * 1_000
143
+ instance.source_exception.should be(source_exception)
144
+
145
+ instance.message.length.should < 1_000
146
+ instance.message.should match(/dfcds/)
147
+ instance.message.should match("a" * 20)
148
+ instance.message.should match(/secname/)
149
+ instance.message.should match(/seto_s/)
150
+ end
151
+ end
152
+
153
+ describe FlexColumns::Errors::InvalidFlexColumnsVersionNumberInDatabaseError do
154
+ it "should inherit from InvalidDataInDatabaseError" do
155
+ should_be_a_subclass(FlexColumns::Errors::InvalidFlexColumnsVersionNumberInDatabaseError, FlexColumns::Errors::InvalidDataInDatabaseError)
156
+ end
157
+
158
+ it "should take data_source, raw_string, version_number_in_database, max_version_number_supported" do
159
+ instance = FlexColumns::Errors::InvalidFlexColumnsVersionNumberInDatabaseError.new(@data_source, "a" * 1_000, 17, 15)
160
+ instance.data_source.should be(@data_source)
161
+ instance.raw_string.should == "a" * 1_000
162
+ instance.version_number_in_database.should == 17
163
+ instance.max_version_number_supported.should == 15
164
+
165
+ instance.message.length.should < 1_000
166
+ instance.message.should match(/dfcds/)
167
+ instance.message.should match("a" * 20)
168
+ instance.message.should match(/17/)
169
+ instance.message.should match(/15/)
170
+ end
171
+ end
172
+
173
+ describe FlexColumns::Errors::UnparseableJsonInDatabaseError do
174
+ it "should inherit from InvalidDataInDatabaseError" do
175
+ should_be_a_subclass(FlexColumns::Errors::UnparseableJsonInDatabaseError, FlexColumns::Errors::InvalidDataInDatabaseError)
176
+ end
177
+
178
+ it "should take data_source, raw_string, and source_exception" do
179
+ source_exception = double("source_exception")
180
+ source_exception_class = double("source_exception_class")
181
+ allow(source_exception_class).to receive(:name).with().and_return("secname")
182
+ allow(source_exception).to receive(:class).with().and_return(source_exception_class)
183
+ allow(source_exception).to receive(:message).with().and_return("semessage")
184
+
185
+ instance = FlexColumns::Errors::UnparseableJsonInDatabaseError.new(@data_source, "a" * 1_000, source_exception)
186
+ instance.data_source.should be(@data_source)
187
+ instance.raw_string.should == "a" * 1_000
188
+ instance.source_exception.should be(source_exception)
189
+
190
+ instance.message.length.should < 1_000
191
+ instance.message.should match(/dfcds/)
192
+ instance.message.should match("a" * 20)
193
+ instance.message.should match(/secname/)
194
+ instance.message.should match(/semessage/)
195
+ end
196
+
197
+ it "should strip out characters that aren't of a valid encoding" do
198
+ source_exception = double("source_exception")
199
+ source_exception_class = double("source_exception_class")
200
+ allow(source_exception_class).to receive(:name).with().and_return("secname")
201
+ allow(source_exception).to receive(:class).with().and_return(source_exception_class)
202
+
203
+ source_message = double("source_message")
204
+ expect(source_message).to receive(:force_encoding).once.with("UTF-8")
205
+ chars = [ double("char-a"), double("char-b"), double("char-c") ]
206
+ allow(chars[0]).to receive(:valid_encoding?).with().and_return(true)
207
+ allow(chars[0]).to receive(:to_s).with().and_return("X")
208
+ allow(chars[0]).to receive(:inspect).with().and_return("X")
209
+ allow(chars[1]).to receive(:valid_encoding?).with().and_return(false)
210
+ allow(chars[2]).to receive(:valid_encoding?).with().and_return(true)
211
+ allow(chars[2]).to receive(:to_s).with().and_return("Z")
212
+ allow(chars[2]).to receive(:inspect).with().and_return("Z")
213
+ allow(source_message).to receive(:chars).with().and_return(chars)
214
+
215
+ allow(source_exception).to receive(:message).with().and_return(source_message)
216
+
217
+ instance = FlexColumns::Errors::UnparseableJsonInDatabaseError.new(@data_source, "a" * 1_000, source_exception)
218
+ instance.data_source.should be(@data_source)
219
+ instance.raw_string.should == "a" * 1_000
220
+ instance.source_exception.should be(source_exception)
221
+
222
+ instance.message.length.should < 1_000
223
+ instance.message.should match(/dfcds/)
224
+ instance.message.should match("a" * 20)
225
+ instance.message.should match(/secname/)
226
+ instance.message.should match(/XZ/)
227
+ end
228
+ end
229
+
230
+ describe FlexColumns::Errors::IncorrectlyEncodedStringInDatabaseError do
231
+ it "should inherit from InvalidDataInDatabaseError" do
232
+ should_be_a_subclass(FlexColumns::Errors::IncorrectlyEncodedStringInDatabaseError, FlexColumns::Errors::InvalidDataInDatabaseError)
233
+ end
234
+
235
+ it "should take data_source and raw_string, and analyze the string" do
236
+ chars = [ ]
237
+ 200.times { |i| chars << double("char-#{i}") }
238
+ chars.each_with_index do |char, i|
239
+ allow(char).to receive(:valid_encoding?).with().and_return(true)
240
+ allow(char).to receive(:to_s).with().and_return((65 + (i % 26)).chr)
241
+ allow(char).to receive(:inspect).with().and_return((65 + (i % 26)).chr)
242
+ end
243
+
244
+ [ 53, 68, 95 ].each do |bad_char_pos|
245
+ allow(chars[bad_char_pos]).to receive(:valid_encoding?).with().and_return(false)
246
+ allow(chars[bad_char_pos]).to receive(:unpack).with("H*").and_return("UPC#{bad_char_pos}")
247
+ end
248
+
249
+ allow(chars[53]).to receive(:valid_encoding?).with().and_return(false)
250
+ allow(chars[68]).to receive(:valid_encoding?).with().and_return(false)
251
+ allow(chars[95]).to receive(:valid_encoding?).with().and_return(false)
252
+
253
+ raw_string = double("raw_string")
254
+ allow(raw_string).to receive(:chars).with().and_return(chars)
255
+
256
+ instance = FlexColumns::Errors::IncorrectlyEncodedStringInDatabaseError.new(@data_source, raw_string)
257
+
258
+ instance.data_source.should be(@data_source)
259
+ instance.raw_string.should match(/^ABCDEF/)
260
+ instance.raw_string.length.should == 197
261
+ instance.raw_string[52..54].should == "ACD"
262
+ instance.raw_string[66..68].should == "PRS"
263
+ instance.raw_string[91..93].should == "PQS"
264
+
265
+ instance.invalid_chars_as_array.should == [ chars[53], chars[68], chars[95] ]
266
+ instance.raw_data_as_array.should == chars
267
+ instance.first_bad_position.should == 53
268
+
269
+ instance.message.should match(/dfcds/)
270
+ instance.message.should match(/3/)
271
+ instance.message.should match(/200/)
272
+ instance.message.should match(/53/)
273
+ instance.message.should match(/UPC53.*UPC68.*UPC95/)
274
+ end
275
+ end
276
+
277
+ describe FlexColumns::Errors::InvalidJsonInDatabaseError do
278
+ it "should inherit from InvalidDataInDatabaseError" do
279
+ should_be_a_subclass(FlexColumns::Errors::InvalidJsonInDatabaseError, FlexColumns::Errors::InvalidDataInDatabaseError)
280
+ end
281
+
282
+ it "should take a data_source, raw_string, and returned_data" do
283
+ raw_string = " [ 1, 2, 3 ] "
284
+ returned_data = [ 9, 12, 15 ]
285
+ instance = FlexColumns::Errors::InvalidJsonInDatabaseError.new(@data_source, raw_string, returned_data)
286
+
287
+ instance.data_source.should be(@data_source)
288
+ instance.raw_string.should == raw_string
289
+ instance.returned_data.should be(returned_data)
290
+
291
+ instance.message.should match(/dfcds/)
292
+ instance.message.should match(raw_string)
293
+ instance.message.should match(/Array/)
294
+ instance.message.should match(/9\s*,\s*12\s*,\s*15\s*/)
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,365 @@
1
+ require 'flex_columns'
2
+ require 'flex_columns/helpers/exception_helpers'
3
+
4
+ describe FlexColumns::HasFlexColumns do
5
+ include FlexColumns::Helpers::ExceptionHelpers
6
+
7
+ before :each do
8
+ @superclass = Class.new
9
+
10
+ @klass = Class.new(@superclass) do
11
+ class << self
12
+ def before_validation(*args)
13
+ @_before_validation_calls ||= [ ]
14
+ @_before_validation_calls << args
15
+ end
16
+
17
+ def before_save(*args)
18
+ @_before_save_calls ||= [ ]
19
+ @_before_save_calls << args
20
+ end
21
+
22
+ def before_validation_calls
23
+ @_before_validation_calls
24
+ end
25
+
26
+ def before_save_calls
27
+ @_before_save_calls
28
+ end
29
+ end
30
+ end
31
+
32
+ @klass_name = "HasFlexColumnsSpec_#{rand(1_000_000_000)}"
33
+ ::Object.const_set(@klass_name, @klass)
34
+
35
+ @klass.send(:include, FlexColumns::HasFlexColumns)
36
+ end
37
+
38
+ it "should call before_validation and before_save to set up hooks properly" do
39
+ @klass.before_validation_calls.length.should == 1
40
+ @klass.before_validation_calls[0].should == [ :_flex_columns_before_validation! ]
41
+ @klass.before_save_calls.length.should == 1
42
+ @klass.before_save_calls[0].should == [ :_flex_columns_before_save! ]
43
+ end
44
+
45
+ it "should normalize names properly" do
46
+ @klass._flex_column_normalize_name(" FoO ").should == :foo
47
+ @klass._flex_column_normalize_name(:fOo).should == :foo
48
+ end
49
+
50
+ it "should say it has flex columns" do
51
+ @klass.has_any_flex_columns?.should be
52
+ end
53
+
54
+ describe "#flex_column" do
55
+ it "should normalize the name of the column" do
56
+ fcc = double("fcc")
57
+ expect(Class).to receive(:new).once.with(FlexColumns::Contents::FlexColumnContentsBase).and_return(fcc)
58
+ expect(fcc).to receive(:setup!).once.with(@klass, :foo, { })
59
+ allow(fcc).to receive(:sync_methods!).with()
60
+
61
+ @klass.flex_column(' fOo ')
62
+ end
63
+
64
+ it "should replace existing columns, call #remove_all_methods! and #sync_methods! appropriately, and define a method that returns the right object" do
65
+ fcc_foo = double("fcc_foo")
66
+ expect(Class).to receive(:new).once.with(FlexColumns::Contents::FlexColumnContentsBase).and_return(fcc_foo)
67
+ expect(fcc_foo).to receive(:quux).once.with(:a, :z, :q)
68
+
69
+ passed_block = nil
70
+ expect(fcc_foo).to receive(:setup!).once.with(@klass, :foo, { }) do |*args, &block|
71
+ passed_block = block
72
+ end
73
+ allow(fcc_foo).to receive(:column_name).with().and_return(:foo)
74
+
75
+ dmm = double("dmm")
76
+ expect(FlexColumns::Util::DynamicMethodsModule).to receive(:new).once.with(@klass, :FlexColumnsDynamicMethods).and_return(dmm)
77
+
78
+ expect(dmm).to receive(:remove_all_methods!).once.with()
79
+ expect(fcc_foo).to receive(:sync_methods!).once.with()
80
+ @klass.flex_column(:foo) do
81
+ quux(:a, :z, :q)
82
+ end
83
+ fcc_foo.instance_eval(&passed_block)
84
+
85
+ @klass._flex_column_class_for(:foo).should be(fcc_foo)
86
+
87
+ instance = @klass.new
88
+ fcc_foo_instance = double("fcc_foo_instance")
89
+ expect(fcc_foo).to receive(:new).once.with(instance).and_return(fcc_foo_instance)
90
+ instance.foo.should be(fcc_foo_instance)
91
+
92
+
93
+
94
+ fcc_bar = double("fcc_bar")
95
+ expect(Class).to receive(:new).once.with(FlexColumns::Contents::FlexColumnContentsBase).and_return(fcc_bar)
96
+ expect(fcc_bar).to receive(:setup!).once.with(@klass, :bar, { })
97
+ allow(fcc_bar).to receive(:column_name).with().and_return(:bar)
98
+
99
+ expect(dmm).to receive(:remove_all_methods!).once.with()
100
+ expect(fcc_foo).to receive(:sync_methods!).once.with()
101
+ expect(fcc_bar).to receive(:sync_methods!).once.with()
102
+ @klass.flex_column(:bar)
103
+
104
+ @klass._flex_column_class_for(:foo).should be(fcc_foo)
105
+ @klass._flex_column_class_for(:bar).should be(fcc_bar)
106
+
107
+ fcc_bar_instance = double("fcc_bar_instance")
108
+ expect(fcc_bar).to receive(:new).once.with(instance).and_return(fcc_bar_instance)
109
+ instance.foo.should be(fcc_foo_instance)
110
+ instance.bar.should be(fcc_bar_instance)
111
+
112
+
113
+ fcc_foo_2 = double("fcc_foo_2")
114
+ expect(Class).to receive(:new).once.with(FlexColumns::Contents::FlexColumnContentsBase).and_return(fcc_foo_2)
115
+ expect(fcc_foo_2).to receive(:setup!).once.with(@klass, :foo, { :a => :b, :c => :d })
116
+ allow(fcc_foo_2).to receive(:column_name).with().and_return(:foo)
117
+
118
+ expect(dmm).to receive(:remove_all_methods!).once.with()
119
+ expect(fcc_foo_2).to receive(:sync_methods!).once.with()
120
+ expect(fcc_bar).to receive(:sync_methods!).once.with()
121
+
122
+ @klass.flex_column(:foo, :a => :b, :c => :d)
123
+
124
+ @klass._flex_column_class_for(:foo).should be(fcc_foo_2)
125
+ @klass._flex_column_class_for(:bar).should be(fcc_bar)
126
+
127
+ instance = @klass.new
128
+ fcc_foo_2_instance = double("fcc_foo_2_instance")
129
+ expect(fcc_foo_2).to receive(:new).once.with(instance).and_return(fcc_foo_2_instance)
130
+ instance.foo.should be(fcc_foo_2_instance)
131
+
132
+ instance = @klass.new
133
+ fcc_bar_instance = double("fcc_bar_instance")
134
+ expect(fcc_bar).to receive(:new).once.with(instance).and_return(fcc_bar_instance)
135
+ instance.bar.should be(fcc_bar_instance)
136
+ end
137
+ end
138
+
139
+ context "with two declared flex columns" do
140
+ before :each do
141
+ @fcc_foo = double("fcc_foo")
142
+ expect(Class).to receive(:new).once.with(FlexColumns::Contents::FlexColumnContentsBase).and_return(@fcc_foo)
143
+ expect(@fcc_foo).to receive(:setup!).once.with(@klass, :foo, { :aaa => :bbb, :ccc => :ddd })
144
+ expect(@fcc_foo).to receive(:sync_methods!).once
145
+
146
+ allow(@fcc_foo).to receive(:column_name).with().and_return(:foo)
147
+
148
+ @dmm = double("dmm")
149
+ expect(FlexColumns::Util::DynamicMethodsModule).to receive(:new).once.with(@klass, :FlexColumnsDynamicMethods).and_return(@dmm)
150
+ expect(@dmm).to receive(:remove_all_methods!).once.with()
151
+
152
+ @klass.flex_column(:foo, :aaa => :bbb, :ccc => :ddd)
153
+
154
+ @fcc_bar = double("fcc_bar")
155
+ expect(Class).to receive(:new).once.with(FlexColumns::Contents::FlexColumnContentsBase).and_return(@fcc_bar)
156
+ expect(@fcc_bar).to receive(:setup!).once.with(@klass, :bar, { })
157
+ expect(@fcc_bar).to receive(:sync_methods!).once
158
+
159
+ allow(@fcc_bar).to receive(:column_name).with().and_return(:bar)
160
+
161
+ expect(@dmm).to receive(:remove_all_methods!).once.with()
162
+ expect(@fcc_foo).to receive(:sync_methods!).once
163
+ @klass.flex_column(:bar)
164
+ end
165
+
166
+ it "should return the same DynamicMethodsModule every time" do
167
+ @klass._flex_column_dynamic_methods_module.should be(@dmm)
168
+ @klass._flex_column_dynamic_methods_module.should be(@dmm)
169
+ end
170
+
171
+ it "should call through on before_validation to only flex column objects that have been touched" do
172
+ instance = @klass.new
173
+ instance._flex_columns_before_validation!
174
+
175
+ fcc_foo_instance = double("fcc_foo_instance")
176
+ expect(@fcc_foo).to receive(:new).once.with(instance).and_return(fcc_foo_instance)
177
+ instance._flex_column_object_for(:foo).should be(fcc_foo_instance)
178
+ allow(fcc_foo_instance).to receive(:touched?).with().and_return(false)
179
+
180
+ instance._flex_columns_before_validation!
181
+
182
+
183
+ fcc_bar_instance = double("fcc_bar_instance")
184
+ expect(@fcc_bar).to receive(:new).once.with(instance).and_return(fcc_bar_instance)
185
+ instance._flex_column_object_for(:bar).should be(fcc_bar_instance)
186
+ allow(fcc_bar_instance).to receive(:touched?).with().and_return(true)
187
+
188
+ expect(fcc_bar_instance).to receive(:before_validation!).once.with()
189
+ instance._flex_columns_before_validation!
190
+
191
+ allow(fcc_foo_instance).to receive(:touched?).with().and_return(true)
192
+ allow(fcc_bar_instance).to receive(:touched?).with().and_return(true)
193
+
194
+ expect(fcc_foo_instance).to receive(:before_validation!).once.with()
195
+ expect(fcc_bar_instance).to receive(:before_validation!).once.with()
196
+ instance._flex_columns_before_validation!
197
+ end
198
+
199
+ it "should call through on before_save to only flex column objects that say they need it" do
200
+ instance = @klass.new
201
+ allow(@fcc_foo).to receive(:requires_serialization_on_save?).with(instance).and_return(false)
202
+ allow(@fcc_bar).to receive(:requires_serialization_on_save?).with(instance).and_return(false)
203
+ instance._flex_columns_before_save!
204
+
205
+ fcc_foo_instance = double("fcc_foo_instance")
206
+ expect(@fcc_foo).to receive(:new).once.with(instance).and_return(fcc_foo_instance)
207
+ instance._flex_column_object_for(:foo).should be(fcc_foo_instance)
208
+ allow(@fcc_foo).to receive(:requires_serialization_on_save?).with(instance).and_return(false)
209
+
210
+ instance._flex_columns_before_save!
211
+
212
+
213
+ fcc_bar_instance = double("fcc_bar_instance")
214
+ expect(@fcc_bar).to receive(:new).once.with(instance).and_return(fcc_bar_instance)
215
+ instance._flex_column_object_for(:bar).should be(fcc_bar_instance)
216
+ allow(@fcc_bar).to receive(:requires_serialization_on_save?).with(instance).and_return(true)
217
+
218
+ expect(fcc_bar_instance).to receive(:before_save!).once.with()
219
+ instance._flex_columns_before_save!
220
+
221
+ allow(@fcc_foo).to receive(:requires_serialization_on_save?).with(instance).and_return(true)
222
+ allow(@fcc_bar).to receive(:requires_serialization_on_save?).with(instance).and_return(true)
223
+
224
+ expect(fcc_foo_instance).to receive(:before_save!).once.with()
225
+ expect(fcc_bar_instance).to receive(:before_save!).once.with()
226
+ instance._flex_columns_before_save!
227
+ end
228
+
229
+ it "should return the flex-column class from #_flex_column_class_for" do
230
+ @klass._flex_column_class_for(:foo).should be(@fcc_foo)
231
+ @klass._flex_column_class_for(:bar).should be(@fcc_bar)
232
+ @klass._flex_column_class_for(:foo).should be(@fcc_foo)
233
+
234
+ e = capture_exception(FlexColumns::Errors::NoSuchColumnError) { @klass._flex_column_class_for(:baz) }
235
+ e.message.should match(/baz/)
236
+ e.message.should match(/foo/)
237
+ e.message.should match(/bar/)
238
+ e.message.should match(@klass_name)
239
+ end
240
+
241
+ it "should create a method that returns a new instance, but only once" do
242
+ instance = @klass.new
243
+ fcc_foo_instance = double("fcc_foo_instance")
244
+ expect(@fcc_foo).to receive(:new).once.with(instance).and_return(fcc_foo_instance)
245
+ instance.foo.should be(fcc_foo_instance)
246
+ instance.foo.should be(fcc_foo_instance)
247
+ end
248
+
249
+ it "should overwrite previous columns with new ones" do
250
+ fcc_conflicting = double("fcc_conflicting")
251
+ expect(Class).to receive(:new).once.with(FlexColumns::Contents::FlexColumnContentsBase).and_return(fcc_conflicting)
252
+ expect(fcc_conflicting).to receive(:setup!).once.with(@klass, :foo, { })
253
+ expect(fcc_conflicting).to receive(:sync_methods!).once
254
+ allow(fcc_conflicting).to receive(:column_name).with().and_return(:foo)
255
+
256
+ expect(@dmm).to receive(:remove_all_methods!).once.with()
257
+
258
+ expect(@fcc_bar).to receive(:sync_methods!).once
259
+ @klass.flex_column(:foo)
260
+
261
+ instance = @klass.new
262
+ fcc_instance = double("fcc_instance")
263
+ expect(fcc_conflicting).to receive(:new).once.with(instance).and_return(fcc_instance)
264
+ instance.foo.should be(fcc_instance)
265
+ instance.foo.should be(fcc_instance)
266
+ end
267
+
268
+ it "should create, and hold on to, flex-column objects properly" do
269
+ instance = @klass.new
270
+
271
+ fcc_foo_instance = double("fcc_foo_instance")
272
+ expect(@fcc_foo).to receive(:new).once.with(instance).and_return(fcc_foo_instance)
273
+ instance._flex_column_object_for(:foo).should be(fcc_foo_instance)
274
+
275
+ fcc_bar_instance = double("fcc_bar_instance")
276
+ expect(@fcc_bar).to receive(:new).once.with(instance).and_return(fcc_bar_instance)
277
+ instance._flex_column_object_for(:bar).should be(fcc_bar_instance)
278
+
279
+ instance._flex_column_object_for(:foo).should be(fcc_foo_instance)
280
+ instance._flex_column_object_for(' bAr ').should be(fcc_bar_instance)
281
+ end
282
+
283
+ it "should re-create flex-column objects on reload, and call super" do
284
+ @superclass.class_eval do
285
+ def reload
286
+ @reloads ||= 0
287
+ @reloads += 1
288
+ end
289
+
290
+ def reloads
291
+ @reloads ||= 0
292
+ end
293
+ end
294
+
295
+ instance = @klass.new
296
+
297
+ fcc_foo_instance = double("fcc_foo_instance")
298
+ expect(@fcc_foo).to receive(:new).once.with(instance).and_return(fcc_foo_instance)
299
+ instance._flex_column_object_for(:foo).should be(fcc_foo_instance)
300
+
301
+ fcc_bar_instance = double("fcc_bar_instance")
302
+ expect(@fcc_bar).to receive(:new).once.with(instance).and_return(fcc_bar_instance)
303
+ instance._flex_column_object_for(:bar).should be(fcc_bar_instance)
304
+
305
+ instance._flex_column_object_for(:foo).should be(fcc_foo_instance)
306
+ instance._flex_column_object_for(:bar).should be(fcc_bar_instance)
307
+
308
+ instance.reloads.should == 0
309
+ instance.reload
310
+ instance.reloads.should == 1
311
+
312
+ fcc_foo_instance_2 = double("fcc_foo_instance_2")
313
+ expect(@fcc_foo).to receive(:new).once.with(instance).and_return(fcc_foo_instance_2)
314
+ instance._flex_column_object_for(:foo).should be(fcc_foo_instance_2)
315
+
316
+ fcc_bar_instance_2 = double("fcc_bar_instance_2")
317
+ expect(@fcc_bar).to receive(:new).once.with(instance).and_return(fcc_bar_instance_2)
318
+ instance._flex_column_object_for(:bar).should be(fcc_bar_instance_2)
319
+ end
320
+
321
+ it "should tell you what flex-column names have been defined" do
322
+ @klass._all_flex_column_names.sort_by(&:to_s).should == [ :foo, :bar ].sort_by(&:to_s)
323
+ end
324
+
325
+ it "should normalize column names properly" do
326
+ @klass._flex_column_normalize_name(:baz).should == :baz
327
+ @klass._flex_column_normalize_name(:' bAz ').should == :baz
328
+ @klass._flex_column_normalize_name(' bAZ ').should == :baz
329
+ end
330
+
331
+ it "should create flex-column objects upon request that aren't attached to a model instance" do
332
+ fcc_foo_instance = double("fcc_foo_instance")
333
+ expect(@fcc_foo).to receive(:new).once.with(nil).and_return(fcc_foo_instance)
334
+ @klass.create_flex_object_from(:foo, nil).should be(fcc_foo_instance)
335
+
336
+ fcc_foo_instance = double("fcc_foo_instance")
337
+ expect(@fcc_foo).to receive(:new).once.with(" JSON string ").and_return(fcc_foo_instance)
338
+ @klass.create_flex_object_from(:foo, " JSON string ").should be(fcc_foo_instance)
339
+
340
+ fcc_bar_instance_1 = double("fcc_bar_instance_1")
341
+ expect(@fcc_bar).to receive(:new).once.with(nil).and_return(fcc_bar_instance_1)
342
+ fcc_bar_instance_2 = double("fcc_bar_instance_2")
343
+ expect(@fcc_bar).to receive(:new).once.with(" JSON string ").and_return(fcc_bar_instance_2)
344
+ @klass.create_flex_objects_from(:bar, [ nil, " JSON string " ]).should == [ fcc_bar_instance_1, fcc_bar_instance_2 ]
345
+ end
346
+ end
347
+
348
+ describe "flex-column objects" do
349
+ it "should prefer to just return super from _flex_column_object_for" do
350
+ superclass = Class.new do
351
+ def _flex_column_object_for(x)
352
+ "A_#{x}_Z"
353
+ end
354
+ end
355
+
356
+ subclass = Class.new(superclass)
357
+ allow(subclass).to receive(:before_validation).with(:_flex_columns_before_validation!)
358
+ allow(subclass).to receive(:before_save).with(:_flex_columns_before_save!)
359
+ subclass.send(:include, FlexColumns::HasFlexColumns)
360
+
361
+ instance = subclass.new
362
+ instance._flex_column_object_for(:foo).should == "A_foo_Z"
363
+ end
364
+ end
365
+ end