rails_stuff 0.1.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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.rubocop.yml +24 -0
- data/.travis.yml +8 -0
- data/Gemfile +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +350 -0
- data/Rakefile +22 -0
- data/bin/console +7 -0
- data/bin/git-hooks/pre-commit +14 -0
- data/bin/install_git_hooks +8 -0
- data/bin/setup +8 -0
- data/lib/net/http/debug.rb +26 -0
- data/lib/rails_stuff/helpers/all.rb +12 -0
- data/lib/rails_stuff/helpers/bootstrap.rb +34 -0
- data/lib/rails_stuff/helpers/forms.rb +21 -0
- data/lib/rails_stuff/helpers/links.rb +38 -0
- data/lib/rails_stuff/helpers/resource_form.rb +49 -0
- data/lib/rails_stuff/helpers/text.rb +28 -0
- data/lib/rails_stuff/helpers/translation.rb +29 -0
- data/lib/rails_stuff/helpers.rb +14 -0
- data/lib/rails_stuff/nullify_blank_attrs.rb +23 -0
- data/lib/rails_stuff/params_parser.rb +121 -0
- data/lib/rails_stuff/railtie.rb +54 -0
- data/lib/rails_stuff/random_uniq_attr.rb +48 -0
- data/lib/rails_stuff/redis_storage.rb +119 -0
- data/lib/rails_stuff/resources_controller/actions.rb +31 -0
- data/lib/rails_stuff/resources_controller/basic_helpers.rb +161 -0
- data/lib/rails_stuff/resources_controller/resource_helper.rb +31 -0
- data/lib/rails_stuff/resources_controller/responder.rb +21 -0
- data/lib/rails_stuff/resources_controller/sti_helpers.rb +62 -0
- data/lib/rails_stuff/resources_controller.rb +42 -0
- data/lib/rails_stuff/sort_scope.rb +71 -0
- data/lib/rails_stuff/statusable.rb +130 -0
- data/lib/rails_stuff/test_helpers/rails.rb +6 -0
- data/lib/rails_stuff/test_helpers/response.rb +34 -0
- data/lib/rails_stuff/types_tracker.rb +50 -0
- data/lib/rails_stuff/version.rb +14 -0
- data/lib/rails_stuff.rb +19 -0
- data/rails_stuff.gemspec +25 -0
- metadata +126 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'active_support/core_ext/array/wrap'
|
2
|
+
module RailsStuff
|
3
|
+
module Helpers
|
4
|
+
# Provides helper for SimpleForm.
|
5
|
+
module ResourceForm
|
6
|
+
# Generates `resource_form` helper to display form with basic arguments,
|
7
|
+
# elements, errors and options. Generated method can work without arguments
|
8
|
+
# in most of cases: it takes object from `resource` method.
|
9
|
+
#
|
10
|
+
# Use `namespace` to add additional path parts to form action:
|
11
|
+
#
|
12
|
+
# # this one will use [:site, resource]
|
13
|
+
# resource_form_for :site
|
14
|
+
#
|
15
|
+
# #### Options
|
16
|
+
#
|
17
|
+
# - `back_url` - default back url. Can be string with code, or hash for `url_for`.
|
18
|
+
# - `resource_method` - method to take resource from.
|
19
|
+
# - `method` - name of generated method.
|
20
|
+
#
|
21
|
+
def resource_form_for(namespace = nil, **options)
|
22
|
+
default_back_url =
|
23
|
+
case options[:back_url]
|
24
|
+
when Hash then "url_for(#{options[:back_url]})"
|
25
|
+
when String then options[:back_url]
|
26
|
+
else 'url_for(object)'
|
27
|
+
end
|
28
|
+
resource_method = options.fetch(:resource_method, :resource)
|
29
|
+
method_name = options.fetch(:method, :resource_form)
|
30
|
+
object_arg = (Array.wrap(namespace).map(&:inspect) + [resource_method]).join(', ')
|
31
|
+
|
32
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
33
|
+
def #{method_name}(object = [#{object_arg}], **options)
|
34
|
+
back_url = options.delete(:back_url) || #{default_back_url}
|
35
|
+
simple_form_for object, options do |f|
|
36
|
+
html = ActiveSupport::SafeBuffer.new
|
37
|
+
msg = f.object.errors[:base].first
|
38
|
+
html << content_tag(:div, msg, class: 'alert alert-danger') if msg
|
39
|
+
html << capture { yield(f) }
|
40
|
+
html << f.button(:submit, class: 'btn-primary')
|
41
|
+
html << ' '
|
42
|
+
html << link_to(translate_action(:cancel), back_url, class: :btn)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
RUBY
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module RailsStuff
|
2
|
+
module Helpers
|
3
|
+
module Text
|
4
|
+
# Replaces blank values with cached placeholder from translations.
|
5
|
+
# When called with block, it'll check value for blankness, but returns
|
6
|
+
# block's result if value is present.
|
7
|
+
#
|
8
|
+
# replace_blank(description)
|
9
|
+
# replace_blank(tags) { tags.join(', ') }
|
10
|
+
# replace_blank(order.paid_at) { |x| l x, format: :long }
|
11
|
+
#
|
12
|
+
def replace_blank(value, &block)
|
13
|
+
if value.blank?
|
14
|
+
blank_placeholder
|
15
|
+
else
|
16
|
+
block_given? ? capture(value, &block) : value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Default placeholder value.
|
21
|
+
def blank_placeholder
|
22
|
+
@_blank_placeholder ||= content_tag :small,
|
23
|
+
"(#{I18n.t(:'helpers.placeholder.blank', default: '-')})",
|
24
|
+
class: :'text-muted'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module RailsStuff
|
2
|
+
module Helpers
|
3
|
+
module Translation
|
4
|
+
# Translates & caches actions within `helpers.actions` scope.
|
5
|
+
def translate_action(action)
|
6
|
+
@translate_action ||= Hash.new do |h, key|
|
7
|
+
h[key] = I18n.t("helpers.actions.#{key}")
|
8
|
+
end
|
9
|
+
@translate_action[action]
|
10
|
+
end
|
11
|
+
|
12
|
+
# Translates & caches confirmations within `helpers.confirmations` scope.
|
13
|
+
def translate_confirmation(action)
|
14
|
+
@translate_confirmation ||= Hash.new do |h, key|
|
15
|
+
h[key] = I18n.t("helpers.confirmations.#{key}", default: [:'helpers.confirm'])
|
16
|
+
end
|
17
|
+
@translate_confirmation[action]
|
18
|
+
end
|
19
|
+
|
20
|
+
# Translates boolean values.
|
21
|
+
def yes_no(val)
|
22
|
+
@translate_yes_no ||= Hash.new do |h, key|
|
23
|
+
h[key] = I18n.t("helpers.yes_no.#{key}")
|
24
|
+
end
|
25
|
+
@translate_yes_no[val.to_s]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'active_support/core_ext/object/blank'
|
2
|
+
|
3
|
+
module RailsStuff
|
4
|
+
# Changes to `nil` assigned blank attributes.
|
5
|
+
#
|
6
|
+
# class App
|
7
|
+
# nullify_blank_attrs :site_url
|
8
|
+
# # ...
|
9
|
+
module NullifyBlankAttrs
|
10
|
+
def nullify_blank_attrs(*attrs)
|
11
|
+
nullify_blank_attrs_methods.class_eval do
|
12
|
+
attrs.each do |attr|
|
13
|
+
define_method("#{attr}=") { |val| super(val.presence) }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Module to store generated methods, so they can be overriden in model.
|
19
|
+
def nullify_blank_attrs_methods
|
20
|
+
@nullify_blank_attrs_methods ||= Module.new.tap { |x| prepend x }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module RailsStuff
|
2
|
+
# Provides parsing and type-casting functions.
|
3
|
+
# Reraises all ocured errors with Error class, so you can handle it together:
|
4
|
+
#
|
5
|
+
# rescue_from RailsStuff::ParamsParser::Error, with: :render_bad_request
|
6
|
+
#
|
7
|
+
# You can define more parsing methods by extending with this module
|
8
|
+
# and using .parse:
|
9
|
+
#
|
10
|
+
# # models/params_parser
|
11
|
+
# module ParamsParser
|
12
|
+
# extend RailsStuff::ParamsParser
|
13
|
+
# extend self
|
14
|
+
#
|
15
|
+
# def parse_money(val)
|
16
|
+
# parse(val) { your_stuff(val) }
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
module ParamsParser
|
21
|
+
# This exceptions is wrapper for any exception occured in parser.
|
22
|
+
# Original exception message can be retrieved with `original_message` method.
|
23
|
+
class Error < ::StandardError
|
24
|
+
attr_reader :original_message, :value
|
25
|
+
|
26
|
+
def initialize(original_message = nil, value = nil)
|
27
|
+
message = "Error while parsing: #{value.inspect}"
|
28
|
+
@original_message = original_message || message
|
29
|
+
@value = value
|
30
|
+
super(message)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Keeps message when passing instance to `raise`.
|
34
|
+
def exception(*)
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
# Show original messages in tests.
|
39
|
+
def to_s
|
40
|
+
"#{super} (#{original_message})"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
extend self
|
45
|
+
|
46
|
+
# Parses value with specified block. Reraises occured error with Error.
|
47
|
+
def parse(val)
|
48
|
+
yield(val) unless val.nil?
|
49
|
+
rescue => e
|
50
|
+
raise Error.new(e.message, val), nil, e.backtrace
|
51
|
+
end
|
52
|
+
|
53
|
+
# Parses each value in array with specified block.
|
54
|
+
# Returns `nil` if `val` is not an array.
|
55
|
+
def parse_array(val)
|
56
|
+
parse(val) { val.map { |x| yield x unless x.nil? } } if val.is_a?(Array)
|
57
|
+
end
|
58
|
+
|
59
|
+
# :method: parse_int
|
60
|
+
# :call-seq: parse_int(val)
|
61
|
+
#
|
62
|
+
# Parse int value.
|
63
|
+
|
64
|
+
# :method: parse_int_array
|
65
|
+
# :call-seq: parse_int_array(val)
|
66
|
+
#
|
67
|
+
# Parses array of ints. Returns `nil` if `val` is not an array.
|
68
|
+
|
69
|
+
# :method: parse_float
|
70
|
+
# :call-seq: parse_float(val)
|
71
|
+
#
|
72
|
+
# Parse float value.
|
73
|
+
|
74
|
+
# :method: parse_float_array
|
75
|
+
# :call-seq: parse_float_array(val)
|
76
|
+
#
|
77
|
+
# Parses array of floats. Returns `nil` if `val` is not an array.
|
78
|
+
|
79
|
+
# :method: parse_string
|
80
|
+
# :call-seq: parse_string(val)
|
81
|
+
#
|
82
|
+
# Parse string value.
|
83
|
+
|
84
|
+
# :method: parse_string_array
|
85
|
+
# :call-seq: parse_string_array(val)
|
86
|
+
#
|
87
|
+
# Parses array of strings. Returns `nil` if `val` is not an array.
|
88
|
+
|
89
|
+
# Parsers for generic types, which are implemented with #to_i, #to_f & #to_s
|
90
|
+
# methods.
|
91
|
+
%w(string int float).each do |type|
|
92
|
+
block = :"to_#{type[0]}".to_proc
|
93
|
+
|
94
|
+
define_method "parse_#{type}" do |val|
|
95
|
+
parse(val, &block)
|
96
|
+
end
|
97
|
+
|
98
|
+
define_method "parse_#{type}_array" do |val|
|
99
|
+
parse_array(val, &block)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Parse boolean using ActiveResord's parser.
|
104
|
+
def parse_boolean(val)
|
105
|
+
parse(val) do
|
106
|
+
@boolean_parser ||= ActiveRecord::Type::Boolean.new
|
107
|
+
@boolean_parser.type_cast_from_user(val)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Parse time in current TZ using `Time.parse`.
|
112
|
+
def parse_datetime(val)
|
113
|
+
parse(val) { Time.zone.parse(val) || raise('Invalid datetime') }
|
114
|
+
end
|
115
|
+
|
116
|
+
# Parse JSON string.
|
117
|
+
def parse_json(val)
|
118
|
+
parse(val) { JSON.parse(val) if val.present? }
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rails/railtie'
|
2
|
+
|
3
|
+
module RailsStuff
|
4
|
+
MODULES = {
|
5
|
+
nullify_blank_attrs: :model,
|
6
|
+
random_uniq_attr: :model,
|
7
|
+
statusable: :model,
|
8
|
+
resources_controller: [
|
9
|
+
:controller,
|
10
|
+
-> { ResourcesController.kaminari! if defined?(::Kaminari) },
|
11
|
+
],
|
12
|
+
sort_scope: -> { defined?(::HasScope) && :controller },
|
13
|
+
}
|
14
|
+
|
15
|
+
class << self
|
16
|
+
# Set it to array of modules to load.
|
17
|
+
#
|
18
|
+
# # config/initializers/rails_stuff.rb
|
19
|
+
# RailsStuff.load_modules = [:statusable, :sort_scope]
|
20
|
+
attr_accessor :load_modules
|
21
|
+
# Override default base classes for models & controllers.
|
22
|
+
attr_writer :base_controller, :base_model
|
23
|
+
|
24
|
+
def base_controller
|
25
|
+
@base_controller || ActionController::Base
|
26
|
+
end
|
27
|
+
|
28
|
+
def base_model
|
29
|
+
@base_model || ActiveRecord::Base
|
30
|
+
end
|
31
|
+
|
32
|
+
# Extends base controller and model classes with modules.
|
33
|
+
# By default uses all modules. Use load_modules= to override this list.
|
34
|
+
def setup_modules!
|
35
|
+
modules_to_load = load_modules || MODULES.keys
|
36
|
+
MODULES.slice(*modules_to_load).each do |m, (type, init)|
|
37
|
+
m = const_get m.to_s.camelize
|
38
|
+
case type.respond_to?(:call) ? type.call : type
|
39
|
+
when :controller
|
40
|
+
RailsStuff.base_controller.extend m
|
41
|
+
when :model
|
42
|
+
RailsStuff.base_model.extend m
|
43
|
+
end
|
44
|
+
init.try!(:call)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Railtie < Rails::Railtie
|
50
|
+
initializer :rails_stuff_setup_modules, after: :load_config_initializers do
|
51
|
+
RailsStuff.setup_modules!
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module RailsStuff
|
2
|
+
# Provides save way to generate uniq random values for ActiveRecord models.
|
3
|
+
# You need to make field nullable and add unique index on it.
|
4
|
+
# The way it works:
|
5
|
+
#
|
6
|
+
# - Instance is saved as usual
|
7
|
+
# - If random fields are not empty, it does nothing
|
8
|
+
# - Generates random value and tries to update instance
|
9
|
+
# - If `RecordNotUnique` is occured, it keeps trying to generate new values.
|
10
|
+
#
|
11
|
+
module RandomUniqAttr
|
12
|
+
DEFAULT_GENERATOR = ->(*) { SecureRandom.hex(32) }
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# Made from `Devise.firendly_token` with increased length.
|
16
|
+
def friendly_token(length = 32)
|
17
|
+
SecureRandom.urlsafe_base64(length).tr('lIO0', 'sxyz')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Generates necessary methods and setups on-create callback for the `field`.
|
22
|
+
# You can optionally pass custom generator function:
|
23
|
+
#
|
24
|
+
# random_uniq_attr(:code) { |instance| my_random(instance) }
|
25
|
+
#
|
26
|
+
def random_uniq_attr(field, &block)
|
27
|
+
set_method = :"set_#{field}"
|
28
|
+
generate_method = :"generate_#{field}"
|
29
|
+
|
30
|
+
after_create set_method, unless: :"#{field}?"
|
31
|
+
|
32
|
+
# def self.generate_key
|
33
|
+
define_singleton_method generate_method, &(block || DEFAULT_GENERATOR)
|
34
|
+
|
35
|
+
# def set_key
|
36
|
+
define_method(set_method) do
|
37
|
+
begin
|
38
|
+
raise 'Available only for persisted record' unless persisted?
|
39
|
+
transaction(requires_new: true) do
|
40
|
+
update_column field, self.class.send(generate_method, self)
|
41
|
+
end
|
42
|
+
rescue ActiveRecord::RecordNotUnique
|
43
|
+
retry
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
|
+
require 'active_support/core_ext/module/remove_method'
|
3
|
+
require 'active_support/core_ext/object/blank'
|
4
|
+
|
5
|
+
module RailsStuff
|
6
|
+
# Provides methods to store data in redis. Can be easily integrated into
|
7
|
+
# ActiveRecor or other class.
|
8
|
+
#
|
9
|
+
# Redis is accessed via with_redis method which uses redis_pool
|
10
|
+
# (default to `Rails.redis_pool`, see `pooled_redis` gem) to checkout connection.
|
11
|
+
# Basic methods are #get, #delete and #set.
|
12
|
+
#
|
13
|
+
# Redis keys are generated from requested key and redis_prefix
|
14
|
+
# (default to underscored class name). You can pass an array as a key and all the
|
15
|
+
# parts will be concatenated with `:`. #set automalically generates
|
16
|
+
# sequential keys, if given key is `nil` (last element of array is `nil`).
|
17
|
+
#
|
18
|
+
# It uses `dump` and `load` methods to encode values
|
19
|
+
# (by default delegated to `Marshal`).
|
20
|
+
module RedisStorage
|
21
|
+
# Serializers
|
22
|
+
delegate :dump, :load, to: Marshal
|
23
|
+
|
24
|
+
# Redis connections pool. Default to `Rails.redis_pool`.
|
25
|
+
# Override this method to change it.
|
26
|
+
def redis_pool
|
27
|
+
Rails.redis_pool
|
28
|
+
end
|
29
|
+
|
30
|
+
# Options to use in SET command. Use to set EX, or smth.
|
31
|
+
def redis_set_options
|
32
|
+
{}
|
33
|
+
end
|
34
|
+
|
35
|
+
# :method: redis_pool=
|
36
|
+
# :call-seq: redis_pool=
|
37
|
+
#
|
38
|
+
# Set redis_pool.
|
39
|
+
|
40
|
+
# :method: redis_set_options=
|
41
|
+
# :call-seq: redis_set_options=
|
42
|
+
#
|
43
|
+
# Set redis_set_options.
|
44
|
+
|
45
|
+
# Setters that overrides methods, so new values are inherited without recursive `super`.
|
46
|
+
[:redis_pool, :redis_set_options].each do |name|
|
47
|
+
define_method "#{name}=" do |val|
|
48
|
+
singleton_class.class_eval do
|
49
|
+
remove_possible_method(name)
|
50
|
+
define_method(name) { val }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Checkout connection & run block with it.
|
56
|
+
def with_redis(&block)
|
57
|
+
redis_pool.with(&block)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Prefix that used in every key for a model. Default to pluralized model name.
|
61
|
+
def redis_prefix
|
62
|
+
@redis_prefix ||= name.underscore
|
63
|
+
end
|
64
|
+
|
65
|
+
# Override default redis_prefix.
|
66
|
+
attr_writer :redis_prefix
|
67
|
+
|
68
|
+
# Generates key for given `id`(s) prefixed with #redis_prefix.
|
69
|
+
# Multiple ids are joined with `:`.
|
70
|
+
def redis_key_for(id)
|
71
|
+
"#{redis_prefix}:#{Array(id).join(':')}"
|
72
|
+
end
|
73
|
+
|
74
|
+
# Generates key to store current maximum id. Examples:
|
75
|
+
#
|
76
|
+
# users_id_seq
|
77
|
+
# user_id_seq:eu
|
78
|
+
def redis_id_seq_key(id = [])
|
79
|
+
postfix = Array(id).join(':')
|
80
|
+
"#{redis_prefix}_id_seq#{":#{postfix}" if postfix.present?}"
|
81
|
+
end
|
82
|
+
|
83
|
+
# Generate next ID. It stores counter separately and uses
|
84
|
+
# it to retrieve next id.
|
85
|
+
def next_id(*args)
|
86
|
+
with_redis { |redis| redis.incr(redis_id_seq_key(*args)) }
|
87
|
+
end
|
88
|
+
|
89
|
+
# Reset ID counter.
|
90
|
+
def reset_id_seq(*args)
|
91
|
+
with_redis { |redis| redis.del(redis_id_seq_key(*args)) }
|
92
|
+
end
|
93
|
+
|
94
|
+
# Saves value to redis. If `id` is `nil`, it's generated with #next_id.
|
95
|
+
# Returns last part of id / generated id.
|
96
|
+
def set(id, value, options = {})
|
97
|
+
id = Array(id)
|
98
|
+
id.push(nil) if id.empty?
|
99
|
+
id[id.size - 1] ||= next_id(id[0..-2])
|
100
|
+
with_redis do |redis|
|
101
|
+
redis.set(redis_key_for(id), dump(value), redis_set_options.merge(options))
|
102
|
+
end
|
103
|
+
id.last
|
104
|
+
end
|
105
|
+
|
106
|
+
# Reads value from redis.
|
107
|
+
def get(id)
|
108
|
+
return unless id
|
109
|
+
with_redis { |redis| redis.get(redis_key_for(id)).try { |data| load(data) } }
|
110
|
+
end
|
111
|
+
|
112
|
+
# Remove record from redis.
|
113
|
+
def delete(id)
|
114
|
+
return true unless id
|
115
|
+
with_redis { |redis| redis.del(redis_key_for(id)) }
|
116
|
+
true
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module RailsStuff
|
2
|
+
module ResourcesController
|
3
|
+
# Basic actions for resources controller.
|
4
|
+
module Actions
|
5
|
+
def new
|
6
|
+
build_resource
|
7
|
+
end
|
8
|
+
|
9
|
+
def create(options = {})
|
10
|
+
if create_resource
|
11
|
+
options[:location] = after_save_url
|
12
|
+
end
|
13
|
+
respond_with(resource, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def update(options = {})
|
17
|
+
if update_resource
|
18
|
+
options[:location] = after_save_url
|
19
|
+
end
|
20
|
+
respond_with(resource, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def destroy(options = {})
|
24
|
+
resource.destroy
|
25
|
+
options[:location] = after_destroy_url
|
26
|
+
flash_errors!
|
27
|
+
respond_with(resource, options)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
module RailsStuff
|
2
|
+
module ResourcesController
|
3
|
+
module BasicHelpers
|
4
|
+
extend ActiveSupport::Concern
|
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
|
+
included do
|
17
|
+
helper_method :resource, :collection
|
18
|
+
self.after_save_action = :show
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
attr_writer :resource_class,
|
23
|
+
:resource_param_name,
|
24
|
+
:permitted_attrs
|
25
|
+
|
26
|
+
# Defines action to redirect after resource was saved. Default to `:show`.
|
27
|
+
attr_accessor :after_save_action
|
28
|
+
|
29
|
+
# Resource class for controller. Default to class, based on
|
30
|
+
# demodulized controller name.
|
31
|
+
def resource_class
|
32
|
+
@resource_class ||=
|
33
|
+
Object.const_get(name.to_s.demodulize.sub(/Controller$/, '').singularize)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Key to lookup for resource attributes in `params`.
|
37
|
+
# Default to class'es `param_key`.
|
38
|
+
def resource_param_name
|
39
|
+
@resource_param_name ||= resource_class.model_name.param_key
|
40
|
+
end
|
41
|
+
|
42
|
+
# Class-level permitted attributes.
|
43
|
+
#
|
44
|
+
# `attr_reader`, default to `[]`.
|
45
|
+
def permitted_attrs
|
46
|
+
@permitted_attrs ||= []
|
47
|
+
end
|
48
|
+
|
49
|
+
# Concats `@permitted_attrs` variable with given attrs.
|
50
|
+
def permit_attrs(*attrs)
|
51
|
+
permitted_attrs.concat attrs
|
52
|
+
end
|
53
|
+
|
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
|
+
# Prevent CanCan's implementation.
|
66
|
+
def authorize_resource
|
67
|
+
raise 'use `before_action :authorize_resource!` instead'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
protected
|
72
|
+
|
73
|
+
# Accesss resources collection.
|
74
|
+
def collection
|
75
|
+
@_collection ||= source_for_collection
|
76
|
+
end
|
77
|
+
|
78
|
+
# End-point relation to be used as source for `collection`.
|
79
|
+
def source_for_collection
|
80
|
+
source_relation
|
81
|
+
end
|
82
|
+
|
83
|
+
# Relation which is used to find and build resources.
|
84
|
+
def source_relation
|
85
|
+
self.class.resource_class
|
86
|
+
end
|
87
|
+
|
88
|
+
# Resource found by `params[:id]`
|
89
|
+
def resource
|
90
|
+
@_resource ||= source_relation.find params[:id]
|
91
|
+
end
|
92
|
+
|
93
|
+
# Instantiate resource with attrs from `resource_params`.
|
94
|
+
def build_resource(attrs = resource_params)
|
95
|
+
@_resource = source_relation.new(attrs)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Builds and saves resource.
|
99
|
+
def create_resource
|
100
|
+
build_resource
|
101
|
+
resource.save
|
102
|
+
end
|
103
|
+
|
104
|
+
# Updates resource with `resource_params`.
|
105
|
+
def update_resource(attrs = resource_params)
|
106
|
+
resource.update_attributes(attrs)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Flashes errors in a safe way. Joins `full_messages` and truncates
|
110
|
+
# result to avoid cookies overflow.
|
111
|
+
def flash_errors!(errors = resource.errors, max_length = 100)
|
112
|
+
flash[:error] = errors.full_messages.join("\n").truncate(max_length) if errors.any?
|
113
|
+
end
|
114
|
+
|
115
|
+
# URL to be used in `Location` header & to redirect to after
|
116
|
+
# resource was created/updated. Default uses `self.class.after_save_action`.
|
117
|
+
def after_save_url
|
118
|
+
action = self.class.after_save_action
|
119
|
+
if action == :index
|
120
|
+
url_for action: :index
|
121
|
+
else
|
122
|
+
url_for action: action, id: resource
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# URL to be used in `Location` header & to redirect after
|
127
|
+
# resource was destroyed. Default to `:index` action.
|
128
|
+
def after_destroy_url
|
129
|
+
url_for action: :index
|
130
|
+
end
|
131
|
+
|
132
|
+
# Override it to return permited params. By default it returns params
|
133
|
+
# using `self.class.resource_param_name` and `permitted_attrs` methods.
|
134
|
+
def resource_params
|
135
|
+
@_resource_params ||= begin
|
136
|
+
key = self.class.resource_param_name
|
137
|
+
params.permit(key => permitted_attrs)[key] || {}
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Default permitted attributes are taken from class method. Override it
|
142
|
+
# to implement request-based permitted attrs.
|
143
|
+
def permitted_attrs
|
144
|
+
self.class.permitted_attrs
|
145
|
+
end
|
146
|
+
|
147
|
+
# Default authorization implementation.
|
148
|
+
# Uses `#authorize!` method which is not implemented here
|
149
|
+
# (use CanCan or other implementation).
|
150
|
+
def authorize_resource!
|
151
|
+
action = action_name.to_sym
|
152
|
+
target =
|
153
|
+
case action
|
154
|
+
when :index, :create, :new then self.class.resource_class.new
|
155
|
+
else resource
|
156
|
+
end
|
157
|
+
authorize!(action, target)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|