rails_stuff 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|