dry_crud 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. data/README.rdoc +10 -7
  2. data/Rakefile +1 -1
  3. data/VERSION +1 -1
  4. data/lib/generators/dry_crud/templates/app/controllers/crud_controller.rb +137 -22
  5. data/lib/generators/dry_crud/templates/app/helpers/crud_helper.rb +10 -8
  6. data/lib/generators/dry_crud/templates/app/helpers/standard_helper.rb +5 -4
  7. data/lib/generators/dry_crud/templates/app/helpers/standard_table_builder.rb +69 -8
  8. data/lib/generators/dry_crud/templates/app/views/crud/_actions_edit.html.erb +2 -0
  9. data/lib/generators/dry_crud/templates/app/views/crud/_actions_index.html.erb +1 -0
  10. data/lib/generators/dry_crud/templates/app/views/crud/_actions_show.html.erb +3 -0
  11. data/lib/generators/dry_crud/templates/app/views/crud/_search.html.erb +2 -0
  12. data/lib/generators/dry_crud/templates/app/views/crud/edit.html.erb +1 -2
  13. data/lib/generators/dry_crud/templates/app/views/crud/index.html.erb +1 -8
  14. data/lib/generators/dry_crud/templates/app/views/crud/show.html.erb +1 -3
  15. data/lib/generators/dry_crud/templates/public/stylesheets/crud.css +15 -7
  16. data/lib/generators/dry_crud/templates/test/crud_test_model.rb +32 -4
  17. data/lib/generators/dry_crud/templates/test/functional/crud_controller_test_helper.rb +19 -1
  18. data/lib/generators/dry_crud/templates/test/functional/crud_test_models_controller_test.rb +47 -1
  19. data/lib/generators/dry_crud/templates/test/unit/helpers/crud_helper_test.rb +73 -9
  20. data/lib/generators/dry_crud/templates/test/unit/helpers/standard_helper_test.rb +4 -4
  21. data/test/templates/app/controllers/cities_controller.rb +2 -2
  22. data/test/templates/app/controllers/people_controller.rb +3 -2
  23. data/test/templates/app/models/city.rb +1 -1
  24. data/test/templates/app/views/ajax/_actions_index.html.erb +4 -0
  25. data/test/templates/db/seeds.rb +55 -0
  26. data/test/templates/test/functional/cities_controller_test.rb +1 -1
  27. data/test/templates/test/functional/people_controller_test.rb +6 -1
  28. metadata +9 -5
  29. data/test/templates/app/views/ajax/index.html.erb +0 -4
data/README.rdoc CHANGED
@@ -20,7 +20,7 @@ To integrate DRY CRUD into your code, only a few additions are required:
20
20
  * To use standard formatting, tables and forms throughout your application, add <tt>helper :standard</tt> to your +ApplicationController+ and benefit everywhere from these little helper methods.
21
21
  * Add a <tt>:label</tt> method to your models for a human-friendly representation.
22
22
 
23
- Version 1.0.0 is built for Rails 3. If you need a version for Rails 2.3, please get version 0.6.0 of the gem or go to the rails-2.3 branch on Github.
23
+ Version 1.2.0 is built for Rails 3. If you need a version for Rails 2.3, please get version 0.6.0 of the gem or go to the rails-2.3 branch on Github.
24
24
 
25
25
  == Overview
26
26
 
@@ -57,7 +57,7 @@ Say you want to manage a +Person+ model. Create the following controller and add
57
57
  end
58
58
  end
59
59
 
60
- That's it. You have an overview of all people, detail pages and forms to edit and create persons. Oh, and of course, you may delete persons as well. By default, all attributes are displayed and formatted according to their column type wherever they appear. This holds for the input fields as well.
60
+ That's it. You have a sortable overview of all people, detail pages and forms to edit and create persons. Oh, and of course, you may delete persons as well. By default, all attributes are displayed and formatted according to their column type wherever they appear. This holds for the input fields as well.
61
61
 
62
62
 
63
63
  ==== Customize single views
@@ -76,7 +76,7 @@ Next, let's adapt a part of the general behavior used in all CRUD controllers. A
76
76
  In <tt>app/controllers/crud_controller.rb</tt>, change the index action to
77
77
 
78
78
  def index
79
- @entries = model_class.paginate({:page => params[:page]}.merge(find_all_options))
79
+ @entries = list_entries.paginate(:page => params[:page])
80
80
  respond_with @entries
81
81
  end
82
82
 
@@ -84,6 +84,9 @@ In <tt>app/views/crud/index.html.erb</tt>, add the following line for the pagina
84
84
  <%= will_paginate @entries %>
85
85
 
86
86
  And we are done again. All our controllers inheriting from +CrudController+, including above +PeopleController+, now have paginated index views. Because our customization for the people table is in the separate <tt> _list</tt> partial, no further modifications are required.
87
+ If the current page should be remembered while viewing or editing an entry, just add :page to the remembered_params in <tt>CrudController::Memory</tt>:
88
+
89
+ controller.remember_params = [:q, :sort, :sort_dir, :page]
87
90
 
88
91
 
89
92
  ==== Special formatting for selected attributes
@@ -136,16 +139,16 @@ DRY CRUD also provides two builder classes for update/create forms and tables fo
136
139
 
137
140
  This is the code to define a table with some attribute columns for a list of same-type entries. Columns get a header corresponding to the attribute name:
138
141
  <%= table(@people) do |t|
139
- t.attrs :lastname, :firstname
142
+ t.sortable_attrs :lastname, :firstname
140
143
  end %>
141
144
 
142
145
  If entries is empty, a basic 'No entries found' message is rendered instead of the table.
143
146
 
144
147
  To render custom columns, use the :col method:
145
148
  <%= table(@people) do |t|
146
- t.attrs :lastname, :firstname
149
+ t.sortable_attrs :lastname, :firstname
147
150
  t.col('', :class => 'center') {|entry| image_tag(entry.picture) }
148
- t.attrs :street
151
+ t.attr :street
149
152
  t.col('Map') {|entry| link_to(entry.city, "http://maps.google.com/?q=#{entry.city}" }
150
153
  end %>
151
154
 
@@ -177,7 +180,7 @@ All generated files are supposed to provide a reasonable foundation for the CRUD
177
180
 
178
181
  === Controller:
179
182
 
180
- {controller/crud_controller.rb}[http://codez.ch/dry_crud/?q=CrudController]:: Abstract controller providing basic CRUD actions. This implementation mainly follows the one of the Rails scaffolding controller and responses to HTML and XML requests. Some enhancements were made to ease extendability. Several protected helper methods are there to be (optionally) overriden by subclasses. With the help of additional callbacks, it is possible to hook into the action procedures without overriding the entire method.
183
+ {controller/crud_controller.rb}[http://codez.ch/dry_crud/?q=CrudController]:: Abstract controller providing basic CRUD actions. This implementation mainly follows the one of the Rails scaffolding controller and responses to HTML and XML requests. Some enhancements were made to ease extendability. Several protected helper methods are there to be (optionally) overriden by subclasses. There are two modules included that provide search and sort functionality for the table displayed in the index action. With the help of additional callbacks, it is possible to hook into the action procedures without overriding the entire method.
181
184
 
182
185
  {controller/render_inheritable.rb}[http://codez.ch/dry_crud/?q=RenderInheritable]:: A controller enhancement that allows one to render inheritable views and partials. If no view file is found for the current controller, the corresponding file is looked up in its superclass hierarchy. Thus, only views or partials that look differently have to be overwritten.
183
186
 
data/Rakefile CHANGED
@@ -112,7 +112,7 @@ desc "Outputs the commands required for a release. Does not perform any other ac
112
112
  task :release do
113
113
  version = File.read('VERSION').strip
114
114
  puts "Issue the following commands to perform a release:"
115
- puts " $ git tag version-#{version}"
115
+ puts " $ git tag version-#{version} -m \"Version tag for dry_crud-#{version}.gem\""
116
116
  puts " $ git push --tags"
117
117
  puts " $ rake repackage"
118
118
  puts " $ gem push pkg/dry_crud-#{version}.gem"
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.0
1
+ 1.2.0
@@ -13,15 +13,12 @@ class CrudController < ApplicationController
13
13
  before_filter :set_entry, :only => [:show, :edit, :update, :destroy]
14
14
 
15
15
  helper :standard
16
- helper_method :model_class, :models_label, :full_entry_label, :search_support?
16
+ helper_method :model_class, :models_label, :full_entry_label
17
17
 
18
18
  delegate :model_class, :model_identifier, :models_label, :to => 'self.class'
19
19
 
20
20
  hide_action :model_class, :models_label, :model_identifier, :run_callbacks, :inheritable_root_controller
21
21
 
22
- # Define an array of searchable columns in your subclassing controllers.
23
- class_attribute :search_columns
24
- self.search_columns = []
25
22
 
26
23
  # Callbacks
27
24
  extend ActiveModel::Callbacks
@@ -51,7 +48,7 @@ class CrudController < ApplicationController
51
48
  # GET /entries
52
49
  # GET /entries.xml
53
50
  def index
54
- @entries = model_class.all find_all_options
51
+ @entries = list_entries
55
52
  respond_with @entries
56
53
  end
57
54
 
@@ -141,7 +138,7 @@ class CrudController < ApplicationController
141
138
  end
142
139
 
143
140
  # Creates a new model entry from the given params.
144
- def build_entry
141
+ def build_entry
145
142
  @entry = model_class.new(params[model_identifier])
146
143
  end
147
144
 
@@ -155,21 +152,9 @@ class CrudController < ApplicationController
155
152
  "#{models_label.singularize} '#{@entry.label}'"
156
153
  end
157
154
 
158
- # Find options used in the index action.
159
- def find_all_options
160
- { :conditions => search_condition }
161
- end
162
-
163
- def search_condition
164
- if search_support? && params[:q].present?
165
- clause = search_columns.collect {|f| "#{f} LIKE ?" }.join(" OR ")
166
- param = "%#{params[:q]}%"
167
- ["(#{clause})"] + [param] * search_columns.size
168
- end
169
- end
170
-
171
- def search_support?
172
- search_columns.present?
155
+ # The entries to be displayed in the current index page.
156
+ def list_entries
157
+ model_class.scoped
173
158
  end
174
159
 
175
160
  # Redirects to the show action of a single entry.
@@ -179,7 +164,7 @@ class CrudController < ApplicationController
179
164
 
180
165
  # Redirects to the main action of this controller.
181
166
  def redirect_to_index
182
- redirect_to polymorphic_path(model_class)
167
+ redirect_to polymorphic_path(model_class, :returning => true)
183
168
  end
184
169
 
185
170
  # Helper method to run before_render callbacks and render the action.
@@ -218,4 +203,134 @@ class CrudController < ApplicationController
218
203
  end
219
204
  end
220
205
 
206
+ # The search functionality for the index table.
207
+ # Extracted into an own module for convenience.
208
+ module Search
209
+ # Adds a :search_columns class attribute.
210
+ def self.included(controller)
211
+ # Define an array of searchable columns in your subclassing controllers.
212
+ controller.class_attribute :search_columns
213
+ controller.search_columns = []
214
+
215
+ controller.helper_method :search_support?
216
+
217
+ controller.alias_method_chain :list_entries, :search
218
+ end
219
+
220
+ protected
221
+
222
+ # Enhance the list entries with an optional search criteria
223
+ def list_entries_with_search
224
+ list_entries_without_search.where(search_condition)
225
+ end
226
+
227
+ # Compose the search condition with a basic SQL OR query.
228
+ def search_condition
229
+ if search_support? && params[:q].present?
230
+ clause = search_columns.collect {|f| "#{model_class.table_name}.#{f} LIKE ?" }.
231
+ join(" OR ")
232
+ param = "%#{params[:q]}%"
233
+ ["(#{clause})"] + [param] * search_columns.size
234
+ end
235
+ end
236
+
237
+ # Returns true if this controller has searchable columns.
238
+ def search_support?
239
+ search_columns.present?
240
+ end
241
+
242
+ end
243
+
244
+ include Search
245
+
246
+ # Sort functionality for the index table.
247
+ # Extracted into an own module for convenience.
248
+ module Sorting
249
+ # Adds a :sort_mappings class attribute.
250
+ def self.included(controller)
251
+ # Define a map of (virtual) attributes to SQL order expressions.
252
+ # May be used for sorting table columns that do not appear directly
253
+ # in the database table. E.g., map :city_id => 'cities.name' to
254
+ # sort the displayed city names.
255
+ controller.class_attribute :sort_mappings
256
+ controller.sort_mappings = {}
257
+
258
+ controller.helper_method :sortable?
259
+
260
+ controller.alias_method_chain :list_entries, :sort
261
+ end
262
+
263
+ protected
264
+
265
+ # Enhance the list entries with an optional sort order.
266
+ def list_entries_with_sort
267
+ if params[:sort].present? && sortable?(params[:sort])
268
+ list_entries_without_sort.except(:order).order(sort_expression)
269
+ else
270
+ list_entries_without_sort
271
+ end
272
+ end
273
+
274
+ # Return the sort expression to be used in the list query.
275
+ def sort_expression
276
+ col = sort_mappings[params[:sort].to_sym] ||
277
+ "#{model_class.table_name}.#{params[:sort]}"
278
+ "#{col} #{sort_dir}"
279
+ end
280
+
281
+ # The sort direction, either 'asc' or 'desc'.
282
+ def sort_dir
283
+ params[:sort_dir] == 'desc' ? 'desc' : 'asc'
284
+ end
285
+
286
+ # Returns true if the passed attribute is sortable.
287
+ def sortable?(attr)
288
+ model_class.column_names.include?(attr.to_s) ||
289
+ sort_mappings.include?(attr.to_sym)
290
+ end
291
+ end
292
+
293
+ include Sorting
294
+
295
+ # Remembers certain params of the index action in order to return
296
+ # to the same list after an entry was viewed or edited.
297
+ # If the index is called with a param :returning, the remembered params
298
+ # will be re-used.
299
+ # Extracted into an own module for convenience.
300
+ module Memory
301
+
302
+ # Adds the :remember_paramas class attribute and a before filter to the index action.
303
+ def self.included(controller)
304
+ # Define a list of param keys that should be remembered for the list action.
305
+ controller.class_attribute :remember_params
306
+ controller.remember_params = [:q, :sort, :sort_dir]
307
+
308
+ controller.before_filter :handle_remember_params, :only => [:index]
309
+ end
310
+
311
+ private
312
+
313
+ # Store and restore the corresponding params.
314
+ def handle_remember_params
315
+ remembered = remembered_params
316
+ if params[:returning]
317
+ # restore params
318
+ remember_params.each {|p| params[p] ||= remembered[p] }
319
+ end
320
+
321
+ # store current params
322
+ remember_params.each {|p| remembered[p] = params[p].presence }
323
+ end
324
+
325
+ # Get the params stored in the session.
326
+ # Params are stored by request path to play nice when a controller
327
+ # is used in different routes.
328
+ def remembered_params
329
+ session[:list_params] ||= {}
330
+ session[:list_params][request.path] ||= {}
331
+ end
332
+ end
333
+
334
+ include Memory
335
+
221
336
  end
@@ -6,13 +6,15 @@ module CrudHelper
6
6
  # Create a table of the @entries variable with the default or
7
7
  # the passed attributes in its columns.
8
8
  def crud_table(*attrs, &block)
9
- if block_given?
10
- table(@entries, *attrs, &block)
11
- else
12
- attributes = attrs.present? ? attrs : default_attrs
13
- table(@entries, *attributes) do |t|
14
- add_list_actions(t)
15
- end
9
+ # only use default attrs if no attrs and no block are given
10
+ attributes = (block_given? || attrs.present?) ? attrs : default_attrs
11
+ table(@entries) do |t|
12
+ t.sortable_attrs(*attributes)
13
+ if block_given?
14
+ yield t
15
+ else
16
+ add_list_actions(t)
17
+ end
16
18
  end
17
19
  end
18
20
 
@@ -41,5 +43,5 @@ module CrudHelper
41
43
  [:id, :position].each {|a| attrs.delete(a) }
42
44
  attrs
43
45
  end
44
-
46
+
45
47
  end
@@ -109,6 +109,7 @@ module StandardHelper
109
109
  end
110
110
 
111
111
  content << labeled(nil, form.submit("Save"))
112
+ content.html_safe
112
113
  end
113
114
  end
114
115
 
@@ -136,7 +137,7 @@ module StandardHelper
136
137
  end
137
138
 
138
139
  # Standard link action to the list page.
139
- def link_action_index(url_options = {:action => 'index'})
140
+ def link_action_index(url_options = {:action => 'index', :returning => true})
140
141
  link_action 'List', url_options
141
142
  end
142
143
 
@@ -158,16 +159,16 @@ module StandardHelper
158
159
  # Formats an active record association
159
160
  def format_assoc(obj, assoc)
160
161
  if assoc_val = obj.send(assoc.name)
161
- link_to_unless(no_assoc_link?(assoc), assoc_val.label, assoc_val)
162
+ link_to_unless(no_assoc_link?(assoc, assoc_val), assoc_val.label, assoc_val)
162
163
  else
163
164
  '(none)'
164
165
  end
165
166
  end
166
167
 
167
168
  # Returns true if no link should be created when formatting the given association.
168
- def no_assoc_link?(assoc)
169
+ def no_assoc_link?(assoc, val)
169
170
  (respond_to?(:no_assoc_links) && no_assoc_links.to_a.include?(assoc.name.to_sym)) ||
170
- !respond_to?("#{assoc.klass.name.underscore}_path".to_sym)
171
+ !respond_to?("#{val.class.name.underscore}_path".to_sym)
171
172
  end
172
173
 
173
174
  # Formats an arbitrary attribute of the given object depending on its data type.
@@ -25,7 +25,7 @@ class StandardTableBuilder
25
25
  def self.table(entries, template)
26
26
  t = new(entries, template)
27
27
  yield t
28
- t.to_html
28
+ t.to_html
29
29
  end
30
30
 
31
31
  # Define a column for the table with the given header, the html_options used for
@@ -42,23 +42,23 @@ class StandardTableBuilder
42
42
  attrs.each do |a|
43
43
  attr(a)
44
44
  end
45
- end
45
+ end
46
46
 
47
47
  # Define a column for the given attribute and an optional header.
48
48
  # If no header is given, the attribute name is used. The cell will
49
49
  # contain the formatted attribute value for the current entry.
50
50
  def attr(a, header = nil)
51
- header ||= captionize(a, entry_class)
52
- col(header, :class => align_class(a)) { |e| format_attr(e, a) }
51
+ header ||= attr_header(a)
52
+ col(header, :class => align_class(a)) { |e| format_attr(e, a) }
53
53
  end
54
54
 
55
55
  # Renders the table as HTML.
56
56
  def to_html
57
57
  content_tag :table, :class => 'list' do
58
- html_header + entries.collect { |e| html_row(e) }.join.html_safe
58
+ html_header + entries.collect { |e| html_row(e) }.join.html_safe
59
59
  end
60
60
  end
61
-
61
+
62
62
  # Returns css classes used for alignment of the cell data.
63
63
  # Based on the column type of the attribute.
64
64
  def align_class(attr)
@@ -71,17 +71,21 @@ class StandardTableBuilder
71
71
  end
72
72
  end
73
73
 
74
+ def attr_header(attr)
75
+ captionize(attr, entry_class)
76
+ end
77
+
74
78
  private
75
79
 
76
80
  def html_header
77
81
  content_tag :tr do
78
- cols.collect { |c| c.html_header }.join
82
+ cols.collect { |c| c.html_header }.join.html_safe
79
83
  end
80
84
  end
81
85
 
82
86
  def html_row(entry)
83
87
  tr_alt do
84
- cols.collect { |c| c.html_cell(entry) }.join
88
+ cols.collect { |c| c.html_cell(entry) }.join.html_safe
85
89
  end
86
90
  end
87
91
 
@@ -107,5 +111,62 @@ class StandardTableBuilder
107
111
  end
108
112
 
109
113
  end
114
+
115
+ # Provides headers with sort links. Expects a method :sortable?(attr)
116
+ # in the template/controller to tell if an attribute is sortable or not.
117
+ # Extracted into an own module for convenience.
118
+ module Sorting
119
+ # Create a header with sort links and a mark for the current sort direction.
120
+ def sort_header(attr, label = nil)
121
+ label ||= attr_header(attr)
122
+ template.link_to(label, sort_params(attr)) + current_mark(attr)
123
+ end
124
+
125
+ # Same as :attrs, except that it renders a sort link in the header
126
+ # if an attr is sortable.
127
+ def sortable_attrs(*attrs)
128
+ attrs.each do |a|
129
+ template.sortable?(a) ? sortable_attr(a) : attr(a)
130
+ end
131
+ end
132
+
133
+ # Renders a sort link header, otherwise similar to :attr.
134
+ def sortable_attr(a, header = nil)
135
+ attr(a, sort_header(a, header))
136
+ end
137
+
138
+ private
139
+
140
+ # Request params for the sort link.
141
+ def sort_params(attr)
142
+ params.merge({:sort => attr, :sort_dir => sort_dir(attr)})
143
+ end
144
+
145
+ # The sort mark, if any, for the given attribute.
146
+ def current_mark(attr)
147
+ if current_sort?(attr)
148
+ (sort_dir(attr) == 'asc' ? ' &uarr;' : ' &darr;').html_safe
149
+ else
150
+ ''
151
+ end
152
+ end
153
+
154
+ # Returns true if the given attribute is the current sort column.
155
+ def current_sort?(attr)
156
+ params[:sort] == attr.to_s
157
+ end
158
+
159
+ # The sort direction to use in the sort link for the given attribute.
160
+ def sort_dir(attr)
161
+ current_sort?(attr) && params[:sort_dir] == 'asc' ? 'desc' : 'asc'
162
+ end
163
+
164
+ # Delegate to template.
165
+ def params
166
+ template.params
167
+ end
168
+ end
169
+
170
+ include Sorting
110
171
 
111
172
  end
@@ -0,0 +1,2 @@
1
+ <%= link_action_show @entry %>
2
+ <%= link_action_destroy @entry %>
@@ -0,0 +1,3 @@
1
+ <%= link_action_index %>
2
+ <%= link_action_edit(@entry) %>
3
+ <%= link_action_destroy(@entry) %>
@@ -1,4 +1,6 @@
1
1
  <%= form_tag(polymorphic_path(model_class), {:method => :get}) do %>
2
+ <%= hidden_field_tag :sort, params[:sort] %>
3
+ <%= hidden_field_tag :sort_dir, params[:sort_dir] %>
2
4
  <%= text_field_tag :q, params[:q] %>
3
5
  <%= submit_tag 'Search' %>
4
6
  <% end %>
@@ -4,5 +4,4 @@
4
4
 
5
5
  <br/>
6
6
 
7
- <%= link_action_show @entry %>
8
- <%= link_action_destroy @entry %>
7
+ <%= render :partial => 'actions_edit' %>
@@ -6,11 +6,4 @@
6
6
 
7
7
  <br/>
8
8
 
9
- <%= link_action_add %>
10
-
11
-
12
-
13
-
14
-
15
-
16
-
9
+ <%= render :partial => 'actions_index' %>
@@ -4,6 +4,4 @@
4
4
 
5
5
  <br/>
6
6
 
7
- <%= link_action_index %>
8
- <%= link_action_edit(@entry) %>
9
- <%= link_action_destroy (@entry) %>
7
+ <%= render :partial => 'actions_show' %>
@@ -1,5 +1,5 @@
1
1
  body, div, p, td, th {
2
- font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
2
+ font-family: Verdana, Geneva, Helvetica, Arial, sans-serif;
3
3
  font-size: 11pt;
4
4
  }
5
5
 
@@ -7,18 +7,26 @@ td {
7
7
  vertical-align: top;
8
8
  }
9
9
 
10
+ table.list {}
11
+
12
+ /* div rendered if no entries available for list */
13
+ div.list {}
14
+
15
+ table.list th a {
16
+ color: darkblue;
17
+ }
18
+
10
19
  tr.even {
11
- background-color: #DDD;
20
+ background-color: #F0F0F0;
12
21
  }
13
22
 
14
23
  tr.odd {
15
- background-color: #EEE;
24
+ background-color: #F8F8F8;
16
25
  }
17
26
 
18
- table.list {}
19
-
20
- /* div rendered if no entries available for list */
21
- div.list {}
27
+ td p {
28
+ margin: 0;
29
+ }
22
30
 
23
31
  .right_align {
24
32
  text-align: right;
@@ -3,19 +3,24 @@ class CrudTestModel < ActiveRecord::Base #:nodoc:
3
3
  validates :name, :presence => true
4
4
  validates :rating, :inclusion => { :in => 1..10 }
5
5
 
6
- default_scope order('name')
6
+ #default_scope :order => 'name'
7
7
 
8
8
  belongs_to :companion, :class_name => 'CrudTestModel'
9
9
 
10
10
  def label
11
11
  name
12
12
  end
13
+
14
+ def chatty
15
+ remarks.size
16
+ end
13
17
  end
14
18
 
15
19
  class CrudTestModelsController < CrudController #:nodoc:
16
20
  HANDLE_PREFIX = 'handle_'
17
21
 
18
22
  self.search_columns = [:name, :whatever, :remarks]
23
+ self.sort_mappings = {:chatty => 'length(remarks)'}
19
24
 
20
25
  before_create :possibly_redirect
21
26
  before_create :handle_name
@@ -32,6 +37,16 @@ class CrudTestModelsController < CrudController #:nodoc:
32
37
  # than just the test route for this controller
33
38
  layout nil
34
39
 
40
+ protected
41
+
42
+ def list_entries
43
+ entries = super
44
+ if params[:filter]
45
+ entries = entries.where(['rating < ?', 3]).order('children DESC')
46
+ end
47
+ entries
48
+ end
49
+
35
50
  private
36
51
 
37
52
  # custom callback
@@ -126,15 +141,28 @@ module CrudTestHelper
126
141
  private
127
142
 
128
143
  def create(index, companion)
129
- c = (index + 64).chr * 5
144
+ c = str(index)
130
145
  CrudTestModel.create!(:name => c,
131
- :children => index,
146
+ :children => 10 - index,
132
147
  :companion => companion,
133
148
  :rating => "#{index}.#{index}".to_f,
134
149
  :income => 10000000 * index + 0.1 * index,
135
150
  :birthdate => "#{1900 + 10 * index}-#{index}-#{index}",
136
151
  :human => index % 2 == 0,
137
- :remarks => "#{c} #{c} #{c}\n" * 3)
152
+ :remarks => "#{c} #{str(index + 1)} #{str(index + 2)}\n" * (index % 3 + 1))
153
+ end
154
+
155
+ def str(index)
156
+ (index + 64).chr * 5
157
+ end
158
+
159
+ def with_test_routing
160
+ with_routing do |set|
161
+ set.draw { resources :crud_test_models }
162
+ # used to define a controller in these tests
163
+ set.default_url_options = {:controller => 'crud_test_models'}
164
+ yield
165
+ end
138
166
  end
139
167
 
140
168
  end
@@ -29,6 +29,24 @@ module CrudControllerTestHelper
29
29
  assert assigns(:entries).include?(test_entry)
30
30
  end
31
31
 
32
+ def test_index_sort_asc
33
+ col = model_class.column_names.first
34
+ get :index, :sort => col, :sort_dir => 'asc'
35
+ assert_response :success
36
+ assert_present assigns(:entries)
37
+ sorted = assigns(:entries).sort_by &(col.to_sym)
38
+ assert sorted, assigns(:entries)
39
+ end
40
+
41
+ def test_index_sort_desc
42
+ col = model_class.column_names.first
43
+ get :index, :sort => col, :sort_dir => 'desc'
44
+ assert_response :success
45
+ assert_present assigns(:entries)
46
+ sorted = assigns(:entries).sort_by &(col.to_sym)
47
+ assert sorted.reverse, assigns(:entries)
48
+ end
49
+
32
50
  def test_show
33
51
  get :show, :id => test_entry.id
34
52
  assert_response :success
@@ -156,7 +174,7 @@ module CrudControllerTestHelper
156
174
  protected
157
175
 
158
176
  def assert_redirected_to_index
159
- assert_redirected_to :action => 'index'
177
+ assert_redirected_to :action => 'index', :returning => true
160
178
  end
161
179
 
162
180
  def assert_test_attrs_equal
@@ -26,7 +26,7 @@ class CrudTestModelsControllerTest < ActionController::TestCase
26
26
  def test_index
27
27
  super
28
28
  assert_equal 6, assigns(:entries).size
29
- assert_equal assigns(:entries).sort_by {|a| a.name }, assigns(:entries)
29
+ assert_equal assigns(:entries).sort_by(&:name), assigns(:entries)
30
30
  end
31
31
 
32
32
  def test_index_search
@@ -34,6 +34,52 @@ class CrudTestModelsControllerTest < ActionController::TestCase
34
34
  assert_equal 1, assigns(:entries).size
35
35
  end
36
36
 
37
+ def test_index_with_custom_options
38
+ get :index, :filter => true
39
+ assert_response :success
40
+ assert_template 'index'
41
+ assert_present assigns(:entries)
42
+ assert_equal 2, assigns(:entries).size
43
+ assert_equal assigns(:entries).sort_by(&:children).reverse, assigns(:entries)
44
+ end
45
+
46
+ def test_index_search_with_custom_options
47
+ get :index, :q => 'DDD', :filter => true
48
+ assert_response :success
49
+ assert_template 'index'
50
+ assert_present assigns(:entries)
51
+ assert_equal 1, assigns(:entries).size
52
+ assert_equal [CrudTestModel.find_by_name('BBBBB')], assigns(:entries)
53
+ end
54
+
55
+ def test_sort_given_column
56
+ get :index, :sort => 'children', :sort_dir => 'asc'
57
+ assert_response :success
58
+ assert_template 'index'
59
+ assert_present assigns(:entries)
60
+ assert_equal 6, assigns(:entries).size
61
+ assert_equal CrudTestModel.order(:children).all, assigns(:entries)
62
+ end
63
+
64
+ def test_sort_virtual_column
65
+ get :index, :sort => 'chatty', :sort_dir => 'desc'
66
+ assert_response :success
67
+ assert_template 'index'
68
+ assert_present assigns(:entries)
69
+ assert_equal 6, assigns(:entries).size
70
+ sorted = CrudTestModel.all.sort_by &:chatty
71
+ assert_equal sorted.reverse.collect(&:name), assigns(:entries).collect(&:name)
72
+ end
73
+
74
+ def test_sort_with_search
75
+ get :index, :q => 'DDD', :sort => 'chatty', :sort_dir => 'asc'
76
+ assert_response :success
77
+ assert_template 'index'
78
+ assert_present assigns(:entries)
79
+ assert_equal 3, assigns(:entries).size
80
+ assert_equal ['CCCCC', 'DDDDD', 'BBBBB'], assigns(:entries).collect(&:name)
81
+ end
82
+
37
83
  def test_new
38
84
  super
39
85
  assert assigns(:companions)
@@ -9,10 +9,6 @@ class CrudHelperTest < ActionView::TestCase
9
9
  setup :reset_db, :setup_db, :create_test_data
10
10
  teardown :reset_db
11
11
 
12
- def model_class
13
- CrudTestModel
14
- end
15
-
16
12
  test "standard crud table" do
17
13
  @entries = CrudTestModel.all
18
14
 
@@ -21,8 +17,8 @@ class CrudHelperTest < ActionView::TestCase
21
17
  end
22
18
 
23
19
  assert_match /(<tr.+?<\/tr>){7}/m, t
24
- assert_match /(<th.+?<\/th>){14}/m, t
25
- assert_match /(<a href.+?<\/a>.*?){18}/m, t
20
+ assert_match /(<th><a .*asc.*>.*<\/a><\/th>){4}/m, t
21
+ assert_match /(<td><a href.+?<\/a><\/td>.*?){18}/m, t # show, edit, delete links
26
22
  end
27
23
 
28
24
  test "custom crud table with attributes" do
@@ -34,7 +30,7 @@ class CrudHelperTest < ActionView::TestCase
34
30
 
35
31
  assert_match /(<tr.+?<\/tr>){7}/m, t
36
32
  assert_match /(<th.+?<\/th>){4}/m, t
37
- assert_match /(<a href.+?<\/a>.*?){18}/m, t
33
+ assert_match /(<td><a href.+?<\/a><\/td>.*?){18}/m, t # show, edit, delete links
38
34
  end
39
35
 
40
36
  test "custom crud table with block" do
@@ -50,7 +46,7 @@ class CrudHelperTest < ActionView::TestCase
50
46
  assert_match /(<tr.+?<\/tr>){7}/m, t
51
47
  assert_match /(<th.+?<\/th>){4}/m, t
52
48
  assert_match /(<span>.+?<\/span>.*?){6}/m, t
53
- assert_no_match /(<a href.+?<\/a>.*?)/m, t
49
+ assert_no_match /(<td><a href.+?<\/a><\/td>.*?)/m, t
54
50
  end
55
51
 
56
52
  test "custom crud table with attributes and block" do
@@ -65,7 +61,58 @@ class CrudHelperTest < ActionView::TestCase
65
61
  assert_match /(<tr.+?<\/tr>){7}/m, t
66
62
  assert_match /(<th.+?<\/th>){4}/m, t
67
63
  assert_match /(<span>.+?<\/span>.*?){6}/m, t
68
- assert_no_match /(<a href.+?<\/a>.*?)/m, t
64
+ assert_no_match /(<td><a href.+?<\/a><\/td>.*?)/m, t
65
+ end
66
+
67
+ test "standard crud table with ascending sort params" do
68
+ def params
69
+ {:sort => 'children', :sort_dir => 'asc'}
70
+ end
71
+
72
+ @entries = CrudTestModel.all
73
+
74
+ t = with_test_routing do
75
+ crud_table
76
+ end
77
+
78
+ assert_match /(<tr.+?<\/tr>){7}/m, t
79
+ assert_match /(<th><a .*desc.*>Children<\/a> &darr;<\/th>){1}/m, t
80
+ assert_match /(<th><a .*asc.*>.*<\/a><\/th>){3}/m, t
81
+ assert_match /(<td><a href.+?<\/a><\/td>.*?){18}/m, t
82
+ end
83
+
84
+ test "standard crud table with descending sort params" do
85
+ def params
86
+ {:sort => 'children', :sort_dir => 'desc'}
87
+ end
88
+
89
+ @entries = CrudTestModel.all
90
+
91
+ t = with_test_routing do
92
+ crud_table
93
+ end
94
+
95
+ assert_match /(<tr.+?<\/tr>){7}/m, t
96
+ assert_match /(<th><a .*asc.*>Children<\/a> &uarr;<\/th>){1}/m, t
97
+ assert_match /(<th><a .*asc.*>.*<\/a><\/th>){4}/m, t
98
+ assert_match /(<td><a href.+?<\/a><\/td>.*?){18}/m, t
99
+ end
100
+
101
+ test "crud table with custom column sort params" do
102
+ def params
103
+ {:sort => 'chatty', :sort_dir => 'asc'}
104
+ end
105
+
106
+ @entries = CrudTestModel.all
107
+
108
+ t = with_test_routing do
109
+ crud_table :name, :children, :chatty
110
+ end
111
+
112
+ assert_match /(<tr.+?<\/tr>){7}/m, t
113
+ assert_match /(<th><a .*desc.*>Chatty<\/a> &darr;<\/th>){1}/m, t
114
+ assert_match /(<th><a .*asc.*>.*<\/a><\/th>){2}/m, t
115
+ assert_match /(<td><a href.+?<\/a><\/td>.*?){18}/m, t
69
116
  end
70
117
 
71
118
  test "crud form" do
@@ -90,12 +137,29 @@ class CrudHelperTest < ActionView::TestCase
90
137
  assert_equal [:name, :whatever, :children, :companion_id, :rating, :income,
91
138
  :birthdate, :human, :remarks, :created_at, :updated_at], default_attrs
92
139
  end
140
+
141
+ # Controller helper methods for the tests
142
+
143
+ def model_class
144
+ CrudTestModel
145
+ end
146
+
147
+ def params
148
+ {}
149
+ end
150
+
151
+ def sortable?(attr)
152
+ true
153
+ end
154
+
93
155
 
94
156
  private
95
157
 
96
158
  def with_test_routing
97
159
  with_routing do |set|
98
160
  set.draw { resources :crud_test_models }
161
+ # used to define a controller in these tests
162
+ set.default_url_options = {:controller => 'crud_test_models'}
99
163
  yield
100
164
  end
101
165
  end
@@ -101,7 +101,7 @@ class StandardHelperTest < ActionView::TestCase
101
101
 
102
102
  test "format integer column" do
103
103
  m = crud_test_models(:AAAAA)
104
- assert_equal '1', format_type(m, :children)
104
+ assert_equal '9', format_type(m, :children)
105
105
 
106
106
  m.children = 10000
107
107
  assert_equal '10,000', format_type(m, :children)
@@ -127,7 +127,7 @@ class StandardHelperTest < ActionView::TestCase
127
127
 
128
128
  test "format text column" do
129
129
  m = crud_test_models(:AAAAA)
130
- assert_equal "<p>AAAAA AAAAA AAAAA\n<br />AAAAA AAAAA AAAAA\n<br />AAAAA AAAAA AAAAA\n</p>", format_type(m, :remarks)
130
+ assert_equal "<p>AAAAA BBBBB CCCCC\n<br />AAAAA BBBBB CCCCC\n</p>", format_type(m, :remarks)
131
131
  assert format_type(m, :remarks).html_safe?
132
132
  end
133
133
 
@@ -170,7 +170,7 @@ class StandardHelperTest < ActionView::TestCase
170
170
  assert_match /option selected="selected" value="1910">1910<\/option>/, f
171
171
  assert_match /option selected="selected" value="1">January<\/option>/, f
172
172
  assert_match /option selected="selected" value="1">1<\/option>/, f
173
- assert_match /input .*?name="crud_test_model\[children\]" .*?type="text" .*?value=\"1\"/, f
173
+ assert_match /input .*?name="crud_test_model\[children\]" .*?type="text" .*?value=\"9\"/, f
174
174
  assert_match /input .*?name="crud_test_model\[human\]" .*?type="checkbox"/, f
175
175
  assert_match /input .*?type="submit" .*?value="Save"/, f
176
176
  end
@@ -207,7 +207,7 @@ class StandardHelperTest < ActionView::TestCase
207
207
  assert_match /option selected="selected" value="1910">1910<\/option>/, f
208
208
  assert_match /option selected="selected" value="1">January<\/option>/, f
209
209
  assert_match /option selected="selected" value="1">1<\/option>/, f
210
- assert_match /input .*?name="crud_test_model\[children\]" .*?type="text" .*?value=\"1\"/, f
210
+ assert_match /input .*?name="crud_test_model\[children\]" .*?type="text" .*?value=\"9\"/, f
211
211
  assert_match /input .*?name="crud_test_model\[human\]" .*?type="checkbox"/, f
212
212
  assert_match /input .*?type="submit" .*?value="Save"/, f
213
213
  end
@@ -3,8 +3,8 @@ class CitiesController < AjaxController
3
3
  def show
4
4
  respond_to do |format|
5
5
  format.html do
6
- flash[:notice] = flash[:notice]
7
- redirect_to :action => 'index'
6
+ flash.keep
7
+ redirect_to_index
8
8
  end
9
9
  format.xml { render :xml => @entry }
10
10
  end
@@ -1,11 +1,12 @@
1
1
  class PeopleController < AjaxController
2
2
 
3
3
  self.search_columns = [:name, :children, :rating, :income, :birthdate, :remarks]
4
+ self.sort_mappings = {:city_id => 'cities.name'}
4
5
 
5
6
  protected
6
7
 
7
- def fetch_all_options
8
- {:include => :city, :order => 'people.name, cities.country_code, cities.name'}
8
+ def list_entries
9
+ super.includes(:city).order('people.name, cities.country_code, cities.name')
9
10
  end
10
11
 
11
12
  end
@@ -4,7 +4,7 @@ class City < ActiveRecord::Base
4
4
 
5
5
  validates_presence_of :name, :country_code
6
6
 
7
- default_scope :order => 'country_code, name'
7
+ default_scope order('country_code, name')
8
8
 
9
9
  def label
10
10
  "#{name} (#{country_code})"
@@ -0,0 +1,4 @@
1
+ <%= render :partial => 'crud/actions_index' %>
2
+
3
+ <%= link_action 'Ajahx', {:action => 'ajax'}, :method => :get, :remote => true %>
4
+ <div id="response"></div>
@@ -0,0 +1,55 @@
1
+ be = City.create!(:name => 'Bern', :country_code => 'CH')
2
+ ny = City.create!(:name => 'New York', :country_code => 'USA')
3
+ sf = City.create!(:name => 'San Francisco', :country_code => 'USA')
4
+ lon = City.create!(:name => 'London', :country_code => 'GB')
5
+ br = City.create!(:name => 'Berlin', :country_code => 'DE')
6
+
7
+ Person.create!(:name => 'Albert Einstein',
8
+ :city => be,
9
+ :children => 2,
10
+ :rating => 9.8,
11
+ :income => 84000,
12
+ :birthdate => '1904-10-18',
13
+ :remarks => "Great physician\n Good cyclist")
14
+ Person.create!(:name => 'Adolf Ogi',
15
+ :city => be,
16
+ :children => 3,
17
+ :rating => 4.2,
18
+ :income => 264000,
19
+ :birthdate => '1938-01-22',
20
+ :remarks => 'Freude herrscht!')
21
+ Person.create!(:name => 'Jay Z',
22
+ :city => ny,
23
+ :children => 0,
24
+ :rating => 7.2,
25
+ :income => 868345,
26
+ :birthdate => '1976-05-02',
27
+ :remarks => "If got 99 problems\nbut you bitch ain't one\nTschie")
28
+ Person.create!(:name => 'Queen Elisabeth',
29
+ :city => lon,
30
+ :children => 1,
31
+ :rating => 1.56,
32
+ :income => 345622,
33
+ :birthdate => '1927-08-11',
34
+ :remarks => '')
35
+ Person.create!(:name => 'Schopenhauer',
36
+ :city => br,
37
+ :children => 7,
38
+ :rating => 6.9,
39
+ :income => 14000,
40
+ :birthdate => '1788-10-18',
41
+ :remarks => 'Neminem laede, immo omnes, quantum potes, iuva.')
42
+ Person.create!(:name => 'ZZ Top',
43
+ :city => ny,
44
+ :children => 185,
45
+ :rating => 1.8,
46
+ :income => 84000,
47
+ :birthdate => '1948-03-18',
48
+ :remarks => 'zzz..')
49
+ Person.create!(:name => 'Andy Warhol',
50
+ :city => ny,
51
+ :children => 0,
52
+ :rating => 7.5,
53
+ :income => 123000,
54
+ :birthdate => '1938-09-08',
55
+ :remarks => 'Tomato Soup')
@@ -14,7 +14,7 @@ class CitiesControllerTest < ActionController::TestCase
14
14
  def test_index
15
15
  super
16
16
  assert_equal 3, assigns(:entries).size
17
- assert_equal City.all(:order => 'country_code, name'), assigns(:entries)
17
+ assert_equal City.order('country_code, name').all, assigns(:entries)
18
18
  end
19
19
 
20
20
  def test_show
@@ -14,7 +14,12 @@ class PeopleControllerTest < ActionController::TestCase
14
14
  def test_index
15
15
  super
16
16
  assert_equal 2, assigns(:entries).size
17
- assert_equal Person.all(:include => :city, :order => 'people.name, cities.country_code, cities.name'), assigns(:entries)
17
+ assert_equal Person.includes(:city).order('people.name, cities.country_code, cities.name').all, assigns(:entries)
18
+ end
19
+
20
+ def test_index_search
21
+ super
22
+ assert_equal 1, assigns(:entries).size
18
23
  end
19
24
 
20
25
  protected
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry_crud
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
4
+ hash: 31
5
5
  prerelease: false
6
6
  segments:
7
7
  - 1
8
- - 1
8
+ - 2
9
9
  - 0
10
- version: 1.1.0
10
+ version: 1.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Pascal Zumkehr
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-09-24 00:00:00 +02:00
18
+ date: 2010-11-26 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -53,6 +53,9 @@ files:
53
53
  - lib/generators/dry_crud/templates/app/helpers/standard_form_builder.rb
54
54
  - lib/generators/dry_crud/templates/app/helpers/standard_helper.rb
55
55
  - lib/generators/dry_crud/templates/app/helpers/standard_table_builder.rb
56
+ - lib/generators/dry_crud/templates/app/views/crud/_actions_edit.html.erb
57
+ - lib/generators/dry_crud/templates/app/views/crud/_actions_index.html.erb
58
+ - lib/generators/dry_crud/templates/app/views/crud/_actions_show.html.erb
56
59
  - lib/generators/dry_crud/templates/app/views/crud/_attrs.html.erb
57
60
  - lib/generators/dry_crud/templates/app/views/crud/_form.html.erb
58
61
  - lib/generators/dry_crud/templates/app/views/crud/_list.html.erb
@@ -84,9 +87,9 @@ files:
84
87
  - test/templates/app/helpers/people_helper.rb
85
88
  - test/templates/app/models/city.rb
86
89
  - test/templates/app/models/person.rb
90
+ - test/templates/app/views/ajax/_actions_index.html.erb
87
91
  - test/templates/app/views/ajax/_hello.html.erb
88
92
  - test/templates/app/views/ajax/ajax.js.rjs
89
- - test/templates/app/views/ajax/index.html.erb
90
93
  - test/templates/app/views/cities/_form.html.erb
91
94
  - test/templates/app/views/cities/_hello.html.erb
92
95
  - test/templates/app/views/cities/_list.html.erb
@@ -94,6 +97,7 @@ files:
94
97
  - test/templates/app/views/people/_attrs.html.erb
95
98
  - test/templates/config/routes.rb
96
99
  - test/templates/db/migrate/20100511174904_create_people_and_cities.rb
100
+ - test/templates/db/seeds.rb
97
101
  - test/templates/test/fixtures/cities.yml
98
102
  - test/templates/test/fixtures/people.yml
99
103
  - test/templates/test/functional/cities_controller_test.rb
@@ -1,4 +0,0 @@
1
- <%= render :file => 'crud/index' %>
2
-
3
- <%= link_to 'Ajahx', {:action => 'ajax'}, :method => :get, :remote => true %>
4
- <div id="response"></div>