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,153 @@
1
+ require 'flex_columns'
2
+ require 'flex_columns/helpers/system_helpers'
3
+
4
+ describe "FlexColumns bulk operations" do
5
+ include FlexColumns::Helpers::SystemHelpers
6
+
7
+ before :each do
8
+ @dh = FlexColumns::Helpers::DatabaseHelper.new
9
+ @dh.setup_activerecord!
10
+
11
+ create_standard_system_spec_tables!
12
+ end
13
+
14
+ after :each do
15
+ drop_standard_system_spec_tables!
16
+ end
17
+
18
+ before :each do
19
+ define_model_class(:User, 'flexcols_spec_users') do
20
+ flex_column :user_attributes do
21
+ field :aaa, :string
22
+ field :bbb, :integer
23
+ end
24
+ end
25
+ end
26
+
27
+ it "should return #to_stored_data correctly on a text column, and return the exact same thing for #to_json" do
28
+ user = ::User.new
29
+ user.name = "User 1"
30
+ user.aaa = "aaa#{rand(1_000_000)}"
31
+ user.bbb = rand(1_000_000)
32
+
33
+ json = user.user_attributes.to_stored_data
34
+ json.class.should be(String)
35
+
36
+ parsed = JSON.parse(json)
37
+ parsed.keys.sort.should == %w{aaa bbb}.sort
38
+ parsed['aaa'].should == user.aaa
39
+
40
+ user.user_attributes.to_json.should == json
41
+ end
42
+
43
+ context "with a binary column" do
44
+ before :each do
45
+ migrate do
46
+ drop_table :flexcols_spec_users rescue nil
47
+ create_table :flexcols_spec_users do |t|
48
+ t.string :name, :null => false
49
+ t.binary :user_attributes
50
+ end
51
+ end
52
+
53
+ ::User.reset_column_information
54
+
55
+ define_model_class(:User, 'flexcols_spec_users') do
56
+ flex_column :user_attributes do
57
+ field :aaa, :string
58
+ field :bbb, :integer
59
+ end
60
+ end
61
+ end
62
+
63
+ it "should return #to_stored_data correctly on a binary column, uncompressed, but return JSON separately with #to_json" do
64
+ user = ::User.new
65
+ user.name = "User 1"
66
+ user.aaa = "aaa#{rand(1_000_000)}"
67
+ user.bbb = rand(1_000_000)
68
+
69
+ stored_data = user.user_attributes.to_stored_data
70
+ stored_data.class.should be(String)
71
+ stored_data.should match(/^FC:01,0,/)
72
+
73
+ stored_data =~ /^FC:01,0,(.*)$/i
74
+ json = $1
75
+ parsed = JSON.parse(json)
76
+ parsed.keys.sort.should == %w{aaa bbb}.sort
77
+ parsed['aaa'].should == user.aaa
78
+
79
+ user.user_attributes.to_json.should == json
80
+ end
81
+
82
+ it "should return #to_stored_data correctly on a binary column, compressed, but return JSON separately with #to_json" do
83
+ user = ::User.new
84
+ user.name = "User 1"
85
+ user.aaa = "aaa#{rand(1_000_000)}" * 1_000
86
+ user.bbb = rand(1_000_000)
87
+
88
+ stored_data = user.user_attributes.to_stored_data
89
+ stored_data.class.should be(String)
90
+ stored_data.should match(/^FC:01,1,/)
91
+
92
+ compressed = stored_data[8..-1]
93
+
94
+ require 'stringio'
95
+ stream = StringIO.new(compressed, "r")
96
+ reader = Zlib::GzipReader.new(stream)
97
+ json = reader.read
98
+
99
+ parsed = JSON.parse(json)
100
+ parsed.keys.sort.should == %w{aaa bbb}.sort
101
+ parsed['aaa'].should == user.aaa
102
+
103
+ user.user_attributes.to_json.should == json
104
+ end
105
+ end
106
+
107
+ it "should be able to instantiate fields without an ActiveRecord model, and then serialize them again" do
108
+ users = [ ]
109
+ 10.times do |i|
110
+ user = ::User.new
111
+ user.name = "User #{i}"
112
+ user.aaa = "aaa#{rand(1_000_000)}"
113
+ user.bbb = rand(1_000_000)
114
+ user.save!
115
+
116
+ users << user
117
+ end
118
+
119
+ json_blobs = [ ]
120
+ ::User.connection.select_all("SELECT id, user_attributes FROM flexcols_spec_users ORDER BY id ASC").each do |row|
121
+ json_blobs << row['user_attributes']
122
+ end
123
+
124
+ as_objects = ::User.create_flex_objects_from(:user_attributes, json_blobs)
125
+ as_objects.each_with_index do |object, i|
126
+ user = users[i]
127
+
128
+ object.aaa.should == user.aaa
129
+ object.bbb.should == user.bbb
130
+
131
+ object.bbb = "cannot-validate"
132
+ object.valid?.should_not be
133
+ object.errors.keys.should == [ :bbb ]
134
+ object.errors[:bbb].length.should == 1
135
+ object.errors[:bbb][0].should match(/is not a number/i)
136
+ end
137
+
138
+ json_blobs.each_with_index do |json_blob, i|
139
+ object = ::User.create_flex_object_from(:user_attributes, json_blob)
140
+
141
+ user = users[i]
142
+
143
+ object.aaa.should == user.aaa
144
+ object.bbb.should == user.bbb
145
+
146
+ object.bbb = "cannot-validate"
147
+ object.valid?.should_not be
148
+ object.errors.keys.should == [ :bbb ]
149
+ object.errors[:bbb].length.should == 1
150
+ object.errors[:bbb][0].should match(/is not a number/i)
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,218 @@
1
+ require 'flex_columns'
2
+ require 'flex_columns/helpers/system_helpers'
3
+
4
+ describe "FlexColumns compression operations" do
5
+ include FlexColumns::Helpers::SystemHelpers
6
+
7
+ before :each do
8
+ @dh = FlexColumns::Helpers::DatabaseHelper.new
9
+ @dh.setup_activerecord!
10
+
11
+ migrate do
12
+ drop_table :flexcols_spec_users rescue nil
13
+ create_table :flexcols_spec_users do |t|
14
+ t.string :name, :null => false
15
+ t.binary :user_attributes
16
+ end
17
+ end
18
+
19
+ define_model_class(:User, 'flexcols_spec_users') do
20
+ flex_column :user_attributes do
21
+ field :foo
22
+ field :bar
23
+ end
24
+ end
25
+
26
+ define_model_class(:UserBackdoor, 'flexcols_spec_users') { }
27
+ end
28
+
29
+ after :each do
30
+ migrate do
31
+ drop_table :flexcols_spec_users rescue nil
32
+ end
33
+ end
34
+
35
+ it "should not add a header, and should not compress data, if passed :header => false" do
36
+ define_model_class(:User, 'flexcols_spec_users') do
37
+ flex_column :user_attributes, :header => false do
38
+ field :foo
39
+ field :bar
40
+ end
41
+ end
42
+
43
+ user = ::User.new
44
+ user.name = 'User 1'
45
+ user.foo = "foo1"
46
+ user.bar = "bar1"
47
+ user.save!
48
+
49
+ user_bd = ::UserBackdoor.find(user.id)
50
+ data = user_bd.user_attributes
51
+ parsed = JSON.parse(data)
52
+ parsed.keys.sort.should == %w{foo bar}.sort
53
+
54
+ user = ::User.new
55
+ user.name = 'User 1'
56
+ user.foo = "foo" * 10_000
57
+ user.bar = "bar1"
58
+ user.save!
59
+
60
+ user_bd = ::UserBackdoor.find(user.id)
61
+ data = user_bd.user_attributes
62
+ data.length.should > 30_000
63
+ parsed = JSON.parse(data)
64
+ parsed.keys.sort.should == %w{foo bar}.sort
65
+ end
66
+
67
+ it "should not compress short data" do
68
+ user = ::User.new
69
+ user.name = 'User 1'
70
+ user.foo = 'foo1'
71
+ user.bar = 'bar1'
72
+ user.save!
73
+
74
+ user_again = ::User.find(user.id)
75
+ user_again.foo.should == 'foo1'
76
+ user_again.bar.should == 'bar1'
77
+
78
+ user_bd = ::UserBackdoor.find(user.id)
79
+ data = user_bd.user_attributes
80
+ data.should match(/foo1/)
81
+ data.should match(/bar1/)
82
+ end
83
+
84
+ it "should compress long data" do
85
+ user = ::User.new
86
+ user.name = 'User 1'
87
+ user.foo = 'foo' * 1000
88
+ user.bar = 'bar1'
89
+ user.save!
90
+
91
+ user_again = ::User.find(user.id)
92
+ user_again.foo.should == 'foo' * 1000
93
+ user_again.bar.should == 'bar1'
94
+
95
+ user_bd = ::UserBackdoor.find(user.id)
96
+ data = user_bd.user_attributes
97
+ data.length.should < 1000
98
+ data.should_not match(/foo/)
99
+ data.should_not match(/bar/)
100
+ end
101
+
102
+ it "should read compressed data fine, even if told not to compress new data" do
103
+ user = ::User.new
104
+ user.name = 'User 1'
105
+ user.foo = 'foo' * 1000
106
+ user.bar = 'bar1'
107
+ user.save!
108
+
109
+ user_again = ::User.find(user.id)
110
+ user_again.foo.should == 'foo' * 1000
111
+ user_again.bar.should == 'bar1'
112
+
113
+ user_bd = ::UserBackdoor.find(user.id)
114
+ data = user_bd.user_attributes
115
+ data.length.should < 1000
116
+ data.should_not match(/foo/)
117
+ data.should_not match(/bar/)
118
+
119
+ define_model_class(:User2, 'flexcols_spec_users') do
120
+ flex_column :user_attributes, :compress => false do
121
+ field :foo
122
+ field :bar
123
+ end
124
+ end
125
+
126
+ user2 = ::User2.find(user.id)
127
+ user.foo.should == 'foo' * 1000
128
+ user.bar.should == 'bar1'
129
+ end
130
+
131
+ it "should not compress long data, if asked not to" do
132
+ define_model_class(:User, 'flexcols_spec_users') do
133
+ flex_column :user_attributes, :compress => false do
134
+ field :foo
135
+ field :bar
136
+ end
137
+ end
138
+
139
+ user = ::User.new
140
+ user.name = 'User 1'
141
+ user.foo = 'foo' * 1000
142
+ user.bar = 'bar1'
143
+ user.save!
144
+
145
+ user_again = ::User.find(user.id)
146
+ user_again.foo.should == 'foo' * 1000
147
+ user_again.bar.should == 'bar1'
148
+
149
+ user_bd = ::UserBackdoor.find(user.id)
150
+ data = user_bd.user_attributes
151
+ data.length.should >= 3000
152
+ data.should match(/foofoofoofoo/)
153
+ data.should match(/bar1/)
154
+ end
155
+
156
+ it "should not compress data if the compressed version is bigger" do
157
+ define_model_class(:User, 'flexcols_spec_users') do
158
+ flex_column :user_attributes, :compress => 1 do
159
+ field :foo
160
+ field :bar
161
+ end
162
+ end
163
+
164
+ user = ::User.new
165
+ user.name = 'User 1'
166
+ user.foo = 'f'
167
+ user.save!
168
+
169
+ user_again = ::User.find(user.id)
170
+ user_again.foo.should == 'f'
171
+ user_again.bar.should be_nil
172
+
173
+ user_bd = ::UserBackdoor.find(user.id)
174
+ data = user_bd.user_attributes
175
+ data.should match(/^FC:01,0,\{/i)
176
+ end
177
+
178
+ it "should not compress data under a certain limit, if asked to" do
179
+ define_model_class(:User, 'flexcols_spec_users') do
180
+ flex_column :user_attributes, :compress => 10_000 do
181
+ field :foo
182
+ field :bar
183
+ end
184
+ end
185
+
186
+ user = ::User.new
187
+ user.name = 'User 1'
188
+ user.foo = 'foo' * 1000
189
+ user.bar = 'bar1'
190
+ user.save!
191
+
192
+ user_again = ::User.find(user.id)
193
+ user_again.foo.should == 'foo' * 1000
194
+ user_again.bar.should == 'bar1'
195
+
196
+ user_bd = ::UserBackdoor.find(user.id)
197
+ data = user_bd.user_attributes
198
+ data.length.should >= 3000
199
+ data.should match(/foofoofoofoo/)
200
+ data.should match(/bar1/)
201
+
202
+ user2 = ::User.new
203
+ user2.name = 'User 1'
204
+ user2.foo = 'foo' * 10_000
205
+ user2.bar = 'bar1'
206
+ user2.save!
207
+
208
+ user2_again = ::User.find(user2.id)
209
+ user2_again.foo.should == 'foo' * 10_000
210
+ user2_again.bar.should == 'bar1'
211
+
212
+ user2_bd = ::UserBackdoor.find(user2.id)
213
+ data = user2_bd.user_attributes
214
+ data.length.should < 10_000
215
+ data.should_not match(/foofoofoofoo/)
216
+ data.should_not match(/bar1/)
217
+ end
218
+ end
@@ -0,0 +1,120 @@
1
+ require 'flex_columns'
2
+ require 'flex_columns/helpers/system_helpers'
3
+
4
+ describe "FlexColumns custom-methods operations" do
5
+ include FlexColumns::Helpers::SystemHelpers
6
+
7
+ before :each do
8
+ @dh = FlexColumns::Helpers::DatabaseHelper.new
9
+ @dh.setup_activerecord!
10
+
11
+ create_standard_system_spec_tables!
12
+ end
13
+
14
+ after :each do
15
+ drop_standard_system_spec_tables!
16
+ end
17
+
18
+ it "should allow defining methods that are present on both the attributes class and the enclosing class" do
19
+ define_model_class(:User, 'flexcols_spec_users') do
20
+ flex_column :user_attributes do
21
+ field :number_of_emails_sent
22
+
23
+ def increment_number_of_emails_sent
24
+ self.number_of_emails_sent += 1
25
+ end
26
+
27
+ def change_number_of_emails_sent(return_value)
28
+ self.number_of_emails_sent = yield number_of_emails_sent
29
+ return_value
30
+ end
31
+ end
32
+ end
33
+
34
+ user = ::User.new
35
+ user.name = 'User 1'
36
+
37
+ user.user_attributes.number_of_emails_sent = 15
38
+ user.user_attributes.increment_number_of_emails_sent
39
+ user.user_attributes.number_of_emails_sent.should == 16
40
+ (user.user_attributes.change_number_of_emails_sent('abc') { |x| x - 5 }).should == 'abc'
41
+ user.user_attributes.number_of_emails_sent.should == 11
42
+
43
+ user.number_of_emails_sent.should == 11
44
+ user.increment_number_of_emails_sent
45
+ user.number_of_emails_sent.should == 12
46
+ (user.change_number_of_emails_sent('abc') { |x| x - 5 }).should == 'abc'
47
+ user.number_of_emails_sent.should == 7
48
+ end
49
+
50
+ it "should not delegate methods if told not to" do
51
+ define_model_class(:User, 'flexcols_spec_users') do
52
+ flex_column :user_attributes, :delegate => false do
53
+ field :number_of_emails_sent
54
+
55
+ def increment_number_of_emails_sent
56
+ self.number_of_emails_sent += 1
57
+ end
58
+
59
+ def change_number_of_emails_sent(return_value)
60
+ self.number_of_emails_sent = yield number_of_emails_sent
61
+ return_value
62
+ end
63
+ end
64
+ end
65
+
66
+ user = ::User.new
67
+ user.name = 'User 1'
68
+
69
+ user.respond_to?(:number_of_emails_sent).should_not be
70
+ user.respond_to?(:increment_number_of_emails_sent).should_not be
71
+ user.respond_to?(:change_number_of_emails_sent).should_not be
72
+ end
73
+
74
+ it "should delegate methods privately if told to" do
75
+ define_model_class(:User, 'flexcols_spec_users') do
76
+ flex_column :user_attributes, :delegate => :private do
77
+ field :number_of_emails_sent
78
+
79
+ def increment_number_of_emails_sent
80
+ self.number_of_emails_sent += 1
81
+ end
82
+
83
+ def change_number_of_emails_sent(return_value)
84
+ self.number_of_emails_sent = yield number_of_emails_sent
85
+ return_value
86
+ end
87
+ end
88
+
89
+ def nes=(x)
90
+ self.number_of_emails_sent = x
91
+ end
92
+
93
+ def nes
94
+ number_of_emails_sent
95
+ end
96
+
97
+ def ies
98
+ increment_number_of_emails_sent
99
+ end
100
+
101
+ def cnes(rv, &block)
102
+ change_number_of_emails_sent(rv, &block)
103
+ end
104
+ end
105
+
106
+ user = ::User.new
107
+ user.name = 'User 1'
108
+
109
+ user.respond_to?(:number_of_emails_sent).should_not be
110
+ user.respond_to?(:increment_number_of_emails_sent).should_not be
111
+ user.respond_to?(:change_number_of_emails_sent).should_not be
112
+
113
+ user.nes = 10
114
+ user.nes.should == 10
115
+ user.ies
116
+ user.nes.should == 11
117
+ (user.cnes('abc') { |x| x - 5 }).should == 'abc'
118
+ user.nes.should == 6
119
+ end
120
+ end