has_dynamic_columns 0.0.5 → 0.1.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.
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