dry_crud 1.7.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +126 -87
- data/VERSION +1 -1
- data/lib/generators/dry_crud/dry_crud_generator.rb +42 -22
- data/lib/generators/dry_crud/templates/INSTALL +5 -5
- data/lib/generators/dry_crud/templates/app/assets/stylesheets/crud.scss +0 -20
- data/lib/generators/dry_crud/templates/app/assets/stylesheets/sample.scss +24 -4
- data/lib/generators/dry_crud/templates/app/controllers/crud/generic_model.rb +89 -0
- data/lib/generators/dry_crud/templates/app/controllers/crud/nestable.rb +70 -0
- data/lib/generators/dry_crud/templates/app/controllers/crud/rememberable.rb +64 -0
- data/lib/generators/dry_crud/templates/app/controllers/crud/render_callbacks.rb +46 -0
- data/lib/generators/dry_crud/templates/app/controllers/crud/responder.rb +31 -0
- data/lib/generators/dry_crud/templates/app/controllers/crud/searchable.rb +55 -0
- data/lib/generators/dry_crud/templates/app/controllers/crud/sortable.rb +63 -0
- data/lib/generators/dry_crud/templates/app/controllers/crud_controller.rb +66 -69
- data/lib/generators/dry_crud/templates/app/controllers/list_controller.rb +23 -326
- data/lib/generators/dry_crud/templates/app/helpers/actions_helper.rb +64 -0
- data/lib/generators/dry_crud/templates/app/helpers/crud/form_builder.rb +331 -0
- data/lib/generators/dry_crud/templates/app/helpers/crud/table_builder.rb +280 -0
- data/lib/generators/dry_crud/templates/app/helpers/form_helper.rb +52 -0
- data/lib/generators/dry_crud/templates/app/helpers/format_helper.rb +164 -0
- data/lib/generators/dry_crud/templates/app/helpers/i18n_helper.rb +85 -0
- data/lib/generators/dry_crud/templates/app/helpers/table_helper.rb +83 -0
- data/lib/generators/dry_crud/templates/app/helpers/utility_helper.rb +84 -0
- data/lib/generators/dry_crud/templates/app/views/crud/_actions_edit.html.erb +3 -3
- data/lib/generators/dry_crud/templates/app/views/crud/_actions_edit.html.haml +3 -3
- data/lib/generators/dry_crud/templates/app/views/crud/_actions_index.html.erb +1 -1
- data/lib/generators/dry_crud/templates/app/views/crud/_actions_index.html.haml +1 -1
- data/lib/generators/dry_crud/templates/app/views/crud/_actions_show.html.erb +3 -3
- data/lib/generators/dry_crud/templates/app/views/crud/_actions_show.html.haml +3 -3
- data/lib/generators/dry_crud/templates/app/views/crud/_attrs.html.erb +1 -1
- data/lib/generators/dry_crud/templates/app/views/crud/_attrs.html.haml +1 -1
- data/lib/generators/dry_crud/templates/app/views/crud/_form.html.erb +1 -1
- data/lib/generators/dry_crud/templates/app/views/crud/_form.html.haml +1 -1
- data/lib/generators/dry_crud/templates/app/views/crud/edit.html.erb +1 -1
- data/lib/generators/dry_crud/templates/app/views/crud/edit.html.haml +1 -1
- data/lib/generators/dry_crud/templates/app/views/crud/new.html.erb +2 -2
- data/lib/generators/dry_crud/templates/app/views/crud/new.html.haml +2 -2
- data/lib/generators/dry_crud/templates/app/views/crud/show.html.erb +1 -1
- data/lib/generators/dry_crud/templates/app/views/crud/show.html.haml +1 -1
- data/lib/generators/dry_crud/templates/app/views/layouts/_flash.html.haml +1 -1
- data/lib/generators/dry_crud/templates/app/views/layouts/_nav.html.erb +1 -1
- data/lib/generators/dry_crud/templates/app/views/layouts/_nav.html.haml +1 -1
- data/lib/generators/dry_crud/templates/app/views/layouts/crud.html.erb +15 -7
- data/lib/generators/dry_crud/templates/app/views/layouts/crud.html.haml +18 -8
- data/lib/generators/dry_crud/templates/app/views/list/_search.html.erb +3 -3
- data/lib/generators/dry_crud/templates/app/views/list/_search.html.haml +3 -3
- data/lib/generators/dry_crud/templates/app/views/list/index.html.erb +1 -1
- data/lib/generators/dry_crud/templates/app/views/list/index.html.haml +1 -1
- data/lib/generators/dry_crud/templates/app/views/shared/_error_messages.html.erb +1 -1
- data/lib/generators/dry_crud/templates/app/views/shared/_error_messages.html.haml +1 -1
- data/lib/generators/dry_crud/templates/app/views/shared/_labeled.html.erb +2 -4
- data/lib/generators/dry_crud/templates/app/views/shared/_labeled.html.haml +2 -3
- data/lib/generators/dry_crud/templates/config/initializers/field_error_proc.rb +5 -1
- data/lib/generators/dry_crud/templates/config/locales/crud.de.yml +64 -0
- data/lib/generators/dry_crud/templates/config/locales/{en_crud.yml → crud.en.yml} +2 -2
- data/lib/generators/dry_crud/templates/spec/controllers/crud_test_models_controller_spec.rb +241 -231
- data/lib/generators/dry_crud/templates/spec/helpers/crud/form_builder_spec.rb +226 -0
- data/lib/generators/dry_crud/templates/spec/helpers/{standard_table_builder_spec.rb → crud/table_builder_spec.rb} +36 -34
- data/lib/generators/dry_crud/templates/spec/helpers/form_helper_spec.rb +238 -0
- data/lib/generators/dry_crud/templates/spec/helpers/format_helper_spec.rb +244 -0
- data/lib/generators/dry_crud/templates/spec/helpers/i18n_helper_spec.rb +132 -0
- data/lib/generators/dry_crud/templates/spec/helpers/table_helper_spec.rb +265 -0
- data/lib/generators/dry_crud/templates/spec/helpers/utility_helper_spec.rb +74 -0
- data/lib/generators/dry_crud/templates/spec/support/crud_controller_examples.rb +185 -100
- data/lib/generators/dry_crud/templates/spec/support/crud_controller_test_helper.rb +58 -49
- data/lib/generators/dry_crud/templates/test/{functional → controllers}/crud_test_models_controller_test.rb +112 -91
- data/lib/generators/dry_crud/templates/test/{unit/helpers/standard_form_builder_test.rb → helpers/crud/form_builder_test.rb} +79 -62
- data/lib/generators/dry_crud/templates/test/{unit/helpers/standard_table_builder_test.rb → helpers/crud/table_builder_test.rb} +31 -28
- data/lib/generators/dry_crud/templates/test/helpers/custom_assertions_test.rb +85 -0
- data/lib/generators/dry_crud/templates/test/helpers/form_helper_test.rb +129 -0
- data/lib/generators/dry_crud/templates/test/helpers/format_helper_test.rb +163 -0
- data/lib/generators/dry_crud/templates/test/helpers/i18n_helper_test.rb +79 -0
- data/lib/generators/dry_crud/templates/test/helpers/table_helper_test.rb +217 -0
- data/lib/generators/dry_crud/templates/test/helpers/utility_helper_test.rb +63 -0
- data/lib/generators/dry_crud/templates/test/{functional → support}/crud_controller_test_helper.rb +70 -59
- data/lib/generators/dry_crud/templates/test/{crud_test_model.rb → support/crud_test_model.rb} +107 -75
- data/lib/generators/dry_crud/templates/test/support/custom_assertions.rb +83 -0
- metadata +83 -146
- data/Rakefile +0 -211
- data/lib/generators/dry_crud/templates/app/helpers/crud_helper.rb +0 -168
- data/lib/generators/dry_crud/templates/app/helpers/list_helper.rb +0 -27
- data/lib/generators/dry_crud/templates/app/helpers/standard_form_builder.rb +0 -261
- data/lib/generators/dry_crud/templates/app/helpers/standard_helper.rb +0 -304
- data/lib/generators/dry_crud/templates/app/helpers/standard_table_builder.rb +0 -178
- data/lib/generators/dry_crud/templates/spec/helpers/crud_helper_spec.rb +0 -146
- data/lib/generators/dry_crud/templates/spec/helpers/list_helper_spec.rb +0 -154
- data/lib/generators/dry_crud/templates/spec/helpers/standard_form_builder_spec.rb +0 -215
- data/lib/generators/dry_crud/templates/spec/helpers/standard_helper_spec.rb +0 -387
- data/lib/generators/dry_crud/templates/test/custom_assertions.rb +0 -78
- data/lib/generators/dry_crud/templates/test/unit/custom_assertions_test.rb +0 -117
- data/lib/generators/dry_crud/templates/test/unit/helpers/crud_helper_test.rb +0 -111
- data/lib/generators/dry_crud/templates/test/unit/helpers/list_helper_test.rb +0 -123
- data/lib/generators/dry_crud/templates/test/unit/helpers/standard_helper_test.rb +0 -281
- data/test/templates/Gemfile +0 -46
- data/test/templates/app/controllers/admin/cities_controller.rb +0 -7
- data/test/templates/app/controllers/admin/countries_controller.rb +0 -13
- data/test/templates/app/controllers/ajax_controller.rb +0 -9
- data/test/templates/app/controllers/people_controller.rb +0 -13
- data/test/templates/app/controllers/vips_controller.rb +0 -19
- data/test/templates/app/helpers/cities_helper.rb +0 -9
- data/test/templates/app/helpers/people_helper.rb +0 -8
- data/test/templates/app/models/city.rb +0 -28
- data/test/templates/app/models/country.rb +0 -16
- data/test/templates/app/models/person.rb +0 -12
- data/test/templates/app/views/admin/cities/_actions_index.html.erb +0 -2
- data/test/templates/app/views/admin/cities/_actions_index.html.haml +0 -2
- data/test/templates/app/views/admin/cities/_attrs.html.erb +0 -1
- data/test/templates/app/views/admin/cities/_attrs.html.haml +0 -1
- data/test/templates/app/views/admin/cities/_form.html.erb +0 -7
- data/test/templates/app/views/admin/cities/_form.html.haml +0 -5
- data/test/templates/app/views/admin/cities/_hello.html.erb +0 -1
- data/test/templates/app/views/admin/cities/_hello.html.haml +0 -1
- data/test/templates/app/views/admin/cities/_list.html.erb +0 -3
- data/test/templates/app/views/admin/cities/_list.html.haml +0 -3
- data/test/templates/app/views/admin/countries/_list.html.erb +0 -4
- data/test/templates/app/views/admin/countries/_list.html.haml +0 -3
- data/test/templates/app/views/ajax/_actions_index.html.erb +0 -8
- data/test/templates/app/views/ajax/_actions_index.html.haml +0 -8
- data/test/templates/app/views/ajax/_actions_show.html.erb +0 -4
- data/test/templates/app/views/ajax/_actions_show.html.haml +0 -4
- data/test/templates/app/views/ajax/_form.html.erb +0 -2
- data/test/templates/app/views/ajax/_form.html.haml +0 -2
- data/test/templates/app/views/ajax/_hello.html.erb +0 -1
- data/test/templates/app/views/ajax/_hello.html.haml +0 -1
- data/test/templates/app/views/ajax/ajax.js.erb +0 -1
- data/test/templates/app/views/ajax/ajax.js.haml +0 -1
- data/test/templates/app/views/ajax/edit.js.erb +0 -1
- data/test/templates/app/views/ajax/edit.js.haml +0 -1
- data/test/templates/app/views/ajax/show.js.erb +0 -1
- data/test/templates/app/views/ajax/show.js.haml +0 -1
- data/test/templates/app/views/ajax/update.js.erb +0 -5
- data/test/templates/app/views/ajax/update.js.haml +0 -5
- data/test/templates/app/views/layouts/_nav.html.erb +0 -6
- data/test/templates/app/views/layouts/_nav.html.haml +0 -5
- data/test/templates/app/views/layouts/bootstrap.html.erb +0 -68
- data/test/templates/app/views/layouts/bootstrap.html.haml +0 -49
- data/test/templates/app/views/people/_attrs.html.erb +0 -5
- data/test/templates/app/views/people/_attrs.html.haml +0 -4
- data/test/templates/app/views/people/_list.html.erb +0 -1
- data/test/templates/app/views/people/_list.html.haml +0 -1
- data/test/templates/config/database.yml +0 -21
- data/test/templates/config/locales/en_cities.yml +0 -56
- data/test/templates/config/routes.rb +0 -32
- data/test/templates/db/migrate/20100511174904_create_people_and_cities.rb +0 -26
- data/test/templates/db/seeds.rb +0 -74
- data/test/templates/spec/controllers/admin/cities_controller_spec.rb +0 -74
- data/test/templates/spec/controllers/admin/countries_controller_spec.rb +0 -56
- data/test/templates/spec/controllers/people_controller_spec.rb +0 -80
- data/test/templates/spec/routing/cities_routing_spec.rb +0 -11
- data/test/templates/spec/routing/countries_routing_spec.rb +0 -11
- data/test/templates/test/fixtures/cities.yml +0 -11
- data/test/templates/test/fixtures/countries.yml +0 -11
- data/test/templates/test/fixtures/people.yml +0 -14
- data/test/templates/test/functional/admin/cities_controller_test.rb +0 -59
- data/test/templates/test/functional/admin/countries_controller_test.rb +0 -42
- data/test/templates/test/functional/people_controller_test.rb +0 -68
@@ -0,0 +1,63 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Crud
|
4
|
+
# Sort functionality for the index table.
|
5
|
+
# Define a default sort expression that is always appended to the
|
6
|
+
# current sort params with the class attribute +default_sort+.
|
7
|
+
module Sortable
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
class_attribute :sort_mappings_with_indifferent_access
|
12
|
+
self.sort_mappings = {}
|
13
|
+
|
14
|
+
class_attribute :default_sort
|
15
|
+
|
16
|
+
helper_method :sortable?
|
17
|
+
|
18
|
+
alias_method_chain :list_entries, :sort
|
19
|
+
end
|
20
|
+
|
21
|
+
# Class methods for sorting.
|
22
|
+
module ClassMethods
|
23
|
+
# Define a map of (virtual) attributes to SQL order expressions.
|
24
|
+
# May be used for sorting table columns that do not appear directly
|
25
|
+
# in the database table. E.g., map city_id: 'cities.name' to
|
26
|
+
# sort the displayed city names.
|
27
|
+
def sort_mappings=(hash)
|
28
|
+
self.sort_mappings_with_indifferent_access =
|
29
|
+
hash.with_indifferent_access
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Enhance the list entries with an optional sort order.
|
36
|
+
def list_entries_with_sort
|
37
|
+
clause = []
|
38
|
+
clause << sort_expression if sortable?(params[:sort])
|
39
|
+
clause << default_sort
|
40
|
+
|
41
|
+
list_entries_without_sort.order(clause.compact.join(', '))
|
42
|
+
end
|
43
|
+
|
44
|
+
# Return the sort expression to be used in the list query.
|
45
|
+
def sort_expression
|
46
|
+
col = sort_mappings_with_indifferent_access[params[:sort]] ||
|
47
|
+
"#{model_class.table_name}.#{params[:sort]}"
|
48
|
+
"#{col} #{sort_dir}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# The sort direction, either 'asc' or 'desc'.
|
52
|
+
def sort_dir
|
53
|
+
params[:sort_dir] == 'desc' ? 'DESC' : 'ASC'
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns true if the passed attribute is sortable.
|
57
|
+
def sortable?(attr)
|
58
|
+
attr.present? && (
|
59
|
+
model_class.column_names.include?(attr.to_s) ||
|
60
|
+
sort_mappings_with_indifferent_access.include?(attr))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -1,31 +1,39 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
1
3
|
# Abstract controller providing basic CRUD actions.
|
2
4
|
# This implementation mainly follows the one of the Rails scaffolding
|
3
|
-
# controller and
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
5
|
+
# controller and responds to HTML and JSON requests.
|
6
|
+
#
|
7
|
+
# Some enhancements were made to ease extendability.
|
8
|
+
# The current model entry is available in the view as an instance variable
|
9
|
+
# named after the +model_class+ or in the helper method +entry+.
|
10
|
+
# Several protected helper methods are there to be (optionally) overriden by
|
11
|
+
# subclasses.
|
12
|
+
# With the help of additional callbacks, it is possible to hook into the action
|
13
|
+
# procedures without overriding the entire method.
|
7
14
|
class CrudController < ListController
|
8
15
|
|
9
|
-
|
16
|
+
self.responder = Crud::Responder
|
17
|
+
|
18
|
+
<% if Rails.version >= '4.0' -%>
|
19
|
+
class_attribute :permitted_attrs
|
10
20
|
|
11
|
-
|
21
|
+
<% end -%>
|
22
|
+
# Defines before and after callback hooks for create, update, save and
|
23
|
+
# destroy actions.
|
12
24
|
define_model_callbacks :create, :update, :save, :destroy
|
13
25
|
|
14
26
|
# Defines before callbacks for the render actions. A virtual callback
|
15
|
-
# unifiying render_new and render_edit, called render_form, is defined
|
27
|
+
# unifiying render_new and render_edit, called render_form, is defined
|
28
|
+
# further down.
|
16
29
|
define_render_callbacks :show, :new, :edit
|
17
30
|
|
18
31
|
after_save :set_success_notice
|
19
32
|
after_destroy :set_success_notice
|
20
33
|
|
21
34
|
helper_method :entry, :full_entry_label
|
22
|
-
|
23
|
-
hide_action :model_identifier, :run_callbacks
|
24
|
-
|
25
|
-
# Simple helper object to give access to required view helper methods.
|
26
|
-
@@helper = Object.new.extend(ActionView::Helpers::TranslationHelper).
|
27
|
-
extend(ActionView::Helpers::OutputSafetyHelper)
|
28
35
|
|
36
|
+
hide_action :run_callbacks
|
29
37
|
|
30
38
|
############## ACTIONS ############################################
|
31
39
|
|
@@ -40,7 +48,7 @@ class CrudController < ListController
|
|
40
48
|
# GET /entries/new
|
41
49
|
# GET /entries/new.json
|
42
50
|
def new(&block)
|
43
|
-
assign_attributes
|
51
|
+
assign_attributes if params[model_identifier]
|
44
52
|
respond_with(entry, &block)
|
45
53
|
end
|
46
54
|
|
@@ -54,7 +62,8 @@ class CrudController < ListController
|
|
54
62
|
def create(options = {}, &block)
|
55
63
|
assign_attributes
|
56
64
|
created = with_callbacks(:create, :save) { entry.save }
|
57
|
-
|
65
|
+
respond_options = options.reverse_merge(success: created)
|
66
|
+
respond_with(entry, respond_options, &block)
|
58
67
|
end
|
59
68
|
|
60
69
|
# Display a form to edit an exisiting entry of this model.
|
@@ -73,7 +82,8 @@ class CrudController < ListController
|
|
73
82
|
def update(options = {}, &block)
|
74
83
|
assign_attributes
|
75
84
|
updated = with_callbacks(:update, :save) { entry.save }
|
76
|
-
|
85
|
+
respond_options = options.reverse_merge(success: updated)
|
86
|
+
respond_with(entry, respond_options, &block)
|
77
87
|
end
|
78
88
|
|
79
89
|
# Destroy an existing entry of this model.
|
@@ -85,9 +95,14 @@ class CrudController < ListController
|
|
85
95
|
# DELETE /entries/1.json
|
86
96
|
def destroy(options = {}, &block)
|
87
97
|
destroyed = run_callbacks(:destroy) { entry.destroy }
|
88
|
-
|
89
|
-
|
90
|
-
|
98
|
+
unless destroyed
|
99
|
+
set_failure_notice
|
100
|
+
location = request.env['HTTP_REFERER'].presence
|
101
|
+
end
|
102
|
+
location ||= index_url
|
103
|
+
respond_options = options.reverse_merge(success: destroyed,
|
104
|
+
location: location)
|
105
|
+
respond_with(entry, respond_options, &block)
|
91
106
|
end
|
92
107
|
|
93
108
|
private
|
@@ -114,24 +129,40 @@ class CrudController < ListController
|
|
114
129
|
entry.attributes = model_params
|
115
130
|
end
|
116
131
|
|
117
|
-
#
|
118
|
-
def
|
119
|
-
|
132
|
+
# The form params for this model.
|
133
|
+
def model_params
|
134
|
+
<% if Rails.version < '4.0' -%>
|
135
|
+
params[model_identifier]
|
136
|
+
<% else -%>
|
137
|
+
params.require(model_identifier).permit(permitted_attrs)
|
138
|
+
<% end -%><%# > fixing rdoc -%>
|
120
139
|
end
|
121
140
|
|
122
|
-
# Url of the index page to return to
|
141
|
+
# Url of the index page to return to.
|
123
142
|
def index_url
|
124
|
-
polymorphic_url(path_args(model_class), :
|
143
|
+
polymorphic_url(path_args(model_class), returning: true)
|
125
144
|
end
|
126
145
|
|
127
|
-
|
146
|
+
# A label for the current entry, including the model name.
|
147
|
+
def full_entry_label
|
148
|
+
"#{models_label(false)} <i>#{ERB::Util.h(entry)}</i>".html_safe
|
149
|
+
end
|
128
150
|
|
129
151
|
# Set a success flash notice when we got a HTML request.
|
130
152
|
def set_success_notice
|
131
|
-
|
153
|
+
if request.format == :html
|
154
|
+
flash[:notice] ||= flash_message(:success)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Set a failure flash notice when we got a HTML request.
|
159
|
+
def set_failure_notice
|
160
|
+
if request.format == :html
|
161
|
+
flash[:alert] ||= error_messages.presence || flash_message(:failure)
|
162
|
+
end
|
132
163
|
end
|
133
164
|
|
134
|
-
# Get an I18n flash message
|
165
|
+
# Get an I18n flash message.
|
135
166
|
# Uses the key {controller_name}.{action_name}.flash.{state}
|
136
167
|
# or crud.{action_name}.flash.{state} as fallback.
|
137
168
|
def flash_message(state)
|
@@ -140,56 +171,22 @@ class CrudController < ListController
|
|
140
171
|
:"#{controller_name}.#{scope}",
|
141
172
|
:"crud.#{scope}_html",
|
142
173
|
:"crud.#{scope}"]
|
143
|
-
|
174
|
+
I18n.t(keys.shift, model: full_entry_label, default: keys).html_safe
|
144
175
|
end
|
145
|
-
|
176
|
+
|
146
177
|
# Html safe error messages of the current entry.
|
147
178
|
def error_messages
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
# The form params for this model.
|
152
|
-
def model_params
|
153
|
-
params[model_identifier]
|
179
|
+
escaped = entry.errors.full_messages.map { |m| ERB::Util.html_escape(m) }
|
180
|
+
escaped.join('<br/>').html_safe
|
154
181
|
end
|
155
182
|
|
156
|
-
|
157
183
|
class << self
|
158
|
-
#
|
159
|
-
#
|
160
|
-
def model_identifier
|
161
|
-
@model_identifier ||= model_class.model_name.param_key
|
162
|
-
end
|
163
|
-
|
164
|
-
# Convenience callback to apply a callback on both form actions (new and edit).
|
184
|
+
# Convenience callback to apply a callback on both form actions
|
185
|
+
# (new and edit).
|
165
186
|
def before_render_form(*methods)
|
166
|
-
before_render_new
|
167
|
-
before_render_edit
|
187
|
+
before_render_new(*methods)
|
188
|
+
before_render_edit(*methods)
|
168
189
|
end
|
169
190
|
end
|
170
191
|
|
171
|
-
# Custom Responder that handles the controller's path_args.
|
172
|
-
# An additional :success option is used to handle action callback chain halts.
|
173
|
-
class Responder < ActionController::Responder
|
174
|
-
|
175
|
-
def initialize(controller, resources, options = {})
|
176
|
-
super(controller, with_path_args(resources, controller), options)
|
177
|
-
end
|
178
|
-
|
179
|
-
private
|
180
|
-
|
181
|
-
# Check whether the resource has errors. Additionally checks the :success option.
|
182
|
-
def has_errors?
|
183
|
-
options[:success] == false || super
|
184
|
-
end
|
185
|
-
|
186
|
-
# Wraps the resources with the path_args for correct nesting.
|
187
|
-
def with_path_args(resources, controller)
|
188
|
-
resources.size == 1 ? Array(controller.send(:path_args, resources.first)) : resources
|
189
|
-
end
|
190
|
-
|
191
|
-
end
|
192
|
-
|
193
|
-
self.responder = Responder
|
194
|
-
|
195
192
|
end
|
@@ -1,16 +1,23 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
1
3
|
# Abstract controller providing a basic list action.
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
4
|
+
# The loaded model entries are available in the view as an instance variable
|
5
|
+
# named after the +model_class+ or by the helper method +entries+.
|
6
|
+
#
|
7
|
+
# The +index+ action lists all entries of a certain model and provides
|
8
|
+
# functionality to search and sort this list.
|
9
|
+
# Furthermore, it remembers the last search and sort parameters after the
|
10
|
+
# user returns from a displayed or edited entry.
|
7
11
|
class ListController < ApplicationController
|
8
12
|
|
9
|
-
|
13
|
+
include Crud::GenericModel
|
14
|
+
include Crud::Nestable
|
15
|
+
include Crud::Rememberable
|
16
|
+
include Crud::RenderCallbacks
|
10
17
|
|
11
|
-
|
18
|
+
define_render_callbacks :index
|
12
19
|
|
13
|
-
|
20
|
+
helper_method :entries
|
14
21
|
|
15
22
|
respond_to :html, :json
|
16
23
|
|
@@ -25,332 +32,22 @@ class ListController < ApplicationController
|
|
25
32
|
|
26
33
|
private
|
27
34
|
|
28
|
-
# Helper method to access the entries to be displayed in the current index
|
35
|
+
# Helper method to access the entries to be displayed in the current index
|
36
|
+
# page in an uniform way.
|
29
37
|
def entries
|
30
38
|
get_model_ivar(true) || set_model_ivar(list_entries)
|
31
39
|
end
|
32
40
|
|
33
41
|
# The base relation used to filter the entries.
|
34
|
-
# This method may be adapted as long it returns an
|
42
|
+
# This method may be adapted as long it returns an
|
43
|
+
# <tt>ActiveRecord::Relation</tt>.
|
44
|
+
# Some of the modules included extend this method.
|
35
45
|
def list_entries
|
36
46
|
model_scope
|
37
47
|
end
|
38
48
|
|
39
|
-
#
|
40
|
-
|
41
|
-
|
42
|
-
def model_scope
|
43
|
-
model_class.scoped
|
44
|
-
end
|
45
|
-
|
46
|
-
# The path arguments to link to the given entry.
|
47
|
-
# If the controller is nested, this provides the required context.
|
48
|
-
def path_args(last)
|
49
|
-
last
|
50
|
-
end
|
51
|
-
|
52
|
-
# Get the instance variable named after the model_class.
|
53
|
-
# If the collection variable is required, pass true as the second argument.
|
54
|
-
def get_model_ivar(plural = false)
|
55
|
-
name = ivar_name(model_class)
|
56
|
-
name = name.pluralize if plural
|
57
|
-
instance_variable_get(:"@#{name}")
|
58
|
-
end
|
59
|
-
|
60
|
-
# Sets an instance variable with the underscored class name if the given value.
|
61
|
-
# If the value is a collection, sets the plural name.
|
62
|
-
def set_model_ivar(value)
|
63
|
-
name = if value.respond_to?(:klass) # ActiveRecord::Relation
|
64
|
-
ivar_name(value.klass).pluralize
|
65
|
-
elsif value.respond_to?(:each) # Array
|
66
|
-
ivar_name(value.first.klass).pluralize
|
67
|
-
else
|
68
|
-
ivar_name(value.class)
|
69
|
-
end
|
70
|
-
instance_variable_set(:"@#{name}", value)
|
71
|
-
end
|
72
|
-
|
73
|
-
def ivar_name(klass)
|
74
|
-
klass.model_name.param_key
|
75
|
-
end
|
76
|
-
|
77
|
-
class << self
|
78
|
-
# The ActiveRecord class of the model.
|
79
|
-
def model_class
|
80
|
-
@model_class ||= controller_name.classify.constantize
|
81
|
-
end
|
82
|
-
|
83
|
-
# A human readable plural name of the model.
|
84
|
-
def models_label(plural = true)
|
85
|
-
opts = {:count => (plural ? 3 : 1)}
|
86
|
-
opts[:default] = model_class.model_name.human.titleize
|
87
|
-
opts[:default] = opts[:default].pluralize if plural
|
88
|
-
|
89
|
-
model_class.model_name.human(opts)
|
90
|
-
end
|
91
|
-
|
92
|
-
end
|
93
|
-
|
94
|
-
# Provide before_render callbacks.
|
95
|
-
module Callbacks
|
96
|
-
|
97
|
-
def self.included(controller)
|
98
|
-
controller.extend ActiveModel::Callbacks
|
99
|
-
controller.extend ClassMethods
|
100
|
-
controller.alias_method_chain :render, :callbacks
|
101
|
-
|
102
|
-
controller.define_render_callbacks :index
|
103
|
-
end
|
104
|
-
|
105
|
-
# Helper method to run before_render callbacks and render the action.
|
106
|
-
# If a callback renders or redirects, the action is not rendered.
|
107
|
-
def render_with_callbacks(*args, &block)
|
108
|
-
options = _normalize_render(*args, &block)
|
109
|
-
callback = "render_#{options[:template]}"
|
110
|
-
run_callbacks(callback) if respond_to?(:"_run_#{callback}_callbacks", true)
|
111
|
-
|
112
|
-
render_without_callbacks(*args, &block) unless performed?
|
113
|
-
end
|
114
|
-
|
115
|
-
private
|
116
|
-
|
117
|
-
# Helper method the run the given block in between the before and after
|
118
|
-
# callbacks of the given kinds.
|
119
|
-
def with_callbacks(*kinds, &block)
|
120
|
-
kinds.reverse.inject(block) do |b, kind|
|
121
|
-
lambda { run_callbacks(kind, &b) }
|
122
|
-
end.call
|
123
|
-
end
|
124
|
-
|
125
|
-
module ClassMethods
|
126
|
-
# Defines before callbacks for the render actions.
|
127
|
-
def define_render_callbacks(*actions)
|
128
|
-
args = actions.collect {|a| :"render_#{a}" }
|
129
|
-
args << {:only => :before,
|
130
|
-
:terminator => "result == false || performed?"}
|
131
|
-
define_model_callbacks *args
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
include Callbacks
|
137
|
-
|
138
|
-
# The search functionality for the index table.
|
139
|
-
# Extracted into an own module for convenience.
|
140
|
-
module Search
|
141
|
-
def self.included(controller)
|
142
|
-
# Define an array of searchable columns in your subclassing controllers.
|
143
|
-
controller.class_attribute :search_columns
|
144
|
-
controller.search_columns = []
|
145
|
-
|
146
|
-
controller.helper_method :search_support?
|
147
|
-
|
148
|
-
controller.alias_method_chain :list_entries, :search
|
149
|
-
end
|
150
|
-
|
151
|
-
private
|
152
|
-
|
153
|
-
# Enhance the list entries with an optional search criteria
|
154
|
-
def list_entries_with_search
|
155
|
-
list_entries_without_search.where(search_condition)
|
156
|
-
end
|
157
|
-
|
158
|
-
# Compose the search condition with a basic SQL OR query.
|
159
|
-
def search_condition
|
160
|
-
if search_support? && params[:q].present?
|
161
|
-
terms = params[:q].split(/\s+/).collect { |t| "%#{t}%" }
|
162
|
-
clause = search_columns.collect do |f|
|
163
|
-
col = f.to_s.include?('.') ? f : "#{model_class.table_name}.#{f}"
|
164
|
-
"#{col} LIKE ?"
|
165
|
-
end.join(" OR ")
|
166
|
-
clause = terms.collect {|t| "(#{clause})" }.join(" AND ")
|
167
|
-
|
168
|
-
["(#{clause})"] + terms.collect {|t| [t] * search_columns.size }.flatten
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
# Returns true if this controller has searchable columns.
|
173
|
-
def search_support?
|
174
|
-
search_columns.present?
|
175
|
-
end
|
176
|
-
|
177
|
-
end
|
178
|
-
|
179
|
-
include Search
|
180
|
-
|
181
|
-
# Sort functionality for the index table.
|
182
|
-
# Extracted into an own module for convenience.
|
183
|
-
module Sort
|
184
|
-
# Adds a :sort_mappings class attribute.
|
185
|
-
def self.included(controller)
|
186
|
-
# Define a map of (virtual) attributes to SQL order expressions.
|
187
|
-
# May be used for sorting table columns that do not appear directly
|
188
|
-
# in the database table. E.g., map :city_id => 'cities.name' to
|
189
|
-
# sort the displayed city names.
|
190
|
-
controller.class_attribute :sort_mappings
|
191
|
-
controller.sort_mappings = {}
|
192
|
-
|
193
|
-
controller.helper_method :sortable?
|
194
|
-
|
195
|
-
controller.alias_method_chain :list_entries, :sort
|
196
|
-
end
|
197
|
-
|
198
|
-
private
|
199
|
-
|
200
|
-
# Enhance the list entries with an optional sort order.
|
201
|
-
def list_entries_with_sort
|
202
|
-
if params[:sort].present? && sortable?(params[:sort])
|
203
|
-
list_entries_without_sort.reorder(sort_expression)
|
204
|
-
else
|
205
|
-
list_entries_without_sort
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
# Return the sort expression to be used in the list query.
|
210
|
-
def sort_expression
|
211
|
-
col = sort_mappings[params[:sort].to_sym] ||
|
212
|
-
"#{model_class.table_name}.#{params[:sort]}"
|
213
|
-
"#{col} #{sort_dir}"
|
214
|
-
end
|
215
|
-
|
216
|
-
# The sort direction, either 'asc' or 'desc'.
|
217
|
-
def sort_dir
|
218
|
-
params[:sort_dir] == 'desc' ? 'desc' : 'asc'
|
219
|
-
end
|
220
|
-
|
221
|
-
# Returns true if the passed attribute is sortable.
|
222
|
-
def sortable?(attr)
|
223
|
-
model_class.column_names.include?(attr.to_s) ||
|
224
|
-
sort_mappings.include?(attr.to_sym)
|
225
|
-
end
|
226
|
-
end
|
227
|
-
|
228
|
-
include Sort
|
229
|
-
|
230
|
-
# Remembers certain params of the index action in order to return
|
231
|
-
# to the same list after an entry was viewed or edited.
|
232
|
-
# If the index is called with a param :returning, the remembered params
|
233
|
-
# will be re-used.
|
234
|
-
# Extracted into an own module for convenience.
|
235
|
-
module Memory
|
236
|
-
|
237
|
-
# Adds the :remember_params class attribute and a before filter to the index action.
|
238
|
-
def self.included(controller)
|
239
|
-
# Define a list of param keys that should be remembered for the list action.
|
240
|
-
controller.class_attribute :remember_params
|
241
|
-
controller.remember_params = [:q, :sort, :sort_dir, :page]
|
242
|
-
|
243
|
-
controller.before_filter :handle_remember_params, :only => [:index]
|
244
|
-
end
|
245
|
-
|
246
|
-
private
|
247
|
-
|
248
|
-
# Store and restore the corresponding params.
|
249
|
-
def handle_remember_params
|
250
|
-
remembered = remembered_params
|
251
|
-
|
252
|
-
restore_params_on_return(remembered)
|
253
|
-
store_current_params(remembered)
|
254
|
-
clear_void_params(remembered)
|
255
|
-
end
|
256
|
-
|
257
|
-
def restore_params_on_return(remembered)
|
258
|
-
if params[:returning]
|
259
|
-
remember_params.each {|p| params[p] ||= remembered[p] }
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
def store_current_params(remembered)
|
264
|
-
remember_params.each do |p|
|
265
|
-
remembered[p] = params[p].presence
|
266
|
-
remembered.delete(p) if remembered[p].nil?
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
def clear_void_params(remembered)
|
271
|
-
session[:list_params].delete(remember_key) if remembered.blank?
|
272
|
-
end
|
273
|
-
|
274
|
-
# Get the params stored in the session.
|
275
|
-
def remembered_params
|
276
|
-
session[:list_params] ||= {}
|
277
|
-
session[:list_params][remember_key] ||= {}
|
278
|
-
end
|
279
|
-
|
280
|
-
# Params are stored by request path to play nice when a controller
|
281
|
-
# is used in different routes.
|
282
|
-
def remember_key
|
283
|
-
request.path
|
284
|
-
end
|
285
|
-
end
|
286
|
-
|
287
|
-
include Memory
|
288
|
-
|
289
|
-
# Provides functionality to nest controllers/resources.
|
290
|
-
# If a controller is nested, the parent classes and namespaces
|
291
|
-
# may be defined as an array in the :nesting class attribute.
|
292
|
-
# For example, a cities controller, nested in country and a admin
|
293
|
-
# namespace, may define this attribute as follows:
|
294
|
-
# self.nesting = :admin, Country
|
295
|
-
module Nesting
|
296
|
-
|
297
|
-
# Adds the :nesting class attribute and parent helper methods
|
298
|
-
# to the including controller.
|
299
|
-
def self.included(controller)
|
300
|
-
controller.class_attribute :nesting
|
301
|
-
|
302
|
-
controller.helper_method :parent, :parents
|
303
|
-
|
304
|
-
controller.alias_method_chain :model_scope, :nesting
|
305
|
-
controller.alias_method_chain :path_args, :nesting
|
306
|
-
end
|
307
|
-
|
308
|
-
private
|
309
|
-
|
310
|
-
# Returns the direct parent ActiveRecord of the current request, if any.
|
311
|
-
def parent
|
312
|
-
parents.select {|p| p.is_a?(ActiveRecord::Base) }.last
|
313
|
-
end
|
314
|
-
|
315
|
-
# Returns the parent entries of the current request, if any.
|
316
|
-
# These are ActiveRecords or namespace symbols, corresponding
|
317
|
-
# to the defined nesting attribute.
|
318
|
-
def parents
|
319
|
-
@parents ||= Array(nesting).collect do |p|
|
320
|
-
if p.is_a?(Class) && p < ActiveRecord::Base
|
321
|
-
parent_entry(p)
|
322
|
-
else
|
323
|
-
p
|
324
|
-
end
|
325
|
-
end
|
326
|
-
end
|
327
|
-
|
328
|
-
# Loads the parent entry for the given ActiveRecord class.
|
329
|
-
# By default, performs a find with the class_name_id param.
|
330
|
-
def parent_entry(clazz)
|
331
|
-
set_model_ivar(clazz.find(params["#{clazz.name.underscore}_id"]))
|
332
|
-
end
|
333
|
-
|
334
|
-
# An array of objects used in url_for and related functions.
|
335
|
-
def path_args_with_nesting(last)
|
336
|
-
parents + [last]
|
337
|
-
end
|
338
|
-
|
339
|
-
# Uses the parent entry (if any) to constrain the model scope.
|
340
|
-
def model_scope_with_nesting
|
341
|
-
if parent.present?
|
342
|
-
parent_scope
|
343
|
-
else
|
344
|
-
model_scope_without_nesting
|
345
|
-
end
|
346
|
-
end
|
347
|
-
|
348
|
-
# The model scope for the current parent resource.
|
349
|
-
def parent_scope
|
350
|
-
parent.send(model_class.name.underscore.pluralize)
|
351
|
-
end
|
352
|
-
end
|
353
|
-
|
354
|
-
include Nesting
|
49
|
+
# Include these modules after the #list_entries method is defined.
|
50
|
+
include Crud::Searchable
|
51
|
+
include Crud::Sortable
|
355
52
|
|
356
53
|
end
|