make_resourceful 1.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 (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