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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.travis.yml +38 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +22 -0
- data/README.md +124 -0
- data/Rakefile +6 -0
- data/flex_columns.gemspec +72 -0
- data/lib/flex_columns.rb +15 -0
- data/lib/flex_columns/active_record/base.rb +57 -0
- data/lib/flex_columns/contents/column_data.rb +376 -0
- data/lib/flex_columns/contents/flex_column_contents_base.rb +188 -0
- data/lib/flex_columns/definition/field_definition.rb +316 -0
- data/lib/flex_columns/definition/field_set.rb +89 -0
- data/lib/flex_columns/definition/flex_column_contents_class.rb +327 -0
- data/lib/flex_columns/errors.rb +236 -0
- data/lib/flex_columns/has_flex_columns.rb +187 -0
- data/lib/flex_columns/including/include_flex_columns.rb +179 -0
- data/lib/flex_columns/util/dynamic_methods_module.rb +86 -0
- data/lib/flex_columns/util/string_utils.rb +31 -0
- data/lib/flex_columns/version.rb +4 -0
- data/spec/flex_columns/helpers/database_helper.rb +174 -0
- data/spec/flex_columns/helpers/exception_helpers.rb +20 -0
- data/spec/flex_columns/helpers/system_helpers.rb +47 -0
- data/spec/flex_columns/system/basic_system_spec.rb +245 -0
- data/spec/flex_columns/system/bulk_system_spec.rb +153 -0
- data/spec/flex_columns/system/compression_system_spec.rb +218 -0
- data/spec/flex_columns/system/custom_methods_system_spec.rb +120 -0
- data/spec/flex_columns/system/delegation_system_spec.rb +175 -0
- data/spec/flex_columns/system/dynamism_system_spec.rb +158 -0
- data/spec/flex_columns/system/error_handling_system_spec.rb +117 -0
- data/spec/flex_columns/system/including_system_spec.rb +285 -0
- data/spec/flex_columns/system/json_alias_system_spec.rb +171 -0
- data/spec/flex_columns/system/performance_system_spec.rb +218 -0
- data/spec/flex_columns/system/postgres_json_column_type_system_spec.rb +85 -0
- data/spec/flex_columns/system/types_system_spec.rb +93 -0
- data/spec/flex_columns/system/unknown_fields_system_spec.rb +126 -0
- data/spec/flex_columns/system/validations_system_spec.rb +111 -0
- data/spec/flex_columns/unit/active_record/base_spec.rb +32 -0
- data/spec/flex_columns/unit/contents/column_data_spec.rb +520 -0
- data/spec/flex_columns/unit/contents/flex_column_contents_base_spec.rb +253 -0
- data/spec/flex_columns/unit/definition/field_definition_spec.rb +617 -0
- data/spec/flex_columns/unit/definition/field_set_spec.rb +142 -0
- data/spec/flex_columns/unit/definition/flex_column_contents_class_spec.rb +733 -0
- data/spec/flex_columns/unit/errors_spec.rb +297 -0
- data/spec/flex_columns/unit/has_flex_columns_spec.rb +365 -0
- data/spec/flex_columns/unit/including/include_flex_columns_spec.rb +144 -0
- data/spec/flex_columns/unit/util/dynamic_methods_module_spec.rb +105 -0
- data/spec/flex_columns/unit/util/string_utils_spec.rb +23 -0
- 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
|