ii_finder 1.0.0 → 1.2.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
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