activerecord_nested_scope 1.0.4 → 1.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
- SHA1:
3
- metadata.gz: 8e410ad54617ffeb9efb8f56397f72ace4642944
4
- data.tar.gz: 8c67e79ea1bd658e73637174c011bf6ab037539e
2
+ SHA256:
3
+ metadata.gz: b1a5162acf18c8e70f87b21f4dc4df15d5a1c5a32de4c63aaf949b3a8b60c5c3
4
+ data.tar.gz: 34e4a88f240b89d517986ad960f11f27874afc8e186b79b7e4f0b7878e7e7191
5
5
  SHA512:
6
- metadata.gz: d280f90f2b40e2668406147cb2913654784e89685923a4b36cc1764530826c8c63ff3e698dbebea4ca53ef075ab7bf98e99a4097f22d4df819d2abdace93cc18
7
- data.tar.gz: 979cb3d3d2c9fdaef9cd6f5feb094eab90c94df90a1fb13703445056bec510134a9d50ccd448d4a2e7d8d693b0a532b817ebe93b3f6b6c7b194aabea27b0b91b
6
+ metadata.gz: a2c93ab0ebd9e53f7b8aee4d1780974b989b5d232550f5feb30dad0a71c170c467e63563ab7774116953bc4aa809f5ffd7f01e0ed0f5b68a11f0a9394d073298
7
+ data.tar.gz: 9d6222989f2dc3d5c14d622f188319de79d7d61f8a60e1d6a686d689e62dd8d958aaddb2f15c3e26684e8cfc7fc23ed066801ab4debee02922394d7b25ecb6f2
data/.gitignore CHANGED
@@ -4,6 +4,7 @@
4
4
  Gemfile.lock
5
5
  _yardoc/
6
6
  coverage/
7
+ gemfiles/*.lock
7
8
  doc/
8
9
  pkg/
9
10
  spec/reports/
@@ -12,4 +13,3 @@ 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
+ - bundle exec rspec
@@ -0,0 +1,27 @@
1
+ # CHANGELOG
2
+
3
+ ## 1.1.0
4
+
5
+ * Add short query feature for simple belongs_to association.
6
+ * Rename `_nested_scope_options` to `nested_scope_options`.
7
+ * Remove support for active_record_union.
8
+
9
+ ## 1.0.4
10
+
11
+ * Fix query for string value.
12
+
13
+ ## 1.0.3
14
+
15
+ * Remove nil from polymorphic types.
16
+
17
+ ## 1.0.2
18
+
19
+ * Fix query error when there are no records in polymorphic tables.
20
+
21
+ ## 1.0.1
22
+
23
+ * Fix query for array value.
24
+
25
+ ## 1.0.0
26
+
27
+ * 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.klass.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.klass.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.klass.to_s,
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).compact
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?(ActiveRecord::Relation)
72
- klass.all.merge(@args)
73
- elsif @args.is_a?(Hash)
74
- klass.where(@args)
75
- elsif @args
76
- klass.where(klass.primary_key => @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,81 @@
1
+ module ActiveRecordNestedScope
2
+ class Node
3
+ attr_accessor :klass, :name, :parent
4
+
5
+ def initialize(klass, name, parent = nil)
6
+ @klass = klass
7
+ @name = name
8
+ @parent = parent
9
+ end
10
+
11
+ def has_options?
12
+ options = @klass.nested_scope_options
13
+ options && options[@name]
14
+ end
15
+
16
+ def options(key)
17
+ options = @klass.nested_scope_options.to_h
18
+ options.dig(@name, key)
19
+ end
20
+
21
+ def reflection
22
+ @klass.reflect_on_association(options(:through))
23
+ end
24
+
25
+ def leaf?
26
+ options(:through).blank?
27
+ end
28
+
29
+ def has_many?
30
+ reflection.class.name.in?(['ActiveRecord::Reflection::HasManyReflection', 'ActiveRecord::Reflection::HasOneReflection'])
31
+ end
32
+
33
+ def belongs_to?
34
+ reflection.class.name == 'ActiveRecord::Reflection::BelongsToReflection' && !reflection.polymorphic?
35
+ end
36
+
37
+ def polymorphic_belongs_to?
38
+ reflection.class.name == 'ActiveRecord::Reflection::BelongsToReflection' && reflection.polymorphic?
39
+ end
40
+
41
+ def children
42
+ @children ||= search_children.select(&:valid?)
43
+ end
44
+
45
+ def search_children
46
+ if leaf?
47
+ []
48
+ elsif polymorphic_belongs_to?
49
+ polymorphic_klasses.map { |klass| Node.new(klass, @name, self) }
50
+ else
51
+ [Node.new(reflection.klass, @name, self)]
52
+ end
53
+ end
54
+
55
+ def valid?
56
+ return false unless has_options?
57
+
58
+ if options(:through) && !reflection
59
+ STDERR.puts "can't find reflection for #{options(:through)} in #{klass}"
60
+ return false
61
+ end
62
+
63
+ return true
64
+ end
65
+
66
+ def has_scope?
67
+ @parent && @parent.reflection.scope.present?
68
+ end
69
+
70
+ def scope
71
+ @klass.all.instance_eval(&@parent.reflection.scope) if has_scope?
72
+ end
73
+
74
+ private
75
+
76
+ def polymorphic_klasses
77
+ types = @klass.unscoped.group(reflection.foreign_type).pluck(reflection.foreign_type).compact
78
+ types.map { |type| type.safe_constantize }.compact
79
+ end
80
+ end
81
+ end
@@ -1,3 +1,3 @@
1
1
  module ActiveRecordNestedScope
2
- VERSION = '1.0.4'
2
+ VERSION = '1.1.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.4
4
+ version: 1.1.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-22 00:00:00.000000000 Z
11
+ date: 2020-09-22 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.1.2
211
171
  signing_key:
212
172
  specification_version: 4
213
173
  summary: An ActiveRecord extension to build nested scopes