rails_stuff 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +13 -5
- data/CHANGELOG.md +31 -0
- data/Gemfile +1 -1
- data/README.md +71 -8
- data/lib/rails_stuff/association_writer.rb +15 -0
- data/lib/rails_stuff/engine.rb +8 -2
- data/lib/rails_stuff/generators/concern/USAGE +11 -0
- data/lib/rails_stuff/generators/concern/concern_generator.rb +26 -0
- data/lib/rails_stuff/generators/concern/templates/concern.rb +14 -0
- data/lib/rails_stuff/helpers/links.rb +1 -1
- data/lib/rails_stuff/helpers/translation.rb +14 -3
- data/lib/rails_stuff/random_uniq_attr.rb +6 -1
- data/lib/rails_stuff/require_nested.rb +22 -0
- data/lib/rails_stuff/resources_controller/basic_helpers.rb +9 -24
- data/lib/rails_stuff/resources_controller/belongs_to.rb +35 -0
- data/lib/rails_stuff/resources_controller/has_scope_helpers.rb +20 -0
- data/lib/rails_stuff/resources_controller/kaminari_helpers.rb +22 -0
- data/lib/rails_stuff/resources_controller/resource_helper.rb +10 -4
- data/lib/rails_stuff/resources_controller.rb +22 -10
- data/lib/rails_stuff/sort_scope.rb +5 -2
- data/lib/rails_stuff/statusable.rb +158 -43
- data/lib/rails_stuff/test_helpers/concurrency.rb +71 -0
- data/lib/rails_stuff/test_helpers/configurator.rb +60 -0
- data/lib/rails_stuff/test_helpers/rails.rb +3 -0
- data/lib/rails_stuff/types_tracker.rb +7 -3
- data/lib/rails_stuff/version.rb +1 -1
- data/lib/rails_stuff.rb +3 -1
- data/rails_stuff.gemspec +1 -1
- metadata +14 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a6bd04d2e9f8e71c29e7b70748e3f1529715e0c
|
4
|
+
data.tar.gz: fa5c7003161e787b1886de2b3c6e10955c812728
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fa5e2a577b9de17bab80c9caf6e6e323d9637969f135e701042745271c9b60422d627a27c07311355feae66c2d29d4918ae498f5db542db1c0a50713a7c09c90
|
7
|
+
data.tar.gz: a70871bf1a0814cd0551a6ad8a7f596b0912751925c04a5353920fdafcd55234344d7e8bb8a0e90c467455fcd3223cf0e3060b0d3c45bd0e21fc57be72d2632d
|
data/.rubocop.yml
CHANGED
@@ -1,23 +1,31 @@
|
|
1
1
|
AllCops:
|
2
|
-
|
2
|
+
Exclude:
|
3
|
+
- lib/rails_stuff/generators/*/templates/*.rb
|
4
|
+
- tmp/**/*
|
5
|
+
Rails: {Enabled: true}
|
3
6
|
|
7
|
+
Style/Alias: {Enabled: false}
|
4
8
|
Style/AlignParameters:
|
5
9
|
# Disable, till rubocop supports combination of styles.
|
6
10
|
# Use one of this styles where appropriate, keep it clean, compact and readable.
|
7
11
|
Enabled: false
|
8
|
-
EnforcedStyle:
|
9
|
-
-
|
10
|
-
- with_fixed_indentation
|
12
|
+
# EnforcedStyle:
|
13
|
+
# - with_first_parameter
|
14
|
+
# - with_fixed_indentation
|
11
15
|
Style/ClosingParenthesisIndentation: {Enabled: false}
|
12
16
|
Style/Documentation: {Enabled: false}
|
13
17
|
Style/DotPosition: {EnforcedStyle: trailing}
|
14
18
|
Style/IfUnlessModifier: {Enabled: false}
|
19
|
+
Style/Lambda: {Enabled: false}
|
15
20
|
Style/ModuleFunction: {Enabled: false}
|
21
|
+
Style/MultilineMethodCallIndentation: {EnforcedStyle: indented}
|
16
22
|
Style/MultilineOperationIndentation: {EnforcedStyle: indented}
|
23
|
+
Style/NestedParenthesizedCalls: {Enabled: false}
|
17
24
|
Style/PredicateName: {Enabled: false}
|
18
25
|
Style/SignalException: {EnforcedStyle: only_raise}
|
19
26
|
Style/SpaceInsideHashLiteralBraces: {EnforcedStyle: no_space}
|
20
|
-
Style/
|
27
|
+
Style/TrailingCommaInArguments: {Enabled: false}
|
28
|
+
Style/TrailingCommaInLiteral: {EnforcedStyleForMultiline: comma}
|
21
29
|
|
22
30
|
Metrics/AbcSize: {Max: 21}
|
23
31
|
Metrics/LineLength: {Max: 100}
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,34 @@
|
|
1
|
+
## Unreleased
|
2
|
+
|
3
|
+
### Controllers
|
4
|
+
|
5
|
+
- `belongs_to`.
|
6
|
+
- `resource_helper` generates enquirer method.
|
7
|
+
- `resources_controller kaminari: false` to skip kaminari for the only controller.
|
8
|
+
- `has_sort_scope` can use custom order method.
|
9
|
+
- Fix: removed source_for_collection from action_methods.
|
10
|
+
|
11
|
+
### Models
|
12
|
+
|
13
|
+
- Statusable supports mappings (store status as integer) and suffixes.
|
14
|
+
- AssociationWriter to override `#field=` & `#field_id=` in single instruction.
|
15
|
+
- Limit retries count for RandomUniqAttr (default to 10).
|
16
|
+
|
17
|
+
### Helpers
|
18
|
+
|
19
|
+
- `Helpers::Translation` methods can raise errors on missing translations.
|
20
|
+
It respects app's `raise_on_missing_translations`, and can be configured manually.
|
21
|
+
|
22
|
+
### Tests
|
23
|
+
|
24
|
+
- Concurrency helper.
|
25
|
+
- RSpec configurator.
|
26
|
+
|
27
|
+
Misc
|
28
|
+
|
29
|
+
- RequireNested to require all files in subdirectory.
|
30
|
+
- `rails g concern %parent%/%module%` generator for concerns.
|
31
|
+
|
1
32
|
## 0.4.0
|
2
33
|
|
3
34
|
- TypesTracker defines scopes for every type.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -16,6 +16,8 @@ Collection of useful modules for Rails.
|
|
16
16
|
|
17
17
|
- __[NullifyBlankAttrs](#nullifyblankattrs)__
|
18
18
|
Proxies writers to replace empty values with `nil`.
|
19
|
+
- __[AssociationWriter](#associationwriter)__
|
20
|
+
Override both writers with single instruction.
|
19
21
|
- __[RandomUniqAttr](#randomuniqattr)__
|
20
22
|
You generate random values for attributes, it'll ensure they are uniq.
|
21
23
|
- __[Statusable](#statusable)__
|
@@ -34,6 +36,9 @@ Collection of useful modules for Rails.
|
|
34
36
|
`require_permitted` helper.
|
35
37
|
- __UrlFor__
|
36
38
|
`#url_for_keeping_params` merges passed options with request's query params.
|
39
|
+
- __[RequireNested](#requirenested)__
|
40
|
+
helper to load files in subdirectory.
|
41
|
+
- `rails g concern %parent%/%module%` generator for concerns.
|
37
42
|
|
38
43
|
#### Helpers:
|
39
44
|
|
@@ -53,6 +58,10 @@ __[Helpers usage](#helpers-usage)__
|
|
53
58
|
|
54
59
|
- __Response__
|
55
60
|
`#json_body` to test json responses.
|
61
|
+
- __Configurator__
|
62
|
+
Provides useful basic configuration for RSpec.
|
63
|
+
- __Concurrency__
|
64
|
+
Helpers for testing with concurrent requests.
|
56
65
|
|
57
66
|
__[Test helpers usage](#test-helpers-usage)__
|
58
67
|
|
@@ -104,7 +113,7 @@ check docs & code (press `t` on github) if you miss something.
|
|
104
113
|
|
105
114
|
### ResourcesController
|
106
115
|
|
107
|
-
Similar to [
|
116
|
+
Similar to [InheritedResource](https://github.com/josevalim/inherited_resources)
|
108
117
|
but much simpler. It adds implementations for basic actions and
|
109
118
|
accessors for collection and resource. There is no options for almost everything,
|
110
119
|
but it's easy to extend.
|
@@ -127,14 +136,20 @@ class ProjectsController < ApplicationController
|
|
127
136
|
after_save_action: :index,
|
128
137
|
source_relation: -> { user.projects }
|
129
138
|
resource_helper :user
|
139
|
+
|
140
|
+
# or just
|
141
|
+
resources_controller sti: true,
|
142
|
+
after_save_action: :index,
|
143
|
+
belongs_to: :user
|
144
|
+
# `belongs_to: [:user, optional: true]` for shallow routes
|
145
|
+
|
130
146
|
permit_attrs :name
|
131
147
|
permit_attrs_for Project::External, :company
|
132
148
|
permit_attrs_for Project::Internal, :department
|
133
149
|
end
|
134
150
|
```
|
135
151
|
|
136
|
-
There is built-in support for pagination with Kaminari.
|
137
|
-
It's enabled automatically if `kaminari` gem is loaded.
|
152
|
+
There is built-in support for `has_scope` gem and pagination with Kaminari.
|
138
153
|
|
139
154
|
Currently depends on `gem 'responders', '> 2.0'`.
|
140
155
|
|
@@ -144,7 +159,7 @@ Currently depends on `gem 'responders', '> 2.0'`.
|
|
144
159
|
# in controller
|
145
160
|
extend RailsStuff::SortScope # when using without railtie
|
146
161
|
|
147
|
-
|
162
|
+
has_sort_scope by: [:name, :created_at, :balance], default: [:name]
|
148
163
|
|
149
164
|
# this scope will accept
|
150
165
|
# - `sort=name`
|
@@ -166,6 +181,17 @@ extend RailsStuff::NullifyBlankAttrs # when using without railtie
|
|
166
181
|
nullify_blank_attrs :email, :title
|
167
182
|
```
|
168
183
|
|
184
|
+
### AssociationWriter
|
185
|
+
|
186
|
+
ActiveRecord's association can be updated with object and by object id.
|
187
|
+
Owerwrite this both writers with single instruction:
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
association_writer :product do |val|
|
191
|
+
super(val).tap { update_price if product }
|
192
|
+
end
|
193
|
+
```
|
194
|
+
|
169
195
|
### RandomUniqAttr
|
170
196
|
|
171
197
|
Uses database's UNIQUE constraints and transactions to generate uniq random values.
|
@@ -194,8 +220,9 @@ end
|
|
194
220
|
|
195
221
|
```ruby
|
196
222
|
class User < ActiveRecord::Base
|
197
|
-
extend RailsStuff::
|
223
|
+
extend RailsStuff::Statusable # when using without railtie
|
198
224
|
|
225
|
+
# Setup with array and it'll store status values as strings.
|
199
226
|
STATUSES = %i(confirmed banned)
|
200
227
|
has_status_field # uses #status field and STATUSES as values
|
201
228
|
|
@@ -204,6 +231,12 @@ class User < ActiveRecord::Base
|
|
204
231
|
# :prefix is used for methods that are build
|
205
232
|
end
|
206
233
|
|
234
|
+
class Order < ActiveRecord::Base
|
235
|
+
# Provide hash, and it'll store mapped values in database.
|
236
|
+
STATUSES_MAPPING = {submitted: 1, confirmed: 2, delivered: 3}
|
237
|
+
has_status_field
|
238
|
+
end
|
239
|
+
|
207
240
|
user = User.first
|
208
241
|
|
209
242
|
# And you get:
|
@@ -212,6 +245,8 @@ User.confirmed.subs_active
|
|
212
245
|
User.not_banned.not_subs_expired
|
213
246
|
# Useful with has_scope
|
214
247
|
User.with_status(param[:status]).with_subscription_status(params[:subs_status])
|
248
|
+
# When using mapped values, scopes will accept status names:
|
249
|
+
Order.with_status(:confirmed)
|
215
250
|
|
216
251
|
# Translation & select helpers (requires activemodel_translation gem)
|
217
252
|
User.status_name(:active)
|
@@ -240,9 +275,7 @@ class Project
|
|
240
275
|
|
241
276
|
# If you want to show all available descendants in development
|
242
277
|
# (ex. in dropdown/select), you definitely want this:
|
243
|
-
|
244
|
-
# or pass folder explicitly:
|
245
|
-
eager_load_types!('lib/path/to/projects')
|
278
|
+
require_nested # will load all .rb files in app/models/project
|
246
279
|
end
|
247
280
|
|
248
281
|
class Project::Big < Project
|
@@ -324,6 +357,21 @@ params.require_permitted(:access_token, :refresh_token)
|
|
324
357
|
params.permit(:access_token, :refresh_token).require(:access_token, :refresh_token)
|
325
358
|
```
|
326
359
|
|
360
|
+
### RequireNested
|
361
|
+
|
362
|
+
```ruby
|
363
|
+
class Project < ApplicationRecord
|
364
|
+
# To load all files in app/models/project with require_dependency:
|
365
|
+
require_nested
|
366
|
+
# or pass folder explicitly:
|
367
|
+
require_nested('lib/path/to/projects')
|
368
|
+
|
369
|
+
# For non-rails apps use
|
370
|
+
RailsStuff::RequireNested.require_nested
|
371
|
+
# or call RailsStuff::RequireNested.setup to add #require_nested to Module
|
372
|
+
end
|
373
|
+
```
|
374
|
+
|
327
375
|
### Helpers usage
|
328
376
|
|
329
377
|
Include helper module into `ApplicationHelper`.
|
@@ -380,6 +428,21 @@ Note that `hashie` conflicts with `Hash` methods, so `.json_body.key` or
|
|
380
428
|
`.json_body.hash` will not work as expected (or at all).
|
381
429
|
Use `json_body['key']` instead.
|
382
430
|
|
431
|
+
There is `Configurator` with useful RSpec configs:
|
432
|
+
|
433
|
+
```ruby
|
434
|
+
RSpec.configure do |config|
|
435
|
+
RailsStuff::TestHelpers::Configurator.tap do |configurator|
|
436
|
+
# Setup DatabaseCleaner with basic settings:
|
437
|
+
configurator.database_cleaner(config)
|
438
|
+
# Flush redis after suite and exampes with `flush_redis: true`:
|
439
|
+
configurator.redis(config)
|
440
|
+
# Run debugger after failed tests:
|
441
|
+
configurator.debug(config)
|
442
|
+
end
|
443
|
+
end
|
444
|
+
```
|
445
|
+
|
383
446
|
### PluginManager
|
384
447
|
|
385
448
|
Provides simple way to create jQuery plugins. Create class and PluginManager
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module RailsStuff
|
2
|
+
# ActiveRecord's association can be updated with object and by object id.
|
3
|
+
# Owerwrite this both writers with single instruction:
|
4
|
+
#
|
5
|
+
# association_writer :product do |val|
|
6
|
+
# super(val).tap { update_price if product }
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
module AssociationWriter
|
10
|
+
def association_writer(name, &block)
|
11
|
+
define_method("#{name}=", &block)
|
12
|
+
define_method("#{name}_id=", &block)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/rails_stuff/engine.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
require 'rails/railtie'
|
2
2
|
|
3
3
|
module RailsStuff
|
4
|
-
MODULES = {
|
4
|
+
MODULES = { # rubocop:disable MutableConstant
|
5
|
+
require_nested: [:require, -> { RequireNested.setup }],
|
6
|
+
association_writer: :model,
|
5
7
|
nullify_blank_attrs: :model,
|
6
8
|
random_uniq_attr: :model,
|
7
9
|
statusable: :model,
|
8
10
|
resources_controller: [
|
9
11
|
:controller,
|
10
|
-
-> { ResourcesController.
|
12
|
+
-> { ResourcesController.use_kaminari! if defined?(::Kaminari) },
|
11
13
|
],
|
12
14
|
sort_scope: -> { defined?(::HasScope) && :controller },
|
13
15
|
strong_parameters: -> { defined?(ActionController::Parameters) && :require },
|
@@ -53,5 +55,9 @@ module RailsStuff
|
|
53
55
|
initializer :rails_stuff_setup_modules, after: :load_config_initializers do
|
54
56
|
RailsStuff.setup_modules!
|
55
57
|
end
|
58
|
+
|
59
|
+
generators do
|
60
|
+
require 'rails_stuff/generators/concern/concern_generator'
|
61
|
+
end
|
56
62
|
end
|
57
63
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Description:
|
2
|
+
Generates concern for the class.
|
3
|
+
|
4
|
+
Example:
|
5
|
+
`rails generate concern User::Authentication`
|
6
|
+
|
7
|
+
create app/models/user/authentication.rb
|
8
|
+
|
9
|
+
`rails generate concern admin_controller/localized`
|
10
|
+
|
11
|
+
create app/controllers/admin_controller/localized.rb
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
module RailsStuff
|
4
|
+
module Generators
|
5
|
+
class ConcernGenerator < Rails::Generators::NamedBase # :nodoc:
|
6
|
+
argument :base,
|
7
|
+
type: :string,
|
8
|
+
required: false,
|
9
|
+
banner: 'Base dir with the class',
|
10
|
+
description: 'Use it when class is not inside app/models or app/controllers.'
|
11
|
+
|
12
|
+
namespace 'rails:concern'
|
13
|
+
source_root File.expand_path('../templates', __FILE__)
|
14
|
+
|
15
|
+
def create_concern_files
|
16
|
+
template 'concern.rb', File.join(base_path, "#{file_path}.rb")
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def base_path
|
22
|
+
base || file_path.include?('_controller/') ? 'app/controllers' : 'app/models'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,10 +1,18 @@
|
|
1
1
|
module RailsStuff
|
2
2
|
module Helpers
|
3
3
|
module Translation
|
4
|
+
class << self
|
5
|
+
def i18n_raise
|
6
|
+
@i18n_raise ||= defined?(Rails) && ActionView::Base.raise_on_missing_translations
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_writer :i18n_raise
|
10
|
+
end
|
11
|
+
|
4
12
|
# Translates & caches actions within `helpers.actions` scope.
|
5
13
|
def translate_action(action)
|
6
14
|
@translate_action ||= Hash.new do |h, key|
|
7
|
-
h[key] = I18n.t("helpers.actions.#{key}")
|
15
|
+
h[key] = I18n.t("helpers.actions.#{key}", raise: Translation.i18n_raise)
|
8
16
|
end
|
9
17
|
@translate_action[action]
|
10
18
|
end
|
@@ -12,7 +20,10 @@ module RailsStuff
|
|
12
20
|
# Translates & caches confirmations within `helpers.confirmations` scope.
|
13
21
|
def translate_confirmation(action)
|
14
22
|
@translate_confirmation ||= Hash.new do |h, key|
|
15
|
-
h[key] = I18n.t("helpers.confirmations.#{key}",
|
23
|
+
h[key] = I18n.t("helpers.confirmations.#{key}",
|
24
|
+
default: [:'helpers.confirm'],
|
25
|
+
raise: Translation.i18n_raise,
|
26
|
+
)
|
16
27
|
end
|
17
28
|
@translate_confirmation[action]
|
18
29
|
end
|
@@ -20,7 +31,7 @@ module RailsStuff
|
|
20
31
|
# Translates boolean values.
|
21
32
|
def yes_no(val)
|
22
33
|
@translate_yes_no ||= Hash.new do |h, key|
|
23
|
-
h[key] = I18n.t("helpers.yes_no.#{key}")
|
34
|
+
h[key] = I18n.t("helpers.yes_no.#{key}", raise: Translation.i18n_raise)
|
24
35
|
end
|
25
36
|
@translate_yes_no[val.to_s]
|
26
37
|
end
|
@@ -10,6 +10,7 @@ module RailsStuff
|
|
10
10
|
#
|
11
11
|
module RandomUniqAttr
|
12
12
|
DEFAULT_GENERATOR = ->(*) { SecureRandom.hex(32) }
|
13
|
+
MAX_ATTEMPTS = 10
|
13
14
|
|
14
15
|
class << self
|
15
16
|
# Made from `Devise.firendly_token` with increased length.
|
@@ -23,9 +24,10 @@ module RailsStuff
|
|
23
24
|
#
|
24
25
|
# random_uniq_attr(:code) { |instance| my_random(instance) }
|
25
26
|
#
|
26
|
-
def random_uniq_attr(field, &block)
|
27
|
+
def random_uniq_attr(field, **options, &block)
|
27
28
|
set_method = :"set_#{field}"
|
28
29
|
generate_method = :"generate_#{field}"
|
30
|
+
max_attempts = options.fetch(:max_attempts) { MAX_ATTEMPTS }
|
29
31
|
|
30
32
|
after_create set_method, unless: :"#{field}?"
|
31
33
|
|
@@ -34,12 +36,15 @@ module RailsStuff
|
|
34
36
|
|
35
37
|
# def set_key
|
36
38
|
define_method(set_method) do
|
39
|
+
attempt = 0
|
37
40
|
begin
|
38
41
|
raise 'Available only for persisted record' unless persisted?
|
39
42
|
transaction(requires_new: true) do
|
40
43
|
update_column field, self.class.send(generate_method, self)
|
41
44
|
end
|
42
45
|
rescue ActiveRecord::RecordNotUnique
|
46
|
+
attempt += 1
|
47
|
+
raise if attempt > max_attempts
|
43
48
|
retry
|
44
49
|
end
|
45
50
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'active_support/dependencies'
|
2
|
+
|
3
|
+
module RailsStuff
|
4
|
+
module RequireNested
|
5
|
+
class << self
|
6
|
+
# Make #require_nested available in module.
|
7
|
+
def setup
|
8
|
+
Module.include(self)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module_function
|
13
|
+
|
14
|
+
# Requires nested modules with `require_dependency`.
|
15
|
+
# Pass custom directory to require its content.
|
16
|
+
# By default uses caller's filename with stripped `.rb` extension from.
|
17
|
+
def require_nested(dir = 0)
|
18
|
+
dir = caller_locations(dir + 1, 1)[0].path.sub(/\.rb$/, '') if dir.is_a?(Integer)
|
19
|
+
Dir["#{dir}/*.rb"].each { |file| require_dependency file }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -3,16 +3,6 @@ module RailsStuff
|
|
3
3
|
module BasicHelpers
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
|
-
class << self
|
7
|
-
# Make source_for_collection use Kaminari-style scopes
|
8
|
-
# to paginate relation.
|
9
|
-
def kaminari!
|
10
|
-
define_method(:source_for_collection) do
|
11
|
-
source_relation.page(params[:page]).per(params[:per])
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
6
|
included do
|
17
7
|
helper_method :resource, :collection
|
18
8
|
self.after_save_action = :show
|
@@ -51,17 +41,6 @@ module RailsStuff
|
|
51
41
|
permitted_attrs.concat attrs
|
52
42
|
end
|
53
43
|
|
54
|
-
# This method overrides default `has_scope`. It calls default implementation
|
55
|
-
# and overrides `collection` to use `apply_scope`.
|
56
|
-
def has_scope(*)
|
57
|
-
super.tap do
|
58
|
-
define_method :collection do
|
59
|
-
@_collection ||= apply_scopes(source_for_collection)
|
60
|
-
end
|
61
|
-
protected :collection
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
44
|
# Prevent CanCan's implementation.
|
66
45
|
def authorize_resource
|
67
46
|
raise 'use `before_action :authorize_resource!` instead'
|
@@ -117,15 +96,21 @@ module RailsStuff
|
|
117
96
|
def after_save_url
|
118
97
|
action = self.class.after_save_action
|
119
98
|
if action == :index
|
120
|
-
|
99
|
+
index_url
|
121
100
|
else
|
122
101
|
url_for action: action, id: resource
|
123
102
|
end
|
124
103
|
end
|
125
104
|
|
126
105
|
# URL to be used in `Location` header & to redirect after
|
127
|
-
# resource was destroyed. Default to
|
106
|
+
# resource was destroyed. Default to #index_url.
|
128
107
|
def after_destroy_url
|
108
|
+
index_url
|
109
|
+
end
|
110
|
+
|
111
|
+
# Extracted from #after_save_url and #after_destroy_url because they use
|
112
|
+
# same value. It's easier to override this urls in one place.
|
113
|
+
def index_url
|
129
114
|
url_for action: :index
|
130
115
|
end
|
131
116
|
|
@@ -134,7 +119,7 @@ module RailsStuff
|
|
134
119
|
def resource_params
|
135
120
|
@_resource_params ||= begin
|
136
121
|
key = self.class.resource_param_name
|
137
|
-
params.permit(key => permitted_attrs)[key] ||
|
122
|
+
params.permit(key => permitted_attrs)[key] || params.class.new
|
138
123
|
end
|
139
124
|
end
|
140
125
|
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module RailsStuff
|
2
|
+
module ResourcesController
|
3
|
+
module BelongsTo
|
4
|
+
class << self
|
5
|
+
# Builds lambda to use as `#source_relation`.
|
6
|
+
def source_relation(subject, collection, optional: false, **)
|
7
|
+
check_subject = :"#{subject}?"
|
8
|
+
lambda do
|
9
|
+
if optional && !send(check_subject)
|
10
|
+
super()
|
11
|
+
else
|
12
|
+
send(subject).public_send(collection)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Builds lambda to use as `#index_url`
|
18
|
+
def index_url(subject, *, field: nil, param: nil)
|
19
|
+
field ||= :"#{subject}_id"
|
20
|
+
param ||= field
|
21
|
+
-> { url_for action: :index, param => resource.public_send(field) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Defines resource helper and source relation
|
26
|
+
def resource_belongs_to(subject, resource_helper: true, urls: true, **options)
|
27
|
+
resource_helper(subject) if resource_helper
|
28
|
+
collection = options[:collection] || resource_class.model_name.plural
|
29
|
+
source_relation_proc = BelongsTo.source_relation(subject, collection, options)
|
30
|
+
protected define_method(:source_relation, &source_relation_proc)
|
31
|
+
protected define_method(:index_url, &BelongsTo.index_url(subject, urls)) if urls
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module RailsStuff
|
2
|
+
module ResourcesController
|
3
|
+
module HasScopeHelpers
|
4
|
+
# This method overrides default `has_scope` so it include helpers from
|
5
|
+
# InstanceMethods only when this method is called.
|
6
|
+
def has_scope(*)
|
7
|
+
super.tap { include InstanceMethods }
|
8
|
+
end
|
9
|
+
|
10
|
+
module InstanceMethods
|
11
|
+
protected
|
12
|
+
|
13
|
+
# Applies `has_scope` scopes to original source.
|
14
|
+
def source_for_collection
|
15
|
+
apply_scopes(super)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module RailsStuff
|
2
|
+
module ResourcesController
|
3
|
+
module KaminariHelpers
|
4
|
+
protected
|
5
|
+
|
6
|
+
# Make source_for_collection use Kaminari-style scopes to paginate relation.
|
7
|
+
def source_for_collection
|
8
|
+
super.page(params[:page]).per(params[:per])
|
9
|
+
end
|
10
|
+
|
11
|
+
module ConfigMethods
|
12
|
+
def use_kaminari!(val = true)
|
13
|
+
@use_kaminari = val
|
14
|
+
end
|
15
|
+
|
16
|
+
def use_kaminari?
|
17
|
+
@use_kaminari
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -15,15 +15,21 @@ module RailsStuff
|
|
15
15
|
# - `class` - class name, default to `resource_name.classify`
|
16
16
|
# - `param` - param name, default to `resource_name.foreign_key`
|
17
17
|
def resource_helper(resource_name, **options)
|
18
|
-
helper_method resource_name
|
18
|
+
helper_method resource_name, :"#{resource_name}?"
|
19
19
|
resource_name = resource_name.to_s
|
20
|
+
class_name = options[:class] || resource_name.classify
|
21
|
+
param_key = options[:param] || resource_name.foreign_key
|
20
22
|
|
21
23
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
24
|
+
protected
|
25
|
+
|
22
26
|
def #{resource_name}
|
23
|
-
@#{resource_name} ||= #{
|
24
|
-
|
27
|
+
@#{resource_name} ||= #{class_name}.find(params[:#{param_key}])
|
28
|
+
end
|
29
|
+
|
30
|
+
def #{resource_name}?
|
31
|
+
params.key?(:#{param_key})
|
25
32
|
end
|
26
|
-
protected :#{resource_name}
|
27
33
|
RUBY
|
28
34
|
end
|
29
35
|
end
|
@@ -7,34 +7,46 @@ module RailsStuff
|
|
7
7
|
module ResourcesController
|
8
8
|
extend ActiveSupport::Autoload
|
9
9
|
|
10
|
-
class << self
|
11
|
-
delegate :kaminari!, to: 'RailsStuff::ResourcesController::BasicHelpers'
|
12
|
-
end
|
13
|
-
|
14
10
|
autoload :Actions
|
15
11
|
autoload :BasicHelpers
|
12
|
+
autoload :BelongsTo
|
13
|
+
autoload :HasScopeHelpers
|
14
|
+
autoload :KaminariHelpers
|
15
|
+
autoload :ResourceHelper
|
16
16
|
autoload :Responder
|
17
17
|
autoload :StiHelpers
|
18
|
-
|
18
|
+
|
19
|
+
extend KaminariHelpers::ConfigMethods
|
20
|
+
|
21
|
+
class << self
|
22
|
+
def inject(base, **options)
|
23
|
+
base.include BasicHelpers
|
24
|
+
base.include KaminariHelpers if options.fetch(:kaminari) { use_kaminari? }
|
25
|
+
base.include StiHelpers if options[:sti]
|
26
|
+
base.include Actions
|
27
|
+
base.extend HasScopeHelpers
|
28
|
+
base.extend ResourceHelper
|
29
|
+
base.extend BelongsTo
|
30
|
+
end
|
31
|
+
end
|
19
32
|
|
20
33
|
# Setups basic actions and helpers in resources controller.
|
21
34
|
#
|
22
35
|
# #### Options
|
23
36
|
#
|
24
37
|
# - `sti` - include STI helpers
|
38
|
+
# - `kaminari` - include Kaminari helpers
|
25
39
|
# - `after_save_action` - action to use for `after_save_url`
|
26
40
|
# - `source_relation` - override `source_relation`
|
27
41
|
def resources_controller(**options)
|
28
|
-
|
29
|
-
include StiHelpers if options[:sti]
|
30
|
-
include Actions
|
31
|
-
extend ResourceHelper
|
42
|
+
ResourcesController.inject(self, **options)
|
32
43
|
|
33
44
|
respond_to :html
|
34
45
|
self.responder = Responder
|
35
46
|
self.after_save_action = options[:after_save_action] || after_save_action
|
36
47
|
|
37
|
-
|
48
|
+
resource_belongs_to(*options[:belongs_to]) if options[:belongs_to]
|
49
|
+
if options[:source_relation]
|
38
50
|
protected define_method(:source_relation, &options[:source_relation])
|
39
51
|
end
|
40
52
|
end
|
@@ -23,7 +23,8 @@ module RailsStuff
|
|
23
23
|
#
|
24
24
|
# - `by` - array of available fields to sort by,
|
25
25
|
# - `default` - default sort expression,
|
26
|
-
# - `only` - bypassed to `has_scope` to limit actions (default to `:index`)
|
26
|
+
# - `only` - bypassed to `has_scope` to limit actions (default to `:index`),
|
27
|
+
# - `order_method` - use custom method to sort instead of `.order`.
|
27
28
|
#
|
28
29
|
# rubocop:disable ClassVars
|
29
30
|
def has_sort_scope(config = {})
|
@@ -31,6 +32,7 @@ module RailsStuff
|
|
31
32
|
default = config[:default] || :id
|
32
33
|
allowed = Array.wrap(config[:by]).map(&:to_s)
|
33
34
|
only_actions = config.fetch(:only, :index)
|
35
|
+
order_method = config.fetch(:order_method, :order)
|
34
36
|
# Counter added into scope name to allow to define multiple scopes in same controller.
|
35
37
|
has_scope("sort_#{@@_sort_scope_id += 1}",
|
36
38
|
as: :sort,
|
@@ -39,7 +41,8 @@ module RailsStuff
|
|
39
41
|
only: only_actions,
|
40
42
|
type: :any,
|
41
43
|
) do |c, scope, val|
|
42
|
-
|
44
|
+
sort_args = SortScope.filter_param(val, c.params, allowed, default)
|
45
|
+
scope.public_send(order_method, sort_args)
|
43
46
|
end
|
44
47
|
end
|
45
48
|
# rubocop:enable ClassVars
|
@@ -15,10 +15,13 @@ module RailsStuff
|
|
15
15
|
# - `#status_sym`
|
16
16
|
# - `status_select_options` helper.
|
17
17
|
#
|
18
|
+
# It supports mapped statuses, just provide a hash with
|
19
|
+
# `{status_name => interna_value}` instead of array of statuses.
|
18
20
|
module Statusable
|
19
21
|
# Defines all helpers working with `field` (default to `status`).
|
20
22
|
# List of values can be given as second argument, otherwise it'll
|
21
|
-
# be read from
|
23
|
+
# be read from consts using pluralized name of `field`
|
24
|
+
# (eg. default to `STATUSES_MAPPING`, `STATUSES`).
|
22
25
|
#
|
23
26
|
# #### Options
|
24
27
|
#
|
@@ -28,29 +31,19 @@ module RailsStuff
|
|
28
31
|
# has_status_field :delivery_status, %i(shipped delivered)
|
29
32
|
#
|
30
33
|
# # this defines #delivery_shipped?, #delivery_shipped! methods
|
31
|
-
# has_status_field :delivery_status, %i(shipped delivered), prefix: :
|
34
|
+
# has_status_field :delivery_status, %i(shipped delivered), prefix: :delivery_
|
35
|
+
#
|
36
|
+
# - `suffix` - similar to `prefix`.
|
32
37
|
#
|
33
38
|
# - `validate` - additional options for validatior. `false` to disable it.
|
34
|
-
def has_status_field(field = :status, statuses = nil, **options)
|
35
|
-
statuses
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
validates_inclusion_of field,
|
40
|
-
{in: statuses.map(&:to_s)}.merge!(options.fetch(:validate, {}))
|
41
|
-
end
|
42
|
-
|
43
|
-
statusable_methods.generate_field_methods field, statuses
|
44
|
-
|
45
|
-
# Scope with given status. Useful for has_scope.
|
46
|
-
scope "with_#{field}", ->(status) { where(field => status) }
|
47
|
-
|
48
|
-
statuses.map(&:to_s).each do |status_name|
|
49
|
-
# Scopes for every status.
|
50
|
-
scope "#{prefix}#{status_name}", -> { where(field => status_name) }
|
51
|
-
scope "not_#{prefix}#{status_name}", -> { where.not(field => status_name) }
|
52
|
-
statusable_methods.status_accessor field, status_name, prefix
|
39
|
+
def has_status_field(field = :status, statuses = nil, **options)
|
40
|
+
unless statuses
|
41
|
+
const_name = "#{field.to_s.pluralize.upcase}_MAPPING"
|
42
|
+
const_name = field.to_s.pluralize.upcase unless const_defined?(const_name)
|
43
|
+
statuses = const_get(const_name)
|
53
44
|
end
|
45
|
+
generator = statuses.is_a?(Hash) ? MappedBuilder : Builder
|
46
|
+
generator.new(self, field, statuses, options).generate
|
54
47
|
end
|
55
48
|
|
56
49
|
# Module to hold generated methods.
|
@@ -58,49 +51,94 @@ module RailsStuff
|
|
58
51
|
# Include generated methods with a module, not right in class.
|
59
52
|
@statusable_methods ||= Module.new.tap do |m|
|
60
53
|
m.const_set :ClassMethods, Module.new
|
61
|
-
m.extend MethodsGenerator
|
62
54
|
include m
|
63
55
|
extend m::ClassMethods
|
64
56
|
end
|
65
57
|
end
|
66
58
|
|
67
|
-
# Generates methods.
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
59
|
+
# Generates methods and scopes.
|
60
|
+
class Builder
|
61
|
+
attr_reader :model, :field, :statuses, :options, :prefix, :suffix
|
62
|
+
alias_method :statuses_list, :statuses
|
63
|
+
|
64
|
+
def initialize(model, field, statuses, **options)
|
65
|
+
@model = model
|
66
|
+
@field = field
|
67
|
+
@statuses = statuses
|
68
|
+
@options = options
|
69
|
+
@prefix = options[:prefix]
|
70
|
+
@suffix = options[:suffix]
|
71
|
+
end
|
72
|
+
|
73
|
+
def generate
|
74
|
+
validations unless options[:validate] == false
|
75
|
+
field_reader
|
76
|
+
field_writer
|
77
|
+
select_options_helper
|
78
|
+
translation_helpers
|
79
|
+
field_scope
|
80
|
+
value_methods
|
81
|
+
end
|
82
|
+
|
83
|
+
def validations
|
84
|
+
model.validates_inclusion_of field,
|
85
|
+
{in: statuses.map(&:to_s)}.merge!(options.fetch(:validate, {}))
|
86
|
+
end
|
87
|
+
|
88
|
+
# Scope with given status. Useful for has_scope.
|
89
|
+
def field_scope
|
90
|
+
field = self.field
|
91
|
+
define_scope "with_#{field}", ->(status) { where(field => status) }
|
92
|
+
end
|
93
|
+
|
94
|
+
def value_methods
|
95
|
+
field = self.field
|
96
|
+
statuses.map(&:to_s).each do |status_name|
|
97
|
+
# Scopes for every status.
|
98
|
+
define_scope "#{prefix}#{status_name}#{suffix}",
|
99
|
+
-> { where(field => status_name) }
|
100
|
+
define_scope "not_#{prefix}#{status_name}#{suffix}",
|
101
|
+
-> { where.not(field => status_name) }
|
102
|
+
status_accessor status_name, status_name
|
103
|
+
end
|
74
104
|
end
|
75
105
|
|
76
106
|
# Generates methods for specific value.
|
77
|
-
def status_accessor(
|
107
|
+
def status_accessor(status_name, value)
|
108
|
+
field = self.field
|
109
|
+
|
78
110
|
# Shortcut to check status.
|
79
|
-
define_method "#{prefix}#{status_name}?" do
|
80
|
-
self[field] ==
|
111
|
+
define_method "#{prefix}#{status_name}#{suffix}?" do
|
112
|
+
self[field] == value
|
81
113
|
end
|
82
114
|
|
83
115
|
# Shortcut to update status.
|
84
|
-
define_method "#{prefix}#{status_name}!" do
|
85
|
-
update_attributes!(field =>
|
116
|
+
define_method "#{prefix}#{status_name}#{suffix}!" do
|
117
|
+
update_attributes!(field => value)
|
86
118
|
end
|
87
119
|
end
|
88
120
|
|
89
|
-
|
90
|
-
|
121
|
+
# Make field accept sympbols.
|
122
|
+
def field_writer
|
91
123
|
define_method "#{field}=" do |val|
|
92
124
|
val = val.to_s if val.is_a?(Symbol)
|
93
125
|
super(val)
|
94
126
|
end
|
127
|
+
end
|
95
128
|
|
96
|
-
|
129
|
+
# Status as symbol.
|
130
|
+
def field_reader
|
131
|
+
field = self.field
|
97
132
|
define_method "#{field}_sym" do
|
98
|
-
val =
|
133
|
+
val = send(field)
|
99
134
|
val && val.to_sym
|
100
135
|
end
|
101
136
|
end
|
102
137
|
|
103
|
-
def translation_helpers
|
138
|
+
def translation_helpers
|
139
|
+
field = self.field
|
140
|
+
sym_method = "#{field}_sym"
|
141
|
+
|
104
142
|
# Class-level translation helper.
|
105
143
|
generate_class_method "#{field}_name" do |status|
|
106
144
|
t(".#{field}_name.#{status}") if status
|
@@ -108,22 +146,99 @@ module RailsStuff
|
|
108
146
|
|
109
147
|
# Translation helper.
|
110
148
|
define_method "#{field}_name" do
|
111
|
-
val = send
|
149
|
+
val = send(sym_method)
|
112
150
|
self.class.t(".#{field}_name.#{val}") if val
|
113
151
|
end
|
114
152
|
end
|
115
153
|
|
116
|
-
def select_options_helper
|
154
|
+
def select_options_helper
|
155
|
+
statuses_list = self.statuses_list
|
117
156
|
translation_method = :"#{field}_name"
|
118
157
|
# Returns array compatible with select_options helper.
|
119
158
|
generate_class_method "#{field}_select_options" do |args = {}|
|
120
|
-
filtered_statuses =
|
159
|
+
filtered_statuses = statuses_list - Array.wrap(args[:except])
|
121
160
|
filtered_statuses.map { |x| [send(translation_method, x), x] }
|
122
161
|
end
|
123
162
|
end
|
124
163
|
|
164
|
+
# Rails 4 doesn't use `instance_exec` for scopes, so we do it manually.
|
165
|
+
def define_scope(name, body)
|
166
|
+
model.singleton_class.send(:define_method, name) do |*args|
|
167
|
+
all.scoping { instance_exec(*args, &body) } || all
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def define_method(method, &block)
|
172
|
+
model.statusable_methods.send(:define_method, method, &block)
|
173
|
+
end
|
174
|
+
|
125
175
|
def generate_class_method(method, &block)
|
126
|
-
|
176
|
+
model.statusable_methods::ClassMethods.send(:define_method, method, &block)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Generates methods and scopes when status names are mapped to internal values.
|
181
|
+
class MappedBuilder < Builder
|
182
|
+
attr_reader :mapping, :statuses_list
|
183
|
+
|
184
|
+
def initialize(*)
|
185
|
+
super
|
186
|
+
@mapping = statuses.with_indifferent_access
|
187
|
+
@statuses_list = statuses.keys
|
188
|
+
end
|
189
|
+
|
190
|
+
def validations
|
191
|
+
model.validates_inclusion_of field,
|
192
|
+
{in: statuses.values}.merge!(options.fetch(:validate, {}))
|
193
|
+
end
|
194
|
+
|
195
|
+
# Scope with given status. Useful for has_scope.
|
196
|
+
def field_scope
|
197
|
+
field = self.field
|
198
|
+
mapping = self.mapping
|
199
|
+
define_scope "with_#{field}", ->(status) do
|
200
|
+
values = Array.wrap(status).map { |x| mapping.fetch(x, x) }
|
201
|
+
where(field => values)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def value_methods
|
206
|
+
field = self.field
|
207
|
+
statuses.each do |status_name, value|
|
208
|
+
# Scopes for every status.
|
209
|
+
define_scope "#{prefix}#{status_name}#{suffix}", -> { where(field => value) }
|
210
|
+
define_scope "not_#{prefix}#{status_name}#{suffix}", -> { where.not(field => value) }
|
211
|
+
status_accessor status_name, value
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def field_reader
|
216
|
+
field = self.field
|
217
|
+
inverse_mapping = statuses.stringify_keys.invert
|
218
|
+
|
219
|
+
# Returns status name.
|
220
|
+
define_method field do |mapped = false|
|
221
|
+
val = super()
|
222
|
+
return val unless mapped && val
|
223
|
+
mapped = inverse_mapping[val]
|
224
|
+
raise "Missing mapping for value #{val.inspect}" unless mapped
|
225
|
+
mapped
|
226
|
+
end
|
227
|
+
|
228
|
+
# Status as symbol.
|
229
|
+
define_method "#{field}_sym" do
|
230
|
+
val = public_send(field, true)
|
231
|
+
val && val.to_sym
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def field_writer
|
236
|
+
mapping = self.mapping
|
237
|
+
# Make field accept sympbols.
|
238
|
+
define_method "#{field}=" do |val|
|
239
|
+
val = val.to_s if val.is_a?(Symbol)
|
240
|
+
super(mapping.fetch(val, val))
|
241
|
+
end
|
127
242
|
end
|
128
243
|
end
|
129
244
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/core_ext/array/wrap'
|
3
|
+
|
4
|
+
module RailsStuff
|
5
|
+
module TestHelpers
|
6
|
+
module Concurrency
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# Default threads count
|
11
|
+
attr_accessor :threads_count
|
12
|
+
end
|
13
|
+
@threads_count = 3
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
# Defines subject which runs parent's value in multiple threads concurrently.
|
17
|
+
# Define `thread_args` or `threads_count` with `let` to configure it.
|
18
|
+
def concurrent_subject!
|
19
|
+
metadata[:concurrent] = true
|
20
|
+
subject do
|
21
|
+
super_proc = super()
|
22
|
+
args = defined?(thread_args) && thread_args
|
23
|
+
args ||= defined?(threads_count) && threads_count
|
24
|
+
-> { concurrently(args, &super_proc) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Runs given block in current context and nested context with concurrent subject.
|
29
|
+
#
|
30
|
+
# subject { -> { increment_value_once } }
|
31
|
+
# # This will create 2 examples. One for current contex, and one
|
32
|
+
# # for current context where subject will run multiple times concurrently.
|
33
|
+
# check_concurrent do
|
34
|
+
# it { should change { value }.by(1) }
|
35
|
+
# end
|
36
|
+
def check_concurrent(&block)
|
37
|
+
instance_eval(&block)
|
38
|
+
context 'running multiple times concurrently' do
|
39
|
+
concurrent_subject!
|
40
|
+
instance_eval(&block)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Runs block concurrently in separate threads.
|
46
|
+
# Pass array of args arrays to run each thread with its own arguments.
|
47
|
+
# Or pass Integer to run specified threads count with same arguments.
|
48
|
+
# Default is to run Concurrency.threads_count threads.
|
49
|
+
#
|
50
|
+
# concurrently { do_something }
|
51
|
+
# concurrently(5) { do_something }
|
52
|
+
# concurrently([[1, opt: true], [2, opt: false]]) do |arg, **options|
|
53
|
+
# do_something(arg, options)
|
54
|
+
# end
|
55
|
+
# # It'll automatically wrap single args into Array:
|
56
|
+
# concurrently(1, 2, {opt: true}, {opt: false}, [1, opt: false]) { ... }
|
57
|
+
#
|
58
|
+
def concurrently(thread_args = nil)
|
59
|
+
thread_args ||= Concurrency.threads_count
|
60
|
+
threads =
|
61
|
+
case thread_args
|
62
|
+
when Integer
|
63
|
+
Array.new(thread_args) { Thread.new { yield } }
|
64
|
+
else
|
65
|
+
thread_args.map { |args| Thread.new { yield(*Array.wrap(args)) } }
|
66
|
+
end
|
67
|
+
threads.each(&:join)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module RailsStuff
|
2
|
+
module TestHelpers
|
3
|
+
# Collection of useful RSpec configurations.
|
4
|
+
#
|
5
|
+
# RailsStuff::TestHelpers::Configurator.tap do |configurator|
|
6
|
+
# configurator.database_cleaner(config)
|
7
|
+
# # ...
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
module Configurator
|
11
|
+
module_function
|
12
|
+
|
13
|
+
# Setups database cleaner to use strategy depending on metadata.
|
14
|
+
# By default it uses `:transaction` for all examples and `:truncation`
|
15
|
+
# for features and examples with `concurrent: true`.
|
16
|
+
#
|
17
|
+
# Other types can be tuned with `config.cleaner_strategy` hash &
|
18
|
+
# `config.cleaner_strategy.default`.
|
19
|
+
def database_cleaner(config)
|
20
|
+
config.use_transactional_fixtures = false
|
21
|
+
config.add_setting :cleaner_strategy
|
22
|
+
config.cleaner_strategy = {feature: :truncation}
|
23
|
+
config.cleaner_strategy.default = :transaction
|
24
|
+
config.around do |ex|
|
25
|
+
strategy = ex.metadata[:concurrent] && :truncation
|
26
|
+
strategy ||= config.cleaner_strategy[ex.metadata[:type]]
|
27
|
+
options = strategy == :truncation ? {except: %w(spatial_ref_sys)} : {}
|
28
|
+
DatabaseCleaner.strategy = strategy, options
|
29
|
+
DatabaseCleaner.cleaning { ex.run }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Setups redis to flush db after suite and before each example with
|
34
|
+
# `flush_redis: :true`. `Rails.redis` client is used by default.
|
35
|
+
# Can be tuned with `config.redis`.
|
36
|
+
def redis(config)
|
37
|
+
config.add_setting :redis
|
38
|
+
config.redis = Rails.redis if defined?(Rails.redis)
|
39
|
+
config.before { |ex| config.redis.flushdb if ex.metadata[:flush_redis] }
|
40
|
+
config.after(:suite) { config.redis.flushdb }
|
41
|
+
end
|
42
|
+
|
43
|
+
# Runs debugger after each failed example. Uses `pry` by default and
|
44
|
+
# runs only for examples with `:debug` tag. This can be configured
|
45
|
+
# with `:debugger` and `:filter` options respectively:
|
46
|
+
#
|
47
|
+
# configurator.debug(config, filter: {my_tag: :val}, debugger: true)
|
48
|
+
# # to disable filter:
|
49
|
+
# configurator.debug(config, filter: nil)
|
50
|
+
def debug(config, filter: {debug: true}, debugger: :pry)
|
51
|
+
config.after(filter) do |ex|
|
52
|
+
if ex.exception
|
53
|
+
debugger == :pry ? binding.pry : self.debugger # rubocop:disable Debugger
|
54
|
+
ex.exception # noop
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'active_support/core_ext/class/attribute'
|
2
|
+
require 'active_support/deprecation'
|
2
3
|
|
3
4
|
module RailsStuff
|
4
5
|
# Adds `types_list` method which tracks all descendants.
|
@@ -29,7 +30,7 @@ module RailsStuff
|
|
29
30
|
else
|
30
31
|
types_list << self
|
31
32
|
end
|
32
|
-
if types_tracker_base.respond_to?(:scope) &&
|
33
|
+
if types_tracker_base.respond_to?(:scope) &&
|
33
34
|
!types_tracker_base.respond_to?(model_name.element)
|
34
35
|
type_name = name
|
35
36
|
types_tracker_base.scope model_name.element, -> { where(type: type_name) }
|
@@ -44,10 +45,13 @@ module RailsStuff
|
|
44
45
|
|
45
46
|
# Shortcut to eager load all descendants.
|
46
47
|
def eager_load_types!(dir = nil)
|
47
|
-
dir
|
48
|
-
Dir["#{dir}/*.rb"].each { |file| require_dependency file }
|
48
|
+
RequireNested.require_nested(dir || 2) # skip 1 more because of deprecation
|
49
49
|
end
|
50
50
|
|
51
|
+
ActiveSupport::Deprecation.deprecate_methods self,
|
52
|
+
deprecator: ActiveSupport::Deprecation.new('0.6', 'RailsStuff'),
|
53
|
+
eager_load_types!: 'Use RequireNested.require_nested instead'
|
54
|
+
|
51
55
|
# Tracks all descendants automatically.
|
52
56
|
def inherited(base)
|
53
57
|
super
|
data/lib/rails_stuff/version.rb
CHANGED
data/lib/rails_stuff.rb
CHANGED
@@ -6,13 +6,15 @@ module RailsStuff
|
|
6
6
|
extend ActiveSupport::Autoload
|
7
7
|
|
8
8
|
autoload :Helpers
|
9
|
+
autoload :AssociationWriter
|
9
10
|
autoload :NullifyBlankAttrs
|
10
11
|
autoload :ParamsParser
|
11
12
|
autoload :RandomUniqAttr
|
12
13
|
autoload :RedisStorage
|
14
|
+
autoload :RequireNested
|
13
15
|
autoload :ResourcesController
|
14
|
-
autoload :Statusable
|
15
16
|
autoload :SortScope
|
17
|
+
autoload :Statusable
|
16
18
|
autoload :TypesTracker
|
17
19
|
end
|
18
20
|
|
data/rails_stuff.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |spec|
|
|
7
7
|
spec.name = 'rails_stuff'
|
8
8
|
spec.version = RailsStuff::VERSION::STRING
|
9
9
|
spec.authors = ['Max Melentiev']
|
10
|
-
spec.email = ['
|
10
|
+
spec.email = ['melentievm@gmail.com']
|
11
11
|
|
12
12
|
spec.summary = 'Collection of useful modules for Rails'
|
13
13
|
spec.homepage = 'https://github.com/printercu/rails_stuff'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_stuff
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Max Melentiev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-10-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -54,7 +54,7 @@ dependencies:
|
|
54
54
|
version: '10.0'
|
55
55
|
description:
|
56
56
|
email:
|
57
|
-
-
|
57
|
+
- melentievm@gmail.com
|
58
58
|
executables: []
|
59
59
|
extensions: []
|
60
60
|
extra_rdoc_files: []
|
@@ -76,7 +76,11 @@ files:
|
|
76
76
|
- lib/assets/stylesheets/rails_stuff/_media_queries.scss
|
77
77
|
- lib/net/http/debug.rb
|
78
78
|
- lib/rails_stuff.rb
|
79
|
+
- lib/rails_stuff/association_writer.rb
|
79
80
|
- lib/rails_stuff/engine.rb
|
81
|
+
- lib/rails_stuff/generators/concern/USAGE
|
82
|
+
- lib/rails_stuff/generators/concern/concern_generator.rb
|
83
|
+
- lib/rails_stuff/generators/concern/templates/concern.rb
|
80
84
|
- lib/rails_stuff/helpers.rb
|
81
85
|
- lib/rails_stuff/helpers/all.rb
|
82
86
|
- lib/rails_stuff/helpers/bootstrap.rb
|
@@ -89,15 +93,21 @@ files:
|
|
89
93
|
- lib/rails_stuff/params_parser.rb
|
90
94
|
- lib/rails_stuff/random_uniq_attr.rb
|
91
95
|
- lib/rails_stuff/redis_storage.rb
|
96
|
+
- lib/rails_stuff/require_nested.rb
|
92
97
|
- lib/rails_stuff/resources_controller.rb
|
93
98
|
- lib/rails_stuff/resources_controller/actions.rb
|
94
99
|
- lib/rails_stuff/resources_controller/basic_helpers.rb
|
100
|
+
- lib/rails_stuff/resources_controller/belongs_to.rb
|
101
|
+
- lib/rails_stuff/resources_controller/has_scope_helpers.rb
|
102
|
+
- lib/rails_stuff/resources_controller/kaminari_helpers.rb
|
95
103
|
- lib/rails_stuff/resources_controller/resource_helper.rb
|
96
104
|
- lib/rails_stuff/resources_controller/responder.rb
|
97
105
|
- lib/rails_stuff/resources_controller/sti_helpers.rb
|
98
106
|
- lib/rails_stuff/sort_scope.rb
|
99
107
|
- lib/rails_stuff/statusable.rb
|
100
108
|
- lib/rails_stuff/strong_parameters.rb
|
109
|
+
- lib/rails_stuff/test_helpers/concurrency.rb
|
110
|
+
- lib/rails_stuff/test_helpers/configurator.rb
|
101
111
|
- lib/rails_stuff/test_helpers/rails.rb
|
102
112
|
- lib/rails_stuff/test_helpers/response.rb
|
103
113
|
- lib/rails_stuff/types_tracker.rb
|
@@ -124,9 +134,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
124
134
|
version: '0'
|
125
135
|
requirements: []
|
126
136
|
rubyforge_project:
|
127
|
-
rubygems_version: 2.
|
137
|
+
rubygems_version: 2.5.1
|
128
138
|
signing_key:
|
129
139
|
specification_version: 4
|
130
140
|
summary: Collection of useful modules for Rails
|
131
141
|
test_files: []
|
132
|
-
has_rdoc:
|