activerecord_nested_scope 1.0.2 → 1.2.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
- SHA1:
3
- metadata.gz: a69c4a48717739fd16dacdb44003583d15f8989c
4
- data.tar.gz: 7362eef6445f2ca2969cd9df5d04618c30aedba5
2
+ SHA256:
3
+ metadata.gz: 6e06209d1f222b99ba36ec6619390cec6465e41000e4dc9100a0de23e47e962b
4
+ data.tar.gz: 7ed179db041bff9e317e0eb4e51b7e6e8877c0a2ef782cbd1e464c5fe640dec3
5
5
  SHA512:
6
- metadata.gz: 43628d21ea4aae5f2dd93d78e913eb7a659b0fab1232a2a0a183f0cbf46f0a39ea6485176edb1dac944bfeb41c136f77fbce9a3defef4da93d9cc5f8b8f7677e
7
- data.tar.gz: b832f7295a60cd32e4af55507cf7a05a504179c0658c6d025ce77def50582e7c6aac24fbf800204801521482bed18c8b5fbbf7ee32fe230328e3f129785bb195
6
+ metadata.gz: 2a2b55f78f24d9e1e1cf50f3d2260dff3e62dc70ce2ba6594835192e0189eee3b222d17a2b55e6f0ff5351a2927befcd35f0f3e660c45ec6679d5a186a3fb18a
7
+ data.tar.gz: 73a819a21306bb1ed00645063af41eea0851687c944942a8aa7a9f8b2f209980ca9bb99b007e778f197c1c40126d0585f199f9a9d5742a57937cd3fb6fd1220b
data/.gitignore CHANGED
@@ -4,12 +4,12 @@
4
4
  Gemfile.lock
5
5
  _yardoc/
6
6
  coverage/
7
+ gemfiles/*.lock
7
8
  doc/
8
9
  pkg/
9
10
  spec/reports/
10
11
  spec/dummy/config/database.yml
11
- spec/dummy/db/schema.rb
12
+ spec/dummy/db/schema*.rb
12
13
  spec/dummy/db/*.sqlite3
13
14
  spec/dummy/log/*.log
14
15
  spec/dummy/tmp/*
15
- tmp/
@@ -3,6 +3,8 @@ rvm:
3
3
  - 2.3
4
4
  - 2.4
5
5
  - 2.5
6
+ - 2.6
7
+ - 2.7
6
8
  services:
7
9
  - mysql
8
10
  - postgresql
@@ -16,10 +18,16 @@ gemfile:
16
18
  - gemfiles/rails50.gemfile
17
19
  - gemfiles/rails51.gemfile
18
20
  - gemfiles/rails52.gemfile
21
+ - gemfiles/rails60.gemfile
22
+ matrix:
23
+ exclude:
24
+ - rvm: 2.3
25
+ gemfile: gemfiles/rails60.gemfile
26
+ - rvm: 2.4
27
+ gemfile: gemfiles/rails60.gemfile
19
28
  before_script:
20
29
  - cd spec/dummy
21
- - bundle exec rake db:create RAILS_ENV=test
22
- - bundle exec rake db:migrate RAILS_ENV=test
23
- - bundle exec rake db:seed RAILS_ENV=test
30
+ - bundle exec rake db:create db:migrate db:seed RAILS_ENV=test
24
31
  - cd ../..
25
- script: bundle exec rspec
32
+ script:
33
+ - DEBUG=1 bundle exec rspec
@@ -0,0 +1,35 @@
1
+ # CHANGELOG
2
+
3
+ ## 1.2.0
4
+
5
+ * Add source_map option to resolve polymorphic type.
6
+
7
+ ## 1.1.1
8
+
9
+ * Fix query for associations with primary key option.
10
+
11
+ ## 1.1.0
12
+
13
+ * Add short query feature for simple belongs_to association.
14
+ * Rename `_nested_scope_options` to `nested_scope_options`.
15
+ * Remove support for active_record_union.
16
+
17
+ ## 1.0.4
18
+
19
+ * Fix query for string value.
20
+
21
+ ## 1.0.3
22
+
23
+ * Remove nil from polymorphic types.
24
+
25
+ ## 1.0.2
26
+
27
+ * Fix query error when there are no records in polymorphic tables.
28
+
29
+ ## 1.0.1
30
+
31
+ * Fix query for array value.
32
+
33
+ ## 1.0.0
34
+
35
+ * First release.
data/README.md CHANGED
@@ -5,7 +5,8 @@ An ActiveRecord extension to build nested scopes through pre-defined association
5
5
  ## Dependencies
6
6
 
7
7
  * ruby 2.3+
8
- * rails 5.0+ (activerecord and activesupport)
8
+ * activerecord 5.0+
9
+ * activesupport 5.0+
9
10
 
10
11
  ## Installation
11
12
 
@@ -57,35 +58,36 @@ UserConfig.in_group(id: 1)
57
58
 
58
59
  ### Polymorphic association
59
60
 
60
- If you define a polymorphic association like that,
61
+ If you define a polymorphic association,
62
+ `in_group` generates union of subqueries for each polymorpchic type as follows:
61
63
 
62
64
  ```ruby
63
- class Name < ActiveRecord::Base
64
- belongs_to :data, polymorphic: true
65
- nested_scope :in_group, through: :data
65
+ class Polymorphism < ActiveRecord::Base
66
+ belongs_to :record, polymorphic: true
67
+ nested_scope :in_group, through: :record
66
68
  end
67
- ```
68
-
69
- `in_group` scope generates union of subqueries for each polymorpchic type:
70
69
 
71
- ```ruby
72
- Name.in_group(id: 1)
73
- #=> SELECT "names"."data_type" FROM "names" GROUP BY "names"."data_type"
74
- # SELECT "names".* FROM (
75
- # SELECT "names".* FROM "names" WHERE "names"."data_type" = 'Group' AND "names"."data_id" IN (SELECT "groups"."id" FROM "groups" WHERE "groups"."id" = 1) UNION
76
- # SELECT "names".* FROM "names" WHERE "names"."data_type" = 'Manager' AND "names"."data_id" IN (SELECT "managers"."id" FROM "managers" WHERE "managers"."id" IN (SELECT "groups"."manager_id" FROM "groups" WHERE "groups"."id" = 1)) UNION
77
- # SELECT "names".* FROM "names" WHERE "names"."data_type" = 'Supervisor' AND "names"."data_id" IN (SELECT "supervisors"."id" FROM "supervisors" WHERE "supervisors"."id" IN (SELECT "managers"."supervisor_id" FROM "managers" WHERE "managers"."id" IN (SELECT "groups"."manager_id" FROM "groups" WHERE "groups"."id" = 1) ORDER BY "managers"."id" ASC)) UNION
78
- # SELECT "names".* FROM "names" WHERE "names"."data_type" = 'User' AND "names"."data_id" IN (SELECT "users"."id" FROM "users" WHERE "users"."group_id" IN (SELECT "groups"."id" FROM "groups" WHERE "groups"."id" = 1))) AS names
70
+ Polymorphism.in_group(id: 1)
71
+ #=> SELECT "polymorphisms"."record_type" FROM "polymorphisms" GROUP BY "polymorphisms"."record_type"
72
+ #=> SELECT "polymorphisms".* FROM (
73
+ # SELECT "polymorphisms".* FROM "polymorphisms" WHERE "polymorphisms"."record_type" = 'Group' AND "polymorphisms"."record_id" IN (SELECT "groups"."id" FROM "groups" WHERE "groups"."id" = 1) UNION
74
+ # SELECT "polymorphisms".* FROM "polymorphisms" WHERE "polymorphisms"."record_type" = 'User' AND "polymorphisms"."record_id" IN (SELECT "users"."id" FROM "users" WHERE "users"."group_id" IN (SELECT "groups"."id" FROM "groups" WHERE "groups"."id" = 1)) UNION
75
+ # SELECT "polymorphisms".* FROM "polymorphisms" WHERE "polymorphisms"."record_type" = 'UserConfig' AND "polymorphisms"."record_id" IN (SELECT "user_configs"."id" FROM "user_configs" WHERE "user_configs"."user_id" IN (SELECT "users"."id" FROM "users" WHERE "users"."group_id" IN (SELECT "groups"."id" FROM "groups" WHERE "groups"."id" = 1)))
76
+ # ) AS polymorphisms
79
77
  ```
80
78
 
81
- Note that the first SQL is executed to load `data_type`, and the second SQL is built using loaded `data_type` variations.
79
+ Note that the first SQL is executed to load `record_type`,
80
+ and the second SQL is built using loaded `record_type` variations.
82
81
 
83
- ### Scope examples
82
+ ### Scope arguments
84
83
 
85
84
  ```ruby
86
85
  # pass a integer
87
86
  User.in_group(1)
88
87
 
88
+ # pass an array
89
+ User.in_group([1, 2, 3])
90
+
89
91
  # pass a hash
90
92
  User.in_group(id: 1)
91
93
 
@@ -21,12 +21,9 @@ Gem::Specification.new do |spec|
21
21
  spec.add_dependency "activesupport", ">= 5.0"
22
22
 
23
23
  spec.add_development_dependency "rails", ">= 5.0"
24
- spec.add_development_dependency "active_record_union"
25
24
  spec.add_development_dependency "mysql2"
26
25
  spec.add_development_dependency "pg"
27
26
  spec.add_development_dependency "sqlite3"
28
27
  spec.add_development_dependency "rspec-rails"
29
28
  spec.add_development_dependency "simplecov"
30
- spec.add_development_dependency "pry-rails"
31
- spec.add_development_dependency "pry-byebug"
32
29
  end
@@ -1,5 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem "rails", "~> 5.2.0.rc1"
3
+ gem "rails", "~> 5.2.0"
4
+ gem "sqlite3", "~> 1.3.6"
4
5
 
5
6
  gemspec path: "../"
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "rails", "~> 6.0.0"
4
+
5
+ gemspec path: "../"
@@ -1,97 +1,102 @@
1
1
  module ActiveRecordNestedScope
2
2
  class Builder
3
3
  def initialize(klass, name, args)
4
- @klass = klass
5
- @name = name
4
+ @node = Node.new(klass, name)
6
5
  @args = args
6
+ @args_type = args_type(args)
7
7
  end
8
8
 
9
- def build
10
- build_for(@klass)
9
+ def build(node = @node)
10
+ if node.leaf?
11
+ leaf_relation(node)
12
+ else
13
+ build_relation(node)
14
+ end
11
15
  end
12
16
 
13
17
  private
14
18
 
15
- def build_for(klass)
16
- if klass._nested_scope_options
17
- if (through = klass._nested_scope_options[@name][:through])
18
- if (ref = klass.reflect_on_association(through))
19
- build_relation(klass, ref)
20
- else
21
- raise ArgumentError.new("can't find reflection #{through} in #{klass}")
22
- end
23
- else
24
- root_relation(klass)
25
- end
19
+ def build_relation(node)
20
+ if node.has_many?
21
+ has_many_relation(node)
22
+ elsif node.polymorphic_belongs_to?
23
+ polymorphic_belongs_to_relation(node)
24
+ elsif node.belongs_to?
25
+ belongs_to_relation(node)
26
26
  else
27
- klass.none
27
+ raise ArgumentError.new("unsupported reflection: #{node.reflection} in #{node.klass}")
28
28
  end
29
29
  end
30
30
 
31
- def build_relation(klass, ref)
32
- case ref.class.to_s
33
- when 'ActiveRecord::Reflection::HasManyReflection', 'ActiveRecord::Reflection::HasOneReflection'
34
- has_many_relation(klass, ref)
35
- when 'ActiveRecord::Reflection::BelongsToReflection'
36
- if ref.polymorphic?
37
- belongs_to_polymorphic_relation(klass, ref)
38
- else
39
- belongs_to_relation(klass, ref)
40
- end
41
- else
42
- raise ArgumentError.new("unsupported reflection: #{ref} in #{klass}")
43
- end
31
+ def has_many_relation(node)
32
+ child = node.children.first
33
+ return node.klass.none unless child
34
+
35
+ relation = child_relation(child, select: node.reflection.foreign_key)
36
+ node.klass.where(node.reflection.active_record_primary_key => relation)
44
37
  end
45
38
 
46
- def has_many_relation(klass, ref)
47
- klass.where(klass.primary_key => build_for(ref.klass).merge(scoped(ref.klass, ref.scope)).select(ref.foreign_key))
39
+ def belongs_to_relation(node)
40
+ child = node.children.first
41
+ return node.klass.none unless child
42
+
43
+ relation = child_relation(child, select: node.reflection.active_record_primary_key)
44
+ node.klass.where(node.reflection.foreign_key => relation)
48
45
  end
49
46
 
50
- def belongs_to_relation(klass, ref)
51
- klass.where(ref.foreign_key => build_for(ref.klass).merge(scoped(ref.klass, ref.scope)).select(klass.primary_key))
47
+ def polymorphic_belongs_to_relation(node)
48
+ rels = node.children.map do |child|
49
+ relation = child_relation(child, select: child.klass.primary_key)
50
+ node.klass.where(
51
+ node.reflection.foreign_type => child.source_type,
52
+ node.reflection.foreign_key => relation
53
+ )
54
+ end
55
+
56
+ union(node.klass, rels)
52
57
  end
53
58
 
54
- def belongs_to_polymorphic_relation(klass, ref)
55
- types = klass.unscoped.group(ref.foreign_type).pluck(ref.foreign_type)
56
- if types.present?
57
- rels = types.map { |type|
58
- if (parent = type.safe_constantize)
59
- klass.where(ref.foreign_type => type, ref.foreign_key => build_for(parent).merge(scoped(parent, ref.scope)))
60
- else
61
- klass.none
62
- end
63
- }
64
- union(klass, rels)
59
+ def child_relation(child, select:)
60
+ if simple_leaf_relation?(child)
61
+ @args
65
62
  else
66
- klass.none
63
+ relation = build(child).select(select)
64
+ relation = relation.merge(child.scope) if child.has_scope?
65
+ relation
67
66
  end
68
67
  end
69
68
 
70
- def root_relation(klass)
71
- if @args.is_a?(Integer) || @args.is_a?(Array) || @args.is_a?(ActiveRecord::Base)
72
- klass.where(klass.primary_key => @args)
73
- elsif @args.is_a?(ActiveRecord::Relation)
74
- klass.all.merge(@args)
75
- elsif @args
76
- klass.where(@args)
69
+ def simple_leaf_relation?(child)
70
+ @args_type == :simple && child.leaf? && child.parent.belongs_to? && !child.has_scope?
71
+ end
72
+
73
+ def leaf_relation(node)
74
+ case @args_type
75
+ when :relation
76
+ node.klass.all.merge(@args)
77
+ when :hash
78
+ node.klass.where(@args)
79
+ when :simple
80
+ node.klass.where(node.klass.primary_key => @args)
77
81
  else
78
- klass.all
82
+ raise ArgumentError.new("unexpected argument type: #{@args_type}")
79
83
  end
80
84
  end
81
85
 
82
- def scoped(klass, scope)
83
- rel = klass.all
84
- rel = rel.instance_eval(&scope) if scope
85
- rel
86
+ def args_type(args)
87
+ if args.is_a?(ActiveRecord::Relation)
88
+ :relation
89
+ elsif args.is_a?(Hash)
90
+ :hash
91
+ else
92
+ :simple
93
+ end
86
94
  end
87
95
 
88
96
  def union(klass, rels)
89
- if defined? ActiveRecordUnion
90
- rels.reduce(:union)
91
- else
92
- union = rels.map { |rel| "#{rel.to_sql}" }.reject(&:empty?).join(' UNION ')
93
- klass.from(Arel.sql("(#{union}) AS #{klass.table_name}"))
94
- end
97
+ return klass.none if rels.blank?
98
+ union = rels.map { |rel| "#{rel.to_sql}" }.reject(&:empty?).join(' UNION ')
99
+ klass.from(Arel.sql("(#{union}) AS #{klass.table_name}"))
95
100
  end
96
101
  end
97
102
  end
@@ -1,4 +1,5 @@
1
1
  require 'active_support'
2
+ require_relative 'node'
2
3
  require_relative 'builder'
3
4
 
4
5
  module ActiveRecordNestedScope
@@ -6,13 +7,13 @@ module ActiveRecordNestedScope
6
7
  extend ActiveSupport::Concern
7
8
 
8
9
  included do
9
- class_attribute :_nested_scope_options
10
+ class_attribute :nested_scope_options
10
11
  end
11
12
 
12
13
  class_methods do
13
14
  def nested_scope(name, options = {})
14
- self._nested_scope_options ||= {}
15
- self._nested_scope_options[name] = options
15
+ self.nested_scope_options ||= {}
16
+ self.nested_scope_options[name] = options
16
17
 
17
18
  scope name, ->(args) {
18
19
  ActiveRecordNestedScope::Builder.new(self, name, args).build
@@ -0,0 +1,111 @@
1
+ module ActiveRecordNestedScope
2
+ class Node
3
+ attr_accessor :klass, :name, :parent, :source_type
4
+
5
+ def initialize(klass, name, parent = nil, source_type = nil)
6
+ @klass = klass
7
+ @name = name
8
+ @parent = parent
9
+ @source_type = source_type
10
+ end
11
+
12
+ def has_options?
13
+ options = @klass.nested_scope_options
14
+ options && options[@name]
15
+ end
16
+
17
+ def options(key)
18
+ options = @klass.nested_scope_options.to_h
19
+ options.dig(@name, key)
20
+ end
21
+
22
+ def reflection
23
+ @klass.reflect_on_association(options(:through))
24
+ end
25
+
26
+ def leaf?
27
+ options(:through).blank?
28
+ end
29
+
30
+ def has_many?
31
+ reflection.class.name.in?(['ActiveRecord::Reflection::HasManyReflection', 'ActiveRecord::Reflection::HasOneReflection'])
32
+ end
33
+
34
+ def belongs_to?
35
+ reflection.class.name == 'ActiveRecord::Reflection::BelongsToReflection' && !reflection.polymorphic?
36
+ end
37
+
38
+ def polymorphic_belongs_to?
39
+ reflection.class.name == 'ActiveRecord::Reflection::BelongsToReflection' && reflection.polymorphic?
40
+ end
41
+
42
+ def children
43
+ @children ||= search_children.select(&:valid?)
44
+ end
45
+
46
+ def search_children
47
+ if leaf?
48
+ []
49
+ elsif polymorphic_belongs_to?
50
+ types = PolymorphicType.new(self, reflection).resolve
51
+ types.map { |klass, source_type| Node.new(klass, @name, self, source_type) }
52
+ else
53
+ [Node.new(reflection.klass, @name, self)]
54
+ end
55
+ end
56
+
57
+ def valid?
58
+ return false unless has_options?
59
+
60
+ if options(:through) && !reflection
61
+ STDERR.puts "can't find reflection for #{options(:through)} in #{klass}"
62
+ return false
63
+ end
64
+
65
+ return true
66
+ end
67
+
68
+ def has_scope?
69
+ @parent && @parent.reflection.scope.present?
70
+ end
71
+
72
+ def scope
73
+ @klass.all.instance_eval(&@parent.reflection.scope) if has_scope?
74
+ end
75
+
76
+ class PolymorphicType
77
+ def initialize(node, reflection)
78
+ @node = node
79
+ @reflection = reflection
80
+ end
81
+
82
+ def resolve
83
+ types = @node.klass.unscoped.group(@reflection.foreign_type).pluck(@reflection.foreign_type).compact
84
+ types.map do |type|
85
+ klass = resolve_source_type(type)&.safe_constantize
86
+ [klass, type] if klass
87
+ end.compact
88
+ end
89
+
90
+ private
91
+
92
+ def resolve_source_type(type)
93
+ if (map = @node.options(:source_map))
94
+ resolve_from_map(type, map)
95
+ else
96
+ type
97
+ end
98
+ end
99
+
100
+ def resolve_from_map(type, map)
101
+ if map.is_a?(Proc)
102
+ map.call(type)
103
+ elsif map.is_a?(Hash)
104
+ map[type]
105
+ else
106
+ raise ArgumentError.new("unsupported argument type for source_map option: #{map.class}.")
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -1,3 +1,3 @@
1
1
  module ActiveRecordNestedScope
2
- VERSION = '1.0.2'
2
+ VERSION = '1.2.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord_nested_scope
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yoshikazu Kaneta
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-03-16 00:00:00.000000000 Z
11
+ date: 2020-11-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -52,20 +52,6 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '5.0'
55
- - !ruby/object:Gem::Dependency
56
- name: active_record_union
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
55
  - !ruby/object:Gem::Dependency
70
56
  name: mysql2
71
57
  requirement: !ruby/object:Gem::Requirement
@@ -136,34 +122,6 @@ dependencies:
136
122
  - - ">="
137
123
  - !ruby/object:Gem::Version
138
124
  version: '0'
139
- - !ruby/object:Gem::Dependency
140
- name: pry-rails
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - ">="
144
- - !ruby/object:Gem::Version
145
- version: '0'
146
- type: :development
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - ">="
151
- - !ruby/object:Gem::Version
152
- version: '0'
153
- - !ruby/object:Gem::Dependency
154
- name: pry-byebug
155
- requirement: !ruby/object:Gem::Requirement
156
- requirements:
157
- - - ">="
158
- - !ruby/object:Gem::Version
159
- version: '0'
160
- type: :development
161
- prerelease: false
162
- version_requirements: !ruby/object:Gem::Requirement
163
- requirements:
164
- - - ">="
165
- - !ruby/object:Gem::Version
166
- version: '0'
167
125
  description: An ActiveRecord extension to build nested scopes through pre-defined
168
126
  associations
169
127
  email:
@@ -175,6 +133,7 @@ files:
175
133
  - ".gitignore"
176
134
  - ".rspec"
177
135
  - ".travis.yml"
136
+ - CHANGELOG.md
178
137
  - Gemfile
179
138
  - LICENSE
180
139
  - README.md
@@ -183,9 +142,11 @@ files:
183
142
  - gemfiles/rails50.gemfile
184
143
  - gemfiles/rails51.gemfile
185
144
  - gemfiles/rails52.gemfile
145
+ - gemfiles/rails60.gemfile
186
146
  - lib/activerecord_nested_scope.rb
187
147
  - lib/activerecord_nested_scope/builder.rb
188
148
  - lib/activerecord_nested_scope/extension.rb
149
+ - lib/activerecord_nested_scope/node.rb
189
150
  - lib/activerecord_nested_scope/railtie.rb
190
151
  - lib/activerecord_nested_scope/version.rb
191
152
  homepage: https://github.com/kanety/activerecord_nested_scope
@@ -206,8 +167,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
206
167
  - !ruby/object:Gem::Version
207
168
  version: '0'
208
169
  requirements: []
209
- rubyforge_project:
210
- rubygems_version: 2.5.2.2
170
+ rubygems_version: 3.0.3
211
171
  signing_key:
212
172
  specification_version: 4
213
173
  summary: An ActiveRecord extension to build nested scopes