make_resourceful 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/Rakefile +31 -0
  2. data/Readme.rdoc +229 -0
  3. data/VERSION +1 -0
  4. data/lib/make_resourceful.rb +11 -0
  5. data/lib/resourceful/base.rb +63 -0
  6. data/lib/resourceful/builder.rb +405 -0
  7. data/lib/resourceful/default/accessors.rb +418 -0
  8. data/lib/resourceful/default/actions.rb +101 -0
  9. data/lib/resourceful/default/callbacks.rb +51 -0
  10. data/lib/resourceful/default/responses.rb +118 -0
  11. data/lib/resourceful/default/urls.rb +136 -0
  12. data/lib/resourceful/generators/resourceful_scaffold/resourceful_scaffold_generator.rb +87 -0
  13. data/lib/resourceful/generators/resourceful_scaffold/templates/controller.rb +5 -0
  14. data/lib/resourceful/generators/resourceful_scaffold/templates/fixtures.yml +10 -0
  15. data/lib/resourceful/generators/resourceful_scaffold/templates/functional_test.rb +50 -0
  16. data/lib/resourceful/generators/resourceful_scaffold/templates/helper.rb +2 -0
  17. data/lib/resourceful/generators/resourceful_scaffold/templates/migration.rb +13 -0
  18. data/lib/resourceful/generators/resourceful_scaffold/templates/model.rb +2 -0
  19. data/lib/resourceful/generators/resourceful_scaffold/templates/unit_test.rb +7 -0
  20. data/lib/resourceful/generators/resourceful_scaffold/templates/view__form.haml +5 -0
  21. data/lib/resourceful/generators/resourceful_scaffold/templates/view_edit.haml +11 -0
  22. data/lib/resourceful/generators/resourceful_scaffold/templates/view_index.haml +5 -0
  23. data/lib/resourceful/generators/resourceful_scaffold/templates/view_new.haml +9 -0
  24. data/lib/resourceful/generators/resourceful_scaffold/templates/view_partial.haml +12 -0
  25. data/lib/resourceful/generators/resourceful_scaffold/templates/view_show.haml +14 -0
  26. data/lib/resourceful/maker.rb +92 -0
  27. data/lib/resourceful/response.rb +33 -0
  28. data/lib/resourceful/serialize.rb +185 -0
  29. data/spec/accessors_spec.rb +474 -0
  30. data/spec/actions_spec.rb +310 -0
  31. data/spec/base_spec.rb +12 -0
  32. data/spec/builder_spec.rb +332 -0
  33. data/spec/callbacks_spec.rb +71 -0
  34. data/spec/integration_spec.rb +394 -0
  35. data/spec/maker_spec.rb +91 -0
  36. data/spec/response_spec.rb +37 -0
  37. data/spec/responses_spec.rb +314 -0
  38. data/spec/serialize_spec.rb +133 -0
  39. data/spec/urls_spec.rb +282 -0
  40. metadata +97 -0
@@ -0,0 +1,136 @@
1
+ module Resourceful
2
+ module Default
3
+ # This file contains various methods to make URL helpers less painful.
4
+ # They provide methods analogous to the standard foo_url and foo_path helpers.
5
+ # However, they use make_resourceful's knowledge of the structure of the controller
6
+ # to allow you to avoid figuring out which method to call and which parent objects it should be passed.
7
+ module URLs
8
+ # This returns the path for the given object,
9
+ # by default current_object[link:classes/Resourceful/Default/Accessors.html#M000012].
10
+ # For example, in HatsController the following are equivalent:
11
+ #
12
+ # object_path #=> "/hats/12"
13
+ # hat_path(@hat) #=> "/hats/12"
14
+ #
15
+ def object_path(object = current_object); object_route(object, 'path'); end
16
+ # Same as object_path, but with the protocol and hostname.
17
+ def object_url (object = current_object); object_route(object, 'url'); end
18
+
19
+ # This is the same as object_path,
20
+ # unless a parent exists.
21
+ # Then it returns the nested path for the object.
22
+ # For example, in HatsController where Person has_many :hats and <tt>params[:person_id] == 42</tt>,
23
+ # the following are equivalent:
24
+ #
25
+ # nested_object_path #=> "/person/42/hats/12"
26
+ # person_hat_path(@person, @hat) #=> "/person/42/hats/12"
27
+ #
28
+ def nested_object_path(object = current_object); nested_object_route(object, 'path'); end
29
+ # Same as nested_object_path, but with the protocol and hostname.
30
+ def nested_object_url (object = current_object); nested_object_route(object, 'url'); end
31
+
32
+ # This returns the path for the edit action for the given object,
33
+ # by default current_object[link:classes/Resourceful/Default/Accessors.html#M000012].
34
+ # For example, in HatsController the following are equivalent:
35
+ #
36
+ # edit_object_path #=> "/hats/12/edit"
37
+ # edit_person_hat_path(@person, @hat) #=> "/hats/12/edit"
38
+ #
39
+ def edit_object_path(object = current_object); edit_object_route(object, 'path'); end
40
+ # Same as edit_object_path, but with the protocol and hostname.
41
+ def edit_object_url (object = current_object); edit_object_route(object, 'url'); end
42
+
43
+ # This returns the path for the collection of the current controller.
44
+ # For example, in HatsController where Person has_many :hats and <tt>params[:person_id] == 42</tt>,
45
+ # the following are equivalent:
46
+ #
47
+ # objects_path #=> "/people/42/hats"
48
+ # person_hats_path(@person) #=> "/people/42/hats"
49
+ #
50
+ def objects_path; objects_route('path'); end
51
+ # Same as objects_path, but with the protocol and hostname.
52
+ def objects_url ; objects_route('url'); end
53
+
54
+ # This returns the path for the new action for the current controller.
55
+ # For example, in HatsController where Person has_many :hats and <tt>params[:person_id] == 42</tt>,
56
+ # the following are equivalent:
57
+ #
58
+ # new_object_path #=> "/people/42/hats/new"
59
+ # new_person_hat_path(@person) #=> "/people/42/hats/new"
60
+ #
61
+ def new_object_path; new_object_route('path'); end
62
+ # Same as new_object_path, but with the protocol and hostname.
63
+ def new_object_url ; new_object_route('url'); end
64
+
65
+ # This returns the path for the parent object.
66
+ #
67
+ def parent_path(object = parent_object)
68
+ instance_route(parent_class_name.underscore, object, 'path')
69
+ end
70
+ # Same as parent_path, but with the protocol and hostname.
71
+ def parent_url(object = parent_object)
72
+ instance_route(parent_class_name.underscore, object, 'url')
73
+ end
74
+
75
+ # This prefix is added to the Rails URL helper names
76
+ # before they're called.
77
+ # By default, it's the underscored list of namespaces of the current controller,
78
+ # or nil if there are no namespaces defined.
79
+ # However, it can be overridden if another prefix is needed.
80
+ # Note that if this is overridden,
81
+ # the new method should return a string ending in an underscore.
82
+ #
83
+ # For example, in Admin::Content::PagesController:
84
+ #
85
+ # url_helper_prefix #=> "admin_content_"
86
+ #
87
+ # Then object_path is the same as <tt>admin_content_page_path(current_object)</tt>.
88
+ def url_helper_prefix
89
+ namespaces.empty? ? nil : "#{namespaces.join('_')}_"
90
+ end
91
+
92
+ # This prefix is added to the Rails URL helper names
93
+ # for the make_resourceful collection URL helpers,
94
+ # objects_path and new_object_path.
95
+ # By default, it's the parent name followed by an underscore if a parent
96
+ # is given, and the empty string otherwise.
97
+ #
98
+ # See also url_helper_prefix.
99
+ def collection_url_prefix
100
+ parent? ? "#{parent_class_name.underscore}_" : ''
101
+ end
102
+
103
+ private
104
+
105
+ def object_route(object, type)
106
+ instance_route(current_model_name.underscore, object, type)
107
+ end
108
+
109
+ def nested_object_route(object, type)
110
+ return object_route(object, type) unless parent?
111
+ send("#{url_helper_prefix}#{parent_class_name.underscore}_#{current_model_name.underscore}_#{type}", parent_object, object)
112
+ end
113
+
114
+ def edit_object_route(object, type)
115
+ instance_route(current_model_name.underscore, object, type, "edit")
116
+ end
117
+
118
+ def objects_route(type)
119
+ collection_route(current_model_name.pluralize.underscore, type)
120
+ end
121
+
122
+ def new_object_route(type)
123
+ collection_route(current_model_name.underscore, type, "new")
124
+ end
125
+
126
+ def instance_route(name, object, type, action = nil)
127
+ send("#{action ? action + '_' : ''}#{url_helper_prefix}#{collection_url_prefix unless shallow?}#{name}_#{type}", *(parent? && !shallow? ? [parent_object, object] : [object]))
128
+ end
129
+
130
+ def collection_route(name, type, action = nil)
131
+ send("#{action ? action + '_' : ''}#{url_helper_prefix}#{collection_url_prefix}#{name}_#{type}",
132
+ *(parent? ? [parent_object] : []))
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,87 @@
1
+ class ResourcefulScaffoldGenerator < Rails::Generators::Base
2
+ attr_reader :controller_class_path,
3
+ :controller_file_path,
4
+ :controller_class_nesting,
5
+ :controller_class_nesting_depth,
6
+ :controller_class_name,
7
+ :controller_underscore_name,
8
+ :controller_plural_name
9
+ alias_method :controller_file_name, :controller_underscore_name
10
+ alias_method :controller_table_name, :controller_plural_name
11
+
12
+ def initialize(runtime_args, runtime_options = {})
13
+ super
14
+
15
+ base_name, @controller_class_path, @controller_file_path, @controller_class_nesting, @controller_class_nesting_depth = extract_modules(@name.pluralize)
16
+ @controller_class_name_without_nesting, @controller_underscore_name, @controller_plural_name = inflect_names(base_name)
17
+
18
+ if @controller_class_nesting.empty?
19
+ @controller_class_name = @controller_class_name_without_nesting
20
+ else
21
+ @controller_class_name = "#{@controller_class_nesting}::#{@controller_class_name_without_nesting}"
22
+ end
23
+ end
24
+
25
+ def manifest
26
+ record do |m|
27
+ # Check for class naming collisions.
28
+ m.class_collisions(controller_class_path, "#{controller_class_name}Controller", "#{controller_class_name}Helper")
29
+ m.class_collisions(class_path, "#{class_name}")
30
+
31
+ # Controller, helper, views, and test directories.
32
+ m.directory(File.join('app/models', class_path))
33
+ m.directory(File.join('app/controllers', controller_class_path))
34
+ m.directory(File.join('app/helpers', controller_class_path))
35
+ m.directory(File.join('app/views', controller_class_path, controller_file_name))
36
+ m.directory(File.join('test/functional', controller_class_path))
37
+ m.directory(File.join('test/unit', class_path))
38
+ m.directory(File.join('test/fixtures', class_path))
39
+
40
+ # Views
41
+ for action in scaffold_views
42
+ m.template("view_#{action}.haml", File.join('app/views', controller_class_path, controller_file_name, "#{action}.html.haml"))
43
+ end
44
+ m.template('view_partial.haml', File.join('app/views', controller_class_path, controller_file_name, "_#{singular_name}.html.haml"))
45
+
46
+ # Helper
47
+ m.template('helper.rb', File.join('app/helpers', controller_class_path, "#{controller_file_name}_helper.rb"))
48
+
49
+ # Model
50
+ m.template('model.rb', File.join('app/models', class_path, "#{file_name}.rb"))
51
+
52
+ unless options[:skip_migration]
53
+ m.migration_template('migration.rb', 'db/migrate',
54
+ :assigns => {
55
+ :migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}",
56
+ :attributes => attributes
57
+ },
58
+ :migration_file_name => "create_#{file_path.gsub(/\//, '_').pluralize}")
59
+ end
60
+
61
+ # Controller
62
+ m.template('controller.rb', File.join('app/controllers', controller_class_path, "#{controller_file_name}_controller.rb"))
63
+
64
+ # Tests
65
+ m.template('functional_test.rb', File.join('test/functional', controller_class_path, "#{controller_file_name}_controller_test.rb"))
66
+ m.template('unit_test.rb', File.join('test/unit', class_path, "#{file_name}_test.rb"))
67
+ m.template('fixtures.yml', File.join('test/fixtures', "#{table_name}.yml"))
68
+
69
+ # Route
70
+ m.route_resources controller_file_name
71
+ end
72
+ end
73
+
74
+ protected
75
+
76
+ def banner
77
+ "Usage: #{$0} resourcefulscaffold ModelName [field:type, field:type]"
78
+ end
79
+
80
+ def scaffold_views
81
+ %w[ index show new edit _form ]
82
+ end
83
+
84
+ def model_name
85
+ class_name.demodulize
86
+ end
87
+ end
@@ -0,0 +1,5 @@
1
+ class <%= controller_class_name %>Controller < ApplicationController
2
+ make_resourceful do
3
+ actions :all
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ one:
2
+ id: 1
3
+ <% for attribute in attributes -%>
4
+ <%= attribute.name %>: <%= attribute.default %>
5
+ <% end -%>
6
+ two:
7
+ id: 2
8
+ <% for attribute in attributes -%>
9
+ <%= attribute.name %>: <%= attribute.default %>
10
+ <% end -%>
@@ -0,0 +1,50 @@
1
+ require File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../test_helper'
2
+ require '<%= controller_file_path %>_controller'
3
+
4
+ # Re-raise errors caught by the controller.
5
+ class <%= controller_class_name %>Controller; def rescue_action(e) raise e end; end
6
+
7
+ class <%= controller_class_name %>ControllerTest < ActionController::TestCase
8
+
9
+ def test_should_get_index
10
+ get :index
11
+ assert_response :success
12
+ assert assigns(:<%= table_name %>)
13
+ end
14
+
15
+ def test_should_get_new
16
+ get :new
17
+ assert_response :success
18
+ end
19
+
20
+ def test_should_create_<%= file_name %>
21
+ old_count = <%= class_name %>.count
22
+ post :create, :<%= file_name %> => { }
23
+ assert_equal old_count + 1, <%= class_name %>.count
24
+
25
+ assert_redirected_to <%= file_name %>_path(assigns(:<%= file_name %>))
26
+ end
27
+
28
+ def test_should_show_<%= file_name %>
29
+ get :show, :id => 1
30
+ assert_response :success
31
+ end
32
+
33
+ def test_should_get_edit
34
+ get :edit, :id => 1
35
+ assert_response :success
36
+ end
37
+
38
+ def test_should_update_<%= file_name %>
39
+ put :update, :id => 1, :<%= file_name %> => { }
40
+ assert_redirected_to <%= file_name %>_path(assigns(:<%= file_name %>))
41
+ end
42
+
43
+ def test_should_destroy_<%= file_name %>
44
+ old_count = <%= class_name %>.count
45
+ delete :destroy, :id => 1
46
+ assert_equal old_count-1, <%= class_name %>.count
47
+
48
+ assert_redirected_to <%= table_name %>_path
49
+ end
50
+ end
@@ -0,0 +1,2 @@
1
+ module <%= controller_class_name %>Helper
2
+ end
@@ -0,0 +1,13 @@
1
+ class <%= migration_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :<%= table_name %>, :force => true do |t|
4
+ <% for attribute in attributes -%>
5
+ t.column :<%= attribute.name %>, :<%= attribute.type %>
6
+ <% end -%>
7
+ end
8
+ end
9
+
10
+ def self.down
11
+ drop_table :<%= table_name %>
12
+ end
13
+ end
@@ -0,0 +1,2 @@
1
+ class <%= class_name %> < ActiveRecord::Base
2
+ end
@@ -0,0 +1,7 @@
1
+ require File.dirname(__FILE__) + '<%= '/..' * class_nesting_depth %>/../test_helper'
2
+
3
+ class <%= class_name %>Test < Test::Unit::TestCase
4
+ def test_truth
5
+ assert true
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ <%- for attribute in attributes -%>
2
+ %p
3
+ %label{:for => "<%= singular_name %>_<%= attribute.name %>"} <%= attribute.column.human_name %>:
4
+ = f.<%= attribute.field_type %> :<%= attribute.name %>
5
+ <% end -%>
@@ -0,0 +1,11 @@
1
+ %h1 Editing <%= singular_name %>
2
+
3
+ = error_messages_for :<%= singular_name %>
4
+
5
+ = form_for(:<%= singular_name %>, :url => object_url, :html => { :method => :put }) do |f|
6
+ = render :partial => "form", :locals => {:f => f}
7
+ %p= submit_tag "Update"
8
+
9
+ = link_to 'Show', object_path
10
+ |
11
+ = link_to 'Back', objects_path
@@ -0,0 +1,5 @@
1
+ %h1 Listing <%= plural_name %>
2
+
3
+ = render :partial => '<%= singular_name %>', :collection => current_objects
4
+
5
+ = link_to 'New <%= singular_name %>', new_object_path
@@ -0,0 +1,9 @@
1
+ %h1 Creating <%= singular_name %>
2
+
3
+ = error_messages_for :<%= singular_name %>
4
+
5
+ = form_for(:<%= singular_name %>, :url => objects_url) do |f|
6
+ = render :partial => "form", :locals => {:f => f}
7
+ %p= submit_tag "Create"
8
+
9
+ = link_to 'Back', objects_path
@@ -0,0 +1,12 @@
1
+ %div[<%= singular_name %>]
2
+ <% for attribute in attributes -%>
3
+ %p.<%= attribute.name %>
4
+ %strong <%= attribute.column.human_name %>
5
+ = h <%= singular_name %>.<%= attribute.name %>
6
+ <% end -%>
7
+
8
+ = link_to 'Show', object_path(<%= singular_name %>)
9
+ |
10
+ = link_to 'Edit', edit_object_path(<%= singular_name %>)
11
+ |
12
+ = link_to 'Destroy', object_path(<%= singular_name %>), :confirm => 'Really destroy <%= singular_name %>?', :method => :delete
@@ -0,0 +1,14 @@
1
+ %h1 Viewing <%= singular_name %>
2
+
3
+ %div[current_object]
4
+ <% for attribute in attributes -%>
5
+ %p.<%= attribute.name %>
6
+ %strong <%= attribute.column.human_name %>
7
+ = h current_object.<%= attribute.name %>
8
+ <% end -%>
9
+
10
+ = link_to 'Edit', edit_object_path
11
+ |
12
+ = link_to 'Destroy', object_path, :confirm => 'Really destroy <%= singular_name %>?', :method => :delete
13
+ |
14
+ = link_to 'Back', objects_path
@@ -0,0 +1,92 @@
1
+ require 'resourceful/builder'
2
+ require 'resourceful/base'
3
+
4
+ module Resourceful
5
+ # This module is extended by the ActionController::Base class object.
6
+ # It provides the actual +make_resourceful+ method
7
+ # and sets up the controller so that everything will work.
8
+ module Maker
9
+ # Called automatically on ActionController::Base.
10
+ # Initializes various inheritable attributes.
11
+ def self.extended(base)
12
+ base.class_attribute :resourceful_callbacks
13
+ base.class_attribute :resourceful_responses
14
+ base.class_attribute :parents
15
+ base.class_attribute :shallow_parent
16
+ base.class_attribute :model_namespace
17
+ base.class_attribute :made_resourceful
18
+
19
+ base.resourceful_callbacks = {}
20
+ base.resourceful_responses = {}
21
+ base.parents = []
22
+ base.model_namespace = nil
23
+ base.made_resourceful = false
24
+ end
25
+
26
+ # :call-seq:
27
+ # make_resourceful(options = {}) { ... }
28
+ #
29
+ # This is the central method, and namesake, of make_resourceful.
30
+ # It takes a block and evaluates it in the context of a Builder,
31
+ # allowing the controller to be customized extensively.
32
+ #
33
+ # See Resourceful::Builder for documentation on the methods available
34
+ # in the context of the block.
35
+ #
36
+ # The only option currently available is <tt>:include</tt>.
37
+ # It takes an object that responds to to_proc
38
+ # (or an array of such objects)
39
+ # and evaluates that proc in the same context as the block.
40
+ # For example:
41
+ #
42
+ # make_resourceful :include => proc { actions :all } do
43
+ # before :show do
44
+ # current_object.current_user = current_user
45
+ # end
46
+ # end
47
+ #
48
+ # This is the same as:
49
+ #
50
+ # make_resourceful do
51
+ # actions :all
52
+ # before :show do
53
+ # current_object.current_user = current_user
54
+ # end
55
+ # end
56
+ #
57
+ def make_resourceful(options = {}, &block)
58
+ # :stopdoc:
59
+ include Resourceful::Base
60
+ # :startdoc:
61
+
62
+ builder = Resourceful::Builder.new(self)
63
+ unless builder.inherited?
64
+ Resourceful::Base.made_resourceful.each { |proc| builder.instance_eval(&proc) }
65
+ end
66
+ Array(options[:include]).each { |proc| builder.instance_eval(&proc) }
67
+ builder.instance_eval(&block)
68
+
69
+ builder.apply
70
+
71
+ add_helpers
72
+ end
73
+
74
+ # Returns whether or not make_resourceful has been called
75
+ # on this controller or any controllers it inherits from.
76
+ def made_resourceful?
77
+ self.class.made_resourceful
78
+ end
79
+
80
+ private
81
+
82
+ def add_helpers
83
+ helper_method(:object_path, :objects_path, :new_object_path, :edit_object_path,
84
+ :object_url, :objects_url, :new_object_url, :edit_object_url,
85
+ :parent_path, :parent_url,
86
+ :nested_object_path, :nested_object_url,
87
+ :current_objects, :current_object, :current_model, :current_model_name,
88
+ :namespaces, :instance_variable_name, :parent_names, :parent_name,
89
+ :parent?, :parent_model, :parent_object, :save_succeeded?)
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,33 @@
1
+ module Resourceful
2
+ # This is the class of the object passed to the Builder#response_for method.
3
+ # It shouldn't be used by users.
4
+ #
5
+ # The Response collects format procs
6
+ # and returns them with the format method,
7
+ # in the order they were given.
8
+ # For example:
9
+ #
10
+ # response.html { redirect_to '/' }
11
+ # response.xml { render :xml => current_object.to_xml }
12
+ # response.js
13
+ # response.formats #=> [[:html, #<Proc>], [:xml, #<Proc>], [:js, #<Proc>]]
14
+ #
15
+ # Note that the <tt>:js</tt> response is the empty proc -
16
+ # the same as <tt>proc {}</tt>.
17
+ class Response # :nodoc:
18
+ # Returns a list of pairs of formats and procs
19
+ # representing the formats passed to the response object.
20
+ # See class description.
21
+ attr :formats
22
+
23
+ # Returns a new Response with no format data.
24
+ def initialize
25
+ @formats = []
26
+ end
27
+
28
+ # Used to dispatch the individual format methods.
29
+ def method_missing(name, &block)
30
+ @formats.push([name, block || proc {}]) unless @formats.any? {|n,b| n == name}
31
+ end
32
+ end
33
+ end