activerecord_nested_scope 1.0.4 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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