activerecord_nested_scope 1.0.1 → 1.1.1

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: b76e612525e8f4409bdf8f89a0b8451df5de9651
4
- data.tar.gz: 4a17a65653f1809dd4104a2cbb5710dd31503d67
2
+ SHA256:
3
+ metadata.gz: 873946315cf0ac4297ef8a4b16db871e009886c757f380006a4b980d67fbf59b
4
+ data.tar.gz: a05ed25fd35de7eb5b3418899f781105da5e8624b200da1689588f50cb00007b
5
5
  SHA512:
6
- metadata.gz: '08365cd0dbccd23a4a09b994e0cc2b197de117fc2077b660cf2287738f28e3f8d97eb1b9ab0d28faa6bcbd5b2015a26687b7517da95bcefba86ebc225b7f42c2'
7
- data.tar.gz: 35f13fa1be4f5b04722fa1d99f736fa3b5cb8b1477f4e2c54e1832543deb034e6e4f37ff9ebc4efdc2771ebe04e8a4ef69ac8e603e87ff01e6cd6707e6455511
6
+ metadata.gz: fe922e968ee53fce622f4fe149582167b6b16336c09d49befb57476ab5d79ee5a8acf2811390ee06d87dff2b676180a220a7579ffb672e47e1d509a0b44bf65a
7
+ data.tar.gz: c3954c2d58bdd3ef006556c0fb84ce613b3249784cad10a829e3eb1ce46cd5cad594ff7ef85070ba33bf025cae5c3457151680bf03ff9ced9fe0e1fb38765c91
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
+ - bundle exec rspec
@@ -0,0 +1,31 @@
1
+ # CHANGELOG
2
+
3
+ ## 1.1.1
4
+
5
+ * Fix query for associations with primary key option.
6
+
7
+ ## 1.1.0
8
+
9
+ * Add short query feature for simple belongs_to association.
10
+ * Rename `_nested_scope_options` to `nested_scope_options`.
11
+ * Remove support for active_record_union.
12
+
13
+ ## 1.0.4
14
+
15
+ * Fix query for string value.
16
+
17
+ ## 1.0.3
18
+
19
+ * Remove nil from polymorphic types.
20
+
21
+ ## 1.0.2
22
+
23
+ * Fix query error when there are no records in polymorphic tables.
24
+
25
+ ## 1.0.1
26
+
27
+ * Fix query for array value.
28
+
29
+ ## 1.0.0
30
+
31
+ * 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,93 +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
44
- end
31
+ def has_many_relation(node)
32
+ child = node.children.first
33
+ return node.klass.none unless child
45
34
 
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))
35
+ relation = child_relation(child, select: node.reflection.foreign_key)
36
+ node.klass.where(node.reflection.active_record_primary_key => relation)
48
37
  end
49
38
 
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))
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)
52
45
  end
53
46
 
54
- def belongs_to_polymorphic_relation(klass, ref)
55
- types = klass.unscoped.group(ref.foreign_type).pluck(ref.foreign_type)
56
- rels = types.map { |type|
57
- if (parent = type.safe_constantize)
58
- klass.where(ref.foreign_type => type, ref.foreign_key => build_for(parent).merge(scoped(parent, ref.scope)))
59
- else
60
- klass.none
61
- end
62
- }
63
- union(klass, rels)
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)
64
57
  end
65
58
 
66
- def root_relation(klass)
67
- if @args.is_a?(Integer) || @args.is_a?(Array) || @args.is_a?(ActiveRecord::Base)
68
- klass.where(klass.primary_key => @args)
69
- elsif @args.is_a?(ActiveRecord::Relation)
70
- klass.all.merge(@args)
71
- elsif @args
72
- klass.where(@args)
59
+ def child_relation(child, select:)
60
+ if simple_leaf_relation?(child)
61
+ @args
73
62
  else
74
- klass.all
63
+ relation = build(child).select(select)
64
+ relation = relation.merge(child.scope) if child.has_scope?
65
+ relation
75
66
  end
76
67
  end
77
68
 
78
- def scoped(klass, scope)
79
- rel = klass.all
80
- rel = rel.instance_eval(&scope) if scope
81
- rel
69
+ def simple_leaf_relation?(child)
70
+ @args_type == :simple && child.leaf? && child.parent.belongs_to? && !child.has_scope?
82
71
  end
83
72
 
84
- def union(klass, rels)
85
- if defined? ActiveRecordUnion
86
- rels.reduce(:union)
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)
81
+ else
82
+ raise ArgumentError.new("unexpected argument type: #{@args_type}")
83
+ end
84
+ end
85
+
86
+ def args_type(args)
87
+ if args.is_a?(ActiveRecord::Relation)
88
+ :relation
89
+ elsif args.is_a?(Hash)
90
+ :hash
87
91
  else
88
- union = rels.map { |rel| "#{rel.to_sql}" }.reject(&:empty?).join(' UNION ')
89
- klass.from(Arel.sql("(#{union}) AS #{klass.table_name}"))
92
+ :simple
90
93
  end
91
94
  end
95
+
96
+ def union(klass, rels)
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}"))
100
+ end
92
101
  end
93
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.1'
2
+ VERSION = '1.1.1'
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.1
4
+ version: 1.1.1
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-01 00:00:00.000000000 Z
11
+ date: 2020-09-28 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