dry_crud 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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