flex_columns 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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