has_dynamic_columns 0.2.1 → 0.3.2
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 +4 -4
- data/Gemfile +8 -3
- data/README.md +25 -1
- data/has_dynamic_columns.gemspec +3 -1
- data/lib/generators/has_dynamic_columns/active_record_generator.rb +1 -1
- data/lib/generators/has_dynamic_columns/templates/migration.rb +4 -4
- data/lib/generators/has_dynamic_columns/templates/migration_0.3.0.rb +89 -0
- data/lib/generators/has_dynamic_columns/upgrade_0_3_0_active_record_generator.rb +22 -0
- data/lib/has_dynamic_columns/active_record/query_methods.rb +1 -167
- data/lib/has_dynamic_columns/active_record/relation.rb +1 -21
- data/lib/has_dynamic_columns/active_record/v3/query_methods.rb +281 -0
- data/lib/has_dynamic_columns/active_record/v3/relation.rb +34 -0
- data/lib/has_dynamic_columns/active_record/v4/query_methods.rb +257 -0
- data/lib/has_dynamic_columns/active_record/v4/relation.rb +34 -0
- data/lib/has_dynamic_columns/dynamic_column_boolean_datum.rb +5 -0
- data/lib/has_dynamic_columns/dynamic_column_date_datum.rb +5 -0
- data/lib/has_dynamic_columns/dynamic_column_datetime_datum.rb +5 -0
- data/lib/has_dynamic_columns/dynamic_column_datum.rb +8 -54
- data/lib/has_dynamic_columns/dynamic_column_enum_datum.rb +5 -0
- data/lib/has_dynamic_columns/dynamic_column_float_datum.rb +5 -0
- data/lib/has_dynamic_columns/dynamic_column_integer_datum.rb +5 -0
- data/lib/has_dynamic_columns/dynamic_column_string_datum.rb +5 -0
- data/lib/has_dynamic_columns/dynamic_column_text_datum.rb +5 -0
- data/lib/has_dynamic_columns/dynamic_column_time_datum.rb +5 -0
- data/lib/has_dynamic_columns/dynamic_column_timestamp_datum.rb +5 -0
- data/lib/has_dynamic_columns/model/class_methods.rb +22 -2
- data/lib/has_dynamic_columns/version.rb +1 -1
- data/lib/has_dynamic_columns.rb +11 -0
- data/rspec_rvm +39 -0
- data/spec/factories/account.rb +24 -0
- data/spec/factories/customer.rb +35 -0
- data/spec/has_dynamic_columns/active_record/query_methods_spec.rb +252 -0
- data/spec/has_dynamic_columns/dynamic_columns_integer_datum_spec.rb +124 -0
- data/spec/has_dynamic_columns/dynamic_columns_string_datum_spec.rb +7 -0
- data/spec/has_dynamic_columns_spec.rb +93 -63
- data/spec/spec_helper.rb +13 -8
- metadata +67 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f872c8cb5a681f06b58bd3e57477bc275a153c3c
|
4
|
+
data.tar.gz: c4d47b6fb08df2fd3bdd4d531988adc00c5312b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dd399b22a0be68a064d8e9f8e67bbc2558d290035c59fce659be6597d0406d928766d75f514b4c7b7cf67d0aa363abaa6af4c0ec79f85251195ef4bbbc8ca367
|
7
|
+
data.tar.gz: 7de3e24535b04a0b3a5abcbd40d573c6688164e954088f5e3f5878c46a2a2e5364a95b5792c19b78db9cb35564953dbabf3595f51d9c59605383fe93b99af191
|
data/Gemfile
CHANGED
@@ -2,13 +2,18 @@ source 'https://rubygems.org'
|
|
2
2
|
|
3
3
|
gem 'rake'
|
4
4
|
|
5
|
-
group :test do
|
6
|
-
|
5
|
+
group :development, :test do
|
6
|
+
activerecord_version = ENV['HAS_DYNAMIC_COLUMNS_ACTIVERECORD_VERSION']
|
7
|
+
|
8
|
+
if ENV['RAILS_VERSION'] == 'edge' || activerecord_version == "edge"
|
9
|
+
gem 'arel', :github => 'rails/arel'
|
7
10
|
gem 'activerecord', :github => 'rails/rails'
|
11
|
+
elsif activerecord_version && activerecord_version.strip != ""
|
12
|
+
gem "activerecord", activerecord_version
|
8
13
|
else
|
9
14
|
gem 'activerecord', (ENV['RAILS_VERSION'] || ['>= 3.0', '< 5.0'])
|
10
15
|
end
|
11
|
-
|
16
|
+
|
12
17
|
gem 'coveralls', :require => false
|
13
18
|
gem 'rspec', '>= 3'
|
14
19
|
gem 'rubocop', '>= 0.25'
|
data/README.md
CHANGED
@@ -3,6 +3,15 @@ has_dynamic_columns
|
|
3
3
|
|
4
4
|
This plugin gives ActiveRecord models the ability to dynamically define collectable data based on ***has_many*** and ***belongs_to*** relationships.
|
5
5
|
|
6
|
+
Release Notes
|
7
|
+
============
|
8
|
+
|
9
|
+
**0.3.0**
|
10
|
+
- Moved to storing data types in separate tables (where/order now correct!)
|
11
|
+
- Added order.by_dynamic_columns
|
12
|
+
- Improved how joins were built to avoid duplicates
|
13
|
+
- Added ActiveRecord 3 and 4 compatibility
|
14
|
+
|
6
15
|
Installation
|
7
16
|
============
|
8
17
|
|
@@ -14,6 +23,7 @@ The Active Record migration is required to create the has_dynamic_columns table.
|
|
14
23
|
running the following command:
|
15
24
|
|
16
25
|
rails generate has_dynamic_columns:active_record
|
26
|
+
rails generate has_dynamic_columns:upgrade_0_3_0_active_record
|
17
27
|
rake db:migrate
|
18
28
|
|
19
29
|
Usage
|
@@ -228,4 +238,18 @@ Customer
|
|
228
238
|
|
229
239
|
## **has_many** relationship
|
230
240
|
|
231
|
-
TODO example.
|
241
|
+
TODO example.
|
242
|
+
|
243
|
+
**Ordering**
|
244
|
+
```ruby
|
245
|
+
|
246
|
+
# ------------------------------------------------
|
247
|
+
# by dynamic column
|
248
|
+
# ------------------------------------------------
|
249
|
+
|
250
|
+
Customer
|
251
|
+
.order
|
252
|
+
.by_dynamic_columns(country: :asc)
|
253
|
+
.with_scope(Account.find(1))
|
254
|
+
|
255
|
+
```
|
data/has_dynamic_columns.gemspec
CHANGED
@@ -18,8 +18,10 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency "activerecord", "
|
21
|
+
spec.add_dependency "activerecord", [">= 3.0", "< 5.0"]
|
22
22
|
|
23
|
+
spec.add_development_dependency "sqlite3", "> 0"
|
24
|
+
spec.add_development_dependency "factory_girl", "> 0"
|
23
25
|
spec.add_development_dependency "bundler", "~> 1.7"
|
24
26
|
spec.add_development_dependency "rake", "~> 10.0"
|
25
27
|
end
|
@@ -8,7 +8,7 @@ class AddHasDynamicColumns < ActiveRecord::Migration
|
|
8
8
|
t.string :key
|
9
9
|
t.string :data_type
|
10
10
|
|
11
|
-
t.timestamps
|
11
|
+
t.timestamps null: false
|
12
12
|
end
|
13
13
|
add_index(:dynamic_columns, [:field_scope_id, :field_scope_type, :dynamic_type], name: 'index1')
|
14
14
|
create_table :dynamic_column_validations do |t|
|
@@ -17,13 +17,13 @@ class AddHasDynamicColumns < ActiveRecord::Migration
|
|
17
17
|
t.string :error
|
18
18
|
t.string :regexp
|
19
19
|
|
20
|
-
t.timestamps
|
20
|
+
t.timestamps null: false
|
21
21
|
end
|
22
22
|
create_table :dynamic_column_options do |t|
|
23
23
|
t.integer :dynamic_column_id
|
24
24
|
t.string :key
|
25
25
|
|
26
|
-
t.timestamps
|
26
|
+
t.timestamps null: false
|
27
27
|
end
|
28
28
|
create_table :dynamic_column_data do |t|
|
29
29
|
t.string :owner_type
|
@@ -32,7 +32,7 @@ class AddHasDynamicColumns < ActiveRecord::Migration
|
|
32
32
|
t.integer :dynamic_column_option_id
|
33
33
|
t.string :value
|
34
34
|
|
35
|
-
t.timestamps
|
35
|
+
t.timestamps null: false
|
36
36
|
end
|
37
37
|
add_index(:dynamic_column_data, [:owner_id, :owner_type, :dynamic_column_id], name: 'index2')
|
38
38
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
class Upgrade030HasDynamicColumns < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
add_column :dynamic_column_data, :datum_type, :string
|
4
|
+
add_column :dynamic_column_data, :datum_id, :integer
|
5
|
+
|
6
|
+
create_table :dynamic_column_boolean_data do |t|
|
7
|
+
t.boolean :value
|
8
|
+
t.timestamps null: false
|
9
|
+
end
|
10
|
+
add_index(:dynamic_column_boolean_data, [:value])
|
11
|
+
|
12
|
+
create_table :dynamic_column_date_data do |t|
|
13
|
+
t.date :value
|
14
|
+
t.timestamps null: false
|
15
|
+
end
|
16
|
+
add_index(:dynamic_column_date_data, [:value])
|
17
|
+
|
18
|
+
create_table :dynamic_column_datetime_data do |t|
|
19
|
+
t.datetime :value
|
20
|
+
t.timestamps null: false
|
21
|
+
end
|
22
|
+
add_index(:dynamic_column_datetime_data, [:value])
|
23
|
+
|
24
|
+
create_table :dynamic_column_enum_data do |t|
|
25
|
+
t.string :value
|
26
|
+
t.timestamps null: false
|
27
|
+
end
|
28
|
+
add_index(:dynamic_column_enum_data, [:value])
|
29
|
+
|
30
|
+
create_table :dynamic_column_float_data do |t|
|
31
|
+
t.float :value
|
32
|
+
t.timestamps null: false
|
33
|
+
end
|
34
|
+
add_index(:dynamic_column_float_data, [:value])
|
35
|
+
|
36
|
+
create_table :dynamic_column_integer_data do |t|
|
37
|
+
t.integer :value
|
38
|
+
t.timestamps null: false
|
39
|
+
end
|
40
|
+
add_index(:dynamic_column_integer_data, [:value])
|
41
|
+
|
42
|
+
create_table :dynamic_column_string_data do |t|
|
43
|
+
t.string :value
|
44
|
+
t.timestamps null: false
|
45
|
+
end
|
46
|
+
add_index(:dynamic_column_string_data, [:value])
|
47
|
+
|
48
|
+
create_table :dynamic_column_text_data do |t|
|
49
|
+
t.text :value
|
50
|
+
t.timestamps null: false
|
51
|
+
end
|
52
|
+
add_index(:dynamic_column_text_data, [:value], :length => 255)
|
53
|
+
|
54
|
+
create_table :dynamic_column_time_data do |t|
|
55
|
+
t.time :value
|
56
|
+
t.timestamps null: false
|
57
|
+
end
|
58
|
+
add_index(:dynamic_column_time_data, [:value])
|
59
|
+
|
60
|
+
create_table :dynamic_column_timestamp_data do |t|
|
61
|
+
t.timestamp :value
|
62
|
+
t.timestamps null: false
|
63
|
+
end
|
64
|
+
add_index(:dynamic_column_timestamp_data, [:value])
|
65
|
+
|
66
|
+
# Migrate data
|
67
|
+
["string","integer","boolean","text"].each { |data_type|
|
68
|
+
ActiveRecord::Base.connection.select_all("
|
69
|
+
SELECT
|
70
|
+
`dynamic_column_data`.*
|
71
|
+
FROM `dynamic_columns`
|
72
|
+
INNER JOIN `dynamic_column_data`
|
73
|
+
ON `dynamic_column_data`.`dynamic_column_id` = `dynamic_columns`.`id`
|
74
|
+
WHERE `dynamic_columns`.`data_type` = '#{data_type}'
|
75
|
+
").each { |i|
|
76
|
+
obj = "::HasDynamicColumns::DynamicColumn#{data_type.camelize}Datum".constantize.create(:value => i["value"])
|
77
|
+
if datum = ::HasDynamicColumns::DynamicColumnDatum.where(:id => i["id"]).first
|
78
|
+
datum.datum = obj
|
79
|
+
datum.save
|
80
|
+
end
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
remove_column :dynamic_column_data, :value
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.down
|
88
|
+
end
|
89
|
+
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 Upgrade_0_3_0_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_0.3.0.rb", "db/migrate/upgrade_0_3_0_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
|
@@ -1,167 +1 @@
|
|
1
|
-
|
2
|
-
module ActiveRecord
|
3
|
-
module QueryMethods
|
4
|
-
def self.included(base)
|
5
|
-
base.class_eval do
|
6
|
-
alias_method_chain :where, :dynamic_columns
|
7
|
-
alias_method_chain :build_arel, :dynamic_columns
|
8
|
-
|
9
|
-
# Recurses through arel nodes until it finds one it can work with
|
10
|
-
def dynamic_column_process_nodes(rel, scope, index)
|
11
|
-
case rel
|
12
|
-
when Arel::Nodes::Grouping
|
13
|
-
dynamic_column_process_nodes(rel.expr, scope, index+1)
|
14
|
-
when Arel::Nodes::Or
|
15
|
-
dynamic_column_process_nodes(rel.left, scope, index+1)
|
16
|
-
dynamic_column_process_nodes(rel.right, scope, index+10000) # Hack - queries with over 10,000 dynamic where conditions may break
|
17
|
-
when Arel::Nodes::And
|
18
|
-
dynamic_column_process_nodes(rel.left, scope, index+1)
|
19
|
-
dynamic_column_process_nodes(rel.right, scope, index+10000) # Hack - queries with over 10,000 dynamic where conditions may break
|
20
|
-
# We can work with this
|
21
|
-
else
|
22
|
-
dynamic_column_build_arel_joins_and_modify_wheres(rel, scope, index+1)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
# Builds the joins required for this dynamic column
|
27
|
-
# Modifies the where to use the dynamic_column_data table alias
|
28
|
-
#
|
29
|
-
# rel - an arel node
|
30
|
-
# scope - scope to run the conditions in
|
31
|
-
# index - unique table identifier
|
32
|
-
def dynamic_column_build_arel_joins_and_modify_wheres(rel, scope, index)
|
33
|
-
col_name = rel.left.name
|
34
|
-
value = rel.right
|
35
|
-
|
36
|
-
field_scope = scope
|
37
|
-
field_scope_id = (!field_scope.nil?) ? field_scope.id : nil
|
38
|
-
field_scope_type = (!field_scope.nil?) ? field_scope.class.name.constantize.to_s : nil
|
39
|
-
|
40
|
-
column_table = HasDynamicColumns::DynamicColumn.arel_table.alias("dynamic_where_#{index}_#{col_name}")
|
41
|
-
column_datum_table = HasDynamicColumns::DynamicColumnDatum.arel_table.alias("dynamic_where_data_#{index}_#{col_name}")
|
42
|
-
|
43
|
-
dynamic_type = rel.left.relation.engine.to_s
|
44
|
-
rel.left.relation = column_datum_table # modify the where to use the aliased table
|
45
|
-
rel.left.name = :value # value is the data storage column searchable on dynamic_column_data table
|
46
|
-
|
47
|
-
rel.right = case rel.right
|
48
|
-
# Map true -> 1
|
49
|
-
when ::TrueClass
|
50
|
-
1
|
51
|
-
# Map false -> 0
|
52
|
-
when ::FalseClass
|
53
|
-
0
|
54
|
-
else
|
55
|
-
rel.right
|
56
|
-
end
|
57
|
-
|
58
|
-
# Join on the column with the key
|
59
|
-
on_query = column_table[:key].eq(col_name)
|
60
|
-
on_query = on_query.and(
|
61
|
-
column_table[:field_scope_type].eq(field_scope_type)
|
62
|
-
) unless field_scope_type.nil?
|
63
|
-
|
64
|
-
on_query = on_query.and(
|
65
|
-
column_table[:field_scope_id].eq(field_scope_id)
|
66
|
-
) unless field_scope_id.nil?
|
67
|
-
|
68
|
-
column_table_join_on = column_table
|
69
|
-
.create_on(
|
70
|
-
on_query
|
71
|
-
)
|
72
|
-
|
73
|
-
column_table_join = table.create_join(column_table, column_table_join_on)
|
74
|
-
self.joins_values += [column_table_join]
|
75
|
-
|
76
|
-
# Join on all the data with the provided key
|
77
|
-
column_table_datum_join_on = column_datum_table
|
78
|
-
.create_on(
|
79
|
-
column_datum_table[:owner_id].eq(table[:id]).and(
|
80
|
-
column_datum_table[:owner_type].eq(dynamic_type)
|
81
|
-
).and(
|
82
|
-
column_datum_table[:dynamic_column_id].eq(column_table[:id])
|
83
|
-
)
|
84
|
-
)
|
85
|
-
|
86
|
-
column_table_datum_join = table.create_join(column_datum_table, column_table_datum_join_on, Arel::Nodes::OuterJoin)
|
87
|
-
self.joins_values += [column_table_datum_join]
|
88
|
-
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
# When arel starts building - filter
|
94
|
-
def build_arel_with_dynamic_columns
|
95
|
-
# Calculate any dynamic scope that was passed
|
96
|
-
self.has_dynamic_columns_values.each_with_index { |dynamic_scope, index_outer|
|
97
|
-
dynamic_scope[:where].each_with_index { |rel, index_inner|
|
98
|
-
# Process each where
|
99
|
-
dynamic_column_process_nodes(rel, dynamic_scope[:scope], (index_outer*1000)+(index_inner*10000))
|
100
|
-
|
101
|
-
# It's now safe to use the original where query
|
102
|
-
# All the conditions in rel have been modified to be a where of the aliases dynamic_where_data table
|
103
|
-
|
104
|
-
# Warning
|
105
|
-
# Must cast rel to a string - I've encountered ***strange*** situations where this will change the 'col_name' to value in the where clause
|
106
|
-
# specifically, the test 'case should restrict if scope specified' will fail
|
107
|
-
self.where_values += [rel.to_sql]
|
108
|
-
}
|
109
|
-
}
|
110
|
-
# At least one dynamic where run - we need to group or we're going to get duplicates
|
111
|
-
if self.has_dynamic_columns_values.length > 0
|
112
|
-
self.group_values += [Arel::Nodes::Group.new(table[:id])]
|
113
|
-
end
|
114
|
-
|
115
|
-
build_arel_without_dynamic_columns
|
116
|
-
end
|
117
|
-
|
118
|
-
# lifted from
|
119
|
-
# http://erniemiller.org/2013/10/07/activerecord-where-not-sane-true/
|
120
|
-
module WhereChainCompatibility
|
121
|
-
#include ::ActiveRecord::QueryMethods
|
122
|
-
#define_method :build_where,
|
123
|
-
# ::ActiveRecord::QueryMethods.instance_method(:build_where)
|
124
|
-
|
125
|
-
# Extends where to chain a has_dynamic_columns method
|
126
|
-
# This builds all the joins needed to search the has_dynamic_columns_data tables
|
127
|
-
def has_dynamic_columns(opts = :chain, *rest)
|
128
|
-
# Map
|
129
|
-
dynamic_columns_value = {
|
130
|
-
:scope => nil,
|
131
|
-
:where => @scope.send(:build_where, opts, rest)
|
132
|
-
}
|
133
|
-
@scope.has_dynamic_columns_values = dynamic_columns_value
|
134
|
-
|
135
|
-
chain = ::ActiveRecord::QueryMethods::WhereChain.new(@scope)
|
136
|
-
chain.instance_eval do
|
137
|
-
# Make outer scope variable accessible
|
138
|
-
@dynamic_columns_value = dynamic_columns_value
|
139
|
-
|
140
|
-
# Extends where to chain with a has_scope method
|
141
|
-
# This scopes the where from above
|
142
|
-
def with_scope(opt)
|
143
|
-
@dynamic_columns_value[:scope] = opt
|
144
|
-
|
145
|
-
@scope
|
146
|
-
end
|
147
|
-
def without_scope
|
148
|
-
@scope
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
chain
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
def where_with_dynamic_columns(opts = :chain, *rest)
|
157
|
-
if opts == :chain
|
158
|
-
scope = spawn
|
159
|
-
chain = ::ActiveRecord::QueryMethods::WhereChain.new(scope)
|
160
|
-
chain.send(:extend, WhereChainCompatibility)
|
161
|
-
else
|
162
|
-
where_without_dynamic_columns(opts, rest)
|
163
|
-
end
|
164
|
-
end
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
1
|
+
require_relative "v#{ActiveRecord::VERSION::MAJOR}/query_methods"
|
@@ -1,21 +1 @@
|
|
1
|
-
|
2
|
-
module ActiveRecord
|
3
|
-
module Relation
|
4
|
-
def self.included(base)
|
5
|
-
base.class_eval do
|
6
|
-
def has_dynamic_columns_values
|
7
|
-
@values[:has_dynamic_columns_values] || []
|
8
|
-
end
|
9
|
-
def has_dynamic_columns_values=values
|
10
|
-
raise ImmutableRelation if @loaded
|
11
|
-
@values[:has_dynamic_columns_values] ||= []
|
12
|
-
@values[:has_dynamic_columns_values] << values
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
base.instance_eval do
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
1
|
+
require_relative "v#{ActiveRecord::VERSION::MAJOR}/relation"
|