flex_columns 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/flex_columns.gemspec +0 -14
- data/lib/flex_columns/contents/column_data.rb +46 -9
- data/lib/flex_columns/contents/flex_column_contents_base.rb +27 -13
- data/lib/flex_columns/definition/flex_column_contents_class.rb +2 -2
- data/lib/flex_columns/has_flex_columns.rb +20 -1
- data/lib/flex_columns/version.rb +1 -1
- data/spec/flex_columns/system/basic_system_spec.rb +53 -10
- data/spec/flex_columns/system/compression_system_spec.rb +27 -0
- data/spec/flex_columns/system/performance_system_spec.rb +1 -1
- data/spec/flex_columns/system/unknown_fields_system_spec.rb +2 -2
- data/spec/flex_columns/unit/contents/column_data_spec.rb +34 -19
- data/spec/flex_columns/unit/contents/flex_column_contents_base_spec.rb +24 -5
- data/spec/flex_columns/unit/definition/flex_column_contents_class_spec.rb +4 -4
- data/spec/flex_columns/unit/has_flex_columns_spec.rb +7 -5
- metadata +4 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: acb66f010a776e668841ca7f12dc0854cad5b4c9
|
4
|
+
data.tar.gz: 91492dda4eab46d062ae8958c62c5e31da33a339
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4469119cb126a4e5767910b6ce4df02037afa3003d735be05baf0a0158ee45c3871b5c29292181c09823ac416184cb2e692f8ad31c9dfd66cea0187d65b5e3d6
|
7
|
+
data.tar.gz: 6614f83b721f635efa9e80f4f194237436890e66f35a662af0ed100d4d5bc4e90436abe859e89332ecc5f913134903e041bb66332a7d424560e64a5187bf0908
|
data/.gitignore
CHANGED
data/flex_columns.gemspec
CHANGED
@@ -45,20 +45,6 @@ Gem::Specification.new do |s|
|
|
45
45
|
|
46
46
|
s.add_dependency "activesupport", ">= 3.0", "<= 4.99.99"
|
47
47
|
|
48
|
-
ar_import_version = case ar_version
|
49
|
-
when nil then nil
|
50
|
-
when 'master', /^4\.0\./ then '~> 0.4.1'
|
51
|
-
when /^3\.0\./ then '~> 0.2.11'
|
52
|
-
when /^3\.1\./, /^3\.2\./ then '~> 0.3.1'
|
53
|
-
else raise "Don't know what activerecord-import version to require for activerecord version #{ar_version.inspect}!"
|
54
|
-
end
|
55
|
-
|
56
|
-
if ar_import_version
|
57
|
-
s.add_dependency("activerecord-import", ar_import_version)
|
58
|
-
else
|
59
|
-
s.add_dependency("activerecord-import")
|
60
|
-
end
|
61
|
-
|
62
48
|
require File.expand_path(File.join(File.dirname(__FILE__), 'spec', 'flex_columns', 'helpers', 'database_helper'))
|
63
49
|
database_gem_name = FlexColumns::Helpers::DatabaseHelper.maybe_database_gem_name
|
64
50
|
|
@@ -80,7 +80,6 @@ module FlexColumns
|
|
80
80
|
|
81
81
|
@field_contents_by_field_name = nil
|
82
82
|
@unknown_field_contents_by_key = nil
|
83
|
-
@touched = false
|
84
83
|
end
|
85
84
|
|
86
85
|
# Returns the data for the given +field_name+. Raises FlexColumns::Errors::NoSuchFieldError if there is no field
|
@@ -109,8 +108,6 @@ module FlexColumns
|
|
109
108
|
|
110
109
|
old_value = field_contents_by_field_name[field_name]
|
111
110
|
|
112
|
-
@touched = true if old_value != new_value
|
113
|
-
|
114
111
|
# We deliberately delete from the hash anything that's being set to +nil+; this is so that we don't end up just
|
115
112
|
# binding keys to +nil+, and returning them in #keys, etc. (Yes, this means that you can't distinguish a key
|
116
113
|
# explicitly set to +nil+ from a key that's not present; this is different from Ruby's semantics for a Hash,
|
@@ -129,17 +126,56 @@ module FlexColumns
|
|
129
126
|
field_contents_by_field_name.keys
|
130
127
|
end
|
131
128
|
|
129
|
+
# Returns a representation of this data as a Hash. This should *not* be used in +flex_columns+ to manipulate
|
130
|
+
# data, as it does not contain a full representation of a column (in particular, unknown-field data is not
|
131
|
+
# represented in the returned Hash); however, it's useful to construct a string (e.g.,
|
132
|
+
# FlexColumnsContentsBase#inspect) to help with debugging.
|
133
|
+
def to_hash
|
134
|
+
deserialize_if_necessary!
|
135
|
+
field_contents_by_field_name.dup.with_indifferent_access
|
136
|
+
end
|
137
|
+
|
132
138
|
# Does nothing, other than making sure the JSON has been deserialized. This therefore has the effect both of
|
133
139
|
# ensuring that the stored data (if any) is valid, and also will remove any unknown keys (on save) if
|
134
140
|
# +:unknown_fields+ was set to +:delete+.
|
135
141
|
def touch!
|
136
142
|
deserialize_if_necessary!
|
137
|
-
@touched = true
|
138
143
|
end
|
139
144
|
|
140
|
-
# Has this object been
|
141
|
-
|
142
|
-
|
145
|
+
# Has this object been deserialized? If it's been deserialized, then we need to do things like run validations
|
146
|
+
# on it, save it back to the database when someone calls #save! on the parent object, and so on.
|
147
|
+
#
|
148
|
+
# Not at all obvious: originally, we had a method called #touched? that let you know whether the given object
|
149
|
+
# had been changed at all. It simply got set on +#[]=+, above. The problem with this is that very frequently,
|
150
|
+
# +flex_columns+ is used to store complex data structures (because that's one of the things that's dramatically
|
151
|
+
# easier in a serialized JSON blob than in a traditional relational structure). But if you have an array stored,
|
152
|
+
# and you call #<< on it to append an element, then +#[]=+ never gets called at all -- because it's still the
|
153
|
+
# same object, just with different contents.
|
154
|
+
#
|
155
|
+
# We could have worked around this by saving off a copy of each field when we deserialized, then comparing them
|
156
|
+
# using a deep equality (#== should work just fine) to determine if they've changed. However, this adds very
|
157
|
+
# significant overhead to each and every single use of a +flex_column+ object, whether or not you rely on or
|
158
|
+
# care about this kind of tracking -- we would have to #dup every flex column field every single time we
|
159
|
+
# deserialized, and, if you have large objects in there, that can get extremely expensive.
|
160
|
+
#
|
161
|
+
# Since almost every object in Ruby is mutable -- even Strings -- there aren't really any easy wins here.
|
162
|
+
# Numbers are the only commonplace object that aren't, and it's not going to be a common use case that someone
|
163
|
+
# uses a +flex_column+ with fields that each simply store one single number. (Storing an array or a hash of
|
164
|
+
# numbers is much more common, but then you're talking about Arrays and Hashes, which are back to being mutable.)
|
165
|
+
#
|
166
|
+
# Another option would be to #freeze all of the fields on a flex column, thus requiring clients to reassign them
|
167
|
+
# with a new object if they wanted to change them at all. That, however, presents an API that most users would
|
168
|
+
# hate -- I don't want to say <tt>user.prefs_map = user.prefs_map.merge(:foo => bar)</tt>; I want to just say
|
169
|
+
# <tt>user.prefs_map[:foo] = bar</tt>.
|
170
|
+
#
|
171
|
+
# Instead, once we deserialize a field, we just assume that it has changed. While this may end up causing the
|
172
|
+
# client to do extra work at times, it's much higher-performance than doing the tracking every time.
|
173
|
+
#
|
174
|
+
# (There is definitely room to add code that would make this configurable, on a per-flex-column or even per-field
|
175
|
+
# basis. As always, patches are welcome; as of this writing, it seems likely that it might just not be an issue
|
176
|
+
# big enough to worry about.)
|
177
|
+
def deserialized?
|
178
|
+
!! field_contents_by_field_name
|
143
179
|
end
|
144
180
|
|
145
181
|
# Returns a String with the current contents of this object as JSON. (This will deserialize from JSON, if it
|
@@ -181,7 +217,8 @@ module FlexColumns
|
|
181
217
|
end
|
182
218
|
end
|
183
219
|
|
184
|
-
|
220
|
+
actual_length = out ? out.length : 0
|
221
|
+
if length_limit && actual_length > length_limit
|
185
222
|
raise FlexColumns::Errors::JsonTooLongError.new(data_source, length_limit, out)
|
186
223
|
end
|
187
224
|
|
@@ -345,7 +382,7 @@ module FlexColumns
|
|
345
382
|
# If we haven't yet deserialized the JSON string, do it now, and store the data appropriately. This also
|
346
383
|
# checks for a validly-encoded string.
|
347
384
|
def deserialize_if_necessary!
|
348
|
-
unless
|
385
|
+
unless deserialized?
|
349
386
|
raw_data = storage_string || ''
|
350
387
|
|
351
388
|
# PostgreSQL's JSON data type, combined with recent-enough adapters and ActiveRecord, will return JSON as a
|
@@ -108,23 +108,18 @@ module FlexColumns
|
|
108
108
|
column_data[field_name] = new_value
|
109
109
|
end
|
110
110
|
|
111
|
-
#
|
112
|
-
#
|
113
|
-
#
|
114
|
-
#
|
115
|
-
# mean that invalid data won't be detected and unknown fields won't be removed (if you've specified
|
116
|
-
# <tt>:unknown_fields => delete</tt>), however.
|
117
|
-
#
|
118
|
-
# There may be times, however, when you want to make sure the column is stored back out (including removing any
|
119
|
-
# unknown fields, if you selected that option), or to make sure that validations get run, no matter what.
|
120
|
-
# In this case, you can call #touch!.
|
111
|
+
# Sometimes you want to deserialize a flex column explicitly, without actually changing anything in it. (For
|
112
|
+
# example, if you set <tt>:unknown_fields => :delete</tt>, then unknown fields are removed from a column only if
|
113
|
+
# it has been deserialized before you save it.) While you could accomplish this by simply accessing any field
|
114
|
+
# of the column, it's cleaner and more clear what you're doing to just call this method.
|
121
115
|
def touch!
|
122
116
|
column_data.touch!
|
123
117
|
end
|
124
118
|
|
125
|
-
# Has
|
126
|
-
|
127
|
-
|
119
|
+
# Has the column been deserialized? A column is deserialized if someone has tried to read from or write to it,
|
120
|
+
# or if someone has called #touch!.
|
121
|
+
def deserialized?
|
122
|
+
column_data.deserialized?
|
128
123
|
end
|
129
124
|
|
130
125
|
# Called via the ActiveRecord::Base#before_validation hook that gets installed on the enclosing model instance.
|
@@ -139,6 +134,25 @@ module FlexColumns
|
|
139
134
|
end
|
140
135
|
end
|
141
136
|
|
137
|
+
INSPECT_MAXIMUM_LENGTH_FOR_ANY_ATTRIBUTE_VALUE = 100
|
138
|
+
|
139
|
+
# **NOTE**: This method *WILL* deserialize the contents of the column, if it hasn't already been deserialized.
|
140
|
+
# This is extremely useful for debugging, and almost certainly what you want, but if, for some reason, you
|
141
|
+
# call #inspect on every single instance of a flex-column you get back from the database, you'll incur a
|
142
|
+
# needless performance penalty. You have been warned.
|
143
|
+
def inspect
|
144
|
+
string_hash = { }
|
145
|
+
column_data.to_hash.each do |k,v|
|
146
|
+
v_string = v.to_s
|
147
|
+
if v_string.length > INSPECT_MAXIMUM_LENGTH_FOR_ANY_ATTRIBUTE_VALUE
|
148
|
+
v_string = "#{v_string[0..(INSPECT_MAXIMUM_LENGTH_FOR_ANY_ATTRIBUTE_VALUE - 1)]}..."
|
149
|
+
end
|
150
|
+
string_hash[k] = v_string
|
151
|
+
end
|
152
|
+
|
153
|
+
"<#{self.class.name}: #{string_hash.inspect}>"
|
154
|
+
end
|
155
|
+
|
142
156
|
# Returns a JSON string representing the current contents of this flex column. Note that this is _not_ always
|
143
157
|
# exactly what gets stored in the database, because of binary columns and compression; for that, use
|
144
158
|
# #to_stored_data, below.
|
@@ -146,11 +146,11 @@ module FlexColumns
|
|
146
146
|
|
147
147
|
# Given a model instance, do we need to save this column? This is true under one of two cases:
|
148
148
|
#
|
149
|
-
# * Someone has
|
149
|
+
# * Someone has deserialized the column by accessing it (or calling #touch! on it);
|
150
150
|
# * The column is non-NULL, and there's no data in it right now. (Saving it will populate it with an empty string.)
|
151
151
|
def requires_serialization_on_save?(model)
|
152
152
|
maybe_flex_object = model._flex_column_object_for(column_name, false)
|
153
|
-
out = true if maybe_flex_object && maybe_flex_object.
|
153
|
+
out = true if maybe_flex_object && maybe_flex_object.deserialized?
|
154
154
|
out ||= true if ((! column.null) && (! model[column_name]))
|
155
155
|
out
|
156
156
|
end
|
@@ -38,7 +38,7 @@ module FlexColumns
|
|
38
38
|
# whether you've changed that particular attribute or not.
|
39
39
|
def _flex_columns_before_validation!
|
40
40
|
_all_present_flex_column_objects.each do |flex_column_object|
|
41
|
-
flex_column_object.before_validation!
|
41
|
+
flex_column_object.before_validation!
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
@@ -72,6 +72,25 @@ module FlexColumns
|
|
72
72
|
@_flex_column_objects = { }
|
73
73
|
end
|
74
74
|
|
75
|
+
# This little-know ActiveRecord method gets called to produce a string for #inspect for a particular attribute.
|
76
|
+
# Because the default implementation uses #read_attribute, if we don't override it, it will simply return our
|
77
|
+
# actual string in the database; if this is compressed data, this is meaningless to a programmer. So we override
|
78
|
+
# this to instead deserialize the column and call #inspect on the actual FlexColumnContentsBase object, which
|
79
|
+
# shows interesting information.
|
80
|
+
#
|
81
|
+
# **NOTE**: See the warning comment above FlexColumnContentsBase#inspect, which points out that this will
|
82
|
+
# deserialize the column if it hasn't already -- so calling this has a performance penalty. This should be fine,
|
83
|
+
# since calling #inspect in bulk isn't something a program should be doing in production mode anyway, but it's
|
84
|
+
# worth noting.
|
85
|
+
def attribute_for_inspect(attr_name)
|
86
|
+
cn = self.class._all_flex_column_names
|
87
|
+
if cn.include?(attr_name.to_sym)
|
88
|
+
_flex_column_object_for(attr_name).inspect
|
89
|
+
else
|
90
|
+
super(attr_name)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
75
94
|
private
|
76
95
|
# Returns the Hash that we keep flex-column objects in, indexed by column name.
|
77
96
|
def _flex_column_objects
|
data/lib/flex_columns/version.rb
CHANGED
@@ -40,6 +40,57 @@ describe "FlexColumns basic operations" do
|
|
40
40
|
user2.user_attributes.keys.should == [ :wants_email ]
|
41
41
|
end
|
42
42
|
|
43
|
+
it "shouldn't complain if there is no data, but you still touch a field" do
|
44
|
+
user = ::User.new
|
45
|
+
user.name = 'User 1'
|
46
|
+
user.user_attributes.wants_email
|
47
|
+
user.save!
|
48
|
+
end
|
49
|
+
|
50
|
+
# This test case was created from a found bug: we were assuming that unless you called "#{method}=" on one of
|
51
|
+
# the attributes of a flex column, then we didn't need to serialize and save the flex column. However, that's not
|
52
|
+
# true, for exactly the reasons seen below (maybe you modified an object that was referred to from the field,
|
53
|
+
# but didn't change that field itself). See also the comment above ColumnData#deserialized?.
|
54
|
+
it "should still save its data even if you change something nested down deep" do
|
55
|
+
user = ::User.new
|
56
|
+
user.name = 'User 1'
|
57
|
+
user.user_attributes.wants_email = { 'foo' => { 'bar' => [ 1, 2, 3 ] } }
|
58
|
+
user.save!
|
59
|
+
|
60
|
+
user_again = ::User.find(user.id)
|
61
|
+
user_again.user_attributes.wants_email['foo']['bar'] << 4
|
62
|
+
user_again.save!
|
63
|
+
|
64
|
+
user_yet_again = ::User.find(user.id)
|
65
|
+
user_yet_again.user_attributes.wants_email['foo']['bar'].should == [ 1, 2, 3, 4 ]
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should return useful data for the column on #inspect, deserializing if necessary" do
|
69
|
+
user = ::User.new
|
70
|
+
user.name = 'User 1'
|
71
|
+
user.wants_email = 'whatEVER, yo'
|
72
|
+
user.save!
|
73
|
+
|
74
|
+
user_again = ::User.find(user.id)
|
75
|
+
s = user_again.user_attributes.inspect
|
76
|
+
s.should match(/UserAttributesFlexContents/i)
|
77
|
+
s.should match(/wants_email/i)
|
78
|
+
s.should match(/whatEVER, yo/)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should return useful data for the column on #inspect from the parent AR model" do
|
82
|
+
user = ::User.new
|
83
|
+
user.name = 'User 1'
|
84
|
+
user.wants_email = 'whatEVER, yo'
|
85
|
+
user.save!
|
86
|
+
|
87
|
+
user_again = ::User.find(user.id)
|
88
|
+
s = user_again.inspect
|
89
|
+
s.should match(/UserAttributesFlexContents/i)
|
90
|
+
s.should match(/wants_email/i)
|
91
|
+
s.should match(/whatEVER, yo/)
|
92
|
+
end
|
93
|
+
|
43
94
|
it "should store its data as standard JSON" do
|
44
95
|
user = ::User.new
|
45
96
|
user.name = 'User 1'
|
@@ -60,7 +111,7 @@ describe "FlexColumns basic operations" do
|
|
60
111
|
contents['wants_email'].should == 'sometimes'
|
61
112
|
end
|
62
113
|
|
63
|
-
it "should not modify that JSON if you don't
|
114
|
+
it "should not modify that JSON if you don't touch it, but should if you do" do
|
64
115
|
define_model_class(:UserBackdoor, 'flexcols_spec_users') { }
|
65
116
|
|
66
117
|
weirdly_spaced_json = ' { "wants_email" : "boop" } '
|
@@ -72,15 +123,7 @@ describe "FlexColumns basic operations" do
|
|
72
123
|
|
73
124
|
user = ::User.find(user_bd.id)
|
74
125
|
user.name.should == 'User 1'
|
75
|
-
user.wants_email
|
76
|
-
user.wants_email = 'boop'
|
77
|
-
user.save!
|
78
|
-
|
79
|
-
user_bd_again = ::UserBackdoor.find(user_bd.id)
|
80
|
-
user_bd_again.name.should == 'User 1'
|
81
|
-
user_bd_again.user_attributes.should == weirdly_spaced_json
|
82
|
-
|
83
|
-
user.user_attributes.touch!
|
126
|
+
user.wants_email
|
84
127
|
user.save!
|
85
128
|
|
86
129
|
user_bd_again = ::UserBackdoor.find(user_bd.id)
|
@@ -99,6 +99,33 @@ describe "FlexColumns compression operations" do
|
|
99
99
|
data.should_not match(/bar/)
|
100
100
|
end
|
101
101
|
|
102
|
+
describe "#inspect" do
|
103
|
+
it "should decompress and deserialize data for #inspect on the column itself, but should abbreviate" do
|
104
|
+
user = ::User.new
|
105
|
+
user.name = 'User 1'
|
106
|
+
user.foo = 'foo' * 1000
|
107
|
+
user.save!
|
108
|
+
|
109
|
+
user_again = ::User.find(user.id)
|
110
|
+
s = user_again.user_attributes.inspect
|
111
|
+
s.should match(/UserAttributesFlexContents/i)
|
112
|
+
s.should match(/foofoofoofoo/i)
|
113
|
+
s.length.should < 1000
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should decompress and deserialize data for #inspect on the parent, too" do
|
117
|
+
user = ::User.new
|
118
|
+
user.name = 'User 1'
|
119
|
+
user.foo = 'foo' * 1000
|
120
|
+
user.save!
|
121
|
+
|
122
|
+
user_again = ::User.find(user.id)
|
123
|
+
s = user_again.inspect
|
124
|
+
s.should match(/UserAttributesFlexContents/i)
|
125
|
+
s.should match(/foofoofoofoo/i)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
102
129
|
it "should read compressed data fine, even if told not to compress new data" do
|
103
130
|
user = ::User.new
|
104
131
|
user.name = 'User 1'
|
@@ -63,7 +63,7 @@ describe "FlexColumns performance" do
|
|
63
63
|
@deserializations[0][:raw_data].should == user_again.user_attributes.to_json
|
64
64
|
end
|
65
65
|
|
66
|
-
it "should not deserialize columns if they aren't
|
66
|
+
it "should not deserialize columns if they aren't accessed" do
|
67
67
|
user = ::User.new
|
68
68
|
user.name = 'User 1'
|
69
69
|
user.wants_email = 'foo'
|
@@ -75,7 +75,7 @@ describe "FlexColumns unknown fields" do
|
|
75
75
|
user_bd_again.some_unknown_attribute.should be_nil
|
76
76
|
end
|
77
77
|
|
78
|
-
it "should
|
78
|
+
it "should delete unknown fields if asked to, even if we only read from the model" do
|
79
79
|
@user_bd.wants_email = 'foo'
|
80
80
|
@user_bd.save!
|
81
81
|
|
@@ -98,7 +98,7 @@ describe "FlexColumns unknown fields" do
|
|
98
98
|
|
99
99
|
user_bd_again = ::UserBackdoor.find(@user_bd.id)
|
100
100
|
user_bd_again.wants_email.should == 'foo'
|
101
|
-
user_bd_again.some_unknown_attribute.
|
101
|
+
user_bd_again.some_unknown_attribute.should_not be
|
102
102
|
end
|
103
103
|
|
104
104
|
it "should have a method that explicitly will purge unknown methods, even if deserialization hasn't happened for any other reason, but not before then" do
|
@@ -131,22 +131,16 @@ describe FlexColumns::Contents::ColumnData do
|
|
131
131
|
lambda { instance.touch! }.should raise_error(FlexColumns::Errors::UnparseableJsonInDatabaseError)
|
132
132
|
end
|
133
133
|
|
134
|
-
it "should
|
135
|
-
@instance.
|
134
|
+
it "should be deserialized if you simply read from it" do
|
135
|
+
@instance.deserialized?.should_not be
|
136
136
|
@instance[:foo]
|
137
|
-
@instance.
|
137
|
+
@instance.deserialized?.should be
|
138
138
|
end
|
139
139
|
|
140
|
-
it "should
|
141
|
-
@instance.
|
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
|
140
|
+
it "should be deserialized if you set a field to something different" do
|
141
|
+
@instance.deserialized?.should_not be
|
148
142
|
@instance[:foo] = 'baz'
|
149
|
-
@instance.
|
143
|
+
@instance.deserialized?.should be
|
150
144
|
end
|
151
145
|
end
|
152
146
|
|
@@ -159,13 +153,34 @@ describe FlexColumns::Contents::ColumnData do
|
|
159
153
|
parsed['baz'].should == 'quux'
|
160
154
|
end
|
161
155
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
156
|
+
describe "#to_hash" do
|
157
|
+
it "should return a hash with the data in it, with indifferent access" do
|
158
|
+
h = @instance.to_hash
|
159
|
+
h.keys.sort.should == %w{foo bar baz}.sort
|
160
|
+
h['foo'].should == 'bar'
|
161
|
+
h['bar'].should == 123
|
162
|
+
h['baz'].should == 'quux'
|
163
|
+
h[:foo].should == 'bar'
|
164
|
+
h[:bar].should == 123
|
165
|
+
h[:baz].should == 'quux'
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should deserialize if needed" do
|
169
|
+
h = new_with_string(@json_string).to_hash
|
170
|
+
h.keys.sort.should == %w{foo bar baz}.sort
|
171
|
+
h['foo'].should == 'bar'
|
172
|
+
h['bar'].should == 123
|
173
|
+
h['baz'].should == 'quux'
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should not return unknown fields" do
|
177
|
+
h = new_with_string({ 'foo' => 'bar', 'baz' => 123, 'quux' => 'whatever' }.to_json).to_hash
|
178
|
+
h.keys.sort.should == %w{foo baz}.sort
|
179
|
+
h['foo'].should == 'bar'
|
180
|
+
h['baz'].should == 123
|
181
|
+
h['bar'].should be_nil
|
182
|
+
h['quux'].should be_nil
|
183
|
+
end
|
169
184
|
end
|
170
185
|
|
171
186
|
it "should accept a Hash as JSON, already parsed by the database stack" do
|
@@ -188,11 +188,11 @@ describe FlexColumns::Contents::FlexColumnContentsBase do
|
|
188
188
|
allow(@model_instance).to receive(:_flex_column_object_for).with(:fcn, false).and_return(@instance)
|
189
189
|
end
|
190
190
|
|
191
|
-
it "should tell you if it's been
|
192
|
-
expect(@column_data).to receive(:
|
193
|
-
@instance.
|
194
|
-
expect(@column_data).to receive(:
|
195
|
-
@instance.
|
191
|
+
it "should tell you if it's been deserialized" do
|
192
|
+
expect(@column_data).to receive(:deserialized?).once.with().and_return(true)
|
193
|
+
@instance.deserialized?.should be
|
194
|
+
expect(@column_data).to receive(:deserialized?).once.with().and_return(false)
|
195
|
+
@instance.deserialized?.should_not be
|
196
196
|
end
|
197
197
|
|
198
198
|
it "should save if the class tells it to" do
|
@@ -224,6 +224,25 @@ describe FlexColumns::Contents::FlexColumnContentsBase do
|
|
224
224
|
@instance[:xxx].should == :yyy
|
225
225
|
end
|
226
226
|
|
227
|
+
it "should return a useful string for #inspect" do
|
228
|
+
expect(@column_data).to receive(:to_hash).once.and_return({ :aaa => 'bbb', :ccc => 'ddd'})
|
229
|
+
i = @instance.inspect
|
230
|
+
i.should match(/aaa/)
|
231
|
+
i.should match(/bbb/)
|
232
|
+
i.should match(/ccc/)
|
233
|
+
i.should match(/ddd/)
|
234
|
+
end
|
235
|
+
|
236
|
+
it "should abbreviate that hash, if necessary" do
|
237
|
+
expect(@column_data).to receive(:to_hash).once.and_return({ :aaa => 'bbb', :ccc => ('ddd' * 1_000)})
|
238
|
+
i = @instance.inspect
|
239
|
+
i.should match(/aaa/)
|
240
|
+
i.should match(/bbb/)
|
241
|
+
i.should match(/ccc/)
|
242
|
+
i.should match(/ddddddddd/)
|
243
|
+
i.length.should < 1_000
|
244
|
+
end
|
245
|
+
|
227
246
|
it "should delegate to the column data on []=" do
|
228
247
|
expect(@column_data).to receive(:[]=).once.with(:xxx, :yyy).and_return(:zzz)
|
229
248
|
(@instance[:xxx] = :yyy).should == :yyy
|
@@ -490,19 +490,19 @@ describe FlexColumns::Definition::FlexColumnContentsClass do
|
|
490
490
|
end
|
491
491
|
|
492
492
|
describe "#requires_serialization_on_save?" do
|
493
|
-
it "should be true if there's an object and it has been
|
493
|
+
it "should be true if there's an object and it has been deserialized" do
|
494
494
|
model = double("model")
|
495
495
|
fco = double("fco")
|
496
496
|
allow(model).to receive(:_flex_column_object_for).with(:foo, false).and_return(fco)
|
497
|
-
allow(fco).to receive(:
|
497
|
+
allow(fco).to receive(:deserialized?).with().and_return(true)
|
498
498
|
@klass.requires_serialization_on_save?(model).should be
|
499
499
|
end
|
500
500
|
|
501
|
-
it "should be false if there's an object but it hasn't been
|
501
|
+
it "should be false if there's an object but it hasn't been deserialized" do
|
502
502
|
model = double("model")
|
503
503
|
fco = double("fco")
|
504
504
|
allow(model).to receive(:_flex_column_object_for).with(:foo, false).and_return(fco)
|
505
|
-
allow(fco).to receive(:
|
505
|
+
allow(fco).to receive(:deserialized?).with().and_return(false)
|
506
506
|
@klass.requires_serialization_on_save?(model).should_not be
|
507
507
|
end
|
508
508
|
|
@@ -168,28 +168,30 @@ describe FlexColumns::HasFlexColumns do
|
|
168
168
|
@klass._flex_column_dynamic_methods_module.should be(@dmm)
|
169
169
|
end
|
170
170
|
|
171
|
-
it "should call through on before_validation to
|
171
|
+
it "should call through on before_validation to all flex column objects, whether or not they've been deserialized" do
|
172
172
|
instance = @klass.new
|
173
173
|
instance._flex_columns_before_validation!
|
174
174
|
|
175
175
|
fcc_foo_instance = double("fcc_foo_instance")
|
176
176
|
expect(@fcc_foo).to receive(:new).once.with(instance).and_return(fcc_foo_instance)
|
177
177
|
instance._flex_column_object_for(:foo).should be(fcc_foo_instance)
|
178
|
-
allow(fcc_foo_instance).to receive(:
|
178
|
+
allow(fcc_foo_instance).to receive(:deserialized?).with().and_return(false)
|
179
179
|
|
180
|
+
expect(fcc_foo_instance).to receive(:before_validation!).once.with()
|
180
181
|
instance._flex_columns_before_validation!
|
181
182
|
|
182
183
|
|
183
184
|
fcc_bar_instance = double("fcc_bar_instance")
|
184
185
|
expect(@fcc_bar).to receive(:new).once.with(instance).and_return(fcc_bar_instance)
|
185
186
|
instance._flex_column_object_for(:bar).should be(fcc_bar_instance)
|
186
|
-
allow(fcc_bar_instance).to receive(:
|
187
|
+
allow(fcc_bar_instance).to receive(:deserialized?).with().and_return(true)
|
187
188
|
|
189
|
+
expect(fcc_foo_instance).to receive(:before_validation!).once.with()
|
188
190
|
expect(fcc_bar_instance).to receive(:before_validation!).once.with()
|
189
191
|
instance._flex_columns_before_validation!
|
190
192
|
|
191
|
-
allow(fcc_foo_instance).to receive(:
|
192
|
-
allow(fcc_bar_instance).to receive(:
|
193
|
+
allow(fcc_foo_instance).to receive(:deserialized?).with().and_return(true)
|
194
|
+
allow(fcc_bar_instance).to receive(:deserialized?).with().and_return(true)
|
193
195
|
|
194
196
|
expect(fcc_foo_instance).to receive(:before_validation!).once.with()
|
195
197
|
expect(fcc_bar_instance).to receive(:before_validation!).once.with()
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flex_columns
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Geweke
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-01-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -148,20 +148,6 @@ dependencies:
|
|
148
148
|
- - <=
|
149
149
|
- !ruby/object:Gem::Version
|
150
150
|
version: 4.99.99
|
151
|
-
- !ruby/object:Gem::Dependency
|
152
|
-
name: activerecord-import
|
153
|
-
requirement: !ruby/object:Gem::Requirement
|
154
|
-
requirements:
|
155
|
-
- - '>='
|
156
|
-
- !ruby/object:Gem::Version
|
157
|
-
version: '0'
|
158
|
-
type: :runtime
|
159
|
-
prerelease: false
|
160
|
-
version_requirements: !ruby/object:Gem::Requirement
|
161
|
-
requirements:
|
162
|
-
- - '>='
|
163
|
-
- !ruby/object:Gem::Version
|
164
|
-
version: '0'
|
165
151
|
- !ruby/object:Gem::Dependency
|
166
152
|
name: mysql2
|
167
153
|
requirement: !ruby/object:Gem::Requirement
|
@@ -251,7 +237,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
251
237
|
version: '0'
|
252
238
|
requirements: []
|
253
239
|
rubyforge_project:
|
254
|
-
rubygems_version: 2.1
|
240
|
+
rubygems_version: 2.2.1
|
255
241
|
signing_key:
|
256
242
|
specification_version: 4
|
257
243
|
summary: Schema-free, structured JSON storage inside a RDBMS.
|
@@ -284,3 +270,4 @@ test_files:
|
|
284
270
|
- spec/flex_columns/unit/including/include_flex_columns_spec.rb
|
285
271
|
- spec/flex_columns/unit/util/dynamic_methods_module_spec.rb
|
286
272
|
- spec/flex_columns/unit/util/string_utils_spec.rb
|
273
|
+
has_rdoc:
|