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,175 @@
1
+ require 'flex_columns'
2
+ require 'flex_columns/helpers/exception_helpers'
3
+ require 'flex_columns/helpers/system_helpers'
4
+
5
+ describe "FlexColumns delegation" do
6
+ include FlexColumns::Helpers::SystemHelpers
7
+ include FlexColumns::Helpers::ExceptionHelpers
8
+
9
+ before :each do
10
+ @dh = FlexColumns::Helpers::DatabaseHelper.new
11
+ @dh.setup_activerecord!
12
+
13
+ create_standard_system_spec_tables!
14
+ end
15
+
16
+ after :each do
17
+ drop_standard_system_spec_tables!
18
+ end
19
+
20
+ it "should delegate methods by default" do
21
+ define_model_class(:User, 'flexcols_spec_users') do
22
+ flex_column :user_attributes do
23
+ field :wants_email
24
+ field :something
25
+ field :something_else
26
+ end
27
+ end
28
+
29
+ user = ::User.new
30
+
31
+ %w{wants_email something something_else}.each do |method_name|
32
+ user.respond_to?(method_name).should be
33
+ user.respond_to?("#{method_name}=").should be
34
+
35
+ user.send(method_name).should be_nil
36
+ value = "abc123#{rand(1_000)}"
37
+ user.send("#{method_name}=", value).should == value
38
+ user.send(method_name).should == value
39
+
40
+ user.user_attributes.send(method_name).should == value
41
+ user.user_attributes.send("#{method_name}=", value + "new").should == value + "new"
42
+ user.send(method_name).should == value + "new"
43
+ end
44
+ end
45
+
46
+ it "should not override columns on the model object" do
47
+ migrate do
48
+ drop_table :flexcols_spec_users
49
+ create_table :flexcols_spec_users do |t|
50
+ t.string :name, :null => false
51
+ t.string :foo
52
+ t.string :baz
53
+ t.text :user_attributes
54
+ end
55
+ end
56
+
57
+ define_model_class(:User, 'flexcols_spec_users') do
58
+ flex_column :user_attributes do
59
+ field :foo
60
+ field :bar
61
+
62
+ def baz
63
+ foo + "!!"
64
+ end
65
+ end
66
+ end
67
+
68
+ ::User.reset_column_information
69
+
70
+ define_model_class(:UserBackdoor, 'flexcols_spec_users') { }
71
+
72
+ user = ::User.new
73
+ user.name = 'User 1'
74
+ user.foo = "outer_foo"
75
+ user.baz = "outer_baz"
76
+ user.user_attributes.foo = "inner_foo"
77
+ user.save!
78
+
79
+ user_again = ::User.find(user.id)
80
+ user_again.foo.should == "outer_foo"
81
+ user_again.user_attributes.foo.should == "inner_foo"
82
+ user_again.baz.should == "outer_baz"
83
+ user_again.user_attributes.baz.should == "inner_foo!!"
84
+
85
+ user_bd = ::UserBackdoor.find(user.id)
86
+ user_bd.foo.should == "outer_foo"
87
+ user_bd.baz.should == "outer_baz"
88
+ parsed = JSON.parse(user_bd.user_attributes)
89
+ parsed.keys.should == [ 'foo' ]
90
+ parsed['foo'].should == "inner_foo"
91
+ end
92
+
93
+ it "should let you turn off delegation for a column" do
94
+ define_model_class(:User, 'flexcols_spec_users') do
95
+ flex_column :user_attributes, :delegate => false do
96
+ field :wants_email
97
+ field :something
98
+ field :something_else
99
+ end
100
+ end
101
+
102
+ user = ::User.new
103
+
104
+ %w{wants_email something something_else}.each do |method_name|
105
+ user.respond_to?(method_name).should_not be
106
+ user.respond_to?("#{method_name}=").should_not be
107
+
108
+ lambda { user.send(method_name) }.should raise_error(NoMethodError.superclass)
109
+ lambda { user.send("#{method_name}=", 1234) }.should raise_error(NoMethodError.superclass)
110
+
111
+ user.user_attributes.send(method_name).should be_nil
112
+ value = "abc123#{rand(1_000)}"
113
+ user.user_attributes.send("#{method_name}=", value).should == value
114
+ user.user_attributes.send(method_name).should == value
115
+ end
116
+ end
117
+
118
+ it "should let you use private delegation for a column" do
119
+ define_model_class(:User, 'flexcols_spec_users') do
120
+ flex_column :user_attributes, :delegate => :private do
121
+ field :wants_email
122
+ field :something
123
+ field :something_else
124
+ end
125
+ end
126
+
127
+ user = ::User.new
128
+
129
+ %w{wants_email something something_else}.each do |method_name|
130
+ user.respond_to?(method_name).should_not be
131
+ user.respond_to?("#{method_name}=").should_not be
132
+
133
+ lambda { eval("user.#{method_name}") }.should raise_error(NoMethodError)
134
+ lambda { eval("user.#{method_name} = 123") }.should raise_error(NoMethodError)
135
+
136
+ user.send(method_name).should be_nil
137
+ value = "abc123#{rand(1_000)}"
138
+ user.send("#{method_name}=", value).should == value
139
+ user.send(method_name).should == value
140
+ end
141
+ end
142
+
143
+ it "should let you add a prefix to methods" do
144
+ define_model_class(:User, 'flexcols_spec_users') do
145
+ flex_column :user_attributes, :prefix => 'bar' do
146
+ field :wants_email
147
+ field :something
148
+ field :something_else
149
+ end
150
+ end
151
+
152
+ user = ::User.new
153
+
154
+ %w{wants_email something something_else}.each do |method_name|
155
+ user.respond_to?(method_name).should_not be
156
+ user.respond_to?("#{method_name}=").should_not be
157
+
158
+ lambda { user.send(method_name) }.should raise_error(NoMethodError.superclass)
159
+ lambda { user.send("#{method_name}=", 1234) }.should raise_error(NoMethodError.superclass)
160
+
161
+ correct_method_name = "bar_#{method_name}"
162
+ user.respond_to?(correct_method_name).should be
163
+ user.respond_to?("#{correct_method_name}=").should be
164
+
165
+ user.send(correct_method_name).should be_nil
166
+ value = "abc123#{rand(1_000)}"
167
+ user.send("#{correct_method_name}=", value).should == value
168
+ user.send(correct_method_name).should == value
169
+
170
+ user.user_attributes.send(method_name).should == value
171
+ user.user_attributes.send("#{method_name}=", value + "new").should == value + "new"
172
+ user.send(correct_method_name).should == value + "new"
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,158 @@
1
+ require 'flex_columns'
2
+ require 'flex_columns/helpers/system_helpers'
3
+
4
+ describe "FlexColumns basic 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 let you redefine flex columns, and obey the new settings" do
19
+ class ::User < ::ActiveRecord::Base
20
+ self.table_name = 'flexcols_spec_users'
21
+
22
+ flex_column :user_attributes do
23
+ field :att1
24
+ field :att2
25
+
26
+ def abc
27
+ "abc!"
28
+ end
29
+ end
30
+ end
31
+
32
+ user = ::User.new
33
+ user.att1 = "foo"
34
+ user.att2 = "bar"
35
+ user.abc.should == 'abc!'
36
+
37
+ class ::User < ::ActiveRecord::Base
38
+ self.table_name = 'flexcols_spec_users'
39
+
40
+ flex_column :user_attributes do
41
+ field :att3
42
+ field :att2
43
+
44
+ def def
45
+ "def!"
46
+ end
47
+ end
48
+ end
49
+
50
+ user2 = ::User.new
51
+ user2.respond_to?(:att1).should_not be
52
+ user2.user_attributes.respond_to?(:att1).should_not be
53
+ user2.respond_to?(:abc).should_not be
54
+ user2.user_attributes.respond_to?(:abc).should_not be
55
+
56
+ # explicitly not testing if user.respond_to?(:att3) or if user.respond_to?)(:att1); we make no guarantees about
57
+ # what happened on older objects
58
+
59
+ class ::User < ::ActiveRecord::Base
60
+ self.table_name = 'flexcols_spec_users'
61
+
62
+ flex_column :user_attributes do
63
+ field :att1
64
+ field :att2
65
+ end
66
+ end
67
+
68
+ user3 = ::User.new
69
+ user3.respond_to?(:att1).should be
70
+ user3.user_attributes.respond_to?(:att1).should be
71
+ user3.respond_to?(:att2).should be
72
+ user3.user_attributes.respond_to?(:att2).should be
73
+ user3.user_attributes.respond_to?(:abc).should_not be
74
+ user3.respond_to?(:abc).should_not be
75
+ user3.user_attributes.respond_to?(:def).should_not be
76
+ user3.respond_to?(:def).should_not be
77
+ end
78
+
79
+ it "should discard all attributes when #reload is called" do
80
+ define_model_class(:User, 'flexcols_spec_users') do
81
+ flex_column :user_attributes do
82
+ field :wants_email
83
+ end
84
+ end
85
+
86
+ user = ::User.new
87
+ user.name = 'User 1'
88
+ user.save!
89
+
90
+ user = ::User.find(user.id)
91
+ user.name = 'User 2'
92
+ user.wants_email = 'bonko'
93
+
94
+ user.reload
95
+
96
+ user.name.should == 'User 1'
97
+ user.wants_email.should be_nil
98
+ end
99
+
100
+ it "should use the most-recently-defined flex-column attribute in delegation, if there's a conflict" do
101
+ define_model_class(:User, 'flexcols_spec_users') do
102
+ flex_column :user_attributes do
103
+ field :att1
104
+ field :att2
105
+ end
106
+
107
+ flex_column :more_attributes do
108
+ field :att2
109
+ field :att3
110
+ end
111
+ end
112
+
113
+ user = ::User.new
114
+ user.name = 'User 1'
115
+ user.att1 = "foo"
116
+ user.att2 = "bar"
117
+ user.att3 = "baz"
118
+
119
+ user.user_attributes.att1.should == "foo"
120
+ user.user_attributes.att2.should be_nil
121
+ user.user_attributes.att2 = "quux"
122
+ user.more_attributes.att2.should == "bar"
123
+ user.more_attributes.att3.should == "baz"
124
+
125
+ user.att2.should == "bar"
126
+
127
+ # Now, reverse them
128
+
129
+ define_model_class(:User, 'flexcols_spec_users') do
130
+ flex_column :more_attributes do
131
+ field :att2
132
+ field :att3
133
+ end
134
+
135
+ flex_column :user_attributes do
136
+ field :att1
137
+ field :att2
138
+ end
139
+ end
140
+
141
+ user = ::User.new
142
+ user.name = 'User 1'
143
+ user.att1 = "foo"
144
+ user.att2 = "bar"
145
+ user.att3 = "baz"
146
+
147
+ user.user_attributes.att1.should == "foo"
148
+ user.user_attributes.att2.should == "bar"
149
+ user.more_attributes.att2.should be_nil
150
+ user.more_attributes.att2 = "quux"
151
+ user.more_attributes.att3.should == "baz"
152
+
153
+ user.user_attributes.att2.should == "bar"
154
+ user.more_attributes.att2.should == "quux"
155
+
156
+ user.att2.should == "bar"
157
+ end
158
+ end
@@ -0,0 +1,117 @@
1
+ require 'flex_columns'
2
+ require 'flex_columns/helpers/system_helpers'
3
+ require 'flex_columns/helpers/exception_helpers'
4
+
5
+ describe "FlexColumns error handling" 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
+ migrate do
14
+ drop_table :flexcols_spec_users rescue nil
15
+ create_table :flexcols_spec_users do |t|
16
+ t.string :name, :null => false
17
+ t.string :user_attributes, :limit => 100
18
+ end
19
+ end
20
+
21
+ define_model_class(:User, 'flexcols_spec_users') do
22
+ flex_column :user_attributes do
23
+ field :wants_email
24
+ end
25
+ end
26
+
27
+ define_model_class(:UserBackdoor, 'flexcols_spec_users') { }
28
+ end
29
+
30
+ after :each do
31
+ migrate do
32
+ drop_table :flexcols_spec_users rescue nil
33
+ end
34
+ end
35
+
36
+ it "should return a nice error if JSON parsing fails" do
37
+ user_bd_1 = ::UserBackdoor.new
38
+ user_bd_1.name = 'User 1'
39
+ user_bd_1.user_attributes = "---unparseable json---"
40
+ user_bd_1.save!
41
+
42
+ user = ::User.find(user_bd_1.id)
43
+
44
+ e = capture_exception(FlexColumns::Errors::UnparseableJsonInDatabaseError) { user.wants_email }
45
+ e.message.should match(/user.*id.*#{user.id}/i)
46
+ e.message.should match(/\-\-\-unparseable json\-\-\-/i)
47
+ e.message.should match(/JSON::ParserError/i)
48
+
49
+ e.data_source.should be(user.user_attributes)
50
+ e.raw_string.should == "---unparseable json---"
51
+ e.source_exception.class.should == JSON::ParserError
52
+ end
53
+
54
+ it "should return a nice error if the string isn't even a validly-encoded string" do
55
+ user = ::UserBackdoor.new
56
+ user.name = 'User 1'
57
+ user.save!
58
+
59
+ user2 = ::User.find(user.id)
60
+
61
+ class << user2
62
+ def [](x)
63
+ if x.to_s == "user_attributes"
64
+ out = "abc\xC3\x28def"
65
+ out = out.force_encoding("UTF-8") if out.respond_to?(:force_encoding)
66
+ out
67
+ else
68
+ super(x)
69
+ end
70
+ end
71
+ end
72
+
73
+ if "foo".respond_to?(:force_encoding) # do we have Ruby >= 1.9?
74
+ e = capture_exception(FlexColumns::Errors::IncorrectlyEncodedStringInDatabaseError) { user2.wants_email }
75
+ e.message.should match(/user.*id.*#{user2.id}/i)
76
+ e.message.should match(/abc.*def/i)
77
+ e.message.should match(/position 3/i)
78
+ e.message.should match(/c3/i)
79
+
80
+ e.data_source.should be(user2.user_attributes)
81
+ e.raw_string.should match(/abc.*def/i)
82
+
83
+ invalid_char = ["C3"].pack("H*")
84
+ invalid_char.force_encoding("UTF-8")
85
+
86
+ e.invalid_chars_as_array.include?(invalid_char).should be
87
+ e.raw_data_as_array.include?(invalid_char).should be
88
+ e.raw_data_as_array.include?("a").should be
89
+ e.first_bad_position.should == 3
90
+ else
91
+ e = capture_exception(FlexColumns::Errors::UnparseableJsonInDatabaseError) { user2.wants_email }
92
+ e.message.should match(/user.*id.*#{user2.id}/i)
93
+ e.message.should match(/abc.*def/i)
94
+ e.message.should match(/JSON::ParserError/i)
95
+
96
+ e.data_source.should be(user2.user_attributes)
97
+ e.raw_string.should match(/abc.*def/i)
98
+ end
99
+ end
100
+
101
+ it "should fail before storing if the JSON produced is too long for the column" do
102
+ user = ::User.new
103
+ user.name = 'User 1'
104
+ user.wants_email = 'aaa' * 10000
105
+
106
+ e = capture_exception(FlexColumns::Errors::JsonTooLongError) { user.save! }
107
+ e.message.should match(/user_attributes/i)
108
+ e.message.should match(/100/i)
109
+ e.message.should match(/aaa/i)
110
+ e.message.should match(/30[0-9][0-9][0-9]/i)
111
+ e.message.length.should < 1000
112
+
113
+ e.data_source.should be(user.user_attributes)
114
+ e.limit.should == 100
115
+ e.json_string.length.should > 30000
116
+ end
117
+ end