thermos 0.5.0 → 0.6.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: 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