flex_columns 1.0.1 → 1.0.2
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 +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:
|