rails_stuff 0.4.0 → 0.5.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 +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:
|