dry_crud 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/README.rdoc +25 -10
  2. data/Rakefile +30 -8
  3. data/VERSION +1 -1
  4. data/lib/generators/dry_crud/dry_crud_generator.rb +15 -2
  5. data/lib/generators/dry_crud/templates/app/controllers/crud_controller.rb +18 -10
  6. data/lib/generators/dry_crud/templates/app/controllers/list_controller.rb +14 -11
  7. data/lib/generators/dry_crud/templates/app/helpers/crud_helper.rb +71 -29
  8. data/lib/generators/dry_crud/templates/app/helpers/standard_form_builder.rb +40 -3
  9. data/lib/generators/dry_crud/templates/app/helpers/standard_helper.rb +8 -27
  10. data/lib/generators/dry_crud/templates/app/helpers/standard_table_builder.rb +13 -6
  11. data/lib/generators/dry_crud/templates/app/views/crud/_form.html.erb +1 -1
  12. data/lib/generators/dry_crud/templates/app/views/crud/_form.html.haml +1 -1
  13. data/lib/generators/dry_crud/templates/app/views/layouts/crud.html.erb +4 -4
  14. data/lib/generators/dry_crud/templates/app/views/layouts/crud.html.haml +5 -5
  15. data/lib/generators/dry_crud/templates/spec/controllers/crud_test_models_controller_spec.rb +433 -0
  16. data/lib/generators/dry_crud/templates/spec/helpers/crud_helper_spec.rb +146 -0
  17. data/lib/generators/dry_crud/templates/spec/helpers/list_helper_spec.rb +154 -0
  18. data/lib/generators/dry_crud/templates/spec/helpers/standard_form_builder_spec.rb +215 -0
  19. data/lib/generators/dry_crud/templates/spec/helpers/standard_helper_spec.rb +387 -0
  20. data/lib/generators/dry_crud/templates/spec/helpers/standard_table_builder_spec.rb +120 -0
  21. data/lib/generators/dry_crud/templates/spec/support/crud_controller_examples.rb +244 -0
  22. data/lib/generators/dry_crud/templates/spec/support/crud_controller_test_helper.rb +160 -0
  23. data/lib/generators/dry_crud/templates/test/crud_test_model.rb +45 -18
  24. data/lib/generators/dry_crud/templates/test/functional/crud_controller_test_helper.rb +19 -9
  25. data/lib/generators/dry_crud/templates/test/functional/crud_test_models_controller_test.rb +11 -14
  26. data/lib/generators/dry_crud/templates/test/unit/helpers/crud_helper_test.rb +26 -13
  27. data/lib/generators/dry_crud/templates/test/unit/helpers/list_helper_test.rb +0 -4
  28. data/lib/generators/dry_crud/templates/test/unit/helpers/standard_form_builder_test.rb +6 -0
  29. data/lib/generators/dry_crud/templates/test/unit/helpers/standard_helper_test.rb +9 -35
  30. data/lib/generators/dry_crud/templates/test/unit/helpers/standard_table_builder_test.rb +5 -4
  31. data/test/templates/Gemfile +3 -1
  32. data/test/templates/app/controllers/people_controller.rb +1 -1
  33. data/test/templates/app/controllers/vips_controller.rb +1 -1
  34. data/test/templates/app/models/city.rb +1 -1
  35. data/test/templates/app/views/admin/cities/_form.html.erb +1 -1
  36. data/test/templates/app/views/admin/cities/_form.html.haml +1 -1
  37. data/test/templates/app/views/admin/countries/_list.html.erb +3 -4
  38. data/test/templates/app/views/admin/countries/_list.html.haml +3 -4
  39. data/test/templates/app/views/ajax/_form.html.erb +1 -1
  40. data/test/templates/app/views/ajax/_form.html.haml +1 -1
  41. data/test/templates/spec/controllers/admin/cities_controller_spec.rb +74 -0
  42. data/test/templates/spec/controllers/admin/countries_controller_spec.rb +56 -0
  43. data/test/templates/spec/controllers/people_controller_spec.rb +80 -0
  44. data/test/templates/spec/routing/cities_routing_spec.rb +11 -0
  45. data/test/templates/spec/routing/countries_routing_spec.rb +11 -0
  46. data/test/templates/test/functional/admin/cities_controller_test.rb +1 -1
  47. data/test/templates/test/functional/admin/countries_controller_test.rb +1 -1
  48. data/test/templates/test/functional/people_controller_test.rb +3 -3
  49. metadata +18 -7
data/README.rdoc CHANGED
@@ -1,18 +1,18 @@
1
1
  = DRY CRUD
2
2
 
3
+ {<img src="https://secure.travis-ci.org/codez/dry_crud.png" />}[http://travis-ci.org/codez/dry_crud]
4
+
3
5
  DRY CRUD generates simple and extendable controller, views and helpers that support you to DRY up the CRUD code in your Rails project. Start with these elements and build a clean base to efficiently develop your application upon.
4
6
 
5
7
  Create your Rails application directly with the DRY CRUD application template:
6
8
 
7
- rails new [APP_NAME] -m https://raw.github.com/codez/dry_crud/master/template.rb
9
+ rails new APP_NAME -m https://raw.github.com/codez/dry_crud/master/template.rb
8
10
 
9
11
  If your application already exists or you prefer the DIY way, then install the Gem (<tt>gem install dry_crud</tt>), add it to your Gemfile and run the generator. You may remove the Gemfile entry again afterwards, it is not required anymore.
10
12
 
11
- rails generate dry_crud
13
+ rails generate dry_crud [--templates haml] [--tests rspec]
12
14
 
13
- If you prefer HAML templates instead of ERB, run
14
-
15
- rails generate dry_crud -t haml
15
+ By default, DRY CRUD generates ERB templates and Test::Unit tests. Pass the options above to generate HAML templates and/or RSpec examples instead.
16
16
 
17
17
  To integrate DRY CRUD into your code, only a few additions are required:
18
18
 
@@ -162,10 +162,10 @@ To render custom columns, use the :col method:
162
162
 
163
163
  Forms work very similar. In the most simple case, you just have to specify which attributes of a model to create input fields for, and you get a complete form with error messages, labeled input fields according the column types and a save button:
164
164
 
165
- <%= standard_form(@person, :firstname, :lastname, :age, :city) -%>
165
+ <%= crud_form(@person, :firstname, :lastname, :age, :city) -%>
166
166
 
167
167
  Of course, custom input fields may be defined as well:
168
- <%= standard_form(@person, :url => custom_update_person_path(@person.id)) do |f| %>
168
+ <%= crud_form(@person, :url => custom_update_person_path(@person.id)) do |f| %>
169
169
  <%= f.labeled_input_fields :firstname, :lastname %>
170
170
  <%= f.labeled(:sex) do %>
171
171
  <%= f.radio_button :sex, true %> female
@@ -286,7 +286,9 @@ views/shared/_error_messages.html.erb:: Partial to display the validation errors
286
286
 
287
287
  views/layouts/crud.html.erb:: An example layout showing how to use the <tt>@title</tt> and +flash+. Most probably you want to merge this with your <tt>application.html.erb</tt> or adapt the main CRUD templates, so you wont need this file.
288
288
 
289
- views/layouts/_menu.html.erb:: An empty file to put your menu items into. Included from <tt>crud.html.erb</tt>.
289
+ views/layouts/_flash.html.erb:: An simple partial to display the various flash messages. Included from <tt>crud.html.erb</tt>.
290
+
291
+ views/layouts/_nav.html.erb:: An empty file to put your navigation into. Included from <tt>crud.html.erb</tt>.
290
292
 
291
293
  app/assets/stylesheets/crud.scss:: A simple SCSS with all the classes and ids used in the CRUD code.
292
294
 
@@ -299,9 +301,22 @@ app/assets/images/action/*.png:: Some sample action icons from the {Open Icon Li
299
301
 
300
302
  {test/custom_assertions.rb}[http://codez.ch/dry_crud/CustomAssertions.html]:: A handful of convenient assertions. Include this module into your <tt>test_helper.rb</tt> file.
301
303
 
302
- {test/functionals/crud_controller_test_helper.rb}[http://codez.ch/dry_crud/CrudControllerTestHelper.html]:: A module to include into the functional tests for your +CrudController+ subclasses. Contains a handful of CRUD functionality tests for the provided implementation. So for each new CRUD controller, you get 20 tests for free.
304
+ {test/functional/crud_controller_test_helper.rb}[http://codez.ch/dry_crud/CrudControllerTestHelper.html]:: A module to include into the functional tests for your +CrudController+ subclasses. Contains a handful of CRUD functionality tests for the provided implementation. So for each new CRUD controller, you get 20 tests for free.
305
+
306
+ test/functional/crud_test_models_controller_test.rb:: Functional tests for the basic +CrudController+ functionality.
303
307
 
304
308
  test/several other tests:: Testing the provided implementation and a great base to test your adaptions of the CRUD code.
305
309
 
306
310
 
307
- {<img src="https://secure.travis-ci.org/codez/dry_crud.png" />}[http://travis-ci.org/codez/dry_crud]
311
+ === Specs:
312
+
313
+ spec/support/crud_controller_examples.rb:: A whole set of shared exampled to include into your controller specs. See <tt>spec/controllers/crud_test_models_controller_spec.rb</tt> for usage. So for each new CRUD controller, you get all the basic specs for free.
314
+
315
+ spec/support/crud_controller_test_helper.rb:: Convenience methods used by the crud controller examples.
316
+
317
+ {spec/support/crud_test_model.rb}[http://codez.ch/dry_crud/CrudTestHelper.html]:: A dummy model to run CRUD tests against.
318
+
319
+ spec/controllers/crud_test_models_controller_spec.rb:: Controller specs to test the basic +CrudController+ functionality.
320
+
321
+ spec/helpers/*_spec.rb:: The specs for all the helpers included in DRY CRUD.
322
+
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'rubygems'
2
2
  require 'rake/testtask'
3
+ require 'rspec/core/rake_task'
3
4
  require 'rubygems/package_task'
4
5
  require 'rdoc/task'
5
6
 
@@ -11,15 +12,25 @@ GENERATOR_ROOT = File.join(File.dirname(__FILE__), 'lib', 'generators', 'dry_cru
11
12
  task :default => :test
12
13
 
13
14
  desc "Run all tests"
14
- task :test => ['test:app:init'] do
15
- Rake::TestTask.new do |test|
15
+ task :test => ['test:unit', 'test:spec']
16
+
17
+
18
+ namespace :test do
19
+
20
+ desc "Run Test::Unit tests"
21
+ Rake::TestTask.new(:unit => 'test:app:init') do |test|
16
22
  test.libs << "test/test_app/test"
17
23
  test.test_files = Dir[ "test/test_app/test/**/*_test.rb" ]
18
24
  test.verbose = true
19
25
  end
20
- end
26
+
27
+ desc "Run RSpec tests"
28
+ RSpec::Core::RakeTask.new(:spec => 'test:app:init') do |t|
29
+ t.ruby_opts = "-I test/test_app/spec"
30
+ t.pattern = "test/test_app/spec/**/*_spec.rb"
31
+ end
32
+
21
33
 
22
- namespace :test do
23
34
  namespace :app do
24
35
  task :environment do
25
36
  ENV['RAILS_ROOT'] = TEST_APP_ROOT
@@ -33,7 +44,8 @@ namespace :test do
33
44
  unless File.exist?(TEST_APP_ROOT)
34
45
  sh "rails new #{TEST_APP_ROOT} --skip-bundle"
35
46
  FileUtils.cp(File.join(File.dirname(__FILE__), 'test', 'templates', 'Gemfile'), TEST_APP_ROOT)
36
- sh "cd #{TEST_APP_ROOT}; bundle install" # update Gemfile.lock
47
+ sh "cd #{TEST_APP_ROOT}; bundle install --local" # update Gemfile.lock
48
+ sh "cd #{TEST_APP_ROOT}; rails g rspec:install"
37
49
  FileUtils.rm_f(File.join(TEST_APP_ROOT, 'test', 'performance', 'browsing_test.rb'))
38
50
  end
39
51
  end
@@ -42,7 +54,10 @@ namespace :test do
42
54
  task :generate_crud => [:create, :environment] do
43
55
  require File.join(GENERATOR_ROOT, 'dry_crud_generator')
44
56
 
45
- DryCrudGenerator.new('', {:force => true, :templates => ENV['HAML'] ? 'haml' : 'erb'}, :destination_root => TEST_APP_ROOT).invoke_all
57
+ DryCrudGenerator.new([], {:force => true,
58
+ :templates => ENV['HAML'] ? 'haml' : 'erb',
59
+ :tests => 'all'},
60
+ :destination_root => TEST_APP_ROOT).invoke_all
46
61
  end
47
62
 
48
63
  desc "Populates the test application with some models and controllers"
@@ -50,6 +65,10 @@ namespace :test do
50
65
  # copy test app templates
51
66
  FileUtils.cp_r(File.join(File.dirname(__FILE__), 'test', 'templates', '.'), TEST_APP_ROOT)
52
67
 
68
+ # copy shared fixtures
69
+ FileUtils.cp_r(File.join(File.dirname(__FILE__), 'test', 'templates', 'test', 'fixtures'),
70
+ File.join(TEST_APP_ROOT, 'spec'))
71
+
53
72
  # replace some unused files
54
73
  FileUtils.rm_f(File.join(TEST_APP_ROOT, 'public', 'index.html'))
55
74
  layouts = File.join(TEST_APP_ROOT, 'app', 'views', 'layouts')
@@ -65,7 +84,10 @@ namespace :test do
65
84
  Dir.glob(File.join(TEST_APP_ROOT, 'app', 'views', '**', "*.#{exclude}")).each do |f|
66
85
  FileUtils.rm(f)
67
86
  end
68
-
87
+ end
88
+
89
+ desc "Insert seed data into the test database"
90
+ task :seed => :populate do
69
91
  # migrate the database
70
92
  FileUtils.cd(TEST_APP_ROOT) do
71
93
  sh "rake db:migrate db:seed RAILS_ENV=development --trace"
@@ -74,7 +96,7 @@ namespace :test do
74
96
  end
75
97
 
76
98
  desc "Initializes the test application with a couple of classes"
77
- task :init => [:populate,
99
+ task :init => [:seed,
78
100
  :customize]
79
101
 
80
102
  desc "Customize some of the functionality provided by dry_crud"
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.6.0
1
+ 1.7.0
@@ -3,6 +3,7 @@ require 'rails/generators'
3
3
  class DryCrudGenerator < Rails::Generators::Base
4
4
 
5
5
  class_options %w(templates -t) => 'erb'
6
+ class_options %w(tests) => 'testunit'
6
7
 
7
8
 
8
9
  def self.source_root
@@ -11,16 +12,28 @@ class DryCrudGenerator < Rails::Generators::Base
11
12
 
12
13
  def install_dry_crud
13
14
  # copy everything in template subfolders
14
- exclude = options[:templates].downcase == 'haml' ? '.erb' : '.haml'
15
+ exclude_template = options[:templates].downcase == 'haml' ? '.erb' : '.haml'
16
+
17
+ exclude_test_dir = case options[:tests].downcase
18
+ when 'rspec' then 'test'
19
+ when 'all' then 'exclude_nothing'
20
+ else 'spec'
21
+ end
15
22
 
16
23
  Dir.chdir(self.class.source_root) do
17
24
  Dir.glob(File.join('**', '**')).sort.each do |file_source|
18
25
  if !File.directory?(file_source) &&
19
- !file_source.end_with?(exclude) &&
26
+ !file_source.end_with?(exclude_template) &&
27
+ !file_source.start_with?(exclude_test_dir) &&
20
28
  file_source != 'INSTALL'
21
29
  copy_file(file_source)
22
30
  end
23
31
  end
32
+
33
+ unless exclude_test_dir == 'spec'
34
+ copy_file(File.join('test', 'crud_test_model.rb'),
35
+ File.join('spec', 'support', 'crud_test_model.rb'))
36
+ end
24
37
  end
25
38
 
26
39
  readme "INSTALL"
@@ -48,12 +48,13 @@ class CrudController < ListController
48
48
  # There are before and after create callbacks to hook into the action.
49
49
  # To customize the response, you may overwrite this action and call
50
50
  # super with a block that gets the format parameter.
51
+ # Specify a :location option if you wish to do a custom redirect.
51
52
  # POST /entries
52
53
  # POST /entries.json
53
- def create(&block)
54
+ def create(options = {}, &block)
54
55
  assign_attributes
55
56
  created = with_callbacks(:create, :save) { entry.save }
56
- respond_with(entry, :success => created, &block)
57
+ respond_with(entry, options.reverse_merge(:success => created), &block)
57
58
  end
58
59
 
59
60
  # Display a form to edit an exisiting entry of this model.
@@ -66,28 +67,30 @@ class CrudController < ListController
66
67
  # There are before and after update callbacks to hook into the action.
67
68
  # To customize the response, you may overwrite this action and call
68
69
  # super with a block that gets the format parameter.
70
+ # Specify a :location option if you wish to do a custom redirect.
69
71
  # PUT /entries/1
70
72
  # PUT /entries/1.json
71
- def update(&block)
73
+ def update(options = {}, &block)
72
74
  assign_attributes
73
75
  updated = with_callbacks(:update, :save) { entry.save }
74
- respond_with(entry, :success => updated, &block)
76
+ respond_with(entry, options.reverse_merge(:success => updated), &block)
75
77
  end
76
78
 
77
79
  # Destroy an existing entry of this model.
78
80
  # There are before and after destroy callbacks to hook into the action.
79
81
  # To customize the response, you may overwrite this action and call
80
82
  # super with a block that gets success and format parameters.
83
+ # Specify a :location option if you wish to do a custom redirect.
81
84
  # DELETE /entries/1
82
85
  # DELETE /entries/1.json
83
- def destroy(&block)
86
+ def destroy(options = {}, &block)
84
87
  destroyed = run_callbacks(:destroy) { entry.destroy }
85
88
  flash[:alert] ||= error_messages.presence || flash_message(:failure) if !destroyed && request.format == :html
86
89
  location = !destroyed && request.env["HTTP_REFERER"].presence || index_url
87
- respond_with(entry, :success => destroyed, :location => location, &block)
90
+ respond_with(entry, options.reverse_merge(:success => destroyed, :location => location), &block)
88
91
  end
89
92
 
90
- protected
93
+ private
91
94
 
92
95
  ############# CUSTOMIZABLE HELPER METHODS ##############################
93
96
 
@@ -108,7 +111,7 @@ class CrudController < ListController
108
111
 
109
112
  # Assigns the attributes from the params to the model entry.
110
113
  def assign_attributes
111
- entry.attributes = params[model_identifier]
114
+ entry.attributes = model_params
112
115
  end
113
116
 
114
117
  # A label for the current entry, including the model name.
@@ -144,13 +147,18 @@ class CrudController < ListController
144
147
  def error_messages
145
148
  @@helper.safe_join(entry.errors.full_messages, '<br/>'.html_safe)
146
149
  end
150
+
151
+ # The form params for this model.
152
+ def model_params
153
+ params[model_identifier]
154
+ end
147
155
 
148
156
 
149
157
  class << self
150
158
  # The identifier of the model used for form parameters.
151
159
  # I.e., the symbol of the underscored model name.
152
160
  def model_identifier
153
- @model_identifier ||= model_class.name.underscore.to_sym
161
+ @model_identifier ||= model_class.model_name.param_key
154
162
  end
155
163
 
156
164
  # Convenience callback to apply a callback on both form actions (new and edit).
@@ -168,7 +176,7 @@ class CrudController < ListController
168
176
  super(controller, with_path_args(resources, controller), options)
169
177
  end
170
178
 
171
- protected
179
+ private
172
180
 
173
181
  # Check whether the resource has errors. Additionally checks the :success option.
174
182
  def has_errors?
@@ -23,7 +23,7 @@ class ListController < ApplicationController
23
23
  respond_with(entries, &block)
24
24
  end
25
25
 
26
- protected
26
+ private
27
27
 
28
28
  # Helper method to access the entries to be displayed in the current index page in an uniform way.
29
29
  def entries
@@ -52,7 +52,7 @@ class ListController < ApplicationController
52
52
  # Get the instance variable named after the model_class.
53
53
  # If the collection variable is required, pass true as the second argument.
54
54
  def get_model_ivar(plural = false)
55
- name = model_class.name.underscore
55
+ name = ivar_name(model_class)
56
56
  name = name.pluralize if plural
57
57
  instance_variable_get(:"@#{name}")
58
58
  end
@@ -61,13 +61,17 @@ class ListController < ApplicationController
61
61
  # If the value is a collection, sets the plural name.
62
62
  def set_model_ivar(value)
63
63
  name = if value.respond_to?(:klass) # ActiveRecord::Relation
64
- value.klass.name.pluralize
64
+ ivar_name(value.klass).pluralize
65
65
  elsif value.respond_to?(:each) # Array
66
- value.first.klass.name.pluralize
66
+ ivar_name(value.first.klass).pluralize
67
67
  else
68
- value.class.name
68
+ ivar_name(value.class)
69
69
  end
70
- instance_variable_set(:"@#{name.underscore}", value)
70
+ instance_variable_set(:"@#{name}", value)
71
+ end
72
+
73
+ def ivar_name(klass)
74
+ klass.model_name.param_key
71
75
  end
72
76
 
73
77
  class << self
@@ -102,14 +106,13 @@ class ListController < ApplicationController
102
106
  # If a callback renders or redirects, the action is not rendered.
103
107
  def render_with_callbacks(*args, &block)
104
108
  options = _normalize_render(*args, &block)
105
- p options if options[:location] == :back
106
109
  callback = "render_#{options[:template]}"
107
110
  run_callbacks(callback) if respond_to?(:"_run_#{callback}_callbacks", true)
108
111
 
109
112
  render_without_callbacks(*args, &block) unless performed?
110
113
  end
111
114
 
112
- protected
115
+ private
113
116
 
114
117
  # Helper method the run the given block in between the before and after
115
118
  # callbacks of the given kinds.
@@ -145,7 +148,7 @@ class ListController < ApplicationController
145
148
  controller.alias_method_chain :list_entries, :search
146
149
  end
147
150
 
148
- protected
151
+ private
149
152
 
150
153
  # Enhance the list entries with an optional search criteria
151
154
  def list_entries_with_search
@@ -192,7 +195,7 @@ class ListController < ApplicationController
192
195
  controller.alias_method_chain :list_entries, :sort
193
196
  end
194
197
 
195
- protected
198
+ private
196
199
 
197
200
  # Enhance the list entries with an optional sort order.
198
201
  def list_entries_with_sort
@@ -302,7 +305,7 @@ class ListController < ApplicationController
302
305
  controller.alias_method_chain :path_args, :nesting
303
306
  end
304
307
 
305
- protected
308
+ private
306
309
 
307
310
  # Returns the direct parent ActiveRecord of the current request, if any.
308
311
  def parent
@@ -3,40 +3,76 @@
3
3
  # is included in CrudController.
4
4
  module CrudHelper
5
5
 
6
- # Renders a generic form for the current entry with :default_attrs or the
7
- # given attribute array, using the StandardFormBuilder. An options hash
8
- # may be given as the last argument.
6
+ # Renders a crud form for the current entry with default_attrs or the
7
+ # given attribute array. An options hash may be given as the last argument.
9
8
  # If a block is given, a custom form may be rendered and attrs is ignored.
10
- def crud_form(*attrs, &block)
11
- attrs = attrs_or_default(attrs) { default_attrs - [:created_at, :updated_at] }
12
- standard_form(path_args(entry), *attrs, &block)
9
+ def entry_form(*attrs, &block)
10
+ options = attrs.extract_options!
11
+ attrs = default_attrs - [:created_at, :updated_at] if attrs.blank?
12
+ attrs << options
13
+ crud_form(path_args(entry), *attrs, &block)
13
14
  end
14
15
 
15
- # Create a table of the entries with the default or
16
- # the passed attributes in its columns. An options hash may be given
17
- # as the last argument.
18
- def crud_table(*attrs, &block)
19
- if block_given?
20
- list_table(*attrs, &block)
21
- else
22
- attrs = attrs_or_default(attrs) { default_attrs }
23
- list_table(*attrs) do |t|
24
- add_table_actions(t)
16
+ # Renders a standard form for the given entry and attributes.
17
+ # The form is rendered with a basic save and cancel button.
18
+ # If a block is given, custom input fields may be rendered and attrs is ignored.
19
+ # An options hash may be given as the last argument.
20
+ def crud_form(object, *attrs, &block)
21
+ options = attrs.extract_options!
22
+ cancel_url = get_cancel_url(object, options)
23
+
24
+ standard_form(object, options) do |form|
25
+ content = if block_given?
26
+ capture(form, &block)
27
+ else
28
+ form.labeled_input_fields(*attrs)
25
29
  end
30
+
31
+ content << form.standard_actions(cancel_url)
32
+ content.html_safe
33
+ end
34
+ end
35
+
36
+ # Create a table of the current entries with the default or the passed
37
+ # attributes in its columns.
38
+ # If attrs are present, the first column will link to the show
39
+ # action. Edit and destroy actions are appended to the end of each row.
40
+ # If a block is given, the column defined there will be inserted
41
+ # between the given attributes and the actions.
42
+ # An options hash for the table builder may be given as the last argument.
43
+ def crud_table(*attrs, &block)
44
+ options = attrs.extract_options!
45
+ attributes = (block_given? || attrs.present?) ? attrs : default_attrs
46
+ first = attributes.shift
47
+ table(entries, options) do |t|
48
+ col_show(t, first) if first
49
+ t.sortable_attrs(*attributes)
50
+ yield t if block_given?
51
+ add_table_actions(t)
26
52
  end
27
53
  end
28
54
 
29
55
  # Adds a set of standard action link column (show, edit, destroy) to the given table.
30
56
  def add_table_actions(table)
31
- action_col_show(table)
32
57
  action_col_edit(table)
33
58
  action_col_destroy(table)
34
59
  end
60
+
61
+ # Renders the passed attr with a link to the show action for
62
+ # the current entry.
63
+ # A block may be given to define the link path for the row entry.
64
+ def col_show(table, attr, &block)
65
+ table.attr(attr, table.sort_header(attr)) do |e|
66
+ link_to(format_attr(e, attr), action_path(e, &block))
67
+ end
68
+ end
35
69
 
36
70
  # Action link to show the row entry inside a table.
37
71
  # A block may be given to define the link path for the row entry.
38
72
  def action_col_show(table, &block)
39
- action_col(table) { |e| link_table_action('zoom-in', action_path(e, &block)) }
73
+ action_col(table) do |e|
74
+ link_table_action('zoom-in', action_path(e, &block))
75
+ end
40
76
  end
41
77
 
42
78
  # Action link to edit inside a table.
@@ -53,8 +89,8 @@ module CrudHelper
53
89
  def action_col_destroy(table, &block)
54
90
  action_col(table) do |e|
55
91
  link_table_action('remove', action_path(e, &block),
56
- :confirm => ti(:confirm_delete),
57
- :method => :delete)
92
+ :data => { :confirm => ti(:confirm_delete),
93
+ :method => :delete })
58
94
  end
59
95
  end
60
96
 
@@ -90,8 +126,8 @@ module CrudHelper
90
126
  def link_action_destroy(path = nil)
91
127
  path ||= path_args(entry)
92
128
  link_action ti(:"link.delete"), 'remove', path,
93
- :confirm => ti(:confirm_delete),
94
- :method => :delete
129
+ :data => { :confirm => ti(:confirm_delete),
130
+ :method => :delete }
95
131
  end
96
132
 
97
133
  # Standard link action to the list page.
@@ -110,17 +146,23 @@ module CrudHelper
110
146
 
111
147
  private
112
148
 
149
+ # Get the cancel url for the given object considering options:
150
+ # 1. Use :cancel_url_new or :cancel_url_edit option, if present
151
+ # 2. Use :cancel_url option, if present
152
+ # 3. Use polymorphic_path(object)
153
+ def get_cancel_url(object, options)
154
+ record = Array(object).last
155
+ cancel_url = options.delete(:cancel_url)
156
+ cancel_url_new = options.delete(:cancel_url_new)
157
+ cancel_url_edit = options.delete(:cancel_url_edit)
158
+ url = record.new_record? ? cancel_url_new : cancel_url_edit
159
+ url || cancel_url || polymorphic_path(object, :returning => true)
160
+ end
161
+
113
162
  # If a block is given, call it to get the path for the current row entry.
114
163
  # Otherwise, return the standard path args.
115
164
  def action_path(e, &block)
116
165
  block_given? ? yield(e) : path_args(e)
117
166
  end
118
167
 
119
- # Returns default attrs for a crud table if no others are passed.
120
- def attrs_or_default(attrs)
121
- options = attrs.extract_options!
122
- attrs = yield if attrs.blank?
123
- attrs << options
124
- end
125
-
126
168
  end
@@ -9,7 +9,7 @@ class StandardFormBuilder < ActionView::Helpers::FormBuilder
9
9
 
10
10
  attr_reader :template
11
11
 
12
- delegate :association, :column_type, :column_property, :captionize, :ta,
12
+ delegate :association, :column_type, :column_property, :captionize, :ti, :ta, :link_to,
13
13
  :content_tag, :safe_join, :capture, :add_css_class, :assoc_and_id_attr,
14
14
  :to => :template
15
15
 
@@ -121,6 +121,28 @@ class StandardFormBuilder < ActionView::Helpers::FormBuilder
121
121
  add_css_class(html_options, 'multiselect')
122
122
  belongs_to_field(attr, html_options)
123
123
  end
124
+
125
+ # Render the error messages for the current form.
126
+ def error_messages
127
+ @template.render('shared/error_messages', :errors => @object.errors, :object => @object)
128
+ end
129
+
130
+ # Render a submit button and a cancel link for this form.
131
+ def standard_actions(cancel_url, submit_label = ti(:"button.save"))
132
+ content_tag(:div, :class => 'form-actions') do
133
+ safe_join([submit_button(submit_label), cancel_link(cancel_url)], ' ')
134
+ end
135
+ end
136
+
137
+ # Render a standard submit button with the given label.
138
+ def submit_button(label = ti(:"button.save"))
139
+ button(label, :class => 'btn btn-primary')
140
+ end
141
+
142
+ # Render a cancel link pointing to the given url.
143
+ def cancel_link(url)
144
+ link_to(ti(:"button.cancel"), url, :class => 'cancel')
145
+ end
124
146
 
125
147
  # Renders a marker if the given attr has to be present.
126
148
  def required_mark(attr)
@@ -158,9 +180,11 @@ class StandardFormBuilder < ActionView::Helpers::FormBuilder
158
180
 
159
181
  # Dispatch methods starting with 'labeled_' to render a label and the corresponding
160
182
  # input field. E.g. labeled_boolean_field(:checked, :class => 'bold')
183
+ # To add an additional help text, use the help option.
184
+ # E.g. labeled_boolean_field(:checked, :help => 'Some Help')
161
185
  def method_missing(name, *args)
162
186
  if field_method = labeled_field_method?(name)
163
- labeled(args.first, send(field_method, *args) + required_mark(args.first))
187
+ build_labeled_field(field_method, *args)
164
188
  else
165
189
  super(name, *args)
166
190
  end
@@ -171,7 +195,12 @@ class StandardFormBuilder < ActionView::Helpers::FormBuilder
171
195
  labeled_field_method?(name).present? || super(name)
172
196
  end
173
197
 
174
- protected
198
+ # Generates a help block for fields
199
+ def help_block(text)
200
+ content_tag(:p, text, :class => 'help-block')
201
+ end
202
+
203
+ private
175
204
 
176
205
  # Returns true if attr is a non-polymorphic association.
177
206
  # If one or more macros are given, the association must be of this kind.
@@ -221,4 +250,12 @@ class StandardFormBuilder < ActionView::Helpers::FormBuilder
221
250
  end
222
251
  end
223
252
 
253
+ def build_labeled_field(field_method, *args)
254
+ options = args.extract_options!
255
+ help = options.delete(:help)
256
+ text = send(field_method, *(args<<options)) + required_mark(args.first)
257
+ text << help_block(help) if help.present?
258
+ labeled(args.first, text)
259
+ end
260
+
224
261
  end
@@ -11,7 +11,8 @@ module StandardHelper
11
11
  def f(value)
12
12
  case value
13
13
  when Fixnum then number_with_delimiter(value)
14
- when Float, BigDecimal then number_with_precision(value, :precision => 2)
14
+ when Float, BigDecimal then number_with_precision(value, :precision => t('number.format.precision'),
15
+ :delimiter => t('number.format.delimiter'))
15
16
  when Date then l(value)
16
17
  when Time then l(value, :format => :time)
17
18
  when true then t(:"global.yes")
@@ -78,6 +79,7 @@ module StandardHelper
78
79
  # If entries is empty, an appropriate message is rendered.
79
80
  # An options hash may be given as the last argument.
80
81
  def table(entries, *attrs, &block)
82
+ entries.inspect # force evaluation of relations
81
83
  if entries.present?
82
84
  StandardTableBuilder.table(entries, self, attrs.extract_options!) do |t|
83
85
  t.attrs(*attrs)
@@ -88,40 +90,19 @@ module StandardHelper
88
90
  end
89
91
  end
90
92
 
91
- # Renders a generic form for all given attributes using StandardFormBuilder.
93
+ # Renders a standard form using StandardFormBuilder.
92
94
  # Before the input fields, the error messages are rendered, if present.
93
- # The form is rendered with a basic save button.
94
- # If a block is given, custom input fields may be rendered and attrs is ignored.
95
- # An options hash may be given as the last argument.
96
- def standard_form(object, *attrs, &block)
97
- options = attrs.extract_options!
95
+ def standard_form(object, options = {}, &block)
98
96
  options[:builder] ||= StandardFormBuilder
99
97
  options[:html] ||= {}
100
98
  add_css_class options[:html], 'form-horizontal'
101
99
 
102
100
  form_for(object, options) do |form|
103
- record = object.is_a?(Array) ? object.last : object
104
- content = render('shared/error_messages', :errors => record.errors, :object => record)
105
-
106
- content << if block_given?
107
- capture(form, &block)
108
- else
109
- form.labeled_input_fields(*attrs)
110
- end
111
-
112
- content << content_tag(:div, :class => 'form-actions') do
113
- form.button(ti(:"button.save"), :class => 'btn btn-primary') +
114
- ' ' +
115
- cancel_link(object)
116
- end
117
- content.html_safe
101
+ content = form.error_messages
102
+ content << capture(form, &block)
118
103
  end
119
104
  end
120
105
 
121
- def cancel_link(object)
122
- link_to(ti(:"button.cancel"), polymorphic_path(object, :returning => true), :class => 'cancel')
123
- end
124
-
125
106
  # Renders a simple unordered list, which will
126
107
  # simply render all passed items or yield them
127
108
  # to your block.
@@ -230,7 +211,7 @@ module StandardHelper
230
211
  end
231
212
  end
232
213
 
233
- protected
214
+ private
234
215
 
235
216
  # Helper methods that are not directly called from templates.
236
217