has_dynamic_columns 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5e48635b860c7cac4c800461dab2a70ee3abc71a
4
+ data.tar.gz: a8bcdec82cd09f2c0d6e78b3b79c5c75c2954c14
5
+ SHA512:
6
+ metadata.gz: a97dd9c49f2551362311091c7e66137e7baf7fd21d6f2c0fd24fa894c39ce34f32f0cffa98bc616ddb9305bd227b1b61661ac29d46e0da50544cbb3c6199490d
7
+ data.tar.gz: fda55ebb312d5a7ce6b65494c6fe70999245a48e639a26e046b14b899869eb749434f4932e07f1a1898fa1dc4829ae6c88bcbb56b3a7c81c4cf53df2b604e011
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ # .rspec
2
+ --format documentation --color --fail-fast
data/Gemfile ADDED
@@ -0,0 +1,19 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rake'
4
+
5
+ group :test do
6
+ if ENV['RAILS_VERSION'] == 'edge'
7
+ gem 'activerecord', :github => 'rails/rails'
8
+ else
9
+ gem 'activerecord', (ENV['RAILS_VERSION'] || ['>= 3.0', '< 5.0'])
10
+ end
11
+
12
+ gem 'coveralls', :require => false
13
+ gem 'rspec', '>= 3'
14
+ gem 'rubocop', '>= 0.25'
15
+ end
16
+
17
+ # Specify your gem's dependencies in has_dynamic_columns.gemspec
18
+ gemspec
19
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Butch Marshall
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ has_dynamic_columns
2
+ ============
3
+
4
+ Add dynamic columns to ActiveRecord models
5
+
6
+ Installation
7
+ ============
8
+
9
+ ```ruby
10
+ gem 'has_dynamic_columns', :git => 'git://github.com/butchmarshall/has_dynamic_columns.git'
11
+ ```
12
+
13
+ The Active Record migration is required to create the has_dynamic_columns table. You can create that table by
14
+ running the following command:
15
+
16
+ rails generate has_dynamic_columns:active_record
17
+ rake db:migrate
18
+
19
+ Usage
20
+ ============
21
+
22
+ ```ruby
23
+ class Account < ActiveRecord::Base
24
+ has_many :customers
25
+ has_dynamic_columns
26
+ end
27
+
28
+ class Customer < ActiveRecord::Base
29
+ belongs_to :account
30
+ has_many :customer_addresses
31
+ has_dynamic_columns field_scope: "account", as: "fields"
32
+ end
33
+
34
+ class CustomerAddress < ActiveRecord::Base
35
+ belongs_to :customer
36
+ has_dynamic_columns field_scope: "customer.account"
37
+ end
38
+ ```
39
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "rspec/core/rake_task"
2
+ require "bundler/gem_tasks"
3
+
4
+ # Default directory to look in is `/specs`
5
+ # Run with `rake spec`
6
+ RSpec::Core::RakeTask.new(:spec) do |task|
7
+ task.rspec_opts = ['--color', '--format', 'nested']
8
+ end
9
+
10
+ task :default => :spec
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'has_dynamic_columns/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "has_dynamic_columns"
8
+ spec.version = HasDynamicColumns::VERSION
9
+ spec.authors = ["Butch Marshall"]
10
+ spec.email = ["butch.a.marshall@gmail.com"]
11
+ spec.summary = "Dynamic fields for activerecord models"
12
+ spec.description = "Adds ability to put dynamic fields into active record models"
13
+ spec.homepage = "https://github.com/butchmarshall/has_dynamic_columns"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activerecord", [">= 3.0", "< 5.0"]
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.7"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ end
@@ -0,0 +1,22 @@
1
+ require "generators/has_dynamic_columns/has_dynamic_columns_generator"
2
+ require "generators/has_dynamic_columns/next_migration_version"
3
+ require "rails/generators/migration"
4
+ require "rails/generators/active_record"
5
+
6
+ # Extend the HasDynamicColumnsGenerator so that it creates an AR migration
7
+ module HasDynamicColumns
8
+ class ActiveRecordGenerator < ::HasDynamicColumnsGenerator
9
+ include Rails::Generators::Migration
10
+ extend NextMigrationVersion
11
+
12
+ source_paths << File.join(File.dirname(__FILE__), "templates")
13
+
14
+ def create_migration_file
15
+ migration_template "migration.rb", "db/migrate/has_dynamic_columns.rb"
16
+ end
17
+
18
+ def self.next_migration_number(dirname)
19
+ ActiveRecord::Generators::Base.next_migration_number dirname
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,6 @@
1
+ require 'rails/generators/base'
2
+ require 'has_dynamic_columns/compatibility'
3
+
4
+ class HasDynamicColumnsGenerator < Rails::Generators::Base
5
+ source_paths << File.join(File.dirname(__FILE__), 'templates')
6
+ end
@@ -0,0 +1,14 @@
1
+ module HasDynamicColumns
2
+ module NextMigrationVersion
3
+ # while methods have moved around this has been the implementation
4
+ # since ActiveRecord 3.0
5
+ def next_migration_number(dirname)
6
+ next_migration_number = current_migration_number(dirname) + 1
7
+ if ActiveRecord::Base.timestamped_migrations
8
+ [Time.now.utc.strftime("%Y%m%d%H%M%S"), format("%.14d", next_migration_number)].max
9
+ else
10
+ format("%.3d", next_migration_number)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,43 @@
1
+ class CreateHasDynamicColumns < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :dynamic_columns do |t|
4
+ t.integer :field_scope_id
5
+ t.string :field_scope_type
6
+
7
+ t.string :dynamic_type
8
+ t.string :key
9
+ t.string :data_type
10
+
11
+ t.timestamps
12
+ end
13
+ add_index(:dynamic_columns, [:field_scope_id, :field_scope_type, :dynamic_type], name: 'index1')
14
+ create_table :dynamic_column_validations do |t|
15
+ t.integer :dynamic_column_id
16
+
17
+ t.string :error
18
+ t.string :regexp
19
+
20
+ t.timestamps
21
+ end
22
+ create_table :dynamic_column_options do |t|
23
+ t.integer :dynamic_column_id
24
+ t.string :key
25
+
26
+ t.timestamps
27
+ end
28
+ create_table :dynamic_column_data do |t|
29
+ t.string :owner_type
30
+ t.integer :owner_id
31
+ t.integer :dynamic_column_id
32
+ t.integer :dynamic_column_option_id
33
+ t.string :value
34
+
35
+ t.timestamps
36
+ end
37
+ add_index(:dynamic_column_data, [:owner_id, :owner_type, :dynamic_column_id], name: 'index2')
38
+ end
39
+
40
+ def self.down
41
+ drop_table :dynamic_columns
42
+ end
43
+ end
@@ -0,0 +1,27 @@
1
+ require 'active_support/version'
2
+
3
+ module HasDynamicColumns
4
+ module Compatibility
5
+ if ActiveSupport::VERSION::MAJOR >= 4
6
+ require 'active_support/proxy_object'
7
+
8
+ def self.executable_prefix
9
+ 'bin'
10
+ end
11
+
12
+ def self.proxy_object_class
13
+ ActiveSupport::ProxyObject
14
+ end
15
+ else
16
+ require 'active_support/basic_object'
17
+
18
+ def self.executable_prefix
19
+ 'script'
20
+ end
21
+
22
+ def self.proxy_object_class
23
+ ActiveSupport::BasicObject
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,6 @@
1
+ module HasDynamicColumns
2
+ class DynamicColumn < ActiveRecord::Base
3
+ has_many :dynamic_column_options, :class_name => "HasDynamicColumns::DynamicColumnOption"
4
+ has_many :dynamic_column_validations, :class_name => "HasDynamicColumns::DynamicColumnValidation"
5
+ end
6
+ end
@@ -0,0 +1,60 @@
1
+ module HasDynamicColumns
2
+ class DynamicColumnDatum < ActiveRecord::Base
3
+ belongs_to :dynamic_column, :class_name => "HasDynamicColumns::DynamicColumn"
4
+ belongs_to :dynamic_column_option, :class_name => "HasDynamicColumns::DynamicColumnOption"
5
+ belongs_to :owner, :polymorphic => true
6
+
7
+ # Get value based on dynamic_column data_type
8
+ def value
9
+ if self.dynamic_column
10
+ case self.dynamic_column.data_type
11
+ when "list"
12
+ if self.dynamic_column_option
13
+ self.dynamic_column_option.key
14
+ end
15
+ when "datetime"
16
+ self[:value]
17
+ when "boolean"
18
+ (self[:value] == 1)
19
+ when "integer"
20
+ self[:value]
21
+ when "date"
22
+ self[:value]
23
+ when "string"
24
+ self[:value]
25
+ end
26
+ else
27
+ self[:value]
28
+ end
29
+ end
30
+
31
+ # Set value base don dynamic_column data_type
32
+ def value=v
33
+ if self.dynamic_column
34
+ case self.dynamic_column.data_type
35
+ when "list"
36
+ # Can only set the value to one of the option values
37
+ if option = self.dynamic_column.dynamic_column_options.select { |i| i.key == v }.first
38
+ self.dynamic_column_option = option
39
+ else
40
+ # Hacky, -1 indicates to the validator that an invalid option was set
41
+ self.dynamic_column_option = nil
42
+ self.dynamic_column_option_id = (v.to_s.length > 0)? -1 : nil
43
+ end
44
+ when "datetime"
45
+ self[:value] = v
46
+ when "boolean"
47
+ self[:value] = v
48
+ when "integer"
49
+ self[:value] = v
50
+ when "date"
51
+ self[:value] = v
52
+ when "string"
53
+ self[:value] = v
54
+ end
55
+ else
56
+ self[:value] = v
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,5 @@
1
+ module HasDynamicColumns
2
+ class DynamicColumnOption < ActiveRecord::Base
3
+ belongs_to :dynamic_column, :class_name => "HasDynamicColumns::DynamicColumn"
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ module HasDynamicColumns
2
+ class DynamicColumnValidation < ActiveRecord::Base
3
+ belongs_to :dynamic_column, :class_name => "HasDynamicColumns::DynamicColumn"
4
+
5
+ def is_valid?(str)
6
+ matches = Regexp.new(self["regexp"]).match(str.to_s)
7
+
8
+ return !matches.nil?
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module HasDynamicColumns
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,280 @@
1
+ require "active_support"
2
+
3
+ require "has_dynamic_columns/version"
4
+ require "has_dynamic_columns/dynamic_column"
5
+ require "has_dynamic_columns/dynamic_column_option"
6
+ require "has_dynamic_columns/dynamic_column_validation"
7
+ require "has_dynamic_columns/dynamic_column_datum"
8
+
9
+ module HasDynamicColumns
10
+ module Model
11
+ def self.included(base)
12
+ base.send :extend, ClassMethods
13
+ end
14
+
15
+ module ClassMethods
16
+ def has_dynamic_columns(*args)
17
+ options = args.extract_options!
18
+ configuration = {
19
+ :as => "dynamic_columns",
20
+ :field_scope => nil,
21
+ }
22
+ configuration.update(options) if options.is_a?(Hash)
23
+
24
+ class_eval <<-EOV
25
+ include ::HasDynamicColumns::Model::InstanceMethods
26
+
27
+ has_many :activerecord_#{configuration[:as]},
28
+ class_name: "HasDynamicColumns::DynamicColumn",
29
+ as: :field_scope
30
+ has_many :activerecord_#{configuration[:as]}_data,
31
+ class_name: "HasDynamicColumns::DynamicColumnDatum",
32
+ as: :owner
33
+
34
+ # only add to attr_accessible
35
+ # if the class has some mass_assignment_protection
36
+ if defined?(accessible_attributes) and !accessible_attributes.blank?
37
+ #attr_accessible :#{configuration[:column]}
38
+ end
39
+
40
+ validate :validate_dynamic_column_data
41
+
42
+ public
43
+ # Order by dynamic columns
44
+ def self.dynamic_order(field_scope, key, direction = :asc)
45
+ table = self.name.constantize.arel_table
46
+ column_table = HasDynamicColumns::DynamicColumn.arel_table.alias("dynamic_order_"+key.to_s)
47
+ column_datum_table = HasDynamicColumns::DynamicColumnDatum.arel_table.alias("dynamic_order_data_"+key.to_s)
48
+
49
+ field_scope_type = field_scope.class.name.constantize.to_s
50
+ dynamic_type = self.name.constantize.to_s
51
+ field_scope_id = field_scope.id
52
+
53
+ # Join on the column with the key
54
+ column_table_join_on = column_table
55
+ .create_on(
56
+ column_table[:field_scope_type].eq(field_scope_type).and(
57
+ column_table[:dynamic_type].eq(dynamic_type)
58
+ ).and(
59
+ column_table[:field_scope_id].eq(field_scope_id)
60
+ ).and(
61
+ column_table[:key].eq(key)
62
+ )
63
+ )
64
+
65
+ column_table_join = table.create_join(column_table, column_table_join_on)
66
+ query = joins(column_table_join)
67
+
68
+ # Join on all the data with the provided key
69
+ column_table_datum_join_on = column_datum_table
70
+ .create_on(
71
+ column_datum_table[:owner_id].eq(table[:id]).and(
72
+ column_datum_table[:owner_type].eq(dynamic_type)
73
+ ).and(
74
+ column_datum_table[:dynamic_column_id].eq(column_table[:id])
75
+ )
76
+ )
77
+
78
+ column_table_datum_join = table.create_join(column_datum_table, column_table_datum_join_on)
79
+ query = query.joins(column_table_datum_join)
80
+
81
+ # Order
82
+ query = query.order(column_datum_table[:value].send(direction))
83
+
84
+ # Group required - we have many rows
85
+ query = query.group(table[:id])
86
+
87
+ query
88
+ end
89
+
90
+ # Find by dynamic columns
91
+ def self.dynamic_where(*args)
92
+ field_scope = args[0]
93
+ options = args.extract_options!
94
+
95
+ field_scope_type = field_scope.class.name.constantize.to_s
96
+ dynamic_type = self.name.constantize.to_s
97
+ field_scope_id = field_scope.id
98
+
99
+ table = self.name.constantize.arel_table
100
+ query = nil
101
+
102
+ # Need to join on each of the keys we are performing where on
103
+ options.each { |key, value|
104
+ column_table = HasDynamicColumns::DynamicColumn.arel_table.alias("dynamic_where_"+key.to_s)
105
+ column_datum_table = HasDynamicColumns::DynamicColumnDatum.arel_table.alias("dynamic_where_data_"+key.to_s)
106
+
107
+ # Join on the column with the key
108
+ column_table_join_on = column_table
109
+ .create_on(
110
+ column_table[:field_scope_type].eq(field_scope_type).and(
111
+ column_table[:dynamic_type].eq(dynamic_type)
112
+ ).and(
113
+ column_table[:field_scope_id].eq(field_scope_id)
114
+ ).and(
115
+ column_table[:key].eq(key)
116
+ )
117
+ )
118
+
119
+ column_table_join = table.create_join(column_table, column_table_join_on)
120
+ query = (query.nil?)? joins(column_table_join) : query.join(column_table_join)
121
+
122
+ # Join on all the data with the provided key
123
+ column_table_datum_join_on = column_datum_table
124
+ .create_on(
125
+ column_datum_table[:owner_id].eq(table[:id]).and(
126
+ column_datum_table[:owner_type].eq(dynamic_type)
127
+ ).and(
128
+ column_datum_table[:dynamic_column_id].eq(column_table[:id])
129
+ ).and(
130
+ column_datum_table[:value].matches("%"+value+"%")
131
+ )
132
+ )
133
+
134
+ column_table_datum_join = table.create_join(column_datum_table, column_table_datum_join_on)
135
+ query = query.joins(column_table_datum_join)
136
+ }
137
+ # Group required - we have many rows
138
+ query = (query.nil?)? group(table[:id]) : query.group(table[:id])
139
+
140
+ query
141
+ end
142
+
143
+ def as_json(*args)
144
+ json = super(*args)
145
+ options = args.extract_options!
146
+
147
+ if !options[:root].nil?
148
+ json[options[:root]][self.dynamic_columns_as] = self.send(self.dynamic_columns_as)
149
+ else
150
+ json[self.dynamic_columns_as] = self.send(self.dynamic_columns_as)
151
+ end
152
+
153
+ json
154
+ end
155
+
156
+ # Setter for dynamic field data
157
+ def #{configuration[:as]}=data
158
+ data.each_pair { |key, value|
159
+ # We dont play well with this key
160
+ if !self.storable_#{configuration[:as].to_s.singularize}_key?(key)
161
+ raise NoMethodError
162
+ end
163
+ dynamic_column = self.#{configuration[:as].to_s.singularize}_key_to_dynamic_column(key)
164
+
165
+ # We already have this key in database
166
+ if existing = self.activerecord_#{configuration[:as]}_data.select { |i| i.dynamic_column == dynamic_column }.first
167
+ existing.value = value
168
+ else
169
+ self.activerecord_#{configuration[:as]}_data.build(:dynamic_column => dynamic_column, :value => value)
170
+ end
171
+ }
172
+ end
173
+
174
+ def #{configuration[:as]}
175
+ h = {}
176
+ self.field_scope_#{configuration[:as]}.each { |i|
177
+ h[i.key] = nil
178
+ }
179
+ self.activerecord_#{configuration[:as]}_data.each { |i|
180
+ h[i.dynamic_column.key] = i.value unless !i.dynamic_column
181
+ }
182
+ h
183
+ end
184
+
185
+ def #{configuration[:as].to_s.singularize}_keys
186
+ self.field_scope_#{configuration[:as]}.collect { |i| i.key }
187
+ end
188
+
189
+ def field_scope_#{configuration[:as]}
190
+ self.field_scope.send("activerecord_"+self.field_scope.dynamic_columns_as).select { |i|
191
+ # Only get things with no dynamic type defined or dynamic types defined as this class
192
+ i.dynamic_type.to_s.empty? || i.dynamic_type.to_s == self.class.to_s
193
+ }
194
+ end
195
+
196
+ def dynamic_columns_as
197
+ "#{configuration[:as].to_s}"
198
+ end
199
+
200
+ protected
201
+ # Whether this is storable
202
+ def storable_#{configuration[:as].to_s.singularize}_key?(key)
203
+ self.#{configuration[:as].to_s.singularize}_keys.include?(key.to_s)
204
+ end
205
+
206
+ # Figures out which dynamic_column has which key
207
+ def #{configuration[:as].to_s.singularize}_key_to_dynamic_column(key)
208
+ found = nil
209
+ if record = self.send("field_scope_"+self.dynamic_columns_as).select { |i| i.key == key.to_s }.first
210
+ found = record
211
+ end
212
+ found
213
+ end
214
+
215
+ def field_scope
216
+ #{configuration[:field_scope]}
217
+ end
218
+ EOV
219
+ end
220
+ end
221
+
222
+ module InstanceMethods
223
+ # Validate all the dynamic_column_data at once
224
+ def validate_dynamic_column_data
225
+ field_scope = self.field_scope
226
+
227
+ if field_scope
228
+ # All the fields defined on the parent model
229
+ dynamic_columns = field_scope.send("activerecord_#{field_scope.dynamic_columns_as}")
230
+
231
+ self.send("activerecord_#{self.dynamic_columns_as}_data").each { |dynamic_column_datum|
232
+ # Collect all validation errors
233
+ validation_errors = []
234
+
235
+ if dynamic_column_datum.dynamic_column_option_id == -1
236
+ validation_errors << "invalid_option"
237
+ end
238
+
239
+ # Find the dynamic_column defined for this datum
240
+ dynamic_column = nil
241
+ dynamic_columns.each { |i|
242
+ if i == dynamic_column_datum.dynamic_column
243
+ dynamic_column = i
244
+ break
245
+ end
246
+ }
247
+ # We have a dynamic_column - validate
248
+ if dynamic_column
249
+ dynamic_column.dynamic_column_validations.each { |validation|
250
+ if !validation.is_valid?(dynamic_column_datum.value.to_s)
251
+ validation_errors << validation.error
252
+ end
253
+ }
254
+ else
255
+ # No field found - this is probably bad - should we throw an error?
256
+ validation_errors << "not_found"
257
+ end
258
+
259
+ # If any errors exist - add them
260
+ if validation_errors.length > 0
261
+ errors.add(:dynamic_columns, { "#{dynamic_column.key}" => validation_errors })
262
+ end
263
+ }
264
+ end
265
+ end
266
+ end
267
+ end
268
+ end
269
+
270
+ if defined?(Rails::Railtie)
271
+ class Railtie < Rails::Railtie
272
+ initializer 'has_dynamic_columns.insert_into_active_record' do
273
+ ActiveSupport.on_load :active_record do
274
+ ActiveRecord::Base.send(:include, HasDynamicColumns::Model)
275
+ end
276
+ end
277
+ end
278
+ else
279
+ ActiveRecord::Base.send(:include, HasDynamicColumns::Model) if defined?(ActiveRecord)
280
+ end
@@ -0,0 +1,178 @@
1
+ require 'spec_helper'
2
+
3
+ describe HasDynamicColumns do
4
+ let (:account) do
5
+ account = Account.new(:name => "Account #1")
6
+
7
+ # Setup dynamic fields for Customer under this account
8
+ account.activerecord_dynamic_columns.build(:dynamic_type => "Customer", :key => "first_name", :data_type => "string")
9
+ account.activerecord_dynamic_columns.build(:dynamic_type => "Customer", :key => "last_name", :data_type => "string")
10
+ account.activerecord_dynamic_columns.build(:dynamic_type => "Customer", :key => "email", :data_type => "string")
11
+
12
+ # Setup dynamic fields for CustomerAddress under this account
13
+ account.activerecord_dynamic_columns.build(:dynamic_type => "CustomerAddress", :key => "address_1", :data_type => "string")
14
+ account.activerecord_dynamic_columns.build(:dynamic_type => "CustomerAddress", :key => "address_2", :data_type => "string")
15
+
16
+ field = account.activerecord_dynamic_columns.build(:dynamic_type => "CustomerAddress", :key => "country", :data_type => "list")
17
+ field.dynamic_column_options.build(:key => "canada")
18
+ field.dynamic_column_options.build(:key => "usa")
19
+ field.dynamic_column_options.build(:key => "mexico")
20
+
21
+ field = account.activerecord_dynamic_columns.build(:dynamic_type => "CustomerAddress", :key => "city", :data_type => "list")
22
+ field.dynamic_column_options.build(:key => "toronto")
23
+ field.dynamic_column_options.build(:key => "alberta")
24
+ field.dynamic_column_options.build(:key => "vancouver")
25
+
26
+ field = account.activerecord_dynamic_columns.build(:dynamic_type => "CustomerAddress", :key => "province", :data_type => "list")
27
+ field.dynamic_column_options.build(:key => "ontario")
28
+ field.dynamic_column_options.build(:key => "quebec")
29
+
30
+ field = account.activerecord_dynamic_columns.build(:dynamic_type => "CustomerAddress", :key => "postal_code", :data_type => "string")
31
+ field.dynamic_column_validations.build(:regexp => "^[^$]+$", :error => "blank")
32
+ field.dynamic_column_validations.build(:regexp => "^[ABCEGHJKLMNPRSTVXY]\\d[ABCEGHJKLMNPRSTVWXYZ]( )?\\d[ABCEGHJKLMNPRSTVWXYZ]\\d$", :error => "invalid_format")
33
+
34
+ account
35
+ end
36
+
37
+ describe Customer do
38
+ subject(:customer) { Customer.new(:account => account) }
39
+ before do
40
+ customer.fields = {
41
+ "first_name" => "Butch",
42
+ "last_name" => "Marshall",
43
+ "email" => "butch.a.marshall@gmail.com",
44
+ }
45
+ end
46
+
47
+ context 'when it is valid' do
48
+ it 'should not find john' do
49
+ c = customer
50
+ c.save
51
+ a = c.account
52
+
53
+ expect(a.customers.dynamic_where(a, { first_name: "John" }).length).to eq(0)
54
+ end
55
+ it 'should find me' do
56
+ c = customer
57
+ c.save
58
+ a = c.account
59
+
60
+ expect(a.customers.dynamic_where(a, { first_name: "Butch" }).length).to eq(1)
61
+ end
62
+ it 'should return fields as json' do
63
+ json = customer.as_json(:root => "customer")
64
+
65
+ expect(json["customer"]["fields"]).to eq({
66
+ "first_name" => "Butch",
67
+ "last_name" => "Marshall",
68
+ "email" => "butch.a.marshall@gmail.com",
69
+ })
70
+ end
71
+ end
72
+
73
+ describe CustomerAddress do
74
+ subject(:customer_address) { CustomerAddress.new(:customer => customer) }
75
+
76
+ context 'when it has partial data' do
77
+ before do
78
+ customer_address.fields = {
79
+ "country" => "canada",
80
+ "province" => "ontario",
81
+ "city" => "toronto",
82
+ "postal_code" => "H0H0H0",
83
+ }
84
+ end
85
+
86
+ it 'should return nil for unset fields' do
87
+ json = customer_address.as_json(:root => "customer_address")
88
+
89
+ expect(json["customer_address"]["fields"]).to eq({
90
+ "address_1" => nil,
91
+ "address_2" => nil,
92
+ "country" => "canada",
93
+ "province" => "ontario",
94
+ "city" => "toronto",
95
+ "postal_code" => "H0H0H0",
96
+ })
97
+ end
98
+ end
99
+
100
+ context 'when it is valid' do
101
+ before do
102
+ customer_address.fields = {
103
+ "address_1" => "555 Bloor Street",
104
+ "country" => "canada",
105
+ "province" => "ontario",
106
+ "city" => "toronto",
107
+ "postal_code" => "H0H0H0",
108
+ }
109
+ end
110
+
111
+ it 'should return parent customer fields as json' do
112
+ json = customer_address.customer.as_json(:root => "customer")
113
+
114
+ expect(json["customer"]["fields"]).to eq({
115
+ "first_name" => "Butch",
116
+ "last_name" => "Marshall",
117
+ "email" => "butch.a.marshall@gmail.com",
118
+ })
119
+ end
120
+
121
+ it 'should return fields as json' do
122
+ json = customer_address.as_json(:root => "customer_address")
123
+ expect(json["customer_address"]["fields"]).to eq({
124
+ "address_1" => "555 Bloor Street",
125
+ "address_2" => nil,
126
+ "country" => "canada",
127
+ "province" => "ontario",
128
+ "city" => "toronto",
129
+ "postal_code" => "H0H0H0",
130
+ })
131
+ end
132
+
133
+ it 'should validate' do
134
+ expect(customer_address).to be_valid
135
+ end
136
+
137
+ it 'should save successfully' do
138
+ sub = customer_address
139
+ expect(sub.save).to eq(true)
140
+ end
141
+
142
+ it 'should should retrieve properly from the database' do
143
+ sub = customer_address
144
+ sub.save
145
+
146
+ customer = CustomerAddress.find(sub.id)
147
+ json = customer.as_json(:root => "customer_address")
148
+
149
+ expect(json["customer_address"]["fields"]).to eq({
150
+ "address_1" => "555 Bloor Street",
151
+ "address_2" => nil,
152
+ "country" => "canada",
153
+ "province" => "ontario",
154
+ "city" => "toronto",
155
+ "postal_code" => "H0H0H0",
156
+ })
157
+ end
158
+ end
159
+
160
+ context 'when it is invalid' do
161
+ before do
162
+ customer_address.fields = {
163
+ "address_1" => "555 Bloor Street",
164
+ "address_2" => nil,
165
+ "country" => "canadaaaaa",
166
+ "province" => "ontario",
167
+ "city" => "toronto",
168
+ "postal_code" => "H0H0H",
169
+ }
170
+ end
171
+
172
+ it 'should not validate' do
173
+ expect(customer_address).to_not be_valid
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,77 @@
1
+ require 'logger'
2
+ require 'rspec'
3
+
4
+ require 'active_support/dependencies'
5
+ require 'active_record'
6
+
7
+ require 'has_dynamic_columns'
8
+
9
+ if ENV['DEBUG_LOGS']
10
+
11
+ else
12
+
13
+ end
14
+ ENV['RAILS_ENV'] = 'test'
15
+
16
+ # Trigger AR to initialize
17
+ ActiveRecord::Base
18
+
19
+ module Rails
20
+ def self.root
21
+ '.'
22
+ end
23
+ end
24
+
25
+ # Add this directory so the ActiveSupport autoloading works
26
+ ActiveSupport::Dependencies.autoload_paths << File.dirname(__FILE__)
27
+
28
+ # Used to test interactions between DJ and an ORM
29
+ ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
30
+ ActiveRecord::Migration.verbose = false
31
+
32
+ require "generators/has_dynamic_columns/templates/migration"
33
+ ActiveRecord::Schema.define do
34
+ CreateHasDynamicColumns.up
35
+
36
+ create_table :accounts, force: true do |t|
37
+ t.string :name
38
+ t.timestamps
39
+ end
40
+ create_table :customers, force: true do |t|
41
+ t.string :name
42
+ t.integer :account_id
43
+ t.timestamps
44
+ end
45
+ create_table :customer_addresses, force: true do |t|
46
+ t.string :name
47
+ t.integer :customer_id
48
+ t.timestamps
49
+ end
50
+ end
51
+
52
+ class Account < ActiveRecord::Base
53
+ has_many :customers
54
+ has_dynamic_columns
55
+ end
56
+
57
+ class Customer < ActiveRecord::Base
58
+ belongs_to :account
59
+ has_many :customer_addresses
60
+ has_dynamic_columns field_scope: "account", dynamic_type: "Customer", as: "fields"
61
+ end
62
+
63
+ class CustomerAddress < ActiveRecord::Base
64
+ belongs_to :customer
65
+ has_dynamic_columns field_scope: "customer.account", dynamic_type: "CustomerAddress", as: "fields"
66
+ end
67
+
68
+
69
+ RSpec.configure do |config|
70
+ config.after(:each) do
71
+
72
+ end
73
+
74
+ config.expect_with :rspec do |c|
75
+ c.syntax = :expect
76
+ end
77
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: has_dynamic_columns
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Butch Marshall
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '5.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '3.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: bundler
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.7'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.7'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '10.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '10.0'
61
+ description: Adds ability to put dynamic fields into active record models
62
+ email:
63
+ - butch.a.marshall@gmail.com
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - ".gitignore"
69
+ - ".rspec"
70
+ - Gemfile
71
+ - LICENSE.txt
72
+ - README.md
73
+ - Rakefile
74
+ - has_dynamic_columns.gemspec
75
+ - lib/generators/has_dynamic_columns/active_record_generator.rb
76
+ - lib/generators/has_dynamic_columns/has_dynamic_columns_generator.rb
77
+ - lib/generators/has_dynamic_columns/next_migration_version.rb
78
+ - lib/generators/has_dynamic_columns/templates/migration.rb
79
+ - lib/has_dynamic_columns.rb
80
+ - lib/has_dynamic_columns/compatibility.rb
81
+ - lib/has_dynamic_columns/dynamic_column.rb
82
+ - lib/has_dynamic_columns/dynamic_column_datum.rb
83
+ - lib/has_dynamic_columns/dynamic_column_option.rb
84
+ - lib/has_dynamic_columns/dynamic_column_validation.rb
85
+ - lib/has_dynamic_columns/version.rb
86
+ - spec/has_dynamic_columns_spec.rb
87
+ - spec/spec_helper.rb
88
+ homepage: https://github.com/butchmarshall/has_dynamic_columns
89
+ licenses:
90
+ - MIT
91
+ metadata: {}
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 2.4.8
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: Dynamic fields for activerecord models
112
+ test_files:
113
+ - spec/has_dynamic_columns_spec.rb
114
+ - spec/spec_helper.rb