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,111 @@
|
|
1
|
+
require 'flex_columns'
|
2
|
+
require 'flex_columns/helpers/exception_helpers'
|
3
|
+
require 'flex_columns/helpers/system_helpers'
|
4
|
+
|
5
|
+
describe "FlexColumns validations" do
|
6
|
+
include FlexColumns::Helpers::SystemHelpers
|
7
|
+
include FlexColumns::Helpers::ExceptionHelpers
|
8
|
+
|
9
|
+
before :each do
|
10
|
+
@dh = FlexColumns::Helpers::DatabaseHelper.new
|
11
|
+
@dh.setup_activerecord!
|
12
|
+
|
13
|
+
create_standard_system_spec_tables!
|
14
|
+
end
|
15
|
+
|
16
|
+
after :each do
|
17
|
+
drop_standard_system_spec_tables!
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should allow validating fields in the column definition" do
|
21
|
+
define_model_class(:User, 'flexcols_spec_users') do
|
22
|
+
flex_column :user_attributes do
|
23
|
+
field :wants_email
|
24
|
+
|
25
|
+
validates :wants_email, :presence => true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
user = ::User.new
|
30
|
+
user.name = 'User 1'
|
31
|
+
user.user_attributes.touch!
|
32
|
+
|
33
|
+
e = capture_exception(::ActiveRecord::RecordInvalid) { user.save! }
|
34
|
+
|
35
|
+
e.record.should be(user)
|
36
|
+
e.record.errors.keys.should == [ :'user_attributes.wants_email' ]
|
37
|
+
messages = e.record.errors.get(:'user_attributes.wants_email')
|
38
|
+
messages.length.should == 1
|
39
|
+
|
40
|
+
message = messages[0]
|
41
|
+
message.should match(/be blank/i)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should allow validating fields outside the column definition" do
|
45
|
+
define_model_class(:User, 'flexcols_spec_users') do
|
46
|
+
flex_column :user_attributes do
|
47
|
+
field :wants_email
|
48
|
+
end
|
49
|
+
|
50
|
+
validates :wants_email, :presence => true
|
51
|
+
end
|
52
|
+
|
53
|
+
user = ::User.new
|
54
|
+
user.name = 'User 1'
|
55
|
+
|
56
|
+
e = capture_exception(::ActiveRecord::RecordInvalid) { user.save! }
|
57
|
+
|
58
|
+
e.record.should be(user)
|
59
|
+
e.record.errors.keys.should == [ :wants_email ]
|
60
|
+
messages = e.record.errors.get(:wants_email)
|
61
|
+
messages.length.should == 1
|
62
|
+
|
63
|
+
message = messages[0]
|
64
|
+
message.should match(/be blank/i)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should combine validations inside and outside the column definition" do
|
68
|
+
define_model_class(:User, 'flexcols_spec_users') do
|
69
|
+
flex_column :user_attributes do
|
70
|
+
field :wants_email
|
71
|
+
|
72
|
+
validates :wants_email, :numericality => { :less_than => 100 }
|
73
|
+
end
|
74
|
+
|
75
|
+
validates :wants_email, :numericality => { :greater_than => 10 }
|
76
|
+
end
|
77
|
+
|
78
|
+
user = ::User.new
|
79
|
+
user.name = 'User 1'
|
80
|
+
user.wants_email = 5
|
81
|
+
|
82
|
+
e = capture_exception(::ActiveRecord::RecordInvalid) { user.save! }
|
83
|
+
|
84
|
+
e.record.should be(user)
|
85
|
+
e.record.errors.keys.should == [ :wants_email ]
|
86
|
+
messages = e.record.errors.get(:wants_email)
|
87
|
+
messages.length.should == 1
|
88
|
+
|
89
|
+
message = messages[0]
|
90
|
+
message.should match(/be greater than 10/i)
|
91
|
+
|
92
|
+
user = ::User.new
|
93
|
+
user.name = 'User 1'
|
94
|
+
user.wants_email = 200
|
95
|
+
|
96
|
+
e = capture_exception(::ActiveRecord::RecordInvalid) { user.save! }
|
97
|
+
|
98
|
+
e.record.should be(user)
|
99
|
+
e.record.errors.keys.should == [ :'user_attributes.wants_email' ]
|
100
|
+
messages = e.record.errors.get(:'user_attributes.wants_email')
|
101
|
+
messages.length.should == 1
|
102
|
+
|
103
|
+
message = messages[0]
|
104
|
+
message.should match(/be less than 100/i)
|
105
|
+
|
106
|
+
user.wants_email = 50
|
107
|
+
user.save!
|
108
|
+
|
109
|
+
user.wants_email.should == 50
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'flex_columns'
|
2
|
+
|
3
|
+
describe FlexColumns::ActiveRecord::Base do
|
4
|
+
before :each do
|
5
|
+
@klass = Class.new
|
6
|
+
@klass.send(:include, FlexColumns::ActiveRecord::Base)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should say #has_any_flex_columns? is false by default" do
|
10
|
+
@klass.has_any_flex_columns?.should_not be
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should include HasFlexColumns on flex_column" do
|
14
|
+
block = lambda { "hi!" }
|
15
|
+
|
16
|
+
expect(@klass).to receive(:include).once.with(FlexColumns::HasFlexColumns) do
|
17
|
+
expect(@klass).to receive(:flex_column).once.with(:foo, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
@klass.flex_column(:foo, &block).should == "hi!"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should include IncludeFlexColumns on include_flex_columns_from" do
|
24
|
+
block = lambda { "hi!" }
|
25
|
+
|
26
|
+
expect(@klass).to receive(:include).once.with(FlexColumns::Including::IncludeFlexColumns) do
|
27
|
+
expect(@klass).to receive(:include_flex_columns_from).once.with(:foo, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
@klass.include_flex_columns_from(:foo, &block).should == "hi!"
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,520 @@
|
|
1
|
+
require 'flex_columns'
|
2
|
+
require 'flex_columns/helpers/exception_helpers'
|
3
|
+
|
4
|
+
describe FlexColumns::Contents::ColumnData do
|
5
|
+
include FlexColumns::Helpers::ExceptionHelpers
|
6
|
+
|
7
|
+
before :each do
|
8
|
+
@field_set = double("field_set")
|
9
|
+
allow(@field_set).to receive(:kind_of?).with(FlexColumns::Definition::FieldSet).and_return(true)
|
10
|
+
allow(@field_set).to receive(:all_field_names).with().and_return([ :foo, :bar, :baz ])
|
11
|
+
|
12
|
+
@field_foo = double("field_foo")
|
13
|
+
allow(@field_foo).to receive(:field_name).and_return(:foo)
|
14
|
+
allow(@field_foo).to receive(:json_storage_name).and_return(:foo)
|
15
|
+
@field_bar = double("field_bar")
|
16
|
+
allow(@field_bar).to receive(:field_name).and_return(:bar)
|
17
|
+
allow(@field_bar).to receive(:json_storage_name).and_return(:bar)
|
18
|
+
@field_baz = double("field_baz")
|
19
|
+
allow(@field_baz).to receive(:field_name).and_return(:baz)
|
20
|
+
allow(@field_baz).to receive(:json_storage_name).and_return(:baz)
|
21
|
+
|
22
|
+
allow(@field_set).to receive(:field_named) do |x|
|
23
|
+
case x.to_sym
|
24
|
+
when :foo then @field_foo
|
25
|
+
when :bar then @field_bar
|
26
|
+
when :baz then @field_baz
|
27
|
+
else nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
allow(@field_set).to receive(:field_with_json_storage_name) do |x|
|
32
|
+
case x.to_sym
|
33
|
+
when :foo then @field_foo
|
34
|
+
when :bar then @field_bar
|
35
|
+
when :baz then @field_baz
|
36
|
+
else nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
@data_source = double("data_source")
|
41
|
+
allow(@data_source).to receive(:describe_flex_column_data_source).with().and_return("describedescribe")
|
42
|
+
allow(@data_source).to receive(:notification_hash_for_flex_column_data_source).and_return(:notif1 => :a, :notif2 => :b)
|
43
|
+
|
44
|
+
@json_string = ' {"bar":123,"foo":"bar","baz":"quux"} '
|
45
|
+
end
|
46
|
+
|
47
|
+
def klass
|
48
|
+
FlexColumns::Contents::ColumnData
|
49
|
+
end
|
50
|
+
|
51
|
+
def new_with_string(s, options = { })
|
52
|
+
new_with(options.merge(:storage_string => s))
|
53
|
+
end
|
54
|
+
|
55
|
+
def new_with(options)
|
56
|
+
effective_options = {
|
57
|
+
:data_source => @data_source, :unknown_fields => :preserve, :storage => :text, :storage_string => nil, :binary_header => true, :null => true
|
58
|
+
}.merge(options)
|
59
|
+
klass.new(@field_set, effective_options)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should validate options properly" do
|
63
|
+
valid_options = {
|
64
|
+
:data_source => @data_source,
|
65
|
+
:unknown_fields => :preserve,
|
66
|
+
:storage => :text
|
67
|
+
}
|
68
|
+
|
69
|
+
lambda { klass.new(double("not_a_field_set"), valid_options) }.should raise_error(ArgumentError)
|
70
|
+
lambda { klass.new(@field_set, valid_options.merge(:data_source => nil)) }.should raise_error(ArgumentError)
|
71
|
+
lambda { klass.new(@field_set, valid_options.merge(:unknown_fields => :foo)) }.should raise_error(ArgumentError)
|
72
|
+
lambda { klass.new(@field_set, valid_options.merge(:storage => :foo)) }.should raise_error(ArgumentError)
|
73
|
+
lambda { klass.new(@field_set, valid_options.merge(:length_limit => 'foo')) }.should raise_error(ArgumentError)
|
74
|
+
lambda { klass.new(@field_set, valid_options.merge(:length_limit => 3)) }.should raise_error(ArgumentError)
|
75
|
+
lambda { klass.new(@field_set, valid_options.merge(:compress_if_over_length => 3.5)) }.should raise_error(ArgumentError)
|
76
|
+
lambda { klass.new(@field_set, valid_options.merge(:compress_if_over_length => 'foo')) }.should raise_error(ArgumentError)
|
77
|
+
lambda { klass.new(@field_set, valid_options.merge(:null => 'foo')) }.should raise_error(ArgumentError)
|
78
|
+
end
|
79
|
+
|
80
|
+
context "with a valid instance" do
|
81
|
+
before :each do
|
82
|
+
@instance = new_with_string(@json_string)
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "[]" do
|
86
|
+
it "should reject invalid field names" do
|
87
|
+
expect(@field_set).to receive(:field_named).with(:quux).and_return(nil)
|
88
|
+
|
89
|
+
e = capture_exception(FlexColumns::Errors::NoSuchFieldError) { @instance[:quux] }
|
90
|
+
e.data_source.should be(@data_source)
|
91
|
+
e.field_name.should == :quux
|
92
|
+
e.all_field_names.should == [ :foo, :bar, :baz ]
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should return data from a valid field correctly" do
|
96
|
+
@instance[:foo].should == 'bar'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "[]=" do
|
101
|
+
it "should reject invalid field names" do
|
102
|
+
expect(@field_set).to receive(:field_named).with(:quux).and_return(nil)
|
103
|
+
|
104
|
+
e = capture_exception(FlexColumns::Errors::NoSuchFieldError) { @instance[:quux] = "a" }
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should assign data to a valid field correctly" do
|
108
|
+
@instance[:foo] = "abc"
|
109
|
+
@instance[:foo].should == "abc"
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should transform Symbols to Strings" do
|
113
|
+
@instance[:foo] = :abc
|
114
|
+
@instance[:foo].should == "abc"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should return keys for #keys" do
|
119
|
+
@instance.keys.sort_by(&:to_s).should == [ :foo, :bar, :baz ].sort_by(&:to_s)
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should not return things set to nil in #keys" do
|
123
|
+
@instance[:bar] = nil
|
124
|
+
@instance.keys.sort_by(&:to_s).should == [ :foo, :baz ].sort_by(&:to_s)
|
125
|
+
end
|
126
|
+
|
127
|
+
describe "touching" do
|
128
|
+
it "should deserialize, if needed, on touch!" do
|
129
|
+
instance = new_with_string("---unparseable JSON---")
|
130
|
+
|
131
|
+
lambda { instance.touch! }.should raise_error(FlexColumns::Errors::UnparseableJsonInDatabaseError)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should not be touched if you simply read from it" do
|
135
|
+
@instance.touched?.should_not be
|
136
|
+
@instance[:foo]
|
137
|
+
@instance.touched?.should_not be
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should not be touched if you set a field to the same thing" do
|
141
|
+
@instance.touched?.should_not be
|
142
|
+
@instance[:foo] = 'bar'
|
143
|
+
@instance.touched?.should_not be
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should be touched if you set a field to something different" do
|
147
|
+
@instance.touched?.should_not be
|
148
|
+
@instance[:foo] = 'baz'
|
149
|
+
@instance.touched?.should be
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should return JSON data with #to_json" do
|
154
|
+
json = @instance.to_json
|
155
|
+
parsed = JSON.parse(json)
|
156
|
+
parsed.keys.sort.should == %w{foo bar baz}.sort
|
157
|
+
parsed['foo'].should == 'bar'
|
158
|
+
parsed['bar'].should == 123
|
159
|
+
parsed['baz'].should == 'quux'
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should return JSON data with #to_json" do
|
163
|
+
json = @instance.to_json
|
164
|
+
parsed = JSON.parse(json)
|
165
|
+
parsed.keys.sort.should == %w{foo bar baz}.sort
|
166
|
+
parsed['foo'].should == 'bar'
|
167
|
+
parsed['bar'].should == 123
|
168
|
+
parsed['baz'].should == 'quux'
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should accept a Hash as JSON, already parsed by the database stack" do
|
172
|
+
@instance = new_with(:storage_string => { 'foo' => 'bar', 'baz' => 123, 'bar' => 'quux' })
|
173
|
+
@instance['foo'].should == 'bar'
|
174
|
+
@instance['bar'].should == 'quux'
|
175
|
+
@instance['baz'].should == 123
|
176
|
+
end
|
177
|
+
|
178
|
+
describe "#to_stored_data" do
|
179
|
+
it "should return JSON data properly" do
|
180
|
+
json = @instance.to_stored_data
|
181
|
+
parsed = JSON.parse(json)
|
182
|
+
parsed.keys.sort.should == %w{foo bar baz}.sort
|
183
|
+
parsed['foo'].should == 'bar'
|
184
|
+
parsed['bar'].should == 123
|
185
|
+
parsed['baz'].should == 'quux'
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should return a raw JSON hash if the column type is :json" do
|
189
|
+
@instance = new_with_string(@json_string, :storage => :json)
|
190
|
+
@instance.to_stored_data.should == { :foo => 'bar', :baz => 'quux', :bar => 123 }
|
191
|
+
end
|
192
|
+
|
193
|
+
describe "with a text column" do
|
194
|
+
it "should return nil if there's no data and the column allows it" do
|
195
|
+
@instance = new_with_string("{}")
|
196
|
+
@instance.to_stored_data.should == nil
|
197
|
+
end
|
198
|
+
|
199
|
+
it "should return the empty string if there's no data and the column does not allow nulls" do
|
200
|
+
@instance = new_with_string("{}", :null => false)
|
201
|
+
@instance.to_stored_data.should == ""
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
describe "with a binary column" do
|
206
|
+
it "should return nil if there's no data and the column allows it" do
|
207
|
+
@instance = new_with_string("{}", :storage => :binary)
|
208
|
+
@instance.to_stored_data.should == nil
|
209
|
+
end
|
210
|
+
|
211
|
+
it "should return the empty string if there's no data and the column does not allow nulls" do
|
212
|
+
@instance = new_with_string("{}", :storage => :binary, :null => false)
|
213
|
+
@instance.to_stored_data.should == ""
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
it "should return JSON from a binary column with :header => false" do
|
218
|
+
@instance = new_with_string(@json_string, :storage => :binary, :binary_header => false)
|
219
|
+
json = @instance.to_stored_data
|
220
|
+
parsed = JSON.parse(json)
|
221
|
+
parsed.keys.sort.should == %w{foo bar baz}.sort
|
222
|
+
parsed['foo'].should == 'bar'
|
223
|
+
parsed['bar'].should == 123
|
224
|
+
parsed['baz'].should == 'quux'
|
225
|
+
end
|
226
|
+
|
227
|
+
it "should return uncompressed JSON from a binary column without compression" do
|
228
|
+
@instance = new_with_string(@json_string, :storage => :binary)
|
229
|
+
stored = @instance.to_stored_data
|
230
|
+
stored.should match(/^FC:01,0,/)
|
231
|
+
json = stored[8..-1]
|
232
|
+
|
233
|
+
parsed = JSON.parse(json)
|
234
|
+
parsed.keys.sort.should == %w{foo bar baz}.sort
|
235
|
+
parsed['foo'].should == 'bar'
|
236
|
+
parsed['bar'].should == 123
|
237
|
+
parsed['baz'].should == 'quux'
|
238
|
+
end
|
239
|
+
|
240
|
+
it "should return compressed JSON from a binary column with compression" do
|
241
|
+
@json_string = ({ :foo => 'bar' * 1000, :bar => 123, :baz => 'quux' }.to_json)
|
242
|
+
@instance = new_with_string(@json_string, :storage => :binary, :compress_if_over_length => 1)
|
243
|
+
stored = @instance.to_stored_data
|
244
|
+
stored.should match(/^FC:01,1,/)
|
245
|
+
compressed = stored[8..-1]
|
246
|
+
|
247
|
+
require 'stringio'
|
248
|
+
input = StringIO.new(compressed, "r")
|
249
|
+
reader = Zlib::GzipReader.new(input)
|
250
|
+
uncompressed = reader.read
|
251
|
+
|
252
|
+
parsed = JSON.parse(uncompressed)
|
253
|
+
parsed.keys.sort.should == %w{foo bar baz}.sort
|
254
|
+
parsed['foo'].should == 'bar' * 1000
|
255
|
+
parsed['bar'].should == 123
|
256
|
+
parsed['baz'].should == 'quux'
|
257
|
+
end
|
258
|
+
|
259
|
+
it "should return uncompressed JSON from a binary column with compression, but that isn't long enough" do
|
260
|
+
@json_string = ({ :foo => 'bar', :bar => 123, :baz => 'quux' }.to_json)
|
261
|
+
@instance = new_with_string(@json_string, :storage => :binary, :compress_if_over_length => 10_000)
|
262
|
+
stored = @instance.to_stored_data
|
263
|
+
stored.should match(/^FC:01,0,/)
|
264
|
+
json = stored[8..-1]
|
265
|
+
|
266
|
+
parsed = JSON.parse(json)
|
267
|
+
parsed.keys.sort.should == %w{foo bar baz}.sort
|
268
|
+
parsed['foo'].should == 'bar'
|
269
|
+
parsed['bar'].should == 123
|
270
|
+
parsed['baz'].should == 'quux'
|
271
|
+
end
|
272
|
+
|
273
|
+
it "should blow up if the string won't fit" do
|
274
|
+
@json_string = ({ :foo => 'bar' * 1000, :bar => 123, :baz => 'quux' }.to_json)
|
275
|
+
@instance = new_with_string(@json_string, :storage => :binary, :length_limit => 1_000)
|
276
|
+
|
277
|
+
e = capture_exception(FlexColumns::Errors::JsonTooLongError) { @instance.to_stored_data }
|
278
|
+
e.data_source.should be(@data_source)
|
279
|
+
e.limit.should == 1_000
|
280
|
+
e.json_string.should match(/^FC:01,0,/)
|
281
|
+
e.json_string.length.should >= 3_000
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
describe "deserialization" do
|
286
|
+
it "should raise an error if encoding is wrong" do
|
287
|
+
bad_encoding = double("bad_encoding")
|
288
|
+
allow(bad_encoding).to receive(:kind_of?).with(String).and_return(true)
|
289
|
+
allow(bad_encoding).to receive(:kind_of?).with(Hash).and_return(false)
|
290
|
+
expect(bad_encoding).to receive(:valid_encoding?).with().and_return(false)
|
291
|
+
|
292
|
+
exception = StandardError.new("bonk")
|
293
|
+
expect(FlexColumns::Errors::IncorrectlyEncodedStringInDatabaseError).to receive(:new).once.with(@data_source, bad_encoding).and_return(exception)
|
294
|
+
|
295
|
+
capture_exception { new_with_string(bad_encoding)[:foo] }.should be(exception)
|
296
|
+
end
|
297
|
+
|
298
|
+
it "should accept blank strings just fine" do
|
299
|
+
instance = new_with_string(" ")
|
300
|
+
instance[:foo].should be_nil
|
301
|
+
instance[:bar].should be_nil
|
302
|
+
instance[:baz].should be_nil
|
303
|
+
end
|
304
|
+
|
305
|
+
it "should raise an error if the JSON doesn't parse" do
|
306
|
+
bogus_json = "---unparseable JSON---"
|
307
|
+
instance = new_with_string(bogus_json)
|
308
|
+
|
309
|
+
e = capture_exception(FlexColumns::Errors::UnparseableJsonInDatabaseError) { instance[:foo] }
|
310
|
+
e.data_source.should be(@data_source)
|
311
|
+
e.raw_string.should == bogus_json
|
312
|
+
e.source_exception.kind_of?(JSON::ParserError).should be
|
313
|
+
e.message.should match(/describedescribe/)
|
314
|
+
end
|
315
|
+
|
316
|
+
it "should raise an error if the JSON doesn't represent a Hash" do
|
317
|
+
bogus_json = "[ 1, 2, 3 ]"
|
318
|
+
instance = new_with_string(bogus_json)
|
319
|
+
|
320
|
+
e = capture_exception(FlexColumns::Errors::InvalidJsonInDatabaseError) { instance[:foo] }
|
321
|
+
e.data_source.should be(@data_source)
|
322
|
+
e.raw_string.should == bogus_json
|
323
|
+
e.returned_data.should == [ 1, 2, 3 ]
|
324
|
+
e.message.should match(/describedescribe/)
|
325
|
+
end
|
326
|
+
|
327
|
+
it "should accept uncompressed strings with a header" do
|
328
|
+
instance = new_with_string("FC:01,0,#{@json_string}")
|
329
|
+
instance[:foo].should == "bar"
|
330
|
+
instance[:bar].should == 123
|
331
|
+
instance[:baz].should == "quux"
|
332
|
+
end
|
333
|
+
|
334
|
+
it "should accept compressed strings with a header" do
|
335
|
+
require 'stringio'
|
336
|
+
stream = StringIO.new("w")
|
337
|
+
writer = Zlib::GzipWriter.new(stream)
|
338
|
+
writer.write(@json_string)
|
339
|
+
writer.close
|
340
|
+
|
341
|
+
header = "FC:01,1,"
|
342
|
+
header.force_encoding("BINARY") if header.respond_to?(:force_encoding)
|
343
|
+
total = header + stream.string
|
344
|
+
total.force_encoding("BINARY") if total.respond_to?(:force_encoding)
|
345
|
+
instance = new_with_string(total)
|
346
|
+
instance[:foo].should == "bar"
|
347
|
+
instance[:bar].should == 123
|
348
|
+
instance[:baz].should == "quux"
|
349
|
+
end
|
350
|
+
|
351
|
+
it "should fail if the version number is too big" do
|
352
|
+
bad_string = "FC:02,0,#{@json_string}"
|
353
|
+
instance = new_with_string(bad_string)
|
354
|
+
|
355
|
+
e = capture_exception(FlexColumns::Errors::InvalidFlexColumnsVersionNumberInDatabaseError) { instance[:foo] }
|
356
|
+
e.data_source.should be(@data_source)
|
357
|
+
e.raw_string.should == bad_string
|
358
|
+
e.version_number_in_database.should == 2
|
359
|
+
e.max_version_number_supported.should == 1
|
360
|
+
e.message.should match(/describedescribe/)
|
361
|
+
end
|
362
|
+
|
363
|
+
it "should fail if the compression number is too big" do
|
364
|
+
require 'stringio'
|
365
|
+
stream = StringIO.new("w")
|
366
|
+
writer = Zlib::GzipWriter.new(stream)
|
367
|
+
writer.write(@json_string)
|
368
|
+
writer.close
|
369
|
+
|
370
|
+
header = "FC:01,2,"
|
371
|
+
header.force_encoding("BINARY") if header.respond_to?(:force_encoding)
|
372
|
+
bad_string = header + stream.string
|
373
|
+
bad_string.force_encoding("BINARY") if bad_string.respond_to?(:force_encoding)
|
374
|
+
|
375
|
+
instance = new_with_string(bad_string)
|
376
|
+
e = capture_exception(FlexColumns::Errors::InvalidDataInDatabaseError) { instance[:foo] }
|
377
|
+
e.data_source.should be(@data_source)
|
378
|
+
e.raw_string.should == bad_string
|
379
|
+
e.message.should match(/2/)
|
380
|
+
e.message.should match(/describedescribe/)
|
381
|
+
end
|
382
|
+
|
383
|
+
it "should fail if the compressed data is bogus" do
|
384
|
+
require 'stringio'
|
385
|
+
stream = StringIO.new("w")
|
386
|
+
writer = Zlib::GzipWriter.new(stream)
|
387
|
+
writer.write(@json_string)
|
388
|
+
writer.close
|
389
|
+
compressed_data = stream.string
|
390
|
+
|
391
|
+
100.times do
|
392
|
+
pos_1 = rand(10)
|
393
|
+
pos_2 = rand(10)
|
394
|
+
tmp = compressed_data[pos_1]
|
395
|
+
compressed_data[pos_1] = compressed_data[pos_2]
|
396
|
+
compressed_data[pos_2] = tmp
|
397
|
+
end
|
398
|
+
|
399
|
+
header = "FC:01,1,"
|
400
|
+
header.force_encoding("BINARY") if header.respond_to?(:force_encoding)
|
401
|
+
bad_string = header + compressed_data
|
402
|
+
bad_string.force_encoding("BINARY") if bad_string.respond_to?(:force_encoding)
|
403
|
+
|
404
|
+
instance = new_with_string(bad_string)
|
405
|
+
e = capture_exception(FlexColumns::Errors::InvalidCompressedDataInDatabaseError) { instance[:foo] }
|
406
|
+
e.data_source.should be(@data_source)
|
407
|
+
e.raw_string.should == bad_string
|
408
|
+
e.source_exception.class.should == Zlib::GzipFile::Error
|
409
|
+
e.message.should match(/describedescribe/)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
describe "notifications" do
|
414
|
+
before :each do
|
415
|
+
@deserializations = [ ]
|
416
|
+
ds = @deserializations
|
417
|
+
|
418
|
+
ActiveSupport::Notifications.subscribe('flex_columns.deserialize') do |name, start, finish, id, payload|
|
419
|
+
ds << payload
|
420
|
+
end
|
421
|
+
|
422
|
+
@serializations = [ ]
|
423
|
+
s = @serializations
|
424
|
+
|
425
|
+
ActiveSupport::Notifications.subscribe('flex_columns.serialize') do |name, start, finish, id, payload|
|
426
|
+
s << payload
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
it "should trigger a notification on deserialization" do
|
431
|
+
@deserializations.length.should == 0
|
432
|
+
|
433
|
+
@instance[:foo].should == 'bar'
|
434
|
+
@deserializations.length.should == 1
|
435
|
+
@deserializations[0].should == { :notif1 => :a, :notif2 => :b, :raw_data => @json_string }
|
436
|
+
end
|
437
|
+
|
438
|
+
it "should trigger a notification on serialization" do
|
439
|
+
@serializations.length.should == 0
|
440
|
+
|
441
|
+
@instance[:foo].should == 'bar'
|
442
|
+
@serializations.length.should == 0
|
443
|
+
|
444
|
+
@instance.to_stored_data
|
445
|
+
|
446
|
+
@serializations.length.should == 1
|
447
|
+
@serializations[0].should == { :notif1 => :a, :notif2 => :b }
|
448
|
+
end
|
449
|
+
|
450
|
+
it "should not trigger a notification on #to_json" do
|
451
|
+
@serializations.length.should == 0
|
452
|
+
|
453
|
+
@instance[:foo].should == 'bar'
|
454
|
+
@serializations.length.should == 0
|
455
|
+
|
456
|
+
@instance.to_json
|
457
|
+
|
458
|
+
@serializations.length.should == 0
|
459
|
+
end
|
460
|
+
|
461
|
+
it "should not deserialize until data is required" do
|
462
|
+
@deserializations.length.should == 0
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
describe "unknown-field handling" do
|
467
|
+
it "should hang on to unknown data if asked" do
|
468
|
+
s = { :foo => 'bar', :quux => 'baz' }.to_json
|
469
|
+
@instance = new_with_string(s)
|
470
|
+
parsed = JSON.parse(@instance.to_json)
|
471
|
+
parsed['quux'].should == 'baz'
|
472
|
+
end
|
473
|
+
|
474
|
+
it "should discard unknown data if asked" do
|
475
|
+
s = { :foo => 'bar', :quux => 'baz' }.to_json
|
476
|
+
@instance = new_with_string(s, :unknown_fields => :delete)
|
477
|
+
parsed = JSON.parse(@instance.to_json)
|
478
|
+
parsed.keys.should == [ 'foo' ]
|
479
|
+
parsed['quux'].should_not be
|
480
|
+
end
|
481
|
+
|
482
|
+
it "should not allow unknown data to conflict with known data" do
|
483
|
+
field_set = double("field_set")
|
484
|
+
allow(field_set).to receive(:kind_of?).with(FlexColumns::Definition::FieldSet).and_return(true)
|
485
|
+
allow(field_set).to receive(:all_field_names).with().and_return([ :foo ])
|
486
|
+
|
487
|
+
field_foo = double("field_foo")
|
488
|
+
allow(field_foo).to receive(:field_name).and_return(:foo)
|
489
|
+
allow(field_foo).to receive(:json_storage_name).and_return(:bar)
|
490
|
+
|
491
|
+
allow(field_set).to receive(:field_named) do |x|
|
492
|
+
case x.to_sym
|
493
|
+
when :foo then field_foo
|
494
|
+
else nil
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
allow(field_set).to receive(:field_with_json_storage_name) do |x|
|
499
|
+
case x.to_sym
|
500
|
+
when :bar then field_foo
|
501
|
+
else nil
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
json_string = { :foo => 'aaa', :bar => 'bbb' }.to_json
|
506
|
+
instance = klass.new(field_set, :storage_string => json_string, :data_source => @data_source,
|
507
|
+
:unknown_fields => :preserve, :storage => :text, :storage_string => json_string, :binary_header => true,
|
508
|
+
:null => true)
|
509
|
+
|
510
|
+
instance[:foo].should == 'bbb'
|
511
|
+
lambda { instance[:bar] }.should raise_error(FlexColumns::Errors::NoSuchFieldError)
|
512
|
+
|
513
|
+
reparsed = JSON.parse(instance.to_json)
|
514
|
+
reparsed.keys.sort.should == %w{foo bar}.sort
|
515
|
+
reparsed['foo'].should == 'aaa'
|
516
|
+
reparsed['bar'].should == 'bbb'
|
517
|
+
end
|
518
|
+
end
|
519
|
+
end
|
520
|
+
end
|