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.
Files changed (158) hide show
  1. checksums.yaml +15 -0
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +126 -87
  4. data/VERSION +1 -1
  5. data/lib/generators/dry_crud/dry_crud_generator.rb +42 -22
  6. data/lib/generators/dry_crud/templates/INSTALL +5 -5
  7. data/lib/generators/dry_crud/templates/app/assets/stylesheets/crud.scss +0 -20
  8. data/lib/generators/dry_crud/templates/app/assets/stylesheets/sample.scss +24 -4
  9. data/lib/generators/dry_crud/templates/app/controllers/crud/generic_model.rb +89 -0
  10. data/lib/generators/dry_crud/templates/app/controllers/crud/nestable.rb +70 -0
  11. data/lib/generators/dry_crud/templates/app/controllers/crud/rememberable.rb +64 -0
  12. data/lib/generators/dry_crud/templates/app/controllers/crud/render_callbacks.rb +46 -0
  13. data/lib/generators/dry_crud/templates/app/controllers/crud/responder.rb +31 -0
  14. data/lib/generators/dry_crud/templates/app/controllers/crud/searchable.rb +55 -0
  15. data/lib/generators/dry_crud/templates/app/controllers/crud/sortable.rb +63 -0
  16. data/lib/generators/dry_crud/templates/app/controllers/crud_controller.rb +66 -69
  17. data/lib/generators/dry_crud/templates/app/controllers/list_controller.rb +23 -326
  18. data/lib/generators/dry_crud/templates/app/helpers/actions_helper.rb +64 -0
  19. data/lib/generators/dry_crud/templates/app/helpers/crud/form_builder.rb +331 -0
  20. data/lib/generators/dry_crud/templates/app/helpers/crud/table_builder.rb +280 -0
  21. data/lib/generators/dry_crud/templates/app/helpers/form_helper.rb +52 -0
  22. data/lib/generators/dry_crud/templates/app/helpers/format_helper.rb +164 -0
  23. data/lib/generators/dry_crud/templates/app/helpers/i18n_helper.rb +85 -0
  24. data/lib/generators/dry_crud/templates/app/helpers/table_helper.rb +83 -0
  25. data/lib/generators/dry_crud/templates/app/helpers/utility_helper.rb +84 -0
  26. data/lib/generators/dry_crud/templates/app/views/crud/_actions_edit.html.erb +3 -3
  27. data/lib/generators/dry_crud/templates/app/views/crud/_actions_edit.html.haml +3 -3
  28. data/lib/generators/dry_crud/templates/app/views/crud/_actions_index.html.erb +1 -1
  29. data/lib/generators/dry_crud/templates/app/views/crud/_actions_index.html.haml +1 -1
  30. data/lib/generators/dry_crud/templates/app/views/crud/_actions_show.html.erb +3 -3
  31. data/lib/generators/dry_crud/templates/app/views/crud/_actions_show.html.haml +3 -3
  32. data/lib/generators/dry_crud/templates/app/views/crud/_attrs.html.erb +1 -1
  33. data/lib/generators/dry_crud/templates/app/views/crud/_attrs.html.haml +1 -1
  34. data/lib/generators/dry_crud/templates/app/views/crud/_form.html.erb +1 -1
  35. data/lib/generators/dry_crud/templates/app/views/crud/_form.html.haml +1 -1
  36. data/lib/generators/dry_crud/templates/app/views/crud/edit.html.erb +1 -1
  37. data/lib/generators/dry_crud/templates/app/views/crud/edit.html.haml +1 -1
  38. data/lib/generators/dry_crud/templates/app/views/crud/new.html.erb +2 -2
  39. data/lib/generators/dry_crud/templates/app/views/crud/new.html.haml +2 -2
  40. data/lib/generators/dry_crud/templates/app/views/crud/show.html.erb +1 -1
  41. data/lib/generators/dry_crud/templates/app/views/crud/show.html.haml +1 -1
  42. data/lib/generators/dry_crud/templates/app/views/layouts/_flash.html.haml +1 -1
  43. data/lib/generators/dry_crud/templates/app/views/layouts/_nav.html.erb +1 -1
  44. data/lib/generators/dry_crud/templates/app/views/layouts/_nav.html.haml +1 -1
  45. data/lib/generators/dry_crud/templates/app/views/layouts/crud.html.erb +15 -7
  46. data/lib/generators/dry_crud/templates/app/views/layouts/crud.html.haml +18 -8
  47. data/lib/generators/dry_crud/templates/app/views/list/_search.html.erb +3 -3
  48. data/lib/generators/dry_crud/templates/app/views/list/_search.html.haml +3 -3
  49. data/lib/generators/dry_crud/templates/app/views/list/index.html.erb +1 -1
  50. data/lib/generators/dry_crud/templates/app/views/list/index.html.haml +1 -1
  51. data/lib/generators/dry_crud/templates/app/views/shared/_error_messages.html.erb +1 -1
  52. data/lib/generators/dry_crud/templates/app/views/shared/_error_messages.html.haml +1 -1
  53. data/lib/generators/dry_crud/templates/app/views/shared/_labeled.html.erb +2 -4
  54. data/lib/generators/dry_crud/templates/app/views/shared/_labeled.html.haml +2 -3
  55. data/lib/generators/dry_crud/templates/config/initializers/field_error_proc.rb +5 -1
  56. data/lib/generators/dry_crud/templates/config/locales/crud.de.yml +64 -0
  57. data/lib/generators/dry_crud/templates/config/locales/{en_crud.yml → crud.en.yml} +2 -2
  58. data/lib/generators/dry_crud/templates/spec/controllers/crud_test_models_controller_spec.rb +241 -231
  59. data/lib/generators/dry_crud/templates/spec/helpers/crud/form_builder_spec.rb +226 -0
  60. data/lib/generators/dry_crud/templates/spec/helpers/{standard_table_builder_spec.rb → crud/table_builder_spec.rb} +36 -34
  61. data/lib/generators/dry_crud/templates/spec/helpers/form_helper_spec.rb +238 -0
  62. data/lib/generators/dry_crud/templates/spec/helpers/format_helper_spec.rb +244 -0
  63. data/lib/generators/dry_crud/templates/spec/helpers/i18n_helper_spec.rb +132 -0
  64. data/lib/generators/dry_crud/templates/spec/helpers/table_helper_spec.rb +265 -0
  65. data/lib/generators/dry_crud/templates/spec/helpers/utility_helper_spec.rb +74 -0
  66. data/lib/generators/dry_crud/templates/spec/support/crud_controller_examples.rb +185 -100
  67. data/lib/generators/dry_crud/templates/spec/support/crud_controller_test_helper.rb +58 -49
  68. data/lib/generators/dry_crud/templates/test/{functional → controllers}/crud_test_models_controller_test.rb +112 -91
  69. data/lib/generators/dry_crud/templates/test/{unit/helpers/standard_form_builder_test.rb → helpers/crud/form_builder_test.rb} +79 -62
  70. data/lib/generators/dry_crud/templates/test/{unit/helpers/standard_table_builder_test.rb → helpers/crud/table_builder_test.rb} +31 -28
  71. data/lib/generators/dry_crud/templates/test/helpers/custom_assertions_test.rb +85 -0
  72. data/lib/generators/dry_crud/templates/test/helpers/form_helper_test.rb +129 -0
  73. data/lib/generators/dry_crud/templates/test/helpers/format_helper_test.rb +163 -0
  74. data/lib/generators/dry_crud/templates/test/helpers/i18n_helper_test.rb +79 -0
  75. data/lib/generators/dry_crud/templates/test/helpers/table_helper_test.rb +217 -0
  76. data/lib/generators/dry_crud/templates/test/helpers/utility_helper_test.rb +63 -0
  77. data/lib/generators/dry_crud/templates/test/{functional → support}/crud_controller_test_helper.rb +70 -59
  78. data/lib/generators/dry_crud/templates/test/{crud_test_model.rb → support/crud_test_model.rb} +107 -75
  79. data/lib/generators/dry_crud/templates/test/support/custom_assertions.rb +83 -0
  80. metadata +83 -146
  81. data/Rakefile +0 -211
  82. data/lib/generators/dry_crud/templates/app/helpers/crud_helper.rb +0 -168
  83. data/lib/generators/dry_crud/templates/app/helpers/list_helper.rb +0 -27
  84. data/lib/generators/dry_crud/templates/app/helpers/standard_form_builder.rb +0 -261
  85. data/lib/generators/dry_crud/templates/app/helpers/standard_helper.rb +0 -304
  86. data/lib/generators/dry_crud/templates/app/helpers/standard_table_builder.rb +0 -178
  87. data/lib/generators/dry_crud/templates/spec/helpers/crud_helper_spec.rb +0 -146
  88. data/lib/generators/dry_crud/templates/spec/helpers/list_helper_spec.rb +0 -154
  89. data/lib/generators/dry_crud/templates/spec/helpers/standard_form_builder_spec.rb +0 -215
  90. data/lib/generators/dry_crud/templates/spec/helpers/standard_helper_spec.rb +0 -387
  91. data/lib/generators/dry_crud/templates/test/custom_assertions.rb +0 -78
  92. data/lib/generators/dry_crud/templates/test/unit/custom_assertions_test.rb +0 -117
  93. data/lib/generators/dry_crud/templates/test/unit/helpers/crud_helper_test.rb +0 -111
  94. data/lib/generators/dry_crud/templates/test/unit/helpers/list_helper_test.rb +0 -123
  95. data/lib/generators/dry_crud/templates/test/unit/helpers/standard_helper_test.rb +0 -281
  96. data/test/templates/Gemfile +0 -46
  97. data/test/templates/app/controllers/admin/cities_controller.rb +0 -7
  98. data/test/templates/app/controllers/admin/countries_controller.rb +0 -13
  99. data/test/templates/app/controllers/ajax_controller.rb +0 -9
  100. data/test/templates/app/controllers/people_controller.rb +0 -13
  101. data/test/templates/app/controllers/vips_controller.rb +0 -19
  102. data/test/templates/app/helpers/cities_helper.rb +0 -9
  103. data/test/templates/app/helpers/people_helper.rb +0 -8
  104. data/test/templates/app/models/city.rb +0 -28
  105. data/test/templates/app/models/country.rb +0 -16
  106. data/test/templates/app/models/person.rb +0 -12
  107. data/test/templates/app/views/admin/cities/_actions_index.html.erb +0 -2
  108. data/test/templates/app/views/admin/cities/_actions_index.html.haml +0 -2
  109. data/test/templates/app/views/admin/cities/_attrs.html.erb +0 -1
  110. data/test/templates/app/views/admin/cities/_attrs.html.haml +0 -1
  111. data/test/templates/app/views/admin/cities/_form.html.erb +0 -7
  112. data/test/templates/app/views/admin/cities/_form.html.haml +0 -5
  113. data/test/templates/app/views/admin/cities/_hello.html.erb +0 -1
  114. data/test/templates/app/views/admin/cities/_hello.html.haml +0 -1
  115. data/test/templates/app/views/admin/cities/_list.html.erb +0 -3
  116. data/test/templates/app/views/admin/cities/_list.html.haml +0 -3
  117. data/test/templates/app/views/admin/countries/_list.html.erb +0 -4
  118. data/test/templates/app/views/admin/countries/_list.html.haml +0 -3
  119. data/test/templates/app/views/ajax/_actions_index.html.erb +0 -8
  120. data/test/templates/app/views/ajax/_actions_index.html.haml +0 -8
  121. data/test/templates/app/views/ajax/_actions_show.html.erb +0 -4
  122. data/test/templates/app/views/ajax/_actions_show.html.haml +0 -4
  123. data/test/templates/app/views/ajax/_form.html.erb +0 -2
  124. data/test/templates/app/views/ajax/_form.html.haml +0 -2
  125. data/test/templates/app/views/ajax/_hello.html.erb +0 -1
  126. data/test/templates/app/views/ajax/_hello.html.haml +0 -1
  127. data/test/templates/app/views/ajax/ajax.js.erb +0 -1
  128. data/test/templates/app/views/ajax/ajax.js.haml +0 -1
  129. data/test/templates/app/views/ajax/edit.js.erb +0 -1
  130. data/test/templates/app/views/ajax/edit.js.haml +0 -1
  131. data/test/templates/app/views/ajax/show.js.erb +0 -1
  132. data/test/templates/app/views/ajax/show.js.haml +0 -1
  133. data/test/templates/app/views/ajax/update.js.erb +0 -5
  134. data/test/templates/app/views/ajax/update.js.haml +0 -5
  135. data/test/templates/app/views/layouts/_nav.html.erb +0 -6
  136. data/test/templates/app/views/layouts/_nav.html.haml +0 -5
  137. data/test/templates/app/views/layouts/bootstrap.html.erb +0 -68
  138. data/test/templates/app/views/layouts/bootstrap.html.haml +0 -49
  139. data/test/templates/app/views/people/_attrs.html.erb +0 -5
  140. data/test/templates/app/views/people/_attrs.html.haml +0 -4
  141. data/test/templates/app/views/people/_list.html.erb +0 -1
  142. data/test/templates/app/views/people/_list.html.haml +0 -1
  143. data/test/templates/config/database.yml +0 -21
  144. data/test/templates/config/locales/en_cities.yml +0 -56
  145. data/test/templates/config/routes.rb +0 -32
  146. data/test/templates/db/migrate/20100511174904_create_people_and_cities.rb +0 -26
  147. data/test/templates/db/seeds.rb +0 -74
  148. data/test/templates/spec/controllers/admin/cities_controller_spec.rb +0 -74
  149. data/test/templates/spec/controllers/admin/countries_controller_spec.rb +0 -56
  150. data/test/templates/spec/controllers/people_controller_spec.rb +0 -80
  151. data/test/templates/spec/routing/cities_routing_spec.rb +0 -11
  152. data/test/templates/spec/routing/countries_routing_spec.rb +0 -11
  153. data/test/templates/test/fixtures/cities.yml +0 -11
  154. data/test/templates/test/fixtures/countries.yml +0 -11
  155. data/test/templates/test/fixtures/people.yml +0 -14
  156. data/test/templates/test/functional/admin/cities_controller_test.rb +0 -59
  157. data/test/templates/test/functional/admin/countries_controller_test.rb +0 -42
  158. 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 responses to HTML and JSON requests. Some enhancements were made to ease extendability.
4
- # Several protected helper methods are there to be (optionally) overriden by subclasses.
5
- # With the help of additional callbacks, it is possible to hook into the action procedures without
6
- # overriding the entire method.
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
- delegate :model_identifier, :to => 'self.class'
16
+ self.responder = Crud::Responder
17
+
18
+ <% if Rails.version >= '4.0' -%>
19
+ class_attribute :permitted_attrs
10
20
 
11
- # Defines before and after callback hooks for create, update, save and destroy actions.
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 further down.
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
- respond_with(entry, options.reverse_merge(:success => created), &block)
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
- respond_with(entry, options.reverse_merge(:success => updated), &block)
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
- flash[:alert] ||= error_messages.presence || flash_message(:failure) if !destroyed && request.format == :html
89
- location = !destroyed && request.env["HTTP_REFERER"].presence || index_url
90
- respond_with(entry, options.reverse_merge(:success => destroyed, :location => location), &block)
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
- # A label for the current entry, including the model name.
118
- def full_entry_label
119
- "#{models_label(false)} <i>#{ERB::Util.h(entry)}</i>".html_safe
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), :returning => true)
143
+ polymorphic_url(path_args(model_class), returning: true)
125
144
  end
126
145
 
127
- private
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
- flash[:notice] ||= flash_message(:success) if request.format == :html
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, considering _html keys as well.
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
- @@helper.t(keys.shift, :model => full_entry_label, :default => keys)
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
- @@helper.safe_join(entry.errors.full_messages, '<br/>'.html_safe)
149
- end
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
- # The identifier of the model used for form parameters.
159
- # I.e., the symbol of the underscored model name.
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 *methods
167
- before_render_edit *methods
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
- # This action lists all entries of a certain model and provides functionality to
3
- # search and sort this list.
4
- # Furthermore, it remembers the last search and sort parameters. When the action
5
- # is called with a param returning=true, these parameters are reused to present
6
- # the user the same list as he left it.
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
- helper_method :model_class, :models_label, :entries, :path_args
13
+ include Crud::GenericModel
14
+ include Crud::Nestable
15
+ include Crud::Rememberable
16
+ include Crud::RenderCallbacks
10
17
 
11
- delegate :model_class, :models_label, :to => 'self.class'
18
+ define_render_callbacks :index
12
19
 
13
- hide_action :model_class, :models_label, :inheritable_root_controller
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 page in an uniform way.
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 ActiveRecord::Relation.
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
- # The scope where model entries will be listed and created.
40
- # This is mainly used for nested models to provide the
41
- # required context.
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