flex_columns 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.travis.yml +38 -0
  4. data/Gemfile +17 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +124 -0
  7. data/Rakefile +6 -0
  8. data/flex_columns.gemspec +72 -0
  9. data/lib/flex_columns.rb +15 -0
  10. data/lib/flex_columns/active_record/base.rb +57 -0
  11. data/lib/flex_columns/contents/column_data.rb +376 -0
  12. data/lib/flex_columns/contents/flex_column_contents_base.rb +188 -0
  13. data/lib/flex_columns/definition/field_definition.rb +316 -0
  14. data/lib/flex_columns/definition/field_set.rb +89 -0
  15. data/lib/flex_columns/definition/flex_column_contents_class.rb +327 -0
  16. data/lib/flex_columns/errors.rb +236 -0
  17. data/lib/flex_columns/has_flex_columns.rb +187 -0
  18. data/lib/flex_columns/including/include_flex_columns.rb +179 -0
  19. data/lib/flex_columns/util/dynamic_methods_module.rb +86 -0
  20. data/lib/flex_columns/util/string_utils.rb +31 -0
  21. data/lib/flex_columns/version.rb +4 -0
  22. data/spec/flex_columns/helpers/database_helper.rb +174 -0
  23. data/spec/flex_columns/helpers/exception_helpers.rb +20 -0
  24. data/spec/flex_columns/helpers/system_helpers.rb +47 -0
  25. data/spec/flex_columns/system/basic_system_spec.rb +245 -0
  26. data/spec/flex_columns/system/bulk_system_spec.rb +153 -0
  27. data/spec/flex_columns/system/compression_system_spec.rb +218 -0
  28. data/spec/flex_columns/system/custom_methods_system_spec.rb +120 -0
  29. data/spec/flex_columns/system/delegation_system_spec.rb +175 -0
  30. data/spec/flex_columns/system/dynamism_system_spec.rb +158 -0
  31. data/spec/flex_columns/system/error_handling_system_spec.rb +117 -0
  32. data/spec/flex_columns/system/including_system_spec.rb +285 -0
  33. data/spec/flex_columns/system/json_alias_system_spec.rb +171 -0
  34. data/spec/flex_columns/system/performance_system_spec.rb +218 -0
  35. data/spec/flex_columns/system/postgres_json_column_type_system_spec.rb +85 -0
  36. data/spec/flex_columns/system/types_system_spec.rb +93 -0
  37. data/spec/flex_columns/system/unknown_fields_system_spec.rb +126 -0
  38. data/spec/flex_columns/system/validations_system_spec.rb +111 -0
  39. data/spec/flex_columns/unit/active_record/base_spec.rb +32 -0
  40. data/spec/flex_columns/unit/contents/column_data_spec.rb +520 -0
  41. data/spec/flex_columns/unit/contents/flex_column_contents_base_spec.rb +253 -0
  42. data/spec/flex_columns/unit/definition/field_definition_spec.rb +617 -0
  43. data/spec/flex_columns/unit/definition/field_set_spec.rb +142 -0
  44. data/spec/flex_columns/unit/definition/flex_column_contents_class_spec.rb +733 -0
  45. data/spec/flex_columns/unit/errors_spec.rb +297 -0
  46. data/spec/flex_columns/unit/has_flex_columns_spec.rb +365 -0
  47. data/spec/flex_columns/unit/including/include_flex_columns_spec.rb +144 -0
  48. data/spec/flex_columns/unit/util/dynamic_methods_module_spec.rb +105 -0
  49. data/spec/flex_columns/unit/util/string_utils_spec.rb +23 -0
  50. metadata +286 -0
@@ -0,0 +1,285 @@
1
+ require 'flex_columns'
2
+ require 'flex_columns/helpers/system_helpers'
3
+
4
+ describe "FlexColumns including" do
5
+ include FlexColumns::Helpers::SystemHelpers
6
+
7
+ before :each do
8
+ @dh = FlexColumns::Helpers::DatabaseHelper.new
9
+ @dh.setup_activerecord!
10
+ end
11
+
12
+ context "with standard setup" do
13
+ before :each do
14
+ create_standard_system_spec_tables!
15
+
16
+ migrate do
17
+ drop_table :flexcols_spec_user_preferences rescue nil
18
+ create_table :flexcols_spec_user_preferences, :id => false do |t|
19
+ t.integer :user_id, :null => false
20
+ t.text :attribs1
21
+ t.text :attribs2
22
+ end
23
+
24
+ add_index :flexcols_spec_user_preferences, :user_id, :unique => true
25
+ end
26
+
27
+ define_model_class(:UserPreference, 'flexcols_spec_user_preferences') do
28
+ self.primary_key = :user_id
29
+
30
+ belongs_to :user
31
+
32
+ flex_column :attribs1 do
33
+ field :foo
34
+ field :bar
35
+
36
+ def inc_foo!
37
+ self.foo += 1
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ after :each do
44
+ drop_standard_system_spec_tables!
45
+
46
+ migrate do
47
+ drop_table :flexcols_spec_user_preferences rescue nil
48
+ end
49
+ end
50
+
51
+ it "should include columns appropriately, including flex-column names and defined methods" do
52
+ define_model_class(:User, 'flexcols_spec_users') do
53
+ has_one :preference, :class_name => 'UserPreference'
54
+
55
+ include_flex_columns_from :preference
56
+ end
57
+
58
+ user = ::User.new
59
+ user.name = 'User 1'
60
+
61
+ user.foo = 123
62
+ user.attribs1.bar = 'bar1'
63
+ user.attribs1.foo.should == 123
64
+ user.bar.should == 'bar1'
65
+
66
+ user.inc_foo!
67
+ user.attribs1.foo.should == 124
68
+ user.foo.should == 124
69
+
70
+ user.save!
71
+
72
+ user_again = ::User.find(user.id)
73
+ user_again.foo.should == 124
74
+ user_again.bar.should == 'bar1'
75
+ user_again.attribs1.foo.should == 124
76
+ user_again.attribs1.bar.should == 'bar1'
77
+
78
+ preferences = ::UserPreference.find(user.id)
79
+ preferences.foo.should == 124
80
+ preferences.bar.should == 'bar1'
81
+ preferences.attribs1.foo.should == 124
82
+ preferences.attribs1.bar.should == 'bar1'
83
+ end
84
+
85
+ it "should allow turning off delegation, but should still include base names, prefixed as needed" do
86
+ define_model_class(:User, 'flexcols_spec_users') do
87
+ has_one :preference, :class_name => 'UserPreference'
88
+
89
+ include_flex_columns_from :preference, :delegate => false, :prefix => :abc
90
+ end
91
+
92
+ user = ::User.new
93
+ user.name = 'User 1'
94
+
95
+ user.respond_to?(:foo).should_not be
96
+ user.respond_to?(:bar).should_not be
97
+ user.respond_to?(:abc_foo).should_not be
98
+ user.respond_to?(:abc_bar).should_not be
99
+ user.respond_to?(:inc_foo!).should_not be
100
+ user.respond_to?(:abc_inc_foo!).should_not be
101
+
102
+ user.abc_attribs1.foo = 123
103
+ user.abc_attribs1.bar = 'bar1'
104
+ user.abc_attribs1.inc_foo!.should == 124
105
+ user.abc_attribs1.foo.should == 124
106
+
107
+ user.save!
108
+
109
+ user_again = ::User.find(user.id)
110
+ user_again.abc_attribs1.foo.should == 124
111
+ user_again.abc_attribs1.bar.should == 'bar1'
112
+
113
+ preferences = ::UserPreference.find(user.id)
114
+ preferences.foo.should == 124
115
+ preferences.bar.should == 'bar1'
116
+ end
117
+
118
+ it "should include columns and methods privately, if requested" do
119
+ define_model_class(:User, 'flexcols_spec_users') do
120
+ has_one :preference, :class_name => 'UserPreference'
121
+
122
+ include_flex_columns_from :preference, :visibility => :private
123
+ end
124
+
125
+ user = ::User.new
126
+ user.name = 'User 1'
127
+
128
+ user.respond_to?(:foo).should_not be
129
+ user.respond_to?(:foo=).should_not be
130
+ user.respond_to?(:bar).should_not be
131
+ user.respond_to?(:bar=).should_not be
132
+ user.respond_to?(:inc_foo).should_not be
133
+ user.respond_to?(:attribs1).should_not be
134
+
135
+ lambda { user.foo }.should raise_error(NoMethodError)
136
+ lambda { user.foo = 123 }.should raise_error(NoMethodError)
137
+ lambda { user.bar }.should raise_error(NoMethodError)
138
+ lambda { user.bar = 123 }.should raise_error(NoMethodError)
139
+ lambda { user.inc_foo }.should raise_error(NoMethodError)
140
+ lambda { user.attribs1 }.should raise_error(NoMethodError)
141
+
142
+ user.send(:foo=, 123).should == 123
143
+ user.send(:bar=, 'bar1').should == 'bar1'
144
+ user.send(:inc_foo!).should == 124
145
+ user.send(:foo).should == 124
146
+ user.send(:attribs1).foo.should == 124
147
+
148
+ user.save!
149
+
150
+ user_again = ::User.find(user.id)
151
+ user_again.send(:foo).should == 124
152
+ user_again.send(:bar).should == 'bar1'
153
+ user_again.send(:attribs1).foo.should == 124
154
+
155
+ preferences = ::UserPreference.find(user.id)
156
+ preferences.foo.should == 124
157
+ preferences.bar.should == 'bar1'
158
+ end
159
+
160
+ it "should prefix included method names, if requested" do
161
+ define_model_class(:User, 'flexcols_spec_users') do
162
+ has_one :preference, :class_name => 'UserPreference'
163
+
164
+ include_flex_columns_from :preference, :prefix => 'abc'
165
+ end
166
+
167
+ user = ::User.new
168
+ user.name = 'User 1'
169
+
170
+ lambda { user.send(:foo) }.should raise_error(NoMethodError)
171
+ lambda { user.send(:foo=, 123) }.should raise_error(NoMethodError)
172
+ lambda { user.send(:bar) }.should raise_error(NoMethodError)
173
+ lambda { user.send(:bar=, 123) }.should raise_error(NoMethodError)
174
+ lambda { user.send(:inc_foo!) }.should raise_error(NoMethodError)
175
+ lambda { user.send(:attribs1) }.should raise_error(NoMethodError)
176
+
177
+ user.abc_foo = 123
178
+ user.abc_bar = 'bar1'
179
+ user.abc_inc_foo!.should == 124
180
+ user.abc_foo.should == 124
181
+ user.abc_attribs1.foo.should == 124
182
+ user.abc_attribs1.bar.should == 'bar1'
183
+
184
+ user.save!
185
+
186
+ user_again = ::User.find(user.id)
187
+ user_again.abc_foo.should == 124
188
+ user_again.abc_bar.should == 'bar1'
189
+ user_again.abc_attribs1.foo.should == 124
190
+ user_again.abc_attribs1.bar.should == 'bar1'
191
+
192
+ preferences = ::UserPreference.find(user.id)
193
+ preferences.foo.should == 124
194
+ preferences.bar.should == 'bar1'
195
+ end
196
+ end
197
+
198
+ it "should not clobber methods that already exist, or columns on the included-into object" do
199
+ migrate do
200
+ drop_table :flexcols_spec_users rescue nil
201
+ create_table :flexcols_spec_users do |t|
202
+ t.string :name, :null => false
203
+ t.string :foo
204
+ t.string :quux
205
+ t.string :attribs1
206
+ end
207
+
208
+ drop_table :flexcols_spec_user_preferences rescue nil
209
+ create_table :flexcols_spec_user_preferences, :id => false do |t|
210
+ t.integer :user_id, :null => false
211
+ t.text :attribs1
212
+ end
213
+
214
+ add_index :flexcols_spec_user_preferences, :user_id, :unique => true
215
+ end
216
+
217
+ define_model_class(:UserPreference2, 'flexcols_spec_user_preferences') do
218
+ self.primary_key = :user_id
219
+
220
+ belongs_to :user, :class_name => 'User2'
221
+
222
+ flex_column :attribs1 do
223
+ field :foo
224
+ field :bar
225
+ field :baz
226
+
227
+ def quux
228
+ self.foo
229
+ end
230
+ end
231
+ end
232
+
233
+ define_model_class(:User2, 'flexcols_spec_users') do
234
+ has_one :preference, :class_name => 'UserPreference2', :foreign_key => :user_id
235
+
236
+ include_flex_columns_from :preference
237
+ end
238
+
239
+ define_model_class(:UserBackdoor2, 'flexcols_spec_users') { }
240
+
241
+ define_model_class(:UserPreferenceBackdoor2, 'flexcols_spec_user_preferences') { }
242
+
243
+ user = ::User2.new
244
+ user.name = 'User 1'
245
+ user.foo = 'user_1 foo'
246
+ user.quux = 'user_1 quux'
247
+ user.attribs1 = "user_1 attribs1"
248
+ user.save!
249
+
250
+ user_bd = ::UserBackdoor2.find(user.id)
251
+ user_bd.attribs1.should == 'user_1 attribs1'
252
+ user_bd.foo.should == 'user_1 foo'
253
+ user_bd.quux.should == 'user_1 quux'
254
+
255
+ user.build_preference
256
+ user.preference.foo = 'prefs foo'
257
+ user.preference.bar = 'prefs bar'
258
+ user.preference.baz = 'prefs baz'
259
+ user.preference.quux.should == 'prefs foo'
260
+
261
+ user.preference.attribs1.class.name.should match(/flexcontents/i)
262
+ user.attribs1.should == 'user_1 attribs1'
263
+ user.save!
264
+
265
+
266
+ user_bd = ::UserBackdoor2.find(user.id)
267
+ user_bd.attribs1.should == 'user_1 attribs1'
268
+ user_bd.foo.should == 'user_1 foo'
269
+ user_bd.quux.should == 'user_1 quux'
270
+
271
+ prefs_bd = ::UserPreferenceBackdoor2.where(:user_id => user.id).first
272
+ parsed = JSON.parse(prefs_bd.attribs1)
273
+ parsed.keys.sort.should == %w{foo bar baz}.sort
274
+ parsed['foo'].should == 'prefs foo'
275
+ parsed['bar'].should == 'prefs bar'
276
+ parsed['baz'].should == 'prefs baz'
277
+
278
+ user_again = ::User2.find(user.id)
279
+ user_again.foo.should == 'user_1 foo'
280
+ user_again.quux.should == 'user_1 quux'
281
+ user_again.attribs1.should == 'user_1 attribs1'
282
+ user_again.bar.should == 'prefs bar'
283
+ user_again.baz.should == 'prefs baz'
284
+ end
285
+ end
@@ -0,0 +1,171 @@
1
+ require 'flex_columns'
2
+ require 'flex_columns/helpers/system_helpers'
3
+ require 'flex_columns/helpers/exception_helpers'
4
+
5
+ describe "FlexColumns JSON aliasing" 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
+
15
+ define_model_class(:UserBackdoor, 'flexcols_spec_users') { }
16
+ end
17
+
18
+ after :each do
19
+ drop_standard_system_spec_tables!
20
+ end
21
+
22
+ it "should store attributes under their JSON alias, but use other names absolutely everywhere else" do
23
+ define_model_class(:User, 'flexcols_spec_users') do
24
+ flex_column :user_attributes do
25
+ field :wants_email, :integer, :json => :we
26
+ field :language_setting, :json => :ls
27
+
28
+ validates :wants_email, :numericality => { :greater_than_or_equal_to => 0 }
29
+ end
30
+ end
31
+
32
+ user = ::User.new
33
+ user.name = 'User 1'
34
+ user.wants_email = 123
35
+ user.language_setting = 'foobar'
36
+ user.save!
37
+
38
+ user_bd = ::UserBackdoor.find(user.id)
39
+ json = user_bd.user_attributes
40
+ parsed = JSON.parse(json)
41
+
42
+ parsed.keys.sort.should == %w{we ls}.sort
43
+ parsed['we'].should == 123
44
+ parsed['ls'].should == 'foobar'
45
+
46
+ # make sure hashes work on original field names
47
+ lambda { user.user_attributes[:we] }.should raise_error(FlexColumns::Errors::NoSuchFieldError)
48
+ lambda { user.user_attributes[:ls] }.should raise_error(FlexColumns::Errors::NoSuchFieldError)
49
+
50
+ # Make sure methods work on original field names
51
+ lambda { user.user_attributes.send(:we) }.should raise_error(NoMethodError)
52
+ lambda { user.user_attributes.send(:ls) }.should raise_error(NoMethodError)
53
+
54
+ # Make sure validations work still
55
+ user.valid?.should be
56
+
57
+ user.wants_email = -123
58
+
59
+ user.valid?.should_not be
60
+ user.errors.keys.should == [ :'user_attributes.wants_email' ]
61
+
62
+ user.user_attributes.valid?.should_not be
63
+ user.user_attributes.errors.keys.should == [ :wants_email ]
64
+ end
65
+
66
+ it "should prohibit conflicting JSON names" do
67
+ e = capture_exception(FlexColumns::Errors::ConflictingJsonStorageNameError) do
68
+ define_model_class(:User, 'flexcols_spec_users') { }
69
+
70
+ define_model_class(:User, 'flexcols_spec_users') do
71
+ flex_column :user_attributes, :unknown_fields => :delete do
72
+ field :wants_email, :json => :aaa
73
+ field :language_setting, :json => :aaa
74
+ end
75
+ end
76
+ end
77
+
78
+ e.model_class.should == ::User
79
+ e.column_name.should == :user_attributes
80
+ e.new_field_name.should == :language_setting
81
+ e.existing_field_name.should == :wants_email
82
+ e.json_storage_name.should == :aaa
83
+
84
+ e.message.should match(/User/i)
85
+ e.message.should match(/user_attributes/i)
86
+ e.message.should match(/language_setting/i)
87
+ e.message.should match(/wants_email/i)
88
+ e.message.should match(/aaa/i)
89
+ end
90
+
91
+ it "should prohibit JSON names from conflicting with non-aliased fields" do
92
+ e = capture_exception(FlexColumns::Errors::ConflictingJsonStorageNameError) do
93
+ define_model_class(:User, 'flexcols_spec_users') { }
94
+
95
+ define_model_class(:User, 'flexcols_spec_users') do
96
+ flex_column :user_attributes, :unknown_fields => :delete do
97
+ field :wants_email
98
+ field :language_setting, :json => :wants_email
99
+ end
100
+ end
101
+ end
102
+
103
+ e.model_class.should == ::User
104
+ e.column_name.should == :user_attributes
105
+ e.new_field_name.should == :language_setting
106
+ e.existing_field_name.should == :wants_email
107
+ e.json_storage_name.should == :wants_email
108
+
109
+ e.message.should match(/User/i)
110
+ e.message.should match(/user_attributes/i)
111
+ e.message.should match(/language_setting/i)
112
+ e.message.should match(/wants_email/i)
113
+ e.message.should match(/wants_email/i)
114
+ end
115
+
116
+ it "should treat field names present in the JSON hash as unknown fields, and delete them if asked to" do
117
+ define_model_class(:User, 'flexcols_spec_users') do
118
+ flex_column :user_attributes, :unknown_fields => :delete do
119
+ field :wants_email, :json => :we
120
+ field :language_setting, :json => :ls
121
+ end
122
+ end
123
+
124
+ user = ::User.new
125
+ user.name = 'User 1'
126
+ user.save!
127
+
128
+ user_bd = ::UserBackdoor.find(user.id)
129
+ user_bd.user_attributes = { 'wants_email' => 123, 'we' => 456, 'language_setting' => 'bonko' }.to_json
130
+ user_bd.save!
131
+
132
+ user_again = ::User.find(user.id)
133
+ user_again.wants_email.should == 456
134
+ user_again.language_setting.should be_nil
135
+ user_again.user_attributes.touch!
136
+ user_again.save!
137
+
138
+ user_bd = ::UserBackdoor.find(user.id)
139
+ JSON.parse(user_bd.user_attributes).keys.sort.should == %w{we}.sort
140
+ end
141
+
142
+ it "should treat field names present in the JSON hash as unknown fields, and preserve them if asked to" do
143
+ define_model_class(:User, 'flexcols_spec_users') do
144
+ flex_column :user_attributes do
145
+ field :wants_email, :json => :we
146
+ field :language_setting, :json => :ls
147
+ end
148
+ end
149
+
150
+ user = ::User.new
151
+ user.name = 'User 1'
152
+ user.save!
153
+
154
+ user_bd = ::UserBackdoor.find(user.id)
155
+ user_bd.user_attributes = { 'wants_email' => 123, 'we' => 456, 'language_setting' => 'bonko' }.to_json
156
+ user_bd.save!
157
+
158
+ user_again = ::User.find(user.id)
159
+ user_again.wants_email.should == 456
160
+ user_again.wants_email = 567
161
+ user_again.language_setting.should be_nil
162
+ user_again.save!
163
+
164
+ user_bd = ::UserBackdoor.find(user.id)
165
+ parsed = JSON.parse(user_bd.user_attributes)
166
+ parsed.keys.sort.should == %w{wants_email we language_setting}.sort
167
+ parsed['wants_email'].should == 123
168
+ parsed['we'].should == 567
169
+ parsed['language_setting'].should == 'bonko'
170
+ end
171
+ end