has_dynamic_columns 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e80b2327256e42bf94544f2002936cf1170de952
4
- data.tar.gz: 8da148b09230ca67502a734597cb595390fa32a4
3
+ metadata.gz: 8adcb2fa0100a0caea34697193e9d0f008eb633a
4
+ data.tar.gz: 7dcd964a4ed65285fb819ad51f1f7656491b2610
5
5
  SHA512:
6
- metadata.gz: 24a2777ffcd2b9feb28512fedb92ad9b224893a399bbdf50ec43160b4dd301e27e14b466736a68979347105d26db496f5e0359ddbcdd3f1b45f325ac53810195
7
- data.tar.gz: e23a81b217be7906f6b8f65d8e7d65baf74f19dc1b7ca1a739a3de5205b1becda01a483e6c229e5cd0cbdbbbef30680b1e3d5351ec9432e833d4ec9a4f269812
6
+ metadata.gz: 0f933582fe5fce22e3cae11bb0af740581b08d221aa24b555fbcbb92323082b49bd5c19191204601d0a24a96a01382ee96f8a07a7387852bec36eddde7fe3904
7
+ data.tar.gz: 8b6f80d4116c9074149469cd4f6c33c047c1b96dabc517217578b2e0878eddbf2de0a770370398b868eaf81fc05491672fce6c131e3a043939f875d5e990f983
@@ -1,3 +1,3 @@
1
1
  module HasDynamicColumns
2
- VERSION = "0.0.5"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -22,12 +22,18 @@ module HasDynamicColumns
22
22
  configuration.update(options) if options.is_a?(Hash)
23
23
 
24
24
  class_eval <<-EOV
25
+ alias_method :as_json_before_#{configuration[:as]}, :as_json
26
+
27
+ # Store all our configurations for usage later
28
+ @@has_dynamic_columns_configurations ||= []
29
+ @@has_dynamic_columns_configurations << #{configuration}
30
+
25
31
  include ::HasDynamicColumns::Model::InstanceMethods
26
32
 
27
- has_many :activerecord_#{configuration[:as]},
33
+ has_many :activerecord_dynamic_columns,
28
34
  class_name: "HasDynamicColumns::DynamicColumn",
29
35
  as: :field_scope
30
- has_many :activerecord_#{configuration[:as]}_data,
36
+ has_many :activerecord_dynamic_column_data,
31
37
  class_name: "HasDynamicColumns::DynamicColumnDatum",
32
38
  as: :owner,
33
39
  autosave: true
@@ -38,7 +44,59 @@ module HasDynamicColumns
38
44
  #attr_accessible :#{configuration[:column]}
39
45
  end
40
46
 
41
- validate :validate_dynamic_column_data
47
+ validate do |field_scope|
48
+ field_scope = self.get_#{configuration[:as]}_field_scope
49
+
50
+ if field_scope
51
+ # has_many association
52
+ if field_scope.respond_to?(:select) && field_scope.respond_to?(:collect)
53
+
54
+ # belongs_to association
55
+ else
56
+ # All the fields defined on the parent model
57
+ dynamic_columns = field_scope.send("activerecord_dynamic_columns")
58
+
59
+ self.send("activerecord_dynamic_column_data").each { |dynamic_column_datum|
60
+ # Collect all validation errors
61
+ validation_errors = []
62
+
63
+ if dynamic_column_datum.dynamic_column_option_id == -1
64
+ validation_errors << "invalid_option"
65
+ end
66
+
67
+ # Find the dynamic_column defined for this datum
68
+ dynamic_column = nil
69
+ dynamic_columns.each { |i|
70
+ if i == dynamic_column_datum.dynamic_column
71
+ dynamic_column = i
72
+ break
73
+ end
74
+ }
75
+ # We have a dynamic_column - validate
76
+ if dynamic_column
77
+ dynamic_column.dynamic_column_validations.each { |validation|
78
+ if !validation.is_valid?(dynamic_column_datum.value.to_s)
79
+ validation_errors << validation.error
80
+ end
81
+ }
82
+ else
83
+ # No field found - this is probably bad - should we throw an error?
84
+ validation_errors << "not_found"
85
+ end
86
+
87
+ # If any errors exist - add them
88
+ if validation_errors.length > 0
89
+ if dynamic_column.nil?
90
+ puts validation_errors.inspect
91
+ #errors.add(:dynamic_columns, { "unknown" => validation_errors })
92
+ else
93
+ errors.add(:dynamic_columns, { dynamic_column.key.to_s => validation_errors })
94
+ end
95
+ end
96
+ }
97
+ end
98
+ end
99
+ end
42
100
 
43
101
  public
44
102
  # Order by dynamic columns
@@ -148,11 +206,13 @@ module HasDynamicColumns
148
206
  json = super(*args)
149
207
  options = args.extract_options!
150
208
 
151
- if !options[:root].nil?
152
- json[options[:root]][self.dynamic_columns_as] = self.send(self.dynamic_columns_as)
153
- else
154
- json[self.dynamic_columns_as] = self.send(self.dynamic_columns_as)
155
- end
209
+ @@has_dynamic_columns_configurations.each { |config|
210
+ if !options[:root].nil?
211
+ json[options[:root]][config[:as].to_s] = self.send(config[:as].to_s)
212
+ else
213
+ json[config[:as].to_s] = self.send(config[:as].to_s)
214
+ end
215
+ }
156
216
 
157
217
  json
158
218
  end
@@ -167,10 +227,10 @@ module HasDynamicColumns
167
227
  dynamic_column = self.#{configuration[:as].to_s.singularize}_key_to_dynamic_column(key)
168
228
 
169
229
  # We already have this key in database
170
- if existing = self.activerecord_#{configuration[:as]}_data.select { |i| i.dynamic_column == dynamic_column }.first
230
+ if existing = self.activerecord_dynamic_column_data.select { |i| i.dynamic_column == dynamic_column }.first
171
231
  existing.value = value
172
232
  else
173
- self.activerecord_#{configuration[:as]}_data.build(:dynamic_column => dynamic_column, :value => value)
233
+ self.activerecord_dynamic_column_data.build(:dynamic_column => dynamic_column, :value => value)
174
234
  end
175
235
  }
176
236
  end
@@ -180,9 +240,11 @@ module HasDynamicColumns
180
240
  self.field_scope_#{configuration[:as]}.each { |i|
181
241
  h[i.key] = nil
182
242
  }
183
- self.activerecord_#{configuration[:as]}_data.each { |i|
184
- h[i.dynamic_column.key] = i.value unless !i.dynamic_column
243
+
244
+ self.activerecord_dynamic_column_data.each { |i|
245
+ h[i.dynamic_column.key] = i.value unless !i.dynamic_column || !h.has_key?(i.dynamic_column.key)
185
246
  }
247
+
186
248
  h
187
249
  end
188
250
 
@@ -191,17 +253,27 @@ module HasDynamicColumns
191
253
  end
192
254
 
193
255
  def field_scope_#{configuration[:as]}
194
- self.field_scope.send("activerecord_"+self.field_scope.dynamic_columns_as).select { |i|
195
- # Only get things with no dynamic type defined or dynamic types defined as this class
196
- i.dynamic_type.to_s.empty? || i.dynamic_type.to_s == self.class.to_s
197
- }
256
+ # has_many relationship
257
+ if self.get_#{configuration[:as]}_field_scope.respond_to?(:select) && self.get_#{configuration[:as]}_field_scope.respond_to?(:collect)
258
+ self.get_#{configuration[:as]}_field_scope.collect { |i|
259
+ i.send("activerecord_dynamic_columns")
260
+ }.flatten.select { |i|
261
+ i.dynamic_type.to_s.empty? || i.dynamic_type.to_s == self.class.to_s
262
+ }
263
+ # belongs_to relationship
264
+ else
265
+ self.get_#{configuration[:as]}_field_scope.send("activerecord_dynamic_columns").select { |i|
266
+ # Only get things with no dynamic type defined or dynamic types defined as this class
267
+ i.dynamic_type.to_s.empty? || i.dynamic_type.to_s == self.class.to_s
268
+ }
269
+ end
198
270
  end
199
271
 
200
- def dynamic_columns_as
201
- "#{configuration[:as].to_s}"
272
+ protected
273
+ def get_#{configuration[:as]}_field_scope
274
+ #{configuration[:field_scope]}
202
275
  end
203
276
 
204
- protected
205
277
  # Whether this is storable
206
278
  def storable_#{configuration[:as].to_s.singularize}_key?(key)
207
279
  self.#{configuration[:as].to_s.singularize}_keys.include?(key.to_s)
@@ -210,62 +282,19 @@ module HasDynamicColumns
210
282
  # Figures out which dynamic_column has which key
211
283
  def #{configuration[:as].to_s.singularize}_key_to_dynamic_column(key)
212
284
  found = nil
213
- if record = self.send("field_scope_"+self.dynamic_columns_as).select { |i| i.key == key.to_s }.first
285
+ if record = self.send('field_scope_#{configuration[:as]}').select { |i| i.key == key.to_s }.first
214
286
  found = record
215
287
  end
216
288
  found
217
289
  end
218
-
219
- def field_scope
220
- #{configuration[:field_scope]}
221
- end
222
290
  EOV
223
291
  end
224
292
  end
225
293
 
226
294
  module InstanceMethods
227
295
  # Validate all the dynamic_column_data at once
228
- def validate_dynamic_column_data
229
- field_scope = self.field_scope
230
-
231
- if field_scope
232
- # All the fields defined on the parent model
233
- dynamic_columns = field_scope.send("activerecord_#{field_scope.dynamic_columns_as}")
234
-
235
- self.send("activerecord_#{self.dynamic_columns_as}_data").each { |dynamic_column_datum|
236
- # Collect all validation errors
237
- validation_errors = []
296
+ def validate_dynamic_column_data(field_scope = nil)
238
297
 
239
- if dynamic_column_datum.dynamic_column_option_id == -1
240
- validation_errors << "invalid_option"
241
- end
242
-
243
- # Find the dynamic_column defined for this datum
244
- dynamic_column = nil
245
- dynamic_columns.each { |i|
246
- if i == dynamic_column_datum.dynamic_column
247
- dynamic_column = i
248
- break
249
- end
250
- }
251
- # We have a dynamic_column - validate
252
- if dynamic_column
253
- dynamic_column.dynamic_column_validations.each { |validation|
254
- if !validation.is_valid?(dynamic_column_datum.value.to_s)
255
- validation_errors << validation.error
256
- end
257
- }
258
- else
259
- # No field found - this is probably bad - should we throw an error?
260
- validation_errors << "not_found"
261
- end
262
-
263
- # If any errors exist - add them
264
- if validation_errors.length > 0
265
- errors.add(:dynamic_columns, { "#{dynamic_column.key}" => validation_errors })
266
- end
267
- }
268
- end
269
298
  end
270
299
  end
271
300
  end
@@ -32,9 +32,154 @@ describe HasDynamicColumns do
32
32
  field.dynamic_column_validations.build(:regexp => "^[^$]+$", :error => "blank")
33
33
  field.dynamic_column_validations.build(:regexp => "^[ABCEGHJKLMNPRSTVXY]\\d[ABCEGHJKLMNPRSTVWXYZ]( )?\\d[ABCEGHJKLMNPRSTVWXYZ]\\d$", :error => "invalid_format")
34
34
 
35
+ # Product fields
36
+ account.activerecord_dynamic_columns.build(:dynamic_type => "Product", :key => "rarity", :data_type => "string")
37
+
35
38
  account
36
39
  end
37
40
 
41
+ describe Product do
42
+ subject(:product) {
43
+ Product.new(:name => "Product #1", :account => account)
44
+ }
45
+ before do
46
+ @category0 = Category.new(:name => "Category 0", :account => product.account)
47
+ @category0.save
48
+
49
+ @category1 = Category.new(:name => "Category 1", :account => product.account)
50
+ @category1.activerecord_dynamic_columns.build(:dynamic_type => "Product", :key => "vin_number", :data_type => "string")
51
+ @category1.save
52
+
53
+ @category2 = Category.new(:name => "Category 2", :account => product.account)
54
+ @category2.activerecord_dynamic_columns.build(:dynamic_type => "Product", :key => "serial_number", :data_type => "string")
55
+ @category2.save
56
+
57
+ @category3 = Category.new(:name => "Category 3", :account => product.account)
58
+ @category3.activerecord_dynamic_columns.build(:dynamic_type => "Product", :key => "funky_data", :data_type => "string")
59
+ @category3.activerecord_dynamic_columns.build(:dynamic_type => "Product", :key => "funkier_data", :data_type => "string")
60
+ @category3.save
61
+
62
+ @category4 = Category.new(:name => "Category 4", :account => product.account)
63
+ @category4.activerecord_dynamic_columns.build(:dynamic_type => "Product", :key => "funkiest_data", :data_type => "string")
64
+ @category4.activerecord_dynamic_columns.build(:dynamic_type => "Product", :key => "ok_data", :data_type => "string")
65
+ end
66
+
67
+ context 'when it has a defined has_many relationship' do
68
+
69
+ context 'when it has_many categories' do
70
+
71
+ it 'should return empty category_fields when no categories associated' do
72
+ json = product.as_json
73
+ expect(json["category_fields"]).to eq({})
74
+ end
75
+
76
+ it 'should return empty category_fields when no category has no dynamic_columns' do
77
+ product.categories << @category0
78
+ json = product.as_json
79
+ expect(json["category_fields"]).to eq({})
80
+ end
81
+
82
+ context 'when not saved' do
83
+ it 'should return a categories fields' do
84
+ product.categories << @category1
85
+ product.category_fields = {
86
+ "vin_number" => "123"
87
+ }
88
+
89
+ json = product.as_json
90
+ expect(json["category_fields"]).to eq({"vin_number"=>"123"})
91
+ expect(product.new_record?).to eq(true)
92
+ end
93
+ end
94
+ context 'when saved' do
95
+ it 'should return a categories fields' do
96
+ product.categories << @category1
97
+ product.category_fields = {
98
+ "vin_number" => "345"
99
+ }
100
+ product.save
101
+
102
+ json = product.as_json
103
+ expect(json["category_fields"]).to eq({"vin_number"=>"345"})
104
+ expect(product.new_record?).to eq(false)
105
+
106
+ product_id = product.id
107
+ product = Product.find(product_id)
108
+ json = product.as_json
109
+ expect(json["category_fields"]).to eq({"vin_number"=>"345"})
110
+ end
111
+ end
112
+
113
+ it 'should kitchen sink' do
114
+ product.product_fields = {
115
+ "rarity" => "very rare"
116
+ }
117
+
118
+ # Add category 1 to the product - it should now have the fields of "vin number"
119
+ product.categories << @category1
120
+ product.categories << @category2
121
+
122
+ product.category_fields = {
123
+ "vin_number" => "first:this is the vin number",
124
+ "serial_number" => "first:serial number!"
125
+ }
126
+ json = product.as_json
127
+ expect(json["product_fields"]).to eq({"rarity"=>"very rare"})
128
+ expect(json["category_fields"]).to eq({"vin_number"=>"first:this is the vin number", "serial_number"=>"first:serial number!"})
129
+
130
+ product.save
131
+ json = product.as_json
132
+ expect(json["product_fields"]).to eq({"rarity"=>"very rare"})
133
+ expect(json["category_fields"]).to eq({"vin_number"=>"first:this is the vin number", "serial_number"=>"first:serial number!"})
134
+
135
+ product_id = product.id
136
+ product = Product.find(product_id)
137
+ json = product.as_json
138
+ expect(json["product_fields"]).to eq({"rarity"=>"very rare"})
139
+ expect(json["category_fields"]).to eq({"vin_number"=>"first:this is the vin number", "serial_number"=>"first:serial number!"})
140
+
141
+ product.category_fields = {
142
+ "serial_number" => "second:serial number!"
143
+ }
144
+ json = product.as_json
145
+ expect(json["product_fields"]).to eq({"rarity"=>"very rare"})
146
+ expect(json["category_fields"]).to eq({"vin_number"=>"first:this is the vin number", "serial_number"=>"second:serial number!"})
147
+
148
+ product.save
149
+ json = product.as_json
150
+ expect(json["product_fields"]).to eq({"rarity"=>"very rare"})
151
+ expect(json["category_fields"]).to eq({"vin_number"=>"first:this is the vin number", "serial_number"=>"second:serial number!"})
152
+
153
+ product = Product.find(product_id)
154
+ json = product.as_json
155
+ expect(json["product_fields"]).to eq({"rarity"=>"very rare"})
156
+ expect(json["category_fields"]).to eq({"vin_number"=>"first:this is the vin number", "serial_number"=>"second:serial number!"})
157
+
158
+ expect(@category4.new_record?).to eq(true)
159
+
160
+ product.categories << @category3
161
+ product.categories << @category4
162
+
163
+ expect(@category4.new_record?).to eq(false)
164
+
165
+ product.category_fields = {
166
+ "funkier_data" => "this is funkier data",
167
+ "ok_data" => "this is ok data"
168
+ }
169
+ json = product.as_json
170
+ expect(json["product_fields"]).to eq({"rarity"=>"very rare"})
171
+ expect(json["category_fields"]).to eq({"vin_number"=>"first:this is the vin number", "serial_number"=>"second:serial number!", "funky_data"=>nil, "funkier_data"=>"this is funkier data", "funkiest_data"=>nil, "ok_data"=>"this is ok data"})
172
+
173
+ product.save
174
+ product = Product.find(product_id)
175
+ json = product.as_json
176
+ expect(json["product_fields"]).to eq({"rarity"=>"very rare"})
177
+ expect(json["category_fields"]).to eq({"vin_number"=>"first:this is the vin number", "serial_number"=>"second:serial number!", "funky_data"=>nil, "funkier_data"=>"this is funkier data", "funkiest_data"=>nil, "ok_data"=>"this is ok data"})
178
+ end
179
+ end
180
+ end
181
+ end
182
+
38
183
  describe Customer do
39
184
  subject(:customer) { Customer.new(:account => account) }
40
185
  before do
@@ -56,7 +201,7 @@ describe HasDynamicColumns do
56
201
  end
57
202
  it 'should find me' do
58
203
  c = customer
59
- c.save
204
+ expect(c.save).to eq(true)
60
205
  a = c.account
61
206
 
62
207
  expect(a.customers.dynamic_where(a, { first_name: "Butch" }).length).to eq(1)
@@ -183,10 +328,10 @@ describe HasDynamicColumns do
183
328
  it 'should should retrieve properly from the database' do
184
329
  sub = customer_address
185
330
  sub.save
186
-
331
+
187
332
  customer = CustomerAddress.find(sub.id)
188
333
  json = customer.as_json(:root => "customer_address")
189
-
334
+
190
335
  expect(json["customer_address"]["fields"]).to eq({
191
336
  "address_1" => "555 Bloor Street",
192
337
  "address_2" => nil,
data/spec/spec_helper.rb CHANGED
@@ -47,6 +47,22 @@ ActiveRecord::Schema.define do
47
47
  t.integer :customer_id
48
48
  t.timestamps
49
49
  end
50
+ create_table :products, force: true do |t|
51
+ t.string :name
52
+ t.integer :account_id
53
+ t.timestamps
54
+ end
55
+ create_table :categories, force: true do |t|
56
+ t.string :name
57
+ t.integer :account_id
58
+ t.timestamps
59
+ end
60
+ create_table :category_owners, force: true do |t|
61
+ t.integer :category_id
62
+ t.integer :owner_id
63
+ t.string :owner_type
64
+ t.timestamps
65
+ end
50
66
  end
51
67
 
52
68
  class Account < ActiveRecord::Base
@@ -65,6 +81,29 @@ class CustomerAddress < ActiveRecord::Base
65
81
  has_dynamic_columns field_scope: "customer.account", dynamic_type: "CustomerAddress", as: "fields"
66
82
  end
67
83
 
84
+ class Product < ActiveRecord::Base
85
+ belongs_to :account
86
+ has_many :category_owners, :as => :owner
87
+ has_many :categories, :through => :category_owners
88
+
89
+ # Fields defined via the account
90
+ has_dynamic_columns field_scope: "account", dynamic_type: "Product", as: "product_fields"
91
+
92
+ # Fields defined via any associated categories
93
+ has_dynamic_columns field_scope: "categories", dynamic_type: "Product", as: "category_fields"
94
+ end
95
+
96
+ class Category < ActiveRecord::Base
97
+ belongs_to :account
98
+ has_many :category_owners
99
+
100
+ has_dynamic_columns field_scope: "account"
101
+ end
102
+
103
+ class CategoryOwner < ActiveRecord::Base
104
+ belongs_to :category
105
+ belongs_to :owner, :polymorphic => true
106
+ end
68
107
 
69
108
  RSpec.configure do |config|
70
109
  config.after(:each) do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: has_dynamic_columns
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Butch Marshall
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-04 00:00:00.000000000 Z
11
+ date: 2015-08-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord