ii_finder 1.0.0 → 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
2
  SHA256:
3
- metadata.gz: 1e3ba37c83b4bc3c8015d95bf52544ea29e90928948c0f5926946e8cb0c49bb3
4
- data.tar.gz: c785e88d9b5c78e1a1409cedd805a2944d526de46d6b4acb22272c8c97d732a8
3
+ metadata.gz: 2fbbf6a8d97c83f3d098f3b13d93039491698409623d0137e0b170c83dedd58d
4
+ data.tar.gz: 9619799961e2416db17aca506f6ef596acd621b3fc1339aa82679b779d3f79e8
5
5
  SHA512:
6
- metadata.gz: 67e33beb2d96672958062a58301da0ff1cc9bdb8855572c2e41fa41c685e08a7901d52e6c99df97fec2d986b8043cd59726bd1fbaf3d0c5790854d6720fc8c6d
7
- data.tar.gz: 6b70c8f8620928ca1997e79f0178aaba61cd04abfedc5d2ff8e24b7296e9c4e0b7cfaa1be9c4bd05a42ee647e7a5d3c0a532a55e09e285fb362876a914c93470
6
+ metadata.gz: 577f3b00606d238e20a9d24075a92b9b5cdc10f170637e4e6bf6dc58db3bd04bf5de13a75da050af0b7a421d1dfe6bc91b4020188f38051394c39b81133b2fa0
7
+ data.tar.gz: abd2108a7892aa866ffc8d6a86545b61df42ce7b0c10af079e4272f5194e74be5f3c7146ed3ed671a96ea5eb6240edeabe77da8e51ab675b2a05427212bb2adf
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.2.0
4
+
5
+ * Add chain feature.
6
+
7
+ ## 1.1.2
8
+
9
+ * Try fetch prior to calling instance method.
10
+
11
+ ## 1.1.1
12
+
13
+ * Check arel_table existence for model.
14
+ * Make mergeability check more flexible.
15
+
16
+ ## 1.1.0
17
+
18
+ * Add log subscriber.
19
+
3
20
  ## 1.0.0
4
21
 
5
22
  * First release.
data/README.md CHANGED
@@ -24,14 +24,14 @@ Then execute:
24
24
  Prepare model:
25
25
 
26
26
  ```ruby
27
- class User < ActiveRecord::Base
27
+ class Item < ActiveRecord::Base
28
28
  end
29
29
  ```
30
30
 
31
31
  Prepare finder:
32
32
 
33
33
  ```ruby
34
- class UsersFinder < IIFinder::Base
34
+ class ItemsFinder < IIFinder::Base
35
35
  parameters :name
36
36
 
37
37
  def name(value)
@@ -43,15 +43,15 @@ end
43
43
  Use finder as follows:
44
44
 
45
45
  ```ruby
46
- UsersFinder.call(name: 'NAME').to_sql
47
- #=> SELECT "users".* FROM "users" WHERE "users"."name" = 'NAME'
46
+ ItemsFinder.call(name: 'NAME').to_sql
47
+ #=> SELECT "items".* FROM "items" WHERE "items"."name" = 'NAME'
48
48
  ```
49
49
 
50
50
  You can also specify relation as first argument:
51
51
 
52
52
  ```ruby
53
- UsersFinder.call(User.where(id: [1, 2, 3]), name: 'NAME').to_sql
54
- #=> SELECT "users".* FROM "users" WHERE "users"."id" IN (1, 2, 3) AND "users"."name" = 'NAME'
53
+ ItemsFinder.call(Item.where(id: [1, 2, 3]), name: 'NAME').to_sql
54
+ #=> SELECT "items".* FROM "items" WHERE "items"."id" IN (1, 2, 3) AND "items"."name" = 'NAME'
55
55
  ```
56
56
 
57
57
  ### Finder
@@ -61,7 +61,7 @@ Finder method will not be called when the value of parameter is blank.
61
61
  If you want to receive such value, set `allow_blank` as follows:
62
62
 
63
63
  ```ruby
64
- class UsersFinder < IIFinder::Base
64
+ class ItemsFinder < IIFinder::Base
65
65
  parameters :name, allow_blank: true
66
66
 
67
67
  def name(value)
@@ -69,14 +69,14 @@ class UsersFinder < IIFinder::Base
69
69
  end
70
70
  end
71
71
 
72
- UsersFinder.call(name: '').to_sql
73
- #=> SELECT "users".* FROM "users" WHERE "users"."name" = ''
72
+ ItemsFinder.call(name: '').to_sql
73
+ #=> SELECT "items".* FROM "items" WHERE "items"."name" = ''
74
74
  ```
75
75
 
76
76
  Finder has following attributes:
77
77
 
78
78
  ```ruby
79
- class UsersFinder < IIFinder::Base
79
+ class ItemsFinder < IIFinder::Base
80
80
  parameters :name
81
81
 
82
82
  def name(value)
@@ -87,10 +87,10 @@ class UsersFinder < IIFinder::Base
87
87
  end
88
88
  end
89
89
 
90
- UsersFinder.call(name: 'NAME')
91
- #=> relation: #<User::ActiveRecord_Relation:
90
+ ItemsFinder.call(name: 'NAME')
91
+ #=> relation: #<Item::ActiveRecord_Relation:
92
92
  # criteria: {:name=>'NAME'}
93
- # model: User
93
+ # model: Item
94
94
  # table: #<Arel::Table ...>
95
95
  ```
96
96
 
@@ -102,7 +102,7 @@ IIFinder.configure do |config|
102
102
  config.merge_relation = false
103
103
  end
104
104
 
105
- class UsersFinder < IIFinder::Base
105
+ class ItemsFinder < IIFinder::Base
106
106
  parameters :name
107
107
 
108
108
  def name(value)
@@ -110,8 +110,8 @@ class UsersFinder < IIFinder::Base
110
110
  end
111
111
  end
112
112
 
113
- UsersFinder.call(name: 'NAME').to_sql
114
- #=> SELECT "users".* FROM "users" WHERE "users"."name" = 'NAME'
113
+ ItemsFinder.call(name: 'NAME').to_sql
114
+ #=> SELECT "items".* FROM "items" WHERE "items"."name" = 'NAME'
115
115
  ```
116
116
 
117
117
  #### Callbacks
@@ -125,7 +125,7 @@ Following callbacks are available.
125
125
  For example:
126
126
 
127
127
  ```ruby
128
- class UsersFinder < IIFinder::Base
128
+ class ItemsFinder < IIFinder::Base
129
129
  after_call :default_order
130
130
 
131
131
  def default_order
@@ -133,14 +133,59 @@ class UsersFinder < IIFinder::Base
133
133
  end
134
134
  end
135
135
 
136
- UsersFinder.call.to_sql
137
- #=> SELECT "users".* FROM "users" ORDER BY "users"."id" DESC
136
+ ItemsFinder.call.to_sql
137
+ #=> SELECT "items".* FROM "items" ORDER BY "items"."id" DESC
138
138
  ```
139
139
 
140
140
  Note that finder does not handle the return value of callback.
141
141
  When you want to update `@relation` in the callback,
142
142
  reassign `@relation` or use methods like `where!` or `order!`.
143
143
 
144
+ #### Chain
145
+
146
+ You can chain multiple finders by using `chain`. For example:
147
+
148
+ ```ruby
149
+ class NameFinder < IIFinder::Base
150
+ parameters :name
151
+
152
+ def name(value)
153
+ @relation.where(name: value)
154
+ end
155
+ end
156
+
157
+ class AgeFinder < IIFinder::Base
158
+ parameters :age
159
+
160
+ def age(value)
161
+ @relation.where(age: value)
162
+ end
163
+ end
164
+
165
+ class ItemsFinder < IIFinder::Base
166
+ chain NameFinder, AgeFinder
167
+ end
168
+
169
+ ItemsFinder.call(Item.all, name: 'name', age: 10).to_sql
170
+ #=> SELECT "items".* FROM "items" WHERE "items"."name" = 'name' AND "items"."age" = 10
171
+ ```
172
+
173
+ You can also use method or block to find finder class dynamically:
174
+
175
+ ```ruby
176
+ class ItemFinder < IIFinder::Base
177
+ chain -> { NameFinder }
178
+ end
179
+
180
+ class ItemFinder < IIFinder::Base
181
+ chain :chain_finder
182
+
183
+ def chain_finder
184
+ NameFinder
185
+ end
186
+ end
187
+ ```
188
+
144
189
  ### Lookup for model
145
190
 
146
191
  Finder lookups related model by its class name when the first argument of `call` is not relation.
@@ -148,30 +193,30 @@ So the name of finder class should be composed of the name of model class.
148
193
  For example:
149
194
 
150
195
  ```ruby
151
- class User < ActiveRecord::Base
196
+ class Item < ActiveRecord::Base
152
197
  end
153
198
 
154
- class UsersFinder < IIFinder::Base
199
+ class ItemsFinder < IIFinder::Base
155
200
  end
156
201
 
157
- IIFinder::Base.lookup(UsersFinder)
158
- #=> User
202
+ IIFinder::Base.lookup(ItemsFinder)
203
+ #=> Item
159
204
  ```
160
205
 
161
206
  Note that superclass of finder is also looked up until related model is found.
162
207
 
163
208
  ```ruby
164
- class User < ActiveRecord::Base
209
+ class Item < ActiveRecord::Base
165
210
  end
166
211
 
167
- class UsersFinder < IIFinder::Base
212
+ class ItemsFinder < IIFinder::Base
168
213
  end
169
214
 
170
- class InheritedUsersFinder < UsersFinder
215
+ class InheritedItemsFinder < ItemsFinder
171
216
  end
172
217
 
173
- IIFinder::Base.lookup(InheritedUsersFinder)
174
- #=> User
218
+ IIFinder::Base.lookup(InheritedItemsFinder)
219
+ #=> Item
175
220
  ```
176
221
 
177
222
  ### Scope for model
@@ -179,12 +224,27 @@ IIFinder::Base.lookup(InheritedUsersFinder)
179
224
  In case you want to call finder from model, include `IIFinder::Scope` into model as follows:
180
225
 
181
226
  ```ruby
182
- class User < ActiveRecord::Base
227
+ class Item < ActiveRecord::Base
183
228
  include IIFinder::Scope
184
229
  end
185
230
 
186
- User.finder_scope(name: 'NAME').to_sql
187
- #=> SELECT "users".* FROM "users" WHERE "users"."name" = 'NAME'
231
+ Item.finder_scope(name: 'NAME').to_sql
232
+ #=> SELECT "items".* FROM "items" WHERE "items"."name" = 'NAME'
233
+ ```
234
+
235
+ ### Logging
236
+
237
+ Finder supports instrumentation hook supplied by `ActiveSupport::Notifications`.
238
+ You can enable log subscriber as follows:
239
+
240
+ ```ruby
241
+ IIFinder::LogSubscriber.attach_to :ii_finder
242
+ ```
243
+
244
+ This subscriber will write logs in debug mode as the following example:
245
+
246
+ ```
247
+ Called ItemsFinder with {:id=>1} (Duration: 9.9ms, Allocations: 915)
188
248
  ```
189
249
 
190
250
  ## Contributing
@@ -1,64 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'lookup'
3
+ require_relative 'core'
4
4
  require_relative 'parameters'
5
5
  require_relative 'callbacks'
6
+ require_relative 'instrumentation'
7
+ require_relative 'lookup'
8
+ require_relative 'chain'
6
9
 
7
10
  module IIFinder
8
11
  class Base
12
+ include Core
9
13
  include Parameters
10
14
  include Callbacks
15
+ include Instrumentation
11
16
  include Lookup
12
-
13
- attr_reader :relation, :criteria, :model, :table
14
-
15
- def initialize(*args)
16
- if args.size == 0 || args.size == 1
17
- @model = self.class.lookup
18
- raise IIFinder::Error.new("could not find model for #{self.class}") unless @model
19
- @relation = @model.all
20
- @criteria = args[0] || {}
21
- else
22
- @relation = args[0]
23
- @criteria = args[1]
24
- @model = @relation.klass
25
- end
26
- @table = @model.arel_table
27
- end
28
-
29
- def call
30
- run_callbacks :call do
31
- self.class._parameters.each do |param|
32
- value = fetch_criteria(param.name)
33
- if value.present? || param.allow_blank?
34
- call_method(param.name, value)
35
- end
36
- end
37
- end
38
-
39
- @relation
40
- end
41
-
42
- def fetch_criteria(name)
43
- if @criteria.respond_to?(name)
44
- @criteria.send(name)
45
- elsif @criteria.respond_to?(:fetch)
46
- @criteria.fetch(name, nil)
47
- end
48
- end
49
-
50
- def call_method(name, value)
51
- result = send(name, value)
52
-
53
- if Config.merge_relation && result.is_a?(ActiveRecord::Relation)
54
- @relation = @relation.merge(result)
55
- end
56
- end
57
-
58
- class << self
59
- def call(*args)
60
- new(*args).call
61
- end
62
- end
17
+ include Chain
63
18
  end
64
19
  end
@@ -9,6 +9,12 @@ module IIFinder
9
9
  define_callbacks :call
10
10
  end
11
11
 
12
+ def call
13
+ run_callbacks :call do
14
+ super
15
+ end
16
+ end
17
+
12
18
  class_methods do
13
19
  def before_call(*args, &block)
14
20
  set_callback(:call, :before, *args, &block)
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IIFinder
4
+ module Chain
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute :_chains
9
+ self._chains = []
10
+ end
11
+
12
+ def call
13
+ lookup.each do |finder|
14
+ merge_relation!(finder.call(*@_args))
15
+ end
16
+ super
17
+ end
18
+
19
+ def lookup
20
+ self.class._chains.map do |finder|
21
+ if finder.is_a?(Symbol) && respond_to?(finder, true)
22
+ send(finder)
23
+ elsif finder.is_a?(Proc)
24
+ instance_exec(&finder)
25
+ else
26
+ finder
27
+ end
28
+ end.flatten.compact
29
+ end
30
+
31
+ class_methods do
32
+ def chain(*finders, &block)
33
+ self._chains = _chains + finders
34
+ self._chains << block if block
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IIFinder
4
+ module Core
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ attr_reader :relation, :criteria, :model, :table
9
+ end
10
+
11
+ def initialize(*args)
12
+ @_args = args;
13
+ if args.size == 0 || args.size == 1
14
+ @model = self.class.lookup
15
+ raise IIFinder::Error.new("could not find model for #{self.class}") unless @model
16
+ @relation = @model.all
17
+ @criteria = args[0] || {}
18
+ else
19
+ @relation = args[0]
20
+ @criteria = args[1]
21
+ @model = @relation.klass
22
+ end
23
+ @table = @model.arel_table if @model.respond_to?(:arel_table)
24
+ end
25
+
26
+ def call
27
+ self.class._parameters.each do |param|
28
+ value = fetch_criteria(param.name)
29
+ if value.present? || param.allow_blank?
30
+ merge_relation!(send(param.name, value))
31
+ end
32
+ end
33
+
34
+ @relation
35
+ end
36
+
37
+ def fetch_criteria(name)
38
+ if @criteria.respond_to?(:fetch)
39
+ @criteria.fetch(name, nil)
40
+ elsif @criteria.respond_to?(name)
41
+ @criteria.send(name)
42
+ end
43
+ end
44
+
45
+ def merge_relation!(relation)
46
+ if relation.respond_to?(:merge) && Config.merge_relation
47
+ @relation = @relation.merge(relation)
48
+ end
49
+ end
50
+
51
+ class_methods do
52
+ def call(*args)
53
+ new(*args).call
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IIFinder
4
+ module Instrumentation
5
+ extend ActiveSupport::Concern
6
+
7
+ def call
8
+ ActiveSupport::Notifications.instrument 'call.ii_finder', finder: self do
9
+ super
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IIFinder
4
+ class LogSubscriber < ActiveSupport::LogSubscriber
5
+ def call(event)
6
+ debug do
7
+ finder = event.payload[:finder]
8
+ "Called #{finder.class} with #{finder.criteria} (#{additional_log(event)})"
9
+ end
10
+ end
11
+
12
+ def additional_log(event)
13
+ additions = ["Duration: %.1fms" % event.duration]
14
+ additions << "Allocations: %d" % event.allocations if event.respond_to?(:allocations)
15
+ additions.join(', ')
16
+ end
17
+ end
18
+ end
@@ -19,13 +19,13 @@ module IIFinder
19
19
  end
20
20
 
21
21
  class << self
22
- class_attribute :_cache
23
- self._cache = {}
22
+ class_attribute :cache
23
+ self.cache = {}
24
24
 
25
25
  def call(klass)
26
26
  return if terminate?(klass)
27
27
 
28
- cache(klass) do
28
+ with_cache(klass) do
29
29
  if klass.name && (resolved = resolve(klass))
30
30
  resolved
31
31
  elsif klass.superclass
@@ -36,9 +36,9 @@ module IIFinder
36
36
 
37
37
  private
38
38
 
39
- def cache(klass)
39
+ def with_cache(klass)
40
40
  if Config.lookup_cache
41
- self._cache[klass] ||= yield
41
+ self.cache[klass] ||= yield
42
42
  else
43
43
  yield
44
44
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IIFinder
4
- VERSION = '1.0.0'
4
+ VERSION = '1.2.0'
5
5
  end
data/lib/ii_finder.rb CHANGED
@@ -5,6 +5,7 @@ require 'ii_finder/config'
5
5
  require 'ii_finder/errors'
6
6
  require 'ii_finder/base'
7
7
  require 'ii_finder/scope'
8
+ require 'ii_finder/log_subscriber'
8
9
 
9
10
  module IIFinder
10
11
  class << self
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ii_finder
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
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: 2021-07-05 00:00:00.000000000 Z
11
+ date: 2021-11-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -120,8 +120,12 @@ files:
120
120
  - lib/ii_finder.rb
121
121
  - lib/ii_finder/base.rb
122
122
  - lib/ii_finder/callbacks.rb
123
+ - lib/ii_finder/chain.rb
123
124
  - lib/ii_finder/config.rb
125
+ - lib/ii_finder/core.rb
124
126
  - lib/ii_finder/errors.rb
127
+ - lib/ii_finder/instrumentation.rb
128
+ - lib/ii_finder/log_subscriber.rb
125
129
  - lib/ii_finder/lookup.rb
126
130
  - lib/ii_finder/parameter.rb
127
131
  - lib/ii_finder/parameters.rb