progressrus 0.1.6 → 1.0.2
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 +5 -5
- data/.travis.yml +4 -2
- data/lib/progressrus.rb +71 -13
- data/lib/progressrus/core_ext/enumerable.rb +2 -2
- data/lib/progressrus/store/redis.rb +8 -4
- data/lib/progressrus/version.rb +3 -1
- data/progressrus.gemspec +1 -2
- data/tasks/redis_store.rake +1 -1
- data/test/integration_test.rb +1 -1
- data/test/progressrus_test.rb +66 -36
- data/test/store/redis_test.rb +15 -3
- metadata +5 -21
- data/lib/progressrus/store.rb +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 564bda7bfb2cfd735379ef412a908303655e1fe2853cd666d6d6fcbcefca5a6b
|
4
|
+
data.tar.gz: 0acaae1e3e6cdd2d69be60b27dfbe5d5185af7f7d6677b5ad0e190918cbab43b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 417d841b14a93351ed90ff754706467c42e29663bd076d55b9d7e23bca8ceef7989095f8eab1ce14cfc97caf51ed9497079f79498c775a6f06a4a20358c7417a
|
7
|
+
data.tar.gz: 514c8bcf9fb63500629630255861d590c35cbbdb5ab05c4798a37d30a32ffe51b4a8c59d11cb3f53ccc5d86aafdb52bab48c0c23972596e2a6f96208b9eb557d
|
data/.travis.yml
CHANGED
data/lib/progressrus.rb
CHANGED
@@ -2,29 +2,75 @@ require 'json'
|
|
2
2
|
require 'securerandom'
|
3
3
|
require 'redis'
|
4
4
|
require 'time'
|
5
|
-
require_relative "progressrus/store"
|
6
5
|
require_relative "progressrus/store/base"
|
7
6
|
require_relative "progressrus/store/redis"
|
8
7
|
require_relative "progressrus/store/progressbar"
|
9
8
|
require_relative "progressrus/core_ext/enumerable"
|
10
9
|
|
11
10
|
class Progressrus
|
11
|
+
@mutex = Mutex.new
|
12
|
+
|
13
|
+
class InvalidStoreError < StandardError
|
14
|
+
def initialize
|
15
|
+
message = <<~MSG
|
16
|
+
The store needs to implement `persist`, `scope`, `find` and `flush`
|
17
|
+
We have a base class that your store can inherit from:
|
18
|
+
Progressrus::Store::Base
|
19
|
+
MSG
|
20
|
+
super(message)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class StoreNotFoundError < StandardError
|
25
|
+
def initialize(store)
|
26
|
+
message = <<~MSG
|
27
|
+
The store `#{store}` does not exists.
|
28
|
+
Available stores: #{Progressrus.stores.keys.join(', ')}
|
29
|
+
MSG
|
30
|
+
super(message)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
12
34
|
class << self
|
35
|
+
attr_reader :mutex
|
36
|
+
|
37
|
+
def clear_stores
|
38
|
+
@stores = {}
|
39
|
+
end
|
40
|
+
|
13
41
|
def stores
|
14
|
-
|
42
|
+
mutex.synchronize do
|
43
|
+
@stores ||= {
|
44
|
+
redis: Store::Redis.new(::Redis.new(host: ENV["PROGRESSRUS_REDIS_HOST"] || "localhost"))
|
45
|
+
}
|
46
|
+
end
|
15
47
|
end
|
16
48
|
|
17
|
-
def
|
18
|
-
|
49
|
+
def add_store(name, store)
|
50
|
+
validate_store!(store)
|
51
|
+
stores[name] = store
|
52
|
+
end
|
53
|
+
|
54
|
+
def scope(scope, store: :redis)
|
55
|
+
stores[store].scope(scope)
|
19
56
|
end
|
20
57
|
alias_method :all, :scope
|
21
58
|
|
22
|
-
def find(scope, id, store: :
|
23
|
-
stores
|
59
|
+
def find(scope, id, store: :redis)
|
60
|
+
stores[store].find(scope, id)
|
61
|
+
end
|
62
|
+
|
63
|
+
def flush(scope, id = nil, store: :redis)
|
64
|
+
stores[store].flush(scope, id)
|
24
65
|
end
|
25
66
|
|
26
|
-
|
27
|
-
|
67
|
+
private
|
68
|
+
|
69
|
+
def validate_store!(store)
|
70
|
+
valid = Store::Base.new.public_methods(false).all? do |method|
|
71
|
+
store.respond_to?(method)
|
72
|
+
end
|
73
|
+
raise InvalidStoreError unless valid
|
28
74
|
end
|
29
75
|
end
|
30
76
|
|
@@ -38,7 +84,7 @@ class Progressrus
|
|
38
84
|
attr_writer :params
|
39
85
|
|
40
86
|
def initialize(scope: "progressrus", total: nil, name: nil,
|
41
|
-
id: SecureRandom.uuid, params: {}, stores:
|
87
|
+
id: SecureRandom.uuid, params: {}, stores: nil,
|
42
88
|
completed_at: nil, started_at: nil, count: 0, failed_at: nil,
|
43
89
|
error_count: 0, persist: false, expires_at: nil, persisted: false)
|
44
90
|
|
@@ -49,7 +95,7 @@ class Progressrus
|
|
49
95
|
@total = total
|
50
96
|
@id = id
|
51
97
|
@params = params
|
52
|
-
@stores = stores
|
98
|
+
@stores = extract_stores(stores)
|
53
99
|
@count = count
|
54
100
|
@error_count = error_count
|
55
101
|
|
@@ -84,7 +130,7 @@ class Progressrus
|
|
84
130
|
end
|
85
131
|
|
86
132
|
def flush
|
87
|
-
stores.
|
133
|
+
stores.each_value { |store| store.flush(scope, id) }
|
88
134
|
end
|
89
135
|
|
90
136
|
def status
|
@@ -160,11 +206,23 @@ class Progressrus
|
|
160
206
|
|
161
207
|
private
|
162
208
|
|
209
|
+
def extract_stores(stores)
|
210
|
+
return Progressrus.stores unless stores
|
211
|
+
|
212
|
+
Array(stores).each_with_object({}) do |store, hash|
|
213
|
+
stored_found = Progressrus.stores[store]
|
214
|
+
raise StoreNotFoundError, store unless stored_found
|
215
|
+
|
216
|
+
hash[store] = stored_found
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
163
220
|
def persist(force: false)
|
164
|
-
stores.
|
221
|
+
stores.each_value do |store|
|
165
222
|
begin
|
166
223
|
store.persist(self, force: force, expires_at: expires_at)
|
167
|
-
rescue Progressrus::Store::BackendError
|
224
|
+
rescue Progressrus::Store::BackendError
|
225
|
+
# noop
|
168
226
|
end
|
169
227
|
end
|
170
228
|
@persisted = true
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Enumerable
|
2
|
-
def with_progress(
|
2
|
+
def with_progress(args = {}, &block)
|
3
3
|
if block_given?
|
4
4
|
progresser = progress(args)
|
5
5
|
begin
|
@@ -25,7 +25,7 @@ module Enumerable
|
|
25
25
|
# Lazily read the size, for some enumerable this may be quite expensive and
|
26
26
|
# using this method should come with a warning in the documentation.
|
27
27
|
total = self.size unless args[:total]
|
28
|
-
@progress = Progressrus.new(
|
28
|
+
@progress = Progressrus.new(total: total, **args)
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
@@ -3,7 +3,7 @@ class Progressrus
|
|
3
3
|
class Redis < Base
|
4
4
|
BACKEND_EXCEPTIONS = [ ::Redis::BaseError ]
|
5
5
|
|
6
|
-
attr_reader :redis, :interval, :
|
6
|
+
attr_reader :redis, :interval, :prefix, :name
|
7
7
|
|
8
8
|
def initialize(instance, prefix: "progressrus", interval: 1, now: Time.now)
|
9
9
|
@name = :redis
|
@@ -31,7 +31,7 @@ class Progressrus
|
|
31
31
|
def scope(scope)
|
32
32
|
scope = redis.hgetall(key(scope))
|
33
33
|
scope.each_pair { |id, value|
|
34
|
-
scope[id] = Progressrus.new(deserialize(value))
|
34
|
+
scope[id] = Progressrus.new(**deserialize(value))
|
35
35
|
}
|
36
36
|
rescue *BACKEND_EXCEPTIONS => e
|
37
37
|
raise Progressrus::Store::BackendError.new(e)
|
@@ -41,7 +41,7 @@ class Progressrus
|
|
41
41
|
value = redis.hget(key(scope), id)
|
42
42
|
return unless value
|
43
43
|
|
44
|
-
Progressrus.new(deserialize(value))
|
44
|
+
Progressrus.new(**deserialize(value))
|
45
45
|
rescue *BACKEND_EXCEPTIONS => e
|
46
46
|
raise Progressrus::Store::BackendError.new(e)
|
47
47
|
end
|
@@ -59,7 +59,11 @@ class Progressrus
|
|
59
59
|
private
|
60
60
|
|
61
61
|
def key(scope)
|
62
|
-
|
62
|
+
if prefix.respond_to?(:call)
|
63
|
+
prefix.call(scope)
|
64
|
+
else
|
65
|
+
"#{prefix}:#{scope.join(":")}"
|
66
|
+
end
|
63
67
|
end
|
64
68
|
|
65
69
|
def deserialize(value)
|
data/lib/progressrus/version.rb
CHANGED
data/progressrus.gemspec
CHANGED
@@ -18,10 +18,9 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency "redis", "
|
21
|
+
spec.add_dependency "redis", ">= 3.0"
|
22
22
|
spec.add_dependency "ruby-progressbar", "~> 1.0"
|
23
23
|
|
24
|
-
spec.add_development_dependency "bundler", "~> 1.3"
|
25
24
|
spec.add_development_dependency "rake"
|
26
25
|
spec.add_development_dependency "mocha", "~> 0.14"
|
27
26
|
spec.add_development_dependency "pry"
|
data/tasks/redis_store.rake
CHANGED
@@ -7,7 +7,7 @@ namespace :progressrus do
|
|
7
7
|
task :flush, :environment do |t, args|
|
8
8
|
scope = *args
|
9
9
|
raise ArgumentError.new("Must pass [scope] to progressrus:store:flush[scope(,parts)] task.") unless scope.length > 0
|
10
|
-
Progressrus.stores.
|
10
|
+
Progressrus.stores[:redis].flush(scope) if Progressrus.stores[:redis]
|
11
11
|
end
|
12
12
|
end
|
13
13
|
end
|
data/test/integration_test.rb
CHANGED
data/test/progressrus_test.rb
CHANGED
@@ -6,16 +6,23 @@ class ProgressrusTest < Minitest::Test
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def teardown
|
9
|
-
Progressrus.
|
9
|
+
Progressrus.clear_stores
|
10
|
+
Progressrus.add_store(:redis, Progressrus::Store::Redis.new(::Redis.new(host: ENV["PROGRESSRUS_REDIS_HOST"] || "localhost")))
|
10
11
|
end
|
11
12
|
|
12
13
|
def test_defaults_to_redis_store
|
13
|
-
assert_instance_of Progressrus::Store::Redis, Progressrus.stores
|
14
|
+
assert_instance_of Progressrus::Store::Redis, Progressrus.stores[:redis]
|
14
15
|
end
|
15
16
|
|
16
17
|
def test_add_to_store
|
17
|
-
Progressrus.
|
18
|
-
assert_instance_of Progressrus::Store::Base, Progressrus.stores[
|
18
|
+
Progressrus.add_store(:test, Progressrus::Store::Base.new)
|
19
|
+
assert_instance_of Progressrus::Store::Base, Progressrus.stores[:test]
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_add_store_raise_error_when_invalid_store
|
23
|
+
assert_raises(Progressrus::InvalidStoreError) do
|
24
|
+
Progressrus.add_store(:test, mock)
|
25
|
+
end
|
19
26
|
end
|
20
27
|
|
21
28
|
def test_scope_should_initialize_with_symbol_or_string
|
@@ -33,6 +40,37 @@ class ProgressrusTest < Minitest::Test
|
|
33
40
|
assert_equal 'Wally', progressrus.name
|
34
41
|
end
|
35
42
|
|
43
|
+
def test_not_passing_stores_default_to_all_stores
|
44
|
+
progressrus = Progressrus.new(scope: ['walruses', 1])
|
45
|
+
assert_equal Progressrus.stores, progressrus.stores
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_passing_stores
|
49
|
+
mysql = mock_store
|
50
|
+
Progressrus.add_store(:mysql, mysql)
|
51
|
+
progressrus = Progressrus.new(scope: ['walruses', 1], stores: :mysql)
|
52
|
+
assert_equal({ mysql: mysql }, progressrus.stores)
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_passing_multiple_stores
|
56
|
+
Progressrus.clear_stores
|
57
|
+
|
58
|
+
mysql = mock_store
|
59
|
+
redis = mock_store
|
60
|
+
|
61
|
+
Progressrus.add_store(:mysql, mysql)
|
62
|
+
Progressrus.add_store(:redis, redis)
|
63
|
+
|
64
|
+
progressrus = Progressrus.new(scope: ['walruses', 1], stores: %i(mysql redis))
|
65
|
+
assert_equal({ mysql: mysql, redis: redis }, progressrus.stores)
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_passing_non_existing_store_raises_error
|
69
|
+
assert_raises(Progressrus::StoreNotFoundError) do
|
70
|
+
Progressrus.new(scope: ['walruses', 1], stores: :not_found)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
36
74
|
def test_initialize_without_name_should_use_id
|
37
75
|
progressrus = Progressrus.new(id: 'oemg')
|
38
76
|
assert_equal 'oemg', progressrus.name
|
@@ -196,19 +234,20 @@ class ProgressrusTest < Minitest::Test
|
|
196
234
|
end
|
197
235
|
|
198
236
|
def test_persist_yields_persist_to_each_store
|
199
|
-
mysql =
|
237
|
+
mysql = mock_store
|
200
238
|
mysql.expects(:persist).once
|
201
239
|
|
202
|
-
redis = Progressrus.stores
|
240
|
+
redis = Progressrus.stores[:redis]
|
203
241
|
redis.expects(:persist).once
|
204
242
|
|
205
|
-
Progressrus.
|
243
|
+
Progressrus.add_store(:mysql, mysql)
|
206
244
|
|
207
|
-
|
245
|
+
progress = Progressrus.new(scope: :progressrus, total: 100)
|
246
|
+
progress.tick
|
208
247
|
end
|
209
248
|
|
210
249
|
def test_tick_and_complete_dont_raise_if_store_is_unavailable
|
211
|
-
store = Progressrus.stores
|
250
|
+
store = Progressrus.stores[:redis]
|
212
251
|
store.redis.expects(:hset).at_least_once.raises(::Redis::BaseError)
|
213
252
|
@progress.tick
|
214
253
|
@progress.complete
|
@@ -234,47 +273,32 @@ class ProgressrusTest < Minitest::Test
|
|
234
273
|
end
|
235
274
|
|
236
275
|
|
237
|
-
def
|
238
|
-
Progressrus.
|
239
|
-
|
240
|
-
mysql = mock()
|
241
|
-
redis = mock()
|
242
|
-
|
243
|
-
Progressrus.stores << mysql
|
244
|
-
Progressrus.stores << redis
|
245
|
-
|
246
|
-
mysql.expects(:scope).once
|
247
|
-
redis.expects(:scope).never
|
248
|
-
|
249
|
-
Progressrus.scope(["oemg"])
|
250
|
-
end
|
276
|
+
def test_default_scope_redis_store
|
277
|
+
Progressrus.clear_stores
|
251
278
|
|
252
|
-
|
253
|
-
|
279
|
+
mysql = mock_store
|
280
|
+
redis = mock_store
|
254
281
|
|
255
|
-
mysql
|
256
|
-
redis
|
257
|
-
|
258
|
-
Progressrus.stores << mysql
|
259
|
-
Progressrus.stores << redis
|
282
|
+
Progressrus.add_store(:mysql, mysql)
|
283
|
+
Progressrus.add_store(:redis, redis)
|
260
284
|
|
261
285
|
mysql.expects(:scope).never
|
262
286
|
redis.expects(:scope).once
|
263
287
|
|
264
|
-
Progressrus.scope(["oemg"]
|
288
|
+
Progressrus.scope(["oemg"])
|
265
289
|
end
|
266
290
|
|
267
291
|
def test_support_scope_by_name
|
268
|
-
Progressrus.
|
292
|
+
Progressrus.clear_stores
|
269
293
|
|
270
|
-
mysql =
|
271
|
-
redis =
|
294
|
+
mysql = mock_store
|
295
|
+
redis = mock_store
|
272
296
|
|
273
297
|
mysql.stubs(:name).returns(:mysql)
|
274
298
|
redis.stubs(:name).returns(:redis)
|
275
299
|
|
276
|
-
Progressrus.
|
277
|
-
Progressrus.
|
300
|
+
Progressrus.add_store(:mysql, mysql)
|
301
|
+
Progressrus.add_store(:redis, redis)
|
278
302
|
|
279
303
|
mysql.expects(:scope).never
|
280
304
|
redis.expects(:scope).once
|
@@ -427,4 +451,10 @@ class ProgressrusTest < Minitest::Test
|
|
427
451
|
|
428
452
|
refute progress.expired?(now: time)
|
429
453
|
end
|
454
|
+
|
455
|
+
private
|
456
|
+
|
457
|
+
def mock_store
|
458
|
+
Progressrus::Store::Base.new
|
459
|
+
end
|
430
460
|
end
|
data/test/store/redis_test.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
require_relative "../test_helper"
|
2
2
|
|
3
3
|
class RedisStoreTest < Minitest::Test
|
4
|
+
class PrefixClass
|
5
|
+
def self.call(scope)
|
6
|
+
"custom_prefix:#{scope.join(':')}"
|
7
|
+
end
|
8
|
+
end
|
4
9
|
def setup
|
5
10
|
@scope = ["walrus", "1234"]
|
6
11
|
@progress = Progressrus.new(
|
@@ -17,11 +22,19 @@ class RedisStoreTest < Minitest::Test
|
|
17
22
|
name: "oemg-name-two"
|
18
23
|
)
|
19
24
|
|
20
|
-
@
|
25
|
+
@redis = ::Redis.new(host: ENV["PROGRESSRUS_REDIS_HOST"] || "localhost")
|
26
|
+
|
27
|
+
@store = Progressrus::Store::Redis.new(@redis)
|
21
28
|
end
|
22
29
|
|
23
30
|
def teardown
|
24
|
-
|
31
|
+
@store.flush(@scope)
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_prefix_can_be_a_proc
|
35
|
+
store = Progressrus::Store::Redis.new(@redis, prefix: PrefixClass)
|
36
|
+
store.persist(@progress)
|
37
|
+
refute_empty(@redis.hgetall('custom_prefix:walrus:1234'))
|
25
38
|
end
|
26
39
|
|
27
40
|
def test_persist_should_set_key_value_if_outdated
|
@@ -99,5 +112,4 @@ class RedisStoreTest < Minitest::Test
|
|
99
112
|
@store.persist(@progress)
|
100
113
|
@store.persist(@progress, force: true)
|
101
114
|
end
|
102
|
-
|
103
115
|
end
|
metadata
CHANGED
@@ -1,27 +1,27 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: progressrus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Simon Eskildsen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-05-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '3.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '3.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
@@ -38,20 +38,6 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: bundler
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '1.3'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '1.3'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
42
|
name: rake
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -153,7 +139,6 @@ files:
|
|
153
139
|
- lib/progressrus/core_ext/enumerable.rb
|
154
140
|
- lib/progressrus/railtie.rb
|
155
141
|
- lib/progressrus/server.rb
|
156
|
-
- lib/progressrus/store.rb
|
157
142
|
- lib/progressrus/store/base.rb
|
158
143
|
- lib/progressrus/store/progressbar.rb
|
159
144
|
- lib/progressrus/store/redis.rb
|
@@ -185,8 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
185
170
|
- !ruby/object:Gem::Version
|
186
171
|
version: '0'
|
187
172
|
requirements: []
|
188
|
-
|
189
|
-
rubygems_version: 2.6.14
|
173
|
+
rubygems_version: 3.0.3
|
190
174
|
signing_key:
|
191
175
|
specification_version: 4
|
192
176
|
summary: Monitor the progress of remote, long-running jobs.
|
data/lib/progressrus/store.rb
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
class Progressrus
|
2
|
-
class Store < Array
|
3
|
-
def initialize(default)
|
4
|
-
@default = default
|
5
|
-
self << default
|
6
|
-
end
|
7
|
-
|
8
|
-
def default
|
9
|
-
@default
|
10
|
-
end
|
11
|
-
|
12
|
-
def default!
|
13
|
-
clear
|
14
|
-
self << default
|
15
|
-
end
|
16
|
-
|
17
|
-
def find_by_name(name)
|
18
|
-
return first if name == :first
|
19
|
-
return last if name == :last
|
20
|
-
|
21
|
-
find { |store| store.name == name }
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|