resource_controller 0.4.9 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/README.rdoc +37 -1
  2. data/Rakefile +2 -2
  3. data/lib/resource_controller.rb +10 -5
  4. data/lib/resource_controller/class_methods.rb +3 -1
  5. data/lib/resource_controller/controller.rb +6 -0
  6. data/lib/resource_controller/helpers/nested.rb +21 -3
  7. data/lib/resource_controller/helpers/singleton_customizations.rb +60 -0
  8. data/lib/resource_controller/helpers/urls.rb +5 -1
  9. data/lib/resource_controller/singleton.rb +15 -0
  10. data/lib/resource_controller/version.rb +2 -2
  11. data/test/app/controllers/accounts_controller.rb +6 -0
  12. data/test/app/controllers/cms/products_controller.rb +1 -1
  13. data/test/app/controllers/images_controller.rb +4 -0
  14. data/test/app/controllers/options_controller.rb +8 -0
  15. data/test/app/helpers/accounts_helper.rb +2 -0
  16. data/test/app/helpers/images_helper.rb +2 -0
  17. data/test/app/models/account.rb +1 -0
  18. data/test/app/models/image.rb +3 -0
  19. data/test/app/models/user.rb +3 -0
  20. data/test/app/views/accounts/_form.html.erb +4 -0
  21. data/test/app/views/accounts/edit.html.erb +14 -0
  22. data/test/app/views/accounts/new.html.erb +12 -0
  23. data/test/app/views/accounts/show.html.erb +5 -0
  24. data/test/app/views/images/_form.html.erb +4 -0
  25. data/test/app/views/images/edit.html.erb +14 -0
  26. data/test/app/views/images/new.html.erb +12 -0
  27. data/test/app/views/options/_form.html.erb +8 -0
  28. data/test/app/views/options/edit.html.erb +16 -0
  29. data/test/app/views/options/index.html.erb +21 -0
  30. data/test/app/views/options/new.html.erb +12 -0
  31. data/test/app/views/options/show.html.erb +10 -0
  32. data/test/config/database.yml +0 -3
  33. data/test/config/environment.rb +2 -2
  34. data/test/config/routes.rb +8 -1
  35. data/test/db/migrate/002_create_products.rb +1 -1
  36. data/test/db/migrate/004_create_options.rb +3 -2
  37. data/test/db/migrate/011_create_images.rb +12 -0
  38. data/test/db/migrate/012_create_users.rb +11 -0
  39. data/test/db/schema.rb +13 -6
  40. data/test/log/development.log +84 -0
  41. data/test/log/test.log +4880 -0
  42. data/test/test/fixtures/images.yml +6 -0
  43. data/test/test/fixtures/users.yml +5 -0
  44. data/test/test/functional/images_controller_test.rb +37 -0
  45. data/test/test/unit/helpers/current_objects_test.rb +6 -0
  46. data/test/test/unit/helpers/nested_test.rb +5 -1
  47. data/test/test/unit/helpers/singleton_current_objects_test.rb +68 -0
  48. data/test/test/unit/helpers/singleton_nested_test.rb +77 -0
  49. data/test/test/unit/helpers/singleton_urls_test.rb +67 -0
  50. data/test/test/unit/helpers/urls_test.rb +5 -1
  51. data/test/test/unit/image_test.rb +7 -0
  52. metadata +35 -5
  53. data/README +0 -282
  54. data/lib/tasks/gem.rake +0 -67
data/README.rdoc CHANGED
@@ -102,6 +102,42 @@ With actions that can fail, the scoping defaults to success. That means that cr
102
102
  end
103
103
 
104
104
  end
105
+
106
+ == Singleton Resource
107
+
108
+ If you want to create a singleton RESTful controller inherit from ResourceController::Singleton.
109
+
110
+ class AccountsController < ResourceController::Singleton
111
+ end
112
+
113
+ *Note:* This type of controllers handle a single resource only so the index action and all the collection helpers (collection_url, collection_path...) are not available for them.
114
+
115
+ Loading objects in singletons is similar to plural controllers with one exception. For non-nested singleton controllers you should override the object method as it defaults to nil for them.
116
+
117
+ class AccountsController < ResourceController::Singleton
118
+ private
119
+ def object
120
+ @object ||= Account.find(session[:account_id])
121
+ end
122
+ end
123
+
124
+ In other cases you can use the default logic and override it only if you use permalinks or anything special.
125
+
126
+ Singleton nesting with both :has_many and :has_one associations is provided...
127
+
128
+ map.resource :account, :has_many => :options # /account/options, account is a singleton parent
129
+ map.resources :users, :has_one => :image # /users/1/image, image is a singleton child
130
+
131
+ If you have the :has_many association with a singleton parent remember to override parent_object for your :has_many controller as it returns nil by default in this case.
132
+
133
+ class OptionsController < ResourceController::Base
134
+ belongs_to :account
135
+
136
+ protected
137
+ def parent_object
138
+ Account.find(session[:account_id])
139
+ end
140
+ end
105
141
 
106
142
  == Helpers (ResourceController::Helpers)
107
143
 
@@ -279,4 +315,4 @@ resource_controller was created, and is maintained by {James Golick}[http://jame
279
315
 
280
316
  == License
281
317
 
282
- resource_controller is available under the {MIT License}[http://en.wikipedia.org/wiki/MIT_License]
318
+ resource_controller is available under the {MIT License}[http://en.wikipedia.org/wiki/MIT_License]
data/Rakefile CHANGED
@@ -2,7 +2,7 @@ require 'rake'
2
2
  require 'rake/testtask'
3
3
  require 'rake/rdoctask'
4
4
  require File.dirname(__FILE__)+'/lib/resource_controller/version'
5
- Dir['lib/tasks/**.rake'].each { |tasks| load tasks }
5
+ Dir['tasks/**.rake'].each { |tasks| load tasks }
6
6
 
7
7
  desc 'Default: run unit tests.'
8
8
  task :default => :test
@@ -19,7 +19,7 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
19
19
  rdoc.rdoc_dir = 'rdoc'
20
20
  rdoc.title = 'ResourceController'
21
21
  rdoc.options << '--line-numbers' << '--inline-source'
22
- rdoc.rdoc_files.include('README')
22
+ rdoc.rdoc_files.include('README.rdoc')
23
23
  rdoc.rdoc_files.include('lib/**/*.rb')
24
24
  end
25
25
 
@@ -1,14 +1,19 @@
1
1
  module ResourceController
2
- ACTIONS = [:index, :show, :new_action, :create, :edit, :update, :destroy].freeze
3
- FAILABLE_ACTIONS = ACTIONS - [:index, :new_action, :edit].freeze
4
- NAME_ACCESSORS = [:model_name, :route_name, :object_name]
2
+ ACTIONS = [:index, :show, :new_action, :create, :edit, :update, :destroy].freeze
3
+ SINGLETON_ACTIONS = (ACTIONS - [:index]).freeze
4
+ FAILABLE_ACTIONS = ACTIONS - [:index, :new_action, :edit].freeze
5
+ NAME_ACCESSORS = [:model_name, :route_name, :object_name]
5
6
 
6
7
  module ActionControllerExtension
7
8
  unloadable
8
9
 
9
- def resource_controller
10
+ def resource_controller(*args)
10
11
  include ResourceController::Controller
11
- end
12
+
13
+ if args.include?(:singleton)
14
+ include ResourceController::Helpers::SingletonCustomizations
15
+ end
16
+ end
12
17
  end
13
18
  end
14
19
 
@@ -10,8 +10,10 @@ module ResourceController
10
10
  config = {}
11
11
  config.merge!(opts.pop) if opts.last.is_a?(Hash)
12
12
 
13
+ all_actions = (singleton? ? ResourceController::SINGLETON_ACTIONS : ResourceController::ACTIONS) - [:new_action] + [:new]
14
+
13
15
  actions_to_remove = []
14
- actions_to_remove += (ResourceController::ACTIONS - [:new_action] + [:new]) - opts unless opts.first == :all
16
+ actions_to_remove += all_actions - opts unless opts.first == :all
15
17
  actions_to_remove += [*config[:except]] if config[:except]
16
18
  actions_to_remove.uniq!
17
19
 
@@ -57,6 +57,12 @@ module ResourceController
57
57
  flash "Successfully removed!"
58
58
  wants.html { redirect_to collection_url }
59
59
  end
60
+
61
+ class << self
62
+ def singleton?
63
+ false
64
+ end
65
+ end
60
66
  end
61
67
  end
62
68
  end
@@ -1,7 +1,7 @@
1
1
  # Nested and Polymorphic Resource Helpers
2
2
  #
3
3
  module ResourceController::Helpers::Nested
4
- protected
4
+ protected
5
5
  # Returns the relevant association proxy of the parent. (i.e. /posts/1/comments # => @post.comments)
6
6
  #
7
7
  def parent_association
@@ -11,7 +11,19 @@ module ResourceController::Helpers::Nested
11
11
  # Returns the type of the current parent
12
12
  #
13
13
  def parent_type
14
- @parent_type ||= [*belongs_to].find { |parent| !params["#{parent}_id".to_sym].nil? }
14
+ @parent_type ||= parent_type_from_params || parent_type_from_request
15
+ end
16
+
17
+ # Returns the type of the current parent extracted from params
18
+ #
19
+ def parent_type_from_params
20
+ [*belongs_to].find { |parent| !params["#{parent}_id".to_sym].nil? }
21
+ end
22
+
23
+ # Returns the type of the current parent extracted form a request path
24
+ #
25
+ def parent_type_from_request
26
+ [*belongs_to].find { |parent| request.path.split('/').include? parent.to_s }
15
27
  end
16
28
 
17
29
  # Returns true/false based on whether or not a parent is present.
@@ -20,6 +32,12 @@ module ResourceController::Helpers::Nested
20
32
  !parent_type.nil?
21
33
  end
22
34
 
35
+ # Returns true/false based on whether or not a parent is a singleton.
36
+ #
37
+ def parent_singleton?
38
+ !parent_type_from_request.nil?
39
+ end
40
+
23
41
  # Returns the current parent param, if there is a parent. (i.e. params[:post_id])
24
42
  def parent_param
25
43
  params["#{parent_type}_id".to_sym]
@@ -34,7 +52,7 @@ module ResourceController::Helpers::Nested
34
52
  # Returns the current parent object if a parent object is present.
35
53
  #
36
54
  def parent_object
37
- parent? ? parent_model.find(parent_param) : nil
55
+ parent? && !parent_singleton? ? parent_model.find(parent_param) : nil
38
56
  end
39
57
 
40
58
  # If there is a parent, returns the relevant association proxy. Otherwise returns model.
@@ -0,0 +1,60 @@
1
+ # Singleton Resource Helpers
2
+ #
3
+ # Used internally to transform a plural RESTful controller into a singleton
4
+ #
5
+ module ResourceController::Helpers::SingletonCustomizations
6
+ def self.included(subclass)
7
+ subclass.class_eval do
8
+ methods_to_undefine = [:param, :index, :collection, :load_collection, :collection_url,
9
+ :collection_path, :hash_for_collection_url, :hash_for_collection_path]
10
+ methods_to_undefine.each { |method| undef_method(method) if method_defined? method }
11
+
12
+ class << self
13
+ def singleton?
14
+ true
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ protected
21
+ # Used to fetch the current object in a singleton controller.
22
+ #
23
+ # By defult this method is able to fetch the current object for resources nested with the :has_one association only. (i.e. /users/1/image # => @user.image)
24
+ # In other cases you should override this method and provide your custom code to fetch a singleton resource object, like using a session hash.
25
+ #
26
+ # class AccountsController < ResourceController::Singleton
27
+ # private
28
+ # def object
29
+ # @object ||= Account.find(session[:account_id])
30
+ # end
31
+ # end
32
+ #
33
+ def object
34
+ @object ||= parent? ? end_of_association_chain : nil
35
+ end
36
+
37
+ # Returns the :has_one association proxy of the parent. (i.e. /users/1/image # => @user.image)
38
+ #
39
+ def parent_association
40
+ @parent_association ||= parent_object.send(model_name.to_sym)
41
+ end
42
+
43
+ # Used internally to provide the options to smart_url in a singleton controller.
44
+ #
45
+ def object_url_options(action_prefix = nil, alternate_object = nil)
46
+ [action_prefix] + namespaces + [parent_url_options, route_name.to_sym]
47
+ end
48
+
49
+ # Builds the object, but doesn't save it, during the new, and create action.
50
+ #
51
+ def build_object
52
+ @object ||= singleton_build_object_base.send parent? ? "build_#{model_name}".to_sym : :new, object_params
53
+ end
54
+
55
+ # Singleton controllers don't build off of association proxy, so we can't use end_of_association_chain here
56
+ #
57
+ def singleton_build_object_base
58
+ parent? ? parent_object : model
59
+ end
60
+ end
@@ -110,7 +110,11 @@ module ResourceController::Helpers::Urls
110
110
  end
111
111
 
112
112
  def parent_url_options
113
- parent? ? [parent_type.to_sym, parent_object] : nil
113
+ if parent?
114
+ parent_singleton? ? parent_type.to_sym : [parent_type.to_sym, parent_object]
115
+ else
116
+ nil
117
+ end
114
118
  end
115
119
 
116
120
  # Returns all of the current namespaces of the current controller, symbolized, in array form.
@@ -0,0 +1,15 @@
1
+ module ResourceController
2
+
3
+ # == ResourceController::Singleton
4
+ #
5
+ # Inherit from this class to create your RESTful singleton controller. See the README for usage.
6
+ #
7
+ class Singleton < ApplicationController
8
+ unloadable
9
+
10
+ def self.inherited(subclass)
11
+ super
12
+ subclass.class_eval { resource_controller :singleton }
13
+ end
14
+ end
15
+ end
@@ -1,8 +1,8 @@
1
1
  module ResourceController
2
2
  module VERSION
3
3
  MAJOR = 0
4
- MINOR = 4
5
- TINY = 9
4
+ MINOR = 5
5
+ TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -0,0 +1,6 @@
1
+ class AccountsController < ResourceController::Singleton
2
+ protected
3
+ def object
4
+ Account.find(:first)
5
+ end
6
+ end
@@ -1,3 +1,3 @@
1
1
  class Cms::ProductsController < ResourceController::Base
2
- create.flash_now 'something'
2
+ create.flash 'something'
3
3
  end
@@ -0,0 +1,4 @@
1
+ class ImagesController < ResourceController::Singleton
2
+ belongs_to :user
3
+ actions :create
4
+ end
@@ -0,0 +1,8 @@
1
+ class OptionsController < ResourceController::Base
2
+ belongs_to :account
3
+
4
+ protected
5
+ def parent_object
6
+ Account.find(:first)
7
+ end
8
+ end
@@ -0,0 +1,2 @@
1
+ module AccountsHelper
2
+ end
@@ -0,0 +1,2 @@
1
+ module ImagesHelper
2
+ end
@@ -1,3 +1,4 @@
1
1
  class Account < ActiveRecord::Base
2
2
  has_many :photos
3
+ has_many :options
3
4
  end
@@ -0,0 +1,3 @@
1
+ class Image < ActiveRecord::Base
2
+ belongs_to :user
3
+ end
@@ -0,0 +1,3 @@
1
+ class User < ActiveRecord::Base
2
+ has_one :image
3
+ end
@@ -0,0 +1,4 @@
1
+ <p>
2
+ <label for="account_name">Name:</label>
3
+ <%= f.text_field :name %>
4
+ </p>
@@ -0,0 +1,14 @@
1
+ <h1>Editing Account</h1>
2
+
3
+ <%= error_messages_for :account %>
4
+
5
+ <% form_for(:account, :url => object_url, :html => { :method => :put }) do |f| %>
6
+ <%= render :partial => "form", :locals => { :f => f } %>
7
+ <p>
8
+ <%=submit_tag "Update"%>
9
+ </p>
10
+ <% end %>
11
+
12
+ <br/>
13
+
14
+ <%= link_to 'Show', object_url %>
@@ -0,0 +1,12 @@
1
+ <h1>New Account</h1>
2
+
3
+ <%= error_messages_for :account %>
4
+
5
+ <% form_for(:account, :url => object_url) do |f| %>
6
+ <%= render :partial => "form", :locals => { :f => f } %>
7
+ <p>
8
+ <%= submit_tag "Create" %>
9
+ </p>
10
+ <% end %>
11
+ <br/>
12
+ <%= link_to 'Back', object_url %>
@@ -0,0 +1,5 @@
1
+ <p>
2
+ <strong>Name:</strong><%=h @account.name %>
3
+ </p>
4
+
5
+ <%= link_to 'Edit', edit_object_url %>
@@ -0,0 +1,4 @@
1
+ <p>
2
+ <label for="image_user_id">User:</label>
3
+ <%= f.text_field :user_id %>
4
+ </p>
@@ -0,0 +1,14 @@
1
+ <h1>Editing Image</h1>
2
+
3
+ <%= error_messages_for :image %>
4
+
5
+ <% form_for(:image, :url => object_url, :html => { :method => :put }) do |f| %>
6
+ <%= render :partial => "form", :locals => { :f => f } %>
7
+ <p>
8
+ <%=submit_tag "Update"%>
9
+ </p>
10
+ <% end %>
11
+
12
+ <br/>
13
+
14
+ <%= link_to 'Show', object_url %>
@@ -0,0 +1,12 @@
1
+ <h1>New Image</h1>
2
+
3
+ <%= error_messages_for :image %>
4
+
5
+ <% form_for(:image, :url => object_url) do |f| %>
6
+ <%= render :partial => "form", :locals => { :f => f } %>
7
+ <p>
8
+ <%= submit_tag "Create" %>
9
+ </p>
10
+ <% end %>
11
+ <br/>
12
+ <%= link_to 'Back', object_url %>
@@ -0,0 +1,8 @@
1
+ <p>
2
+ <label for="option_account_id">Account:</label>
3
+ <%= f.text_field :account_id %>
4
+ </p>
5
+ <p>
6
+ <label for="option_name">Title:</label>
7
+ <%= f.text_field :title %>
8
+ </p>
@@ -0,0 +1,16 @@
1
+ <h1>Editing Option</h1>
2
+
3
+ <%= error_messages_for :option %>
4
+
5
+ <% form_for(:option, :url => object_url, :html => { :method => :put }) do |f| %>
6
+ <%= render :partial => "form", :locals => { :f => f } %>
7
+ <p>
8
+ <%=submit_tag "Update"%>
9
+ </p>
10
+ <% end %>
11
+
12
+ <br/>
13
+
14
+ <%= link_to 'Show', object_url %>
15
+ |
16
+ <%= link_to 'Back', collection_url %>
@@ -0,0 +1,21 @@
1
+ <h1>Listing Options</h1>
2
+
3
+ <table>
4
+ <tr>
5
+ <th>Account</th>
6
+ <th>Title</th>
7
+ </tr>
8
+ <%- @options.each do |option|%>
9
+ <tr>
10
+ <td><%=h option.account_id %></td>
11
+ <td><%=h option.title %></td>
12
+
13
+ <td><%=link_to 'Show', object_url(option) %></td>
14
+ <td><%=link_to 'Edit', edit_object_url(option) %></td>
15
+ <td><%=link_to 'Destroy', object_url(option), :confirm => 'Are you sure?', :method => :delete %></td>
16
+ </tr>
17
+ <% end %>
18
+ </table>
19
+ <br/>
20
+
21
+ <%= link_to 'New Option', new_object_url %>