thermos 0.5.0 → 0.6.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: 87ceed3d0f1b54b2cd9e9b0afde15e90252fcbc9f575c5bf11a66e414045c5d5
4
- data.tar.gz: dc3bc76cd808f474b80676c5f2d1f2e50681c6028c09412e12eb565b4494cfc4
3
+ metadata.gz: f3979389850e8fc2f1d7ffede3e023a8e9f81e5d1239f403fa79c1df49dd8f3e
4
+ data.tar.gz: 89806ca4b6ae5a81d121ae42adb784585852dd1af385d2f8067631b9cdd330a1
5
5
  SHA512:
6
- metadata.gz: d86ada3f6a3b07ed8bbf35c481e73ac4563a0aced184f6e47a986847287f0bf4a3c02c42e5d7600db1f5d8c2358f5ba4975fd20c4ccceee8fc3ec0cfcaae7811
7
- data.tar.gz: 51f90cb754d381d81293e433261e09cb4e184dc1dcddd05fbfcbf7baacb577330f6a4cc6a0621cc8cd9964d7677e499554a8dd533d65762adb421d56cef58458
6
+ metadata.gz: 9ceaaf4c53a3b06e012d66f1f60476c0278d973543a3d8dfb050e9637338dbe789cbb2b206d87d3e5b0f0cf60a130d85bc2119ec059f21bfffce7e1eef16eeb7
7
+ data.tar.gz: 1f5fc62f26e008e76059775cf273fcf05ea8b1be930acf5b25af3d3a0b36a11f9beae3f7a9b9083cb5869efb45105bb98e3cb1d22b443f61910934fed26c9315
data/Rakefile CHANGED
@@ -27,6 +27,10 @@ Rake::TestTask.new(:test) do |t|
27
27
  t.verbose = false
28
28
  end
29
29
 
30
+ task(:format) do
31
+ system('bundle exec rbprettier --write {lib,test}/**/*.rb')
32
+ end
33
+
30
34
  task(:default) do
31
35
  Dir.chdir('test/dummy')
32
36
  system('rake db:migrate')
@@ -2,27 +2,44 @@
2
2
 
3
3
  module Thermos
4
4
  class Beverage
5
- attr_reader :key, :model, :deps, :action, :lookup_key, :filter
5
+ attr_reader :key, :model, :deps, :action, :lookup_key, :filter, :queue
6
6
 
7
- def initialize(key:, model:, deps:, action:, lookup_key: nil, filter: nil)
7
+ def initialize(
8
+ key:,
9
+ model:,
10
+ deps:,
11
+ action:,
12
+ lookup_key: nil,
13
+ filter: nil,
14
+ queue: nil
15
+ )
8
16
  @key = key
9
17
  @model = model
10
18
  @lookup_key = lookup_key || :id
11
19
  @filter = filter || nil
12
20
  @deps = generate_deps(model, deps)
13
21
  @action = action
22
+ @queue = queue || ActiveJob::Base.default_queue_name
14
23
 
15
24
  set_observers
16
25
  end
17
26
 
18
27
  def lookup_keys_for_dep_model(dep_model)
19
- @deps.select do |dep|
20
- dep.klass == dep_model.class
21
- end.flat_map do |dep|
22
- @model.joins(dep.path)
23
- .where(dep.table => { id: dep_model.id })
24
- .pluck(@lookup_key)
25
- end.uniq
28
+ @deps
29
+ .select { |dep| dep.klass == dep_model.class }
30
+ .flat_map do |dep|
31
+ lookup_keys = []
32
+
33
+ @model
34
+ .joins(dep.path)
35
+ .where(dep.table => { id: dep_model.id })
36
+ .find_each do |model|
37
+ lookup_keys << model.send(@lookup_key) if should_fill?(model)
38
+ end
39
+
40
+ lookup_keys
41
+ end
42
+ .uniq
26
43
  end
27
44
 
28
45
  def should_fill?(model)
@@ -43,25 +60,26 @@ module Thermos
43
60
  def generate_deps(model, deps, root = nil, path = nil)
44
61
  deps.reduce([]) do |acc, dep|
45
62
  if dep.is_a? Symbol
46
- acc << Dependency.new(
47
- model: root || model,
48
- ref: model.reflect_on_association(dep),
49
- path: path || dep)
63
+ acc <<
64
+ Dependency.new(
65
+ model: root || model,
66
+ ref: model.reflect_on_association(dep),
67
+ path: path || dep,
68
+ )
50
69
  elsif dep.is_a? Array
51
- dep.each do |d|
52
- acc << Dependency.new(
53
- model: root || model,
54
- ref: model.reflect_on_association(d),
55
- path: path || d)
70
+ dep.each do |d|
71
+ acc <<
72
+ Dependency.new(
73
+ model: root || model,
74
+ ref: model.reflect_on_association(d),
75
+ path: path || d,
76
+ )
56
77
  end
57
78
  elsif dep.is_a? Hash
58
- dep.each do |k,v|
79
+ dep.each do |k, v|
59
80
  ref = model.reflect_on_association(k)
60
- acc << Dependency.new(
61
- model: root || model,
62
- ref: ref,
63
- path: path || k
64
- )
81
+ acc <<
82
+ Dependency.new(model: root || model, ref: ref, path: path || k)
65
83
  acc.concat(generate_deps(ref.class_name.constantize, v, model, dep))
66
84
  end
67
85
  end
@@ -4,9 +4,7 @@ module Thermos
4
4
  module Notifier
5
5
  extend ActiveSupport::Concern
6
6
 
7
- included do
8
- after_commit :notify_thermos
9
- end
7
+ included { after_commit :notify_thermos, on: %i[create update] }
10
8
 
11
9
  private
12
10
 
@@ -10,18 +10,22 @@ module Thermos
10
10
  def refill_primary_caches(model)
11
11
  BeverageStorage.instance.beverages.each do |beverage|
12
12
  if beverage.model == model.class && beverage.should_fill?(model)
13
- Thermos::RebuildCacheJob.perform_later(beverage.key, model.send(beverage.lookup_key))
13
+ Thermos::RebuildCacheJob
14
+ .set(queue: beverage.queue)
15
+ .perform_later(beverage.key, model.send(beverage.lookup_key))
14
16
  end
15
17
  end
16
18
  end
17
19
 
18
20
  def refill_dependency_caches(model)
19
21
  BeverageStorage.instance.beverages.each do |beverage|
20
- if beverage.should_fill?(model)
21
- beverage.lookup_keys_for_dep_model(model).each do |lookup_key|
22
- Thermos::RebuildCacheJob.perform_later(beverage.key, lookup_key)
22
+ beverage
23
+ .lookup_keys_for_dep_model(model)
24
+ .each do |lookup_key|
25
+ Thermos::RebuildCacheJob
26
+ .set(queue: beverage.queue)
27
+ .perform_later(beverage.key, lookup_key)
23
28
  end
24
- end
25
29
  end
26
30
  end
27
31
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Thermos
4
- VERSION = '0.5.0'
4
+ VERSION = '0.6.0'
5
5
  end
data/lib/thermos.rb CHANGED
@@ -8,20 +8,38 @@ require 'thermos/refill_job'
8
8
  require 'thermos/rebuild_cache_job'
9
9
 
10
10
  module Thermos
11
- def self.keep_warm(key:, model:, id:, deps: [], lookup_key: nil, filter: nil, &block)
12
- fill(key: key, model: model, deps: deps, lookup_key: lookup_key, filter: filter, &block)
11
+ def self.keep_warm(key:, model:, id:, deps: [], lookup_key: nil, filter: nil, queue: nil, &block)
12
+ fill(
13
+ key: key,
14
+ model: model,
15
+ deps: deps,
16
+ lookup_key: lookup_key,
17
+ filter: filter,
18
+ queue: queue,
19
+ &block
20
+ )
13
21
  drink(key: key, id: id)
14
22
  end
15
23
 
16
- def self.fill(key:, model:, deps: [], lookup_key: nil, filter: nil, &block)
24
+ def self.fill(key:, model:, deps: [], lookup_key: nil, filter: nil, queue: nil, &block)
17
25
  BeverageStorage.instance.add_beverage(
18
- Beverage.new(key: key, model: model, deps: deps, action: block, lookup_key: lookup_key, filter: filter)
26
+ Beverage.new(
27
+ key: key,
28
+ model: model,
29
+ deps: deps,
30
+ action: block,
31
+ lookup_key: lookup_key,
32
+ filter: filter,
33
+ queue: queue
34
+ )
19
35
  )
20
36
  end
21
37
 
22
38
  def self.drink(key:, id:)
23
- Rails.cache.fetch([key, id]) do
24
- BeverageStorage.instance.get_beverage(key).action.call(id)
25
- end
39
+ Rails
40
+ .cache
41
+ .fetch([key, id]) do
42
+ BeverageStorage.instance.get_beverage(key).action.call(id)
43
+ end
26
44
  end
27
45
  end
@@ -0,0 +1,307 @@
1
+ require 'test_helper'
2
+
3
+ class DependenciesTest < ActiveSupport::TestCase
4
+ self.use_transactional_tests = true
5
+ teardown :clear_cache
6
+
7
+ test 'rebuilds the cache on has_many model change' do
8
+ mock = Minitest::Mock.new
9
+ category = categories(:baseball)
10
+ category_item = category_items(:baseball_glove)
11
+
12
+ Thermos.fill(key: 'key', model: Category, deps: [:category_items]) do |id|
13
+ mock.call(id)
14
+ end
15
+
16
+ mock.expect(:call, 1, [category.id])
17
+ assert_equal 1, Thermos.drink(key: 'key', id: category.id)
18
+ mock.verify
19
+
20
+ mock.expect(:call, 2, [category.id])
21
+ category_item.update!(name: 'foo')
22
+ mock.verify
23
+
24
+ mock.expect(:call, 3, [category.id])
25
+ assert_equal 2, Thermos.drink(key: 'key', id: category.id)
26
+ assert_raises(MockExpectationError) { mock.verify }
27
+ end
28
+
29
+ test 'does not rebuild the cache for an unrelated has_many model change' do
30
+ mock = Minitest::Mock.new
31
+ category = categories(:baseball)
32
+ category_item = CategoryItem.create(category: nil)
33
+
34
+ Thermos.fill(key: 'key', model: Category, deps: [:category_items]) do |id|
35
+ mock.call(id)
36
+ end
37
+
38
+ mock.expect(:call, 1, [category.id])
39
+ assert_equal 1, Thermos.drink(key: 'key', id: category.id)
40
+ mock.verify
41
+
42
+ mock.expect(:call, 2, [category.id])
43
+ category_item.update!(name: 'foo')
44
+ assert_raises(MockExpectationError) { mock.verify }
45
+
46
+ mock.expect(:call, 3, [category.id])
47
+ assert_equal 1, Thermos.drink(key: 'key', id: category.id)
48
+ assert_raises(MockExpectationError) { mock.verify }
49
+ end
50
+
51
+ test 're-builds the cache for new has_many records' do
52
+ mock = Minitest::Mock.new
53
+ category = categories(:baseball)
54
+
55
+ Thermos.fill(key: 'key', model: Category, deps: [:category_items]) do |id|
56
+ mock.call(id)
57
+ end
58
+
59
+ mock.expect(:call, 1, [category.id])
60
+ CategoryItem.create!(category: category)
61
+ mock.verify
62
+
63
+ mock.expect(:call, 2, [category.id])
64
+ assert_equal 1, Thermos.drink(key: 'key', id: category.id)
65
+ assert_raises(MockExpectationError) { mock.verify }
66
+ end
67
+
68
+ test 're-builds the cache for has_many record changes when filter condition is met' do
69
+ mock = Minitest::Mock.new
70
+ category = categories(:baseball)
71
+ filter = ->(model) { model.ball? }
72
+
73
+ Thermos.fill(
74
+ key: 'key',
75
+ model: Category,
76
+ deps: [:category_items],
77
+ filter: filter,
78
+ ) { |id| mock.call(id) }
79
+
80
+ mock.expect(:call, 1, [category.id])
81
+ CategoryItem.create!(category: category)
82
+ mock.verify
83
+
84
+ category.update!(name: 'hockey')
85
+
86
+ mock.expect(:call, 1, [category.id])
87
+ CategoryItem.create!(category: category)
88
+ assert_raises(MockExpectationError) { mock.verify }
89
+ end
90
+
91
+ test 'rebuilds the cache on belongs_to model change' do
92
+ mock = Minitest::Mock.new
93
+ category = categories(:baseball)
94
+ store = stores(:sports)
95
+
96
+ Thermos.fill(key: 'key', model: Category, deps: [:store]) do |id|
97
+ mock.call(id)
98
+ end
99
+
100
+ mock.expect(:call, 1, [category.id])
101
+ assert_equal 1, Thermos.drink(key: 'key', id: category.id)
102
+ mock.verify
103
+
104
+ mock.expect(:call, 2, [category.id])
105
+ store.update!(name: 'foo')
106
+ mock.verify
107
+
108
+ mock.expect(:call, 3, [category.id])
109
+ assert_equal 2, Thermos.drink(key: 'key', id: category.id)
110
+ assert_raises(MockExpectationError) { mock.verify }
111
+ end
112
+
113
+ test 'does not rebuild the cache for an unrelated belongs_to model change' do
114
+ mock = Minitest::Mock.new
115
+ category = categories(:baseball)
116
+ store = Store.create!
117
+
118
+ Thermos.fill(key: 'key', model: Category, deps: [:store]) do |id|
119
+ mock.call(id)
120
+ end
121
+
122
+ mock.expect(:call, 1, [category.id])
123
+ assert_equal 1, Thermos.drink(key: 'key', id: category.id)
124
+ mock.verify
125
+
126
+ mock.expect(:call, 2, [category.id])
127
+ store.update!(name: 'foo')
128
+ assert_raises(MockExpectationError) { mock.verify }
129
+
130
+ mock.expect(:call, 3, [category.id])
131
+ assert_equal 1, Thermos.drink(key: 'key', id: category.id)
132
+ assert_raises(MockExpectationError) { mock.verify }
133
+ end
134
+
135
+ test 're-builds the cache for new belongs_to records' do
136
+ mock = Minitest::Mock.new
137
+ category = categories(:baseball)
138
+
139
+ Thermos.fill(key: 'key', model: Category, deps: [:store]) do |id|
140
+ mock.call(id)
141
+ end
142
+
143
+ mock.expect(:call, 1, [category.id])
144
+ mock.expect(:call, 1, [category.id])
145
+ Store.create!(name: 'foo', categories: [category])
146
+ mock.verify
147
+
148
+ mock.expect(:call, 2, [category.id])
149
+ assert_equal 1, Thermos.drink(key: 'key', id: category.id)
150
+ assert_raises(MockExpectationError) { mock.verify }
151
+ end
152
+
153
+ test 're-builds the cache for belongs_to record changes when filter condition is met' do
154
+ mock = Minitest::Mock.new
155
+ category = categories(:baseball)
156
+ filter = ->(model) { model.ball? }
157
+
158
+ Thermos.fill(
159
+ key: 'key',
160
+ model: Category,
161
+ deps: [:store],
162
+ filter: filter,
163
+ ) { |id| mock.call(id) }
164
+
165
+ mock.expect(:call, 1, [category.id])
166
+ mock.expect(:call, 1, [category.id])
167
+ Store.create!(name: 'foo', categories: [category])
168
+ mock.verify
169
+
170
+ category.update!(name: 'hockey')
171
+
172
+ mock.expect(:call, 2, [category.id])
173
+ Store.create!(name: 'bar', categories: [category])
174
+ assert_raises(MockExpectationError) { mock.verify }
175
+ end
176
+
177
+ test 'rebuilds the cache on has_many through model change' do
178
+ mock = Minitest::Mock.new
179
+ category = categories(:baseball)
180
+ product = products(:glove)
181
+
182
+ Thermos.fill(key: 'key', model: Category, deps: [:products]) do |id|
183
+ mock.call(id)
184
+ end
185
+
186
+ mock.expect(:call, 1, [category.id])
187
+ assert_equal 1, Thermos.drink(key: 'key', id: category.id)
188
+ mock.verify
189
+
190
+ mock.expect(:call, 2, [category.id])
191
+ product.update!(name: 'foo')
192
+ mock.verify
193
+
194
+ mock.expect(:call, 3, [category.id])
195
+ assert_equal 2, Thermos.drink(key: 'key', id: category.id)
196
+ assert_raises(MockExpectationError) { mock.verify }
197
+ end
198
+
199
+ test 'does not rebuild the cache for an unrelated has_many through model change' do
200
+ mock = Minitest::Mock.new
201
+ category = categories(:baseball)
202
+ product = Product.create!
203
+
204
+ Thermos.fill(key: 'key', model: Category, deps: [:products]) do |id|
205
+ mock.call(id)
206
+ end
207
+
208
+ mock.expect(:call, 1, [category.id])
209
+ assert_equal 1, Thermos.drink(key: 'key', id: category.id)
210
+ mock.verify
211
+
212
+ mock.expect(:call, 2, [category.id])
213
+ product.update!(name: 'foo')
214
+ assert_raises(MockExpectationError) { mock.verify }
215
+
216
+ mock.expect(:call, 3, [category.id])
217
+ assert_equal 1, Thermos.drink(key: 'key', id: category.id)
218
+ assert_raises(MockExpectationError) { mock.verify }
219
+ end
220
+
221
+ test 're-builds the cache for new has_many through records' do
222
+ mock = Minitest::Mock.new
223
+ category = categories(:baseball)
224
+
225
+ Thermos.fill(key: 'key', model: Category, deps: [:products]) do |id|
226
+ mock.call(id)
227
+ end
228
+
229
+ mock.expect(:call, 1, [category.id])
230
+ Product.create!(categories: [category])
231
+ mock.verify
232
+
233
+ mock.expect(:call, 2, [category.id])
234
+ assert_equal 1, Thermos.drink(key: 'key', id: category.id)
235
+ assert_raises(MockExpectationError) { mock.verify }
236
+ end
237
+
238
+ test 're-builds the cache for has_many through record changes when filter condition is met' do
239
+ mock = Minitest::Mock.new
240
+ category = categories(:baseball)
241
+ filter = ->(model) { model.ball? }
242
+
243
+ Thermos.fill(
244
+ key: 'key',
245
+ model: Category,
246
+ deps: [:products],
247
+ filter: filter,
248
+ ) { |id| mock.call(id) }
249
+
250
+ mock.expect(:call, 1, [category.id])
251
+ Product.create!(categories: [category])
252
+ mock.verify
253
+
254
+ category.update!(name: 'hockey')
255
+
256
+ mock.expect(:call, 2, [category.id])
257
+ Product.create!(categories: [category])
258
+ assert_raises(MockExpectationError) { mock.verify }
259
+ end
260
+
261
+ test 'handles indirect associations' do
262
+ mock = Minitest::Mock.new
263
+ category = categories(:baseball)
264
+ store = category.store
265
+
266
+ Thermos.fill(
267
+ key: 'key',
268
+ model: Store,
269
+ deps: [categories: [:products]],
270
+ ) { |id| mock.call(id) }
271
+
272
+ mock.expect(:call, 1, [store.id])
273
+ category.update!(name: 'foo')
274
+ mock.verify
275
+
276
+ mock.expect(:call, 2, [store.id])
277
+ assert_equal 1, Thermos.drink(key: 'key', id: store.id)
278
+ assert_raises(MockExpectationError) { mock.verify }
279
+ Product.create!(categories: [category])
280
+ mock.verify
281
+
282
+ mock.expect(:call, 3, [store.id])
283
+ assert_equal 2, Thermos.drink(key: 'key', id: store.id)
284
+ assert_raises(MockExpectationError) { mock.verify }
285
+ end
286
+
287
+ test 'only rebuilds cache for stated dependencies, even if another cache has an associated model of the primary' do
288
+ category_mock = Minitest::Mock.new
289
+ product_mock = Minitest::Mock.new
290
+ category = categories(:baseball)
291
+ product = products(:glove)
292
+
293
+ Thermos.fill(key: 'category_key', model: Category) do |id|
294
+ category_mock.call(id)
295
+ end
296
+
297
+ Thermos.fill(key: 'product_key', model: Product) do |id|
298
+ product_mock.call(id)
299
+ end
300
+
301
+ category_mock.expect(:call, 2, [category.id])
302
+ product_mock.expect(:call, 2, [product.id])
303
+ product.update!(name: 'foo')
304
+ assert_raises(MockExpectationError) { category_mock.verify }
305
+ product_mock.verify
306
+ end
307
+ end
@@ -3,16 +3,18 @@ class Category < ActiveRecord::Base
3
3
  has_many :products, through: :category_items
4
4
  belongs_to :store
5
5
 
6
+ def ball?
7
+ name.match('ball')
8
+ end
9
+
6
10
  def as_json(*args)
7
11
  {
8
12
  name: name,
9
13
  store_name: store.name,
10
- category_items: category_items.map do |item|
11
- {
12
- name: item.name,
13
- product_name: item.product.name
14
- }
15
- end
14
+ category_items:
15
+ category_items.map do |item|
16
+ { name: item.name, product_name: item.product.name }
17
+ end,
16
18
  }
17
19
  end
18
20
  end
@@ -3,21 +3,18 @@ require File.expand_path('../boot', __FILE__)
3
3
  require 'rails/all'
4
4
 
5
5
  Bundler.require(*Rails.groups)
6
- require "thermos"
6
+ require 'thermos'
7
7
 
8
8
  module Dummy
9
9
  class Application < Rails::Application
10
10
  # Settings in config/environments/* take precedence over those specified here.
11
11
  # Application configuration should go into files in config/initializers
12
12
  # -- all .rb files in that directory are automatically loaded.
13
-
14
13
  # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
15
14
  # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
16
15
  # config.time_zone = 'Central Time (US & Canada)'
17
-
18
16
  # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
19
17
  # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
20
18
  # config.i18n.default_locale = :de
21
19
  end
22
20
  end
23
-
@@ -10,7 +10,7 @@ Rails.application.configure do
10
10
  config.eager_load = false
11
11
 
12
12
  # Show full error reports and disable caching.
13
- config.consider_all_requests_local = true
13
+ config.consider_all_requests_local = true
14
14
  config.action_controller.perform_caching = false
15
15
 
16
16
  # Don't care if the mailer can't send.
@@ -11,7 +11,7 @@ Rails.application.configure do
11
11
  config.eager_load = true
12
12
 
13
13
  # Full error reports are disabled and caching is turned on.
14
- config.consider_all_requests_local = false
14
+ config.consider_all_requests_local = false
15
15
  config.action_controller.perform_caching = true
16
16
 
17
17
  # Enable Rack::Cache to put a simple HTTP cache in front of your application
@@ -26,6 +26,7 @@ Rails.application.configure do
26
26
 
27
27
  # Compress JavaScripts and CSS.
28
28
  config.assets.js_compressor = :uglifier
29
+
29
30
  # config.assets.css_compressor = :sass
30
31
 
31
32
  # Do not fallback to assets pipeline if a precompiled asset is missed.
@@ -13,11 +13,11 @@ Rails.application.configure do
13
13
  config.eager_load = false
14
14
 
15
15
  # Configure static file server for tests with Cache-Control for performance.
16
- config.serve_static_files = true
16
+ config.serve_static_files = true
17
17
  config.static_cache_control = 'public, max-age=3600'
18
18
 
19
19
  # Show full error reports and disable caching.
20
- config.consider_all_requests_local = true
20
+ config.consider_all_requests_local = true
21
21
  config.action_controller.perform_caching = false
22
22
 
23
23
  # Raise exceptions instead of rendering exception templates.
@@ -1,19 +1,14 @@
1
1
  Rails.application.routes.draw do
2
2
  # The priority is based upon order of creation: first created -> highest priority.
3
3
  # See how all your routes lay out with "rake routes".
4
-
5
4
  # You can have the root of your site routed with "root"
6
5
  # root 'welcome#index'
7
-
8
6
  # Example of regular route:
9
7
  # get 'products/:id' => 'catalog#view'
10
-
11
8
  # Example of named route that can be invoked with purchase_url(id: product.id)
12
9
  # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase
13
-
14
10
  # Example resource route (maps HTTP verbs to controller actions automatically):
15
11
  # resources :products
16
-
17
12
  # Example resource route with options:
18
13
  # resources :products do
19
14
  # member do
@@ -25,13 +20,11 @@ Rails.application.routes.draw do
25
20
  # get 'sold'
26
21
  # end
27
22
  # end
28
-
29
23
  # Example resource route with sub-resources:
30
24
  # resources :products do
31
25
  # resources :comments, :sales
32
26
  # resource :seller
33
27
  # end
34
-
35
28
  # Example resource route with more complex sub-resources:
36
29
  # resources :products do
37
30
  # resources :comments
@@ -39,14 +32,12 @@ Rails.application.routes.draw do
39
32
  # get 'recent', on: :collection
40
33
  # end
41
34
  # end
42
-
43
35
  # Example resource route with concerns:
44
36
  # concern :toggleable do
45
37
  # post 'toggle'
46
38
  # end
47
39
  # resources :posts, concerns: :toggleable
48
40
  # resources :photos, concerns: :toggleable
49
-
50
41
  # Example resource route within a namespace:
51
42
  # namespace :admin do
52
43
  # # Directs /admin/products/* to Admin::ProductsController