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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +8 -3
  3. data/README.md +25 -1
  4. data/has_dynamic_columns.gemspec +3 -1
  5. data/lib/generators/has_dynamic_columns/active_record_generator.rb +1 -1
  6. data/lib/generators/has_dynamic_columns/templates/migration.rb +4 -4
  7. data/lib/generators/has_dynamic_columns/templates/migration_0.3.0.rb +89 -0
  8. data/lib/generators/has_dynamic_columns/upgrade_0_3_0_active_record_generator.rb +22 -0
  9. data/lib/has_dynamic_columns/active_record/query_methods.rb +1 -167
  10. data/lib/has_dynamic_columns/active_record/relation.rb +1 -21
  11. data/lib/has_dynamic_columns/active_record/v3/query_methods.rb +281 -0
  12. data/lib/has_dynamic_columns/active_record/v3/relation.rb +34 -0
  13. data/lib/has_dynamic_columns/active_record/v4/query_methods.rb +257 -0
  14. data/lib/has_dynamic_columns/active_record/v4/relation.rb +34 -0
  15. data/lib/has_dynamic_columns/dynamic_column_boolean_datum.rb +5 -0
  16. data/lib/has_dynamic_columns/dynamic_column_date_datum.rb +5 -0
  17. data/lib/has_dynamic_columns/dynamic_column_datetime_datum.rb +5 -0
  18. data/lib/has_dynamic_columns/dynamic_column_datum.rb +8 -54
  19. data/lib/has_dynamic_columns/dynamic_column_enum_datum.rb +5 -0
  20. data/lib/has_dynamic_columns/dynamic_column_float_datum.rb +5 -0
  21. data/lib/has_dynamic_columns/dynamic_column_integer_datum.rb +5 -0
  22. data/lib/has_dynamic_columns/dynamic_column_string_datum.rb +5 -0
  23. data/lib/has_dynamic_columns/dynamic_column_text_datum.rb +5 -0
  24. data/lib/has_dynamic_columns/dynamic_column_time_datum.rb +5 -0
  25. data/lib/has_dynamic_columns/dynamic_column_timestamp_datum.rb +5 -0
  26. data/lib/has_dynamic_columns/model/class_methods.rb +22 -2
  27. data/lib/has_dynamic_columns/version.rb +1 -1
  28. data/lib/has_dynamic_columns.rb +11 -0
  29. data/rspec_rvm +39 -0
  30. data/spec/factories/account.rb +24 -0
  31. data/spec/factories/customer.rb +35 -0
  32. data/spec/has_dynamic_columns/active_record/query_methods_spec.rb +252 -0
  33. data/spec/has_dynamic_columns/dynamic_columns_integer_datum_spec.rb +124 -0
  34. data/spec/has_dynamic_columns/dynamic_columns_string_datum_spec.rb +7 -0
  35. data/spec/has_dynamic_columns_spec.rb +93 -63
  36. data/spec/spec_helper.rb +13 -8
  37. metadata +67 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 448332c912f1846a9b67d402a0be6560fe2562c5
4
- data.tar.gz: 256541a493f2e38a31683c7076239b42a6da5f6b
3
+ metadata.gz: f872c8cb5a681f06b58bd3e57477bc275a153c3c
4
+ data.tar.gz: c4d47b6fb08df2fd3bdd4d531988adc00c5312b0
5
5
  SHA512:
6
- metadata.gz: 95a6df76f9a6000ebffeac04915cc8c8f8a422ded713a25f4fda3a02ea52d0c9273fbb37398945d16a0ff26754ffd716e6cf4dd4216bb5571b3f540392b74373
7
- data.tar.gz: f27225cb04d612c509225626c44625b25f0cc1b1822a4d364e609a6a25e616e538459e3bc368420f3078fa1c78eef43b5d9971adac6ddddcb0d4369d2fe22441
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
- if ENV['RAILS_VERSION'] == 'edge'
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
+ ```
@@ -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", "~> 4.2"
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
@@ -16,7 +16,7 @@ module HasDynamicColumns
16
16
  end
17
17
 
18
18
  def self.next_migration_number(dirname)
19
- ActiveRecord::Generators::Base.next_migration_number dirname
19
+ ::ActiveRecord::Generators::Base.next_migration_number dirname
20
20
  end
21
21
  end
22
22
  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
- module HasDynamicColumns
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
- module HasDynamicColumns
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"