├── .gitignore ├── .idea ├── .gitignore ├── inspectionProfiles │ └── Project_Default.xml ├── make_resourceful.iml ├── misc.xml ├── modules.xml └── vcs.xml ├── DEFAULTS ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.rdoc ├── Rakefile ├── TODO ├── VERSION ├── lib ├── make_resourceful.rb └── resourceful │ ├── base.rb │ ├── builder.rb │ ├── default │ ├── accessors.rb │ ├── actions.rb │ ├── callbacks.rb │ ├── responses.rb │ └── urls.rb │ ├── generators │ └── resourceful_scaffold │ │ ├── resourceful_scaffold_generator.rb │ │ └── templates │ │ ├── controller.rb │ │ ├── fixtures.yml │ │ ├── functional_test.rb │ │ ├── helper.rb │ │ ├── migration.rb │ │ ├── model.rb │ │ ├── unit_test.rb │ │ ├── view__form.haml │ │ ├── view_edit.haml │ │ ├── view_index.haml │ │ ├── view_new.haml │ │ ├── view_partial.haml │ │ └── view_show.haml │ ├── maker.rb │ ├── response.rb │ └── serialize.rb ├── make_resourceful.gemspec └── spec ├── accessors_spec.rb ├── actions_spec.rb ├── base_spec.rb ├── builder_spec.rb ├── callbacks_spec.rb ├── integration_spec.rb ├── maker_spec.rb ├── response_spec.rb ├── responses_spec.rb ├── rspec-rails ├── LICENSE ├── redirect_to.rb └── render_template.rb ├── serialize_spec.rb ├── spec_helper.rb ├── support ├── helper_methods.rb └── integration_helpers.rb ├── urls_spec.rb └── views └── things ├── create.rjs ├── destroy.rjs ├── edit.html.erb ├── edit.rjs ├── index.html.erb ├── index.rjs ├── new.html.erb ├── new.rjs ├── show.html.erb ├── show.rjs └── update.rjs /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | tags 3 | testapp 4 | Gemfile.lock 5 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/make_resourceful.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /DEFAULTS: -------------------------------------------------------------------------------- 1 | There's a rough equivalence between a basic @make_resourceful@ call 2 | and a hand-made controller. 3 | This: 4 | 5 | class PostsController < ApplicationController 6 | make_resourceful { actions :all } 7 | end 8 | 9 | Creates a controller that works more or less like the one that follows. 10 | Note that the real code generated by make_resourceful 11 | is more extensible in various ways. 12 | Thus whenever possible, there are comments in the following controller 13 | indicating how to customize various bits of the controller. 14 | 15 | class PostsController < ApplicationController 16 | def index 17 | # Override #current_objects to change this 18 | @posts = Post.all 19 | 20 | # Use before :index to add something here 21 | 22 | # Use response_for :index to change this 23 | respond_to { |f| f.html; f.js } 24 | end 25 | 26 | def show 27 | # Override #current_object to change this 28 | @post = Post.find(params[:id]) 29 | 30 | # Use before :show to add something here 31 | 32 | # Use response_for :show to change this 33 | respond_to { |f| f.html; f.js } 34 | end 35 | 36 | def create 37 | # Override #build_object to change this 38 | @post = Post.new(params[:post]) 39 | 40 | # Use before :create to add something here 41 | 42 | if @post.save 43 | # Use after :create to add something here 44 | 45 | # Use response_for :create to change this 46 | respond_to do |f| 47 | f.html do 48 | flash[:notice] = "Create successful!" 49 | redirect_to post_path(@post) 50 | end 51 | f.js 52 | end 53 | else 54 | # Use after :create_fails to add something here 55 | 56 | # Use response_for :create_fails to change this 57 | respond_to do |f| 58 | format.html 59 | flash[:error] = "There was a problem!" 60 | render :action => :new, :status => 422 61 | end 62 | format.js 63 | end 64 | end 65 | end 66 | 67 | def update 68 | # Override #current_object to change this 69 | @post = Post.find(params[:id]) 70 | 71 | # Use before :update to do something here 72 | 73 | if @post.update_attributes params[:post] 74 | # Use after :update to add something here 75 | 76 | # Use response_for :update to change this 77 | respond_to do |f| 78 | f.html do 79 | flash[:notice] = "Save successful!" 80 | redirect_to post_path(@post) 81 | end 82 | f.js 83 | end 84 | else 85 | # Use after :update_fails to add something here 86 | 87 | # Use response_for :update_fails to change this 88 | respond_to do |f| 89 | format.html 90 | flash[:error] = "There was a problem saving!" 91 | render :action => :edit, :status => 422 92 | end 93 | format.js 94 | end 95 | end 96 | end 97 | 98 | def new 99 | # Override #build_object to change this 100 | @post = Post.new(params[:post]) 101 | 102 | # Use before :new to add something here 103 | 104 | # Use response_for :new to change this 105 | respond_to { |f| f.html; f.js } 106 | end 107 | 108 | def edit 109 | # Override #current_object to change this 110 | @post = Post.find(params[:id]) 111 | 112 | # Use before :edit to add something here 113 | 114 | # Use response_for :edit to change this 115 | respond_to { |f| f.html; f.js } 116 | end 117 | 118 | def destroy 119 | # Override #current_object to change this 120 | @post = Post.find(params[:id]) 121 | 122 | # Use before :destroy to do something here 123 | 124 | if @post.destroy 125 | # Use after :destroy to add something here 126 | 127 | # Use response_for :destroy to change this 128 | respond_to do |f| 129 | f.html do 130 | flash[:notice] = "Record deleted!" 131 | redirect_to posts_path(@post) 132 | end 133 | f.js 134 | end 135 | else 136 | # Use after :destroy_fails to add something here 137 | 138 | # Use response_for :destroy_fails to change this 139 | respond_to do |f| 140 | format.html 141 | flash[:error] = "There was a problem deleting!" 142 | render :back 143 | end 144 | format.js 145 | end 146 | end 147 | end 148 | end 149 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | gem 'mocha' 6 | gem 'rspec-rails' 7 | gem 'activemodel' 8 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | make_resourceful (2.0.0) 5 | 6 | GEM 7 | remote: https://rubygems.org/ 8 | specs: 9 | actionpack (7.1.3.4) 10 | actionview (= 7.1.3.4) 11 | activesupport (= 7.1.3.4) 12 | nokogiri (>= 1.8.5) 13 | racc 14 | rack (>= 2.2.4) 15 | rack-session (>= 1.0.1) 16 | rack-test (>= 0.6.3) 17 | rails-dom-testing (~> 2.2) 18 | rails-html-sanitizer (~> 1.6) 19 | actionview (7.1.3.4) 20 | activesupport (= 7.1.3.4) 21 | builder (~> 3.1) 22 | erubi (~> 1.11) 23 | rails-dom-testing (~> 2.2) 24 | rails-html-sanitizer (~> 1.6) 25 | activemodel (7.1.3.4) 26 | activesupport (= 7.1.3.4) 27 | activesupport (7.1.3.4) 28 | base64 29 | bigdecimal 30 | concurrent-ruby (~> 1.0, >= 1.0.2) 31 | connection_pool (>= 2.2.5) 32 | drb 33 | i18n (>= 1.6, < 2) 34 | minitest (>= 5.1) 35 | mutex_m 36 | tzinfo (~> 2.0) 37 | base64 (0.2.0) 38 | bigdecimal (3.1.8) 39 | builder (3.3.0) 40 | concurrent-ruby (1.3.3) 41 | connection_pool (2.4.1) 42 | crass (1.0.6) 43 | diff-lcs (1.5.1) 44 | drb (2.2.1) 45 | erubi (1.13.0) 46 | i18n (1.14.5) 47 | concurrent-ruby (~> 1.0) 48 | io-console (0.7.2) 49 | irb (1.13.2) 50 | rdoc (>= 4.0.0) 51 | reline (>= 0.4.2) 52 | loofah (2.22.0) 53 | crass (~> 1.0.2) 54 | nokogiri (>= 1.12.0) 55 | mini_portile2 (2.8.7) 56 | minitest (5.24.0) 57 | mocha (2.4.0) 58 | ruby2_keywords (>= 0.0.5) 59 | mutex_m (0.2.0) 60 | nokogiri (1.16.6) 61 | mini_portile2 (~> 2.8.2) 62 | racc (~> 1.4) 63 | psych (5.1.2) 64 | stringio 65 | racc (1.8.0) 66 | rack (3.1.4) 67 | rack-session (2.0.0) 68 | rack (>= 3.0.0) 69 | rack-test (2.1.0) 70 | rack (>= 1.3) 71 | rackup (2.1.0) 72 | rack (>= 3) 73 | webrick (~> 1.8) 74 | rails-dom-testing (2.2.0) 75 | activesupport (>= 5.0.0) 76 | minitest 77 | nokogiri (>= 1.6) 78 | rails-html-sanitizer (1.6.0) 79 | loofah (~> 2.21) 80 | nokogiri (~> 1.14) 81 | railties (7.1.3.4) 82 | actionpack (= 7.1.3.4) 83 | activesupport (= 7.1.3.4) 84 | irb 85 | rackup (>= 1.0.0) 86 | rake (>= 12.2) 87 | thor (~> 1.0, >= 1.2.2) 88 | zeitwerk (~> 2.6) 89 | rake (13.2.1) 90 | rdoc (6.7.0) 91 | psych (>= 4.0.0) 92 | reline (0.5.9) 93 | io-console (~> 0.5) 94 | rspec-core (3.13.0) 95 | rspec-support (~> 3.13.0) 96 | rspec-expectations (3.13.1) 97 | diff-lcs (>= 1.2.0, < 2.0) 98 | rspec-support (~> 3.13.0) 99 | rspec-mocks (3.13.1) 100 | diff-lcs (>= 1.2.0, < 2.0) 101 | rspec-support (~> 3.13.0) 102 | rspec-rails (6.1.3) 103 | actionpack (>= 6.1) 104 | activesupport (>= 6.1) 105 | railties (>= 6.1) 106 | rspec-core (~> 3.13) 107 | rspec-expectations (~> 3.13) 108 | rspec-mocks (~> 3.13) 109 | rspec-support (~> 3.13) 110 | rspec-support (3.13.1) 111 | ruby2_keywords (0.0.5) 112 | stringio (3.1.1) 113 | thor (1.3.1) 114 | tzinfo (2.0.6) 115 | concurrent-ruby (~> 1.0) 116 | webrick (1.8.1) 117 | zeitwerk (2.6.16) 118 | 119 | PLATFORMS 120 | ruby 121 | 122 | DEPENDENCIES 123 | activemodel 124 | make_resourceful! 125 | mocha 126 | rspec-rails 127 | 128 | BUNDLED WITH 129 | 2.5.9 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007-2010 Hampton Catlin 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = make_resourceful 2 | ===== Take back control of your Controllers. Make them awesome. Make them sleek. Make them resourceful. 3 | 4 | REST is a fine pattern for designing controllers, 5 | but it can be pretty repetitive. 6 | Who wants to write out the same actions and copy the same model lookup logic 7 | all over their application? 8 | 9 | make_resourceful handles all that for you. 10 | It sets up all your RESTful actions and responses with next to no code. 11 | Everything has full, sensible default functionality. 12 | 13 | Of course, no controller _only_ uses the defaults. 14 | So make_resourceful can be massively customized, 15 | while still keeping your controllers trim and readable. 16 | 17 | == Get it! 18 | 19 | Rails 3.0+ 20 | 21 | gem "make_resourceful" #ZOMG, SO EASY 22 | 23 | Git 24 | 25 | $ git clone git://github.com/hcatlin/make_resourceful.git 26 | 27 | == Use it! 28 | 29 | If you want to try make_resourceful on one of your current controllers, 30 | just replace the mess of repetition with this: 31 | 32 | class FooController < ApplicationController 33 | make_resourceful do 34 | actions :all 35 | end 36 | end 37 | 38 | Those three lines will replace the entire default controller 39 | that comes out of the scaffold_resource generator. 40 | 41 | === Really? 42 | 43 | Yes. 44 | 45 | === Can I do nested resources? 46 | 47 | make_resourceful do 48 | actions :all 49 | belongs_to :post 50 | end 51 | 52 | === What if I want to use fancy permalinks? 53 | 54 | def current_object 55 | @current_object ||= current_model.find_by_permalink(params[:id]) 56 | end 57 | 58 | === What about paging? 59 | 60 | def current_objects 61 | @current_objects ||= current_model.find(:all, 62 | :order => "created_at DESC", :page => {:current => params[:page], :size => 10 } ) 63 | end 64 | 65 | === What if I want to do something in the middle of an action? 66 | 67 | before :show, :index do 68 | @page_title = "Awesome!" 69 | end 70 | 71 | after :create_fails do 72 | @page_title = "Not So Awesome!" 73 | end 74 | 75 | === What about all of my awesome respond_to blocks for my XML APIs and RJS responses? 76 | 77 | response_for :show do |format| 78 | format.html 79 | format.js 80 | format.xml 81 | end 82 | 83 | response_for :update_fails do |format| 84 | format.html { render :action => 'edit' } 85 | format.json { render :json => false.to_json, :status => 422 } 86 | end 87 | 88 | === So I guess I have to write responses for all my actions? 89 | 90 | Nope! make_resourceful makes them do the right thing by default. 91 | You only need to customize them if you want to do something special. 92 | 93 | === Seriously?! 94 | 95 | Yes! 96 | 97 | == Grok it! 98 | 99 | === +make_resourceful+ the Method 100 | 101 | The +make_resourceful+ block is where most of the action happens. 102 | Here you specify which actions you want to auto-generate, 103 | what code you want to run for given callbacks, 104 | and so forth. 105 | 106 | You also use the block to declare various bits of information about your controller. 107 | For instance, if the controller is nested, you'd call +belongs_to+. 108 | If you wanted to expose your models as some sort of text format, 109 | you'd call +publish+. 110 | 111 | Check out the documentation of Resourceful::Builder 112 | for more information on the methods you can call here. 113 | 114 | === Helper Methods 115 | 116 | make_resourceful provides lots of useful methods 117 | that can be used in your callbacks and in your views. 118 | They range from accessing the records you're looking up 119 | to easily generating URLs for a record 120 | to getting information about the action itself. 121 | 122 | Two of the most useful methods are +current_object+ and +current_objects+ 123 | (note the subtle plurality difference). 124 | +current_objects+ only works for +index+, 125 | and returns all the records in the current model. 126 | +current_object+ works for all actions other than +index+, 127 | and returns the record that's currently being dealt with. 128 | 129 | The full documentation of the helper methods 130 | is in Resourceful::Default::Accessors and Resourceful::Default::URLs. 131 | 132 | === Nested Resources 133 | 134 | make_resourceful supports easy management of nested resources. 135 | This is set up with the Resourceful::Builder#belongs_to declaration. 136 | Pass in the name of the parent model, 137 | 138 | belongs_to :user 139 | 140 | and everything will be taken care of. 141 | When +index+ is run for GET /users/12/albums, 142 | parent_object 143 | will get User.find(params[:user_id]), 144 | and current_objects 145 | will get parent_object.albums. 146 | When +create+ is run for POST /users/12/albums, 147 | the newly created Album will automatically belong to the user 148 | with id 12. 149 | 150 | The normal non-scoped actions still work, too. 151 | GET /albums/15 runs just fine. 152 | make_resourceful knows that since there's no params[:user_id], 153 | you just want to deal with the album. 154 | 155 | You can even have a single resource nested under several different resources. 156 | Just pass multiple parent names to the Resourceful::Builder#belongs_to, like 157 | 158 | belongs_to :user, :artist 159 | 160 | Then /users/15/albums and /artists/7/albums will both work. 161 | 162 | This does, however, mean that make_resourceful only supports one level of nesting. 163 | There's no automatic handling of /users/15/collections/437/albums. 164 | However, this is really the best way to organize most resources anyway; 165 | see this {article}[http://weblog.jamisbuck.org/2007/2/5/nesting-resources]. 166 | 167 | If you really need a deeply nested controller, 168 | it should be easy enough to set up on your own. 169 | Just override current_model. 170 | See the next section for more details. 171 | 172 | === Overriding Methods 173 | 174 | Not only are helper methods useful to the developer to use, 175 | they're used internally by the actions created by make_resourceful. 176 | Thus one of the main ways make_resourceful can be customized 177 | is by overriding accessors. 178 | 179 | For instance, if you want to only look up the 10 most recent records for +index+, 180 | you're override +current_objects+. 181 | If you wanted to use a different model than that suggested by the name of the controller, 182 | you'd override +current_model+. 183 | 184 | When you're overriding methods that do SQL lookups, though, be a little cautious. 185 | By default, these methods cache their values in instance variables 186 | so that multiple SQL queries aren't run on multiple calls. 187 | When overriding them, it's wise for you to do the same. 188 | For instance, 189 | 190 | def current_object 191 | @current_object ||= current_model.find_by_name(params[:name]) 192 | end 193 | 194 | === For More Information... 195 | 196 | Haven't found all the information you need in the RDoc? 197 | Still a little confused about something? 198 | Don't despair, there are still more resources available! 199 | 200 | * Read the source code! 201 | It's very straightforward, 202 | and make_resourceful is built to encourage overriding methods 203 | and hacking the source. 204 | * Nathan Weizenbaum has some good blog posts about make_resourceful. 205 | They may be a little outdated, but they should still be useful and explanatory. 206 | * On nesting and associations: {here}[http://nex-3.com/posts/55-nesting-and-make_resourceful]. 207 | * An overview of make_resourceful 0.2.0 and 0.2.2: {here}[http://localhost:3000/posts/54-make_resourceful-0-2-0]. 208 | * On Resourceful::Builder#publish 209 | and Resourceful::Serialize: 210 | {here}[http://nex-3.com/posts/35-make_resourceful-the-basics-of-publish] and 211 | {here}[http://nex-3.com/posts/36-make_resourceful-publish-extras]. 212 | * There's an excellent, active Google Group http://groups.google.com/group/make_resourceful 213 | where people will be happy to answer your questions. 214 | 215 | --- 216 | 217 | Copyright 2007-2010 Hampton Catlin and Nathan Weizenbaum. 218 | Contributions by: 219 | 220 | * Russell Norris 221 | * Jonathan Linowes 222 | * Cristi Balan 223 | * Mike Ferrier 224 | * James Golick 225 | * Don Petersen 226 | * Alex Ross 227 | * Tom Stuart 228 | * Glenn Powell 229 | * Johannes Jörg Schmidt -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rdoc/task' 3 | require 'rspec/core/rake_task' 4 | 5 | desc 'Default: run specs.' 6 | task :default => :spec 7 | 8 | spec_files = Rake::FileList["spec/**/*_spec.rb"] 9 | 10 | desc "Run specs" 11 | RSpec::Core::RakeTask.new(:spec) do |t| 12 | t.pattern = "spec/**/*_spec.rb" 13 | t.rspec_opts = ["-c"] 14 | end 15 | 16 | desc "Generate code coverage" 17 | RSpec::Core::RakeTask.new(:coverage) do |t| 18 | t.spec_files = spec_files 19 | t.rcov = true 20 | t.rcov_opts = ['--exclude', 'spec,/var/lib/gems'] 21 | end 22 | 23 | desc 'Generate documentation for the make_resourceful plugin.' 24 | Rake::RDocTask.new(:rdoc) do |rdoc| 25 | rdoc.rdoc_dir = 'rdoc' 26 | rdoc.title = 'make_resourceful' 27 | rdoc.options << '--line-numbers' << '--inline-source' 28 | rdoc.main = 'README' 29 | rdoc.rdoc_files.include(FileList.new('*').exclude(/[^A-Z0-9]/)) 30 | rdoc.rdoc_files.include('lib/**/*.rb') 31 | end 32 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * Generators need to be ported to the new syntax http://guides.rubyonrails.org/generators.html 2 | * Add better strong parameters support 3 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 2.0.0 2 | -------------------------------------------------------------------------------- /lib/make_resourceful.rb: -------------------------------------------------------------------------------- 1 | 2 | class MakeResourcefulTie < Rails::Railtie 3 | initializer "my_railtie.configure_rails_initialization" do 4 | require File.join(File.dirname(__FILE__), 'resourceful/maker') 5 | ActionController::Base.extend Resourceful::Maker 6 | end 7 | end -------------------------------------------------------------------------------- /lib/resourceful/base.rb: -------------------------------------------------------------------------------- 1 | # The module containing all the code for make_resourceful. 2 | # 3 | # For methods available in the +make_resourceful+ block, 4 | # see Resourceful::Builder. 5 | # 6 | # For helper methods and methods you can override 7 | # to customize the behavior of your actions, 8 | # see Resourceful::Default::Accessors 9 | # and Resourceful::Default::URLs. 10 | module Resourceful 11 | # We want to define some stuff before we load other modules 12 | 13 | # The default actions generated by make_resourceful. 14 | ACTIONS = [:index, :show, :edit, :update, :create, :new, :destroy] 15 | 16 | # The actions that modify the database. 17 | MODIFYING_ACTIONS = [:update, :create, :destroy] 18 | 19 | # The actions that act on multiple records. 20 | PLURAL_ACTIONS = [:index] 21 | 22 | # The actions that act on just one record. 23 | SINGULAR_ACTIONS = ACTIONS - PLURAL_ACTIONS 24 | 25 | # The actions that act on just one record. 26 | SINGULAR_PRELOADED_ACTIONS = SINGULAR_ACTIONS - [:new, :create] 27 | end 28 | 29 | require 'resourceful/default/accessors' 30 | require 'resourceful/default/responses' 31 | require 'resourceful/default/callbacks' 32 | require 'resourceful/default/urls' 33 | 34 | # All modules included by this module 35 | # are made available to the controller as accessors. 36 | module Resourceful::Base 37 | @@made_resourceful_callbacks = [] 38 | 39 | # This method is meant to be called by included classes. 40 | # It takes a block of the same form as that given to Maker#make_resourceful. 41 | # The Maker will then run that block 42 | # along with the blocks given by the individual controllers. 43 | def self.made_resourceful(&block) 44 | if block 45 | @@made_resourceful_callbacks << block 46 | else 47 | @@made_resourceful_callbacks 48 | end 49 | end 50 | 51 | include Resourceful::Default::Accessors 52 | include Resourceful::Default::Responses 53 | include Resourceful::Default::Callbacks 54 | include Resourceful::Default::URLs 55 | 56 | # FIXME HACK 57 | # making methods assigned to controller private 58 | # prevents access from dispatcher. 59 | private *Resourceful::Default::Accessors.public_instance_methods 60 | private *Resourceful::Default::Responses.public_instance_methods 61 | private *Resourceful::Default::Callbacks.public_instance_methods 62 | private *Resourceful::Default::URLs.public_instance_methods 63 | end 64 | -------------------------------------------------------------------------------- /lib/resourceful/builder.rb: -------------------------------------------------------------------------------- 1 | require 'resourceful/response' 2 | require 'resourceful/serialize' 3 | require 'resourceful/default/actions' 4 | 5 | module Resourceful 6 | # The Maker#make_resourceful block is evaluated in the context 7 | # of an instance of this class. 8 | # It provides various methods for customizing the behavior of the actions 9 | # built by make_resourceful. 10 | # 11 | # All instance methods of this class are available in the +make_resourceful+ block. 12 | class Builder 13 | # The klass of the controller on which the builder is working. 14 | attr :controller, true 15 | 16 | # The constructor is only meant to be called internally. 17 | # 18 | # This takes the klass (class object) of a controller 19 | # and constructs a Builder ready to apply the make_resourceful 20 | # additions to the controller. 21 | def initialize(kontroller) 22 | @controller = kontroller 23 | @inherited = !kontroller.resourceful_responses.blank? 24 | @action_module = Resourceful::Default::Actions.dup 25 | @ok_actions = [] 26 | @callbacks = {:before => {}, :after => {}} 27 | @responses = {} 28 | @publish = {} 29 | @parents = [] 30 | @shallow_parent = nil 31 | @custom_member_actions = [] 32 | @custom_collection_actions = [] 33 | @permitted_params = [] 34 | end 35 | 36 | # This method is only meant to be called internally. 37 | # 38 | # Applies all the changes that have been declared 39 | # via the instance methods of this Builder 40 | # to the kontroller passed in to the constructor. 41 | def apply 42 | apply_publish 43 | 44 | kontroller = @controller 45 | 46 | Resourceful::ACTIONS.each do |action_named| 47 | # See if this is a method listed by #actions 48 | unless @ok_actions.include? action_named 49 | # If its not listed, then remove the method 50 | # No one can hit it... if its DEAD! 51 | @action_module.send :remove_method, action_named 52 | end 53 | end 54 | 55 | if kontroller.respond_to?(:hidden_actions) 56 | kontroller.hidden_actions.reject! &@ok_actions.method(:include?) 57 | end 58 | kontroller.send :include, @action_module 59 | 60 | merged_callbacks = kontroller.resourceful_callbacks.merge @callbacks 61 | merged_responses = kontroller.resourceful_responses.merge @responses 62 | 63 | kontroller.permitted_params = @permitted_params 64 | kontroller.resourceful_callbacks = merged_callbacks 65 | kontroller.resourceful_responses = merged_responses 66 | kontroller.made_resourceful = true 67 | 68 | kontroller.parent_controllers = @parents 69 | kontroller.shallow_parent = @shallow_parent 70 | kontroller.model_namespace = @model_namespace 71 | kontroller.before_action :load_object, :only => (@ok_actions & SINGULAR_PRELOADED_ACTIONS) + @custom_member_actions 72 | kontroller.before_action :load_objects, :only => (@ok_actions & PLURAL_ACTIONS) + @custom_collection_actions 73 | kontroller.before_action :load_parent_object, :only => @ok_actions + @custom_member_actions + @custom_collection_actions 74 | end 75 | 76 | # :call-seq: 77 | # actions(*available_actions) 78 | # actions :all 79 | # 80 | # Adds the default RESTful actions to the controller. 81 | # 82 | # If the only argument is :all, 83 | # adds all the actions listed in Resourceful::ACTIONS[link:classes/Resourceful.html] 84 | # (or Resourceful::SINGULAR_ACTIONS[link:classes/Resourceful.html] 85 | # for a singular controller). 86 | # 87 | # Otherwise, this adds all actions 88 | # whose names were passed as arguments. 89 | # 90 | # For example: 91 | # 92 | # actions :show, :new, :create 93 | # 94 | # This adds the +show+, +new+, and +create+ actions 95 | # to the controller. 96 | # 97 | # The available actions are defined in Default::Actions. 98 | def actions(*available_actions) 99 | # FIXME HACK 100 | # made all methods private, so plural?, too. 101 | # Did not want to make an exception for that and i do not like it to 102 | # come up on actions_methods. 103 | # TODO: maybe we can define plural? as class_method 104 | if available_actions.first == :all 105 | if controller.respond_to?(:new_without_capture) 106 | available_actions = controller.new_without_capture.send(:plural?) ? ACTIONS : SINGULAR_ACTIONS 107 | else 108 | available_actions = controller.new.send(:plural?) ? ACTIONS : SINGULAR_ACTIONS 109 | end 110 | end 111 | 112 | available_actions.each { |action| @ok_actions << action.to_sym } 113 | end 114 | alias build actions 115 | 116 | # :call-seq: 117 | # member_actions(*available_actions) 118 | # Registers custom member actions which will use the load_object before_action. 119 | # These actions are not created, but merely registered for filtering. 120 | def member_actions(*available_actions) 121 | available_actions.each { |action| @custom_member_actions << action.to_sym } 122 | end 123 | 124 | # :call-seq: 125 | # collection_actions(*available_actions) 126 | # 127 | # Registers custom collection actions which will use the load_objects before_action. 128 | # These actions are not created, but merely registered for filtering. 129 | def collection_actions(*available_actions) 130 | available_actions.each { |action| @custom_collection_actions << action.to_sym } 131 | end 132 | 133 | # :call-seq: 134 | # permitted_params(*params_allowed) 135 | # 136 | # When using strong parameters, specifies which params are permitted 137 | # 138 | # If the only argument is :all, 139 | # all parameters are permitted 140 | def permitted_params(*params_permitted) 141 | if params_permitted.first == :all 142 | @permitted_params = params_permitted.first 143 | else 144 | @permitted_params = [] unless @permitted_params.is_a?(Array) 145 | params_permitted.each { |param| @permitted_params << param.to_sym } 146 | end 147 | end 148 | 149 | # :call-seq: 150 | # before(*events) { ... } 151 | # 152 | # Sets up a block of code to run before one or more events. 153 | # 154 | # All the default actions can be used as +before+ events: 155 | # :index, :show, :create, :update, :new, :edit, and :destroy. 156 | # 157 | # +before+ events are run after any objects are loaded[link:classes/Resourceful/Default/Accessors.html#M000015], 158 | # but before any database operations or responses. 159 | # 160 | # For example: 161 | # 162 | # before :show, :edit do 163 | # @page_title = current_object.title 164 | # end 165 | # 166 | # This will set the @page_title variable 167 | # to the current object's title 168 | # for the show and edit actions. 169 | # 170 | # Successive before blocks for the same action will be chained and executed 171 | # in order when the event occurs. 172 | # 173 | # For example: 174 | # 175 | # before :show, :edit do 176 | # @page_title = current_object.title 177 | # end 178 | # 179 | # before :show do 180 | # @side_bar = true 181 | # end 182 | # 183 | # These before blocks will both be executed for the show action and in the 184 | # same order as they were defined. 185 | def before(*events, &block) 186 | add_callback :before, *events, &block 187 | end 188 | 189 | # :call-seq: 190 | # after(*events) { ... } 191 | # 192 | # Sets up a block of code to run after one or more events. 193 | # 194 | # There are two sorts of +after+ events. 195 | # :create, :update, and :destroy 196 | # are run after their respective database operations 197 | # have been completed successfully. 198 | # :create_fails, :update_fails, and :destroy_fails, 199 | # on the other hand, 200 | # are run after the database operations fail. 201 | # 202 | # +after+ events are run after the database operations 203 | # but before any responses. 204 | # 205 | # For example: 206 | # 207 | # after :create_fails, :update_fails do 208 | # current_object.password = nil 209 | # end 210 | # 211 | # This will nillify the password of the current object 212 | # if the object creation/modification failed. 213 | def after(*events, &block) 214 | add_callback :after, *events, &block 215 | end 216 | 217 | # :call-seq: 218 | # response_for(*actions) { ... } 219 | # response_for(*actions) { |format| ... } 220 | # 221 | # Sets up a block of code to run 222 | # instead of the default responses for one or more events. 223 | # 224 | # If the block takes a format parameter, 225 | # it has the same semantics as Rails' +respond_to+ method. 226 | # Various format methods are called on the format object 227 | # with blocks that say what to do for each format. 228 | # For example: 229 | # 230 | # response_for :index do |format| 231 | # format.html 232 | # format.atom do 233 | # headers['Content-Type'] = 'application/atom+xml; charset=utf-8' 234 | # render :action => 'atom', :layout => false 235 | # end 236 | # end 237 | # 238 | # This doesn't do anything special for the HTML 239 | # other than ensure that the proper view will be rendered, 240 | # but for ATOM it sets the proper content type 241 | # and renders the atom template. 242 | # 243 | # If you only need to set the HTML response, 244 | # you can omit the format parameter. 245 | # For example: 246 | # 247 | # response_for :new do 248 | # render :action => 'edit' 249 | # end 250 | # 251 | # This is the same as 252 | # 253 | # response_for :new do |format| 254 | # format.html { render :action => 'edit' } 255 | # end 256 | # 257 | # The default responses are defined by 258 | # Default::Responses.included[link:classes/Resourceful/Default/Responses.html#M000011]. 259 | def response_for(*actions, &block) 260 | raise "Must specify one or more actions for response_for." if actions.empty? 261 | 262 | if block.arity < 1 263 | response_for(*actions) do |format| 264 | format.html(&block) 265 | end 266 | else 267 | response = Response.new 268 | block.call response 269 | 270 | actions.each do |action| 271 | @responses[action.to_sym] = response.formats 272 | end 273 | end 274 | end 275 | 276 | # :call-seq: 277 | # publish *formats, options = {}, :attributes => [ ... ] 278 | # 279 | # publish allows you to easily expose information about resourcess in a variety of formats. 280 | # The +formats+ parameter is a list of formats 281 | # in which to publish the resources. 282 | # The formats supported by default are +xml+, +yaml+, and +json+, 283 | # but other formats may be added by defining +to_format+ methods 284 | # for the Array and Hash classes 285 | # and registering the mime type with Rails' Mime::Type.register[http://api.rubyonrails.org/classes/Mime/Type.html#M001115]. 286 | # See Resourceful::Serialize for more details.. 287 | # 288 | # The :attributes option is mandatory. 289 | # It takes an array of attributes (as symbols) to make public. 290 | # These attributes can refer to any method on current_object; 291 | # they aren't limited to database fields. 292 | # For example: 293 | # 294 | # # posts_controller.rb 295 | # publish :yaml, :attributes => [:title, :created_at, :rendered_content] 296 | # 297 | # Then GET /posts/12.yaml would render 298 | # 299 | # --- 300 | # post: 301 | # title: Cool Stuff 302 | # rendered_content: |- 303 | #

This is a post.

304 | #

It's about really cool stuff.

305 | # created_at: 2007-04-28 04:32:08 -07:00 306 | # 307 | # The :attributes array can even contain attributes 308 | # that are themselves models. 309 | # In this case, you must use a hash to specify their attributes as well. 310 | # For example: 311 | # 312 | # # person_controller.rb 313 | # publish :xml, :json, :attributes => [ 314 | # :name, :favorite_color, { 315 | # :pet_cat => [:name, :breed], 316 | # :hat => [:type] 317 | # }] 318 | # 319 | # Then GET /people/18.xml would render 320 | # 321 | # 322 | # 323 | # Nathan 324 | # blue 325 | # 326 | # Jasmine 327 | # panther 328 | # 329 | # 330 | # top 331 | # 332 | # 333 | # 334 | # publish will also allow the +index+ action 335 | # to render lists of objects. 336 | # An example would be too big, 337 | # but play with it a little on your own to see. 338 | # 339 | # publish takes only one optional option: only. 340 | # This specifies which action to publish the resources for. 341 | # By default, they're published for both +show+ and +index+. 342 | # For example: 343 | # 344 | # # cats_controller.rb 345 | # publish :json, :only => :index, :attributes => [:name, :breed] 346 | # 347 | # Then GET /cats.json would work, but GET /cats/294.json would fail. 348 | def publish(*formats) 349 | options = { 350 | :only => [:show, :index] 351 | }.merge(Hash === formats.last ? formats.pop : {}) 352 | raise "Must specify :attributes option" unless options[:attributes] 353 | 354 | Array(options.delete(:only)).each do |action| 355 | @publish[action] ||= [] 356 | formats.each do |format| 357 | format = format.to_sym 358 | @publish[action] << [format, proc do 359 | render_action = [:json, :xml].include?(format) ? format : :text 360 | render render_action => (plural_action? ? current_objects : current_object).serialize(format, options) 361 | end] 362 | end 363 | end 364 | end 365 | 366 | # Specifies parent resources for the current resource. 367 | # Each of these parents will be loaded automatically 368 | # if the proper id parameter is given. 369 | # For example, 370 | # 371 | # # cake_controller.rb 372 | # belongs_to :baker, :customer 373 | # 374 | # Then on GET /bakers/12/cakes, 375 | # 376 | # params[:baker_id] #=> 12 377 | # parent? #=> true 378 | # parent_name #=> "baker" 379 | # parent_model #=> Baker 380 | # parent_object #=> Baker.find(12) 381 | # current_objects #=> Baker.find(12).cakes 382 | # 383 | def belongs_to(*parents) 384 | options = parents.extract_options! 385 | @parents = parents.map(&:to_s) 386 | if options[:shallow] 387 | options[:shallow] = options[:shallow].to_s 388 | raise ArgumentError, ":shallow needs the name of a parent resource" unless @parents.include? options[:shallow] 389 | @shallow_parent = options[:shallow] 390 | end 391 | end 392 | 393 | # Specifies a namespace for the resource model. It can be given as a 394 | # Module::NameSpace, 'Module::NameSpace' (in a string), or 395 | # 'module/name_space' (underscored form). 396 | def model_namespace(ns) 397 | @model_namespace = ns.to_s.camelize 398 | end 399 | 400 | # This method is only meant to be called internally. 401 | # 402 | # Returns whether or not the Builder's controller 403 | # inherits make_resourceful settings from a parent controller. 404 | def inherited? 405 | @inherited 406 | end 407 | 408 | private 409 | 410 | def apply_publish 411 | @publish.each do |action, types| 412 | @responses[action.to_sym] ||= [] 413 | @responses[action.to_sym] += types 414 | end 415 | end 416 | 417 | def add_callback(type, *events, &block) 418 | events.each do |event| 419 | @callbacks[type][event.to_sym] ||= [] 420 | @callbacks[type][event.to_sym] << block 421 | end 422 | end 423 | end 424 | end 425 | -------------------------------------------------------------------------------- /lib/resourceful/default/accessors.rb: -------------------------------------------------------------------------------- 1 | module Resourceful 2 | # This module contains various methods 3 | # that are available from actions and callbacks. 4 | # Default::Accessors and Default::URLs are the most useful to users; 5 | # the rest are mostly used internally. 6 | # 7 | # However, if you want to poke around the internals a little, 8 | # check out Default::Actions, which has the default Action definitions, 9 | # and Default::Responses.included, which defines the default response_for[link:classes/Resourceful/Builder.html#M000061] blocks. 10 | module Default 11 | # This module contains all sorts of useful methods 12 | # that allow access to the resources being worked with, 13 | # metadata about the controller and action, 14 | # and so forth. 15 | # 16 | # Many of these accessors call other accessors 17 | # and are called by the default make_resourceful actions[link:classes/Resourceful/Default/Actions.html]. 18 | # This means that overriding one method 19 | # can affect everything else. 20 | # 21 | # This can be dangerous, but it can also be very powerful. 22 | # make_resourceful is designed to take advantage of overriding, 23 | # so as long as the new methods accomplish the same purpose as the old ones, 24 | # everything will just work. 25 | # Even if you make a small mistake, 26 | # it's hard to break the controller in any unexpected ways. 27 | # 28 | # For example, suppose your controller is called TagsController, 29 | # but your model is called PhotoTag. 30 | # All you have to do is override current_model_name: 31 | # 32 | # def current_model_name 33 | # "PhotoTag" 34 | # end 35 | # 36 | # Then current_model will return the PhotoTag model, 37 | # current_object will call PhotoTag.find, 38 | # and so forth. 39 | # 40 | # Overriding current_objects and current_object is particularly useful 41 | # for providing customized model lookup logic. 42 | module Accessors 43 | # Returns an array of all the objects of the model corresponding to the controller. 44 | # For UsersController, it essentially runs User.find(:all). 45 | # 46 | # However, there are a few important differences. 47 | # First, this method caches is results in the @current_objects instance variable. 48 | # That way, multiple calls won't run multiple queries. 49 | # 50 | # Second, this method uses the current_model accessor, 51 | # which provides a lot of flexibility 52 | # (see the documentation for current_model for details). 53 | def current_objects 54 | @current_objects ||= current_model.all 55 | end 56 | 57 | # Calls current_objects and stores 58 | # the result in an instance variable 59 | # named after the controller. 60 | # 61 | # This is called automatically by the default make_resourceful actions. 62 | # You shouldn't need to use it directly unless you're creating a new action. 63 | # 64 | # For example, in UsersController, 65 | # calling +load_objects+ sets @users = current_objects. 66 | def load_objects 67 | instance_variable_set("@#{instance_variable_name}", current_objects) 68 | end 69 | 70 | # Returns the object referenced by the id parameter 71 | # (or the newly-created object for the +new+ and +create+ actions). 72 | # For UsersController, it essentially runs User.find(params[:id]). 73 | # 74 | # However, there are a few important differences. 75 | # First, this method caches is results in the @current_objects instance variable. 76 | # That way, multiple calls won't run multiple queries. 77 | # 78 | # Second, this method uses the current_model accessor, 79 | # which provides a lot of flexibility 80 | # (see the documentation for current_model for details). 81 | # 82 | # Note that this is different for a singleton controller, 83 | # where there's only one resource per parent resource. 84 | # Then this just returns that resource. 85 | # For example, if Person has_one Hat, 86 | # then in HatsController current_object essentially runs Person.find(params[:person_id]).hat. 87 | def current_object 88 | @current_object ||= if !parent? || plural? 89 | current_model.find(params[:id]) if params[:id] 90 | else 91 | parent_object.send(instance_variable_name.singularize) 92 | end 93 | end 94 | 95 | 96 | # Calls current_object and stores 97 | # the result in an instance variable 98 | # named after the controller. 99 | # 100 | # This is called automatically by the default make_resourceful actions. 101 | # You shouldn't need to use it directly unless you're creating a new action. 102 | # 103 | # For example, in UsersController, 104 | # calling +load_object+ sets @user = current_object. 105 | def load_object 106 | instance_variable_set("@#{instance_variable_name.singularize}", current_object) 107 | end 108 | 109 | # Creates a new object of the type of the current model 110 | # with the current object's parameters. 111 | # +current_object+ then returns this object for this action 112 | # instead of looking up a new object. 113 | # 114 | # This is called automatically by the default make_resourceful actions. 115 | # You shouldn't need to use it directly unless you're creating a new action. 116 | # 117 | # Note that if a parent object exists, 118 | # the newly created object will automatically be a child of the parent object. 119 | # For example, on POST /people/4/things, 120 | # 121 | # build_object 122 | # current_object.person.id #=> 4 123 | # 124 | def build_object 125 | @current_object = if current_model.respond_to? :build 126 | current_model.build(object_parameters) 127 | else 128 | current_model.new(object_parameters).tap do |obj| 129 | if singular? && parent? 130 | obj.send("#{parent_name}_id=", parent_object.id) 131 | obj.send("#{parent_name}_type=", parent_object.class.to_s) if polymorphic_parent? 132 | end 133 | end 134 | end 135 | end 136 | 137 | def namespaced_model_name 138 | [self.class.model_namespace, current_model_name].compact.join('::') 139 | end 140 | 141 | # The string name of the current model. 142 | # By default, this is derived from the name of the controller. 143 | def current_model_name 144 | controller_name.singularize.camelize 145 | end 146 | 147 | # An array of namespaces under which the current controller is. 148 | # For example, in Admin::Content::PagesController: 149 | # 150 | # namespaces #=> [:admin, :content] 151 | # 152 | def namespaces 153 | @namespaces ||= self.class.name.split('::').slice(0...-1).map(&:underscore).map(&:to_sym) 154 | end 155 | 156 | # The name of the instance variable that load_object and load_objects should assign to. 157 | def instance_variable_name 158 | controller_name 159 | end 160 | 161 | # The class of the current model. 162 | # Note that if a parent object exists, 163 | # this instead returns the association object. 164 | # For example, in HatsController where Person has_many :hats, 165 | # 166 | # current_model #=> Person.find(params[:person_id]).hats 167 | # 168 | # This is useful because the association object uses duck typing 169 | # to act like a model class. 170 | # It supplies a find method that's automatically scoped 171 | # to ensure that the object returned is actually a child of the parent, 172 | # and so forth. 173 | def current_model 174 | if !parent? || singular? 175 | namespaced_model_name.constantize 176 | else 177 | parent_object.send(instance_variable_name) 178 | end 179 | end 180 | 181 | # Returns the hash passed as HTTP parameters 182 | # that defines the new (or updated) attributes 183 | # of the current object. 184 | # This is only meaningful for +create+ or +update+. 185 | def object_parameters 186 | if params.respond_to?(:permit) && self.class.permitted_params && params[:action].to_s != "new" 187 | permitable_method = if self.class.permitted_params == :all 188 | [:permit!] 189 | else 190 | [:permit, self.class.permitted_params] 191 | end 192 | params.require(namespaced_model_name.underscore.tr('/', '_')).send(*permitable_method) 193 | else 194 | params[namespaced_model_name.underscore.tr('/', '_')] 195 | end 196 | end 197 | 198 | # Returns a list of the names of all the potential parents of the current model. 199 | # For a non-nested controller, this is []. 200 | # For example, in HatsController where Rack has_many :hats and Person has_many :hats, 201 | # 202 | # parents #=> ["rack", "person"] 203 | # 204 | # Note that the parents must be declared via Builder#belongs_to. 205 | def parent_names 206 | self.class.parent_controllers 207 | end 208 | 209 | # Returns true if an appropriate parent id parameter has been supplied. 210 | # For example, in HatsController where Rack has_many :hats and Person has_many :hats, 211 | # if params[:rack_id] or params[:person_id] is given, 212 | # 213 | # parent? #=> true 214 | # 215 | # Otherwise, if both params[:rack_id] and params[:rack_id] are nil, 216 | # 217 | # parent? #=> false 218 | # 219 | # Note that parents must be declared via Builder#belongs_to. 220 | def parent? 221 | !!parent_name 222 | end 223 | 224 | # Returns true if no parent id parameter can be found _and_ a belongs_to 225 | # relationship on this controller was declared with a parent for shallow 226 | # routing. 227 | def shallow? 228 | self.class.shallow_parent && 229 | (parent_name.nil? || parent_name == self.class.shallow_parent) 230 | end 231 | 232 | # Returns whether the parent (if it exists) is polymorphic 233 | def polymorphic_parent? 234 | !!polymorphic_parent_name 235 | end 236 | 237 | # Returns the name of the current parent object if a parent id is given, 238 | # or nil otherwise. For example, in HatsController where Rack has_many 239 | # :hats and Person has_many :hats, if params[:rack_id] is 240 | # given, 241 | # 242 | # parent_name #=> "rack" 243 | # 244 | # If params[:person_id] is given, 245 | # 246 | # parent_name #=> "person" 247 | # 248 | # If both params[:rack_id] and params[:person_id] are 249 | # nil, 250 | # 251 | # parent_name #=> nil 252 | # 253 | # There are several things to note about this method. First, 254 | # make_resourceful only supports single-level model nesting. Thus, if 255 | # neither params[:rack_id] nor params[:rack_id] are 256 | # nil, the return value of +parent_name+ is undefined. 257 | # 258 | # Second, don't use parent_name to check whether a parent id is given. 259 | # It's better to use the more semantic parent? method. 260 | # 261 | # Third, parent_name caches its return value in the 262 | # @parent_name variable, which you should keep in mind if 263 | # you're overriding it. However, because @parent_name == nil 264 | # could mean that there is no parent _or_ that the method hasn't been 265 | # run yet, it uses defined?(@parent_name) to do the caching 266 | # rather than @parent_name ||=. See the source code. 267 | # 268 | # Finally, note that parents must be declared via Builder#belongs_to. 269 | # 270 | # FIXME - Perhaps this logic should be moved to parent?() or another 271 | # init method 272 | def parent_name 273 | return @parent_name if defined?(@parent_name) 274 | @parent_name = parent_names.find { |name| params["#{name}_id"] } 275 | if @parent_name.nil? 276 | # get any polymorphic parents through :as association inspection 277 | names = params.keys.inject({}) do |hsh, key| 278 | hsh[key] = key.chomp("_id") if key.to_s =~ /_id$/ 279 | hsh 280 | end 281 | names.each do |key, name| 282 | begin 283 | klass = name.camelize.constantize 284 | if association = klass.reflect_on_all_associations.detect { |association| association.options[:as] && parent_names.include?(association.options[:as].to_s) } 285 | @parent_name = name 286 | @polymorphic_parent_name = association.options[:as].to_s 287 | @parent_class_name = name.camelize 288 | @parent_object = klass.find(params[key]) 289 | break 290 | end 291 | rescue 292 | end 293 | end 294 | else 295 | @parent_class_name = params["#{parent_name}_type"] 296 | @polymorphic_parent = !@parent_class_name.nil? # NEVER USED 297 | end 298 | @parent_name 299 | end 300 | 301 | def polymorphic_parent_name 302 | @polymorphic_parent_name 303 | end 304 | 305 | # Returns the class name of the current parent. 306 | # For example, in HatsController where Person has_many :hats, 307 | # if params[:person_id] is given, 308 | # 309 | # parent_class_name #=> 'Person' 310 | # 311 | # Note that parents must be declared via Builder#belongs_to. 312 | def parent_class_name 313 | parent_name # to init @parent_class_name 314 | @parent_class_name ||= parent_name.nil? ? nil : parent_name.camelize 315 | end 316 | 317 | # Returns the model class of the current parent. 318 | # For example, in HatsController where Person has_many :hats, 319 | # if params[:person_id] is given, 320 | # 321 | # parent_models #=> Person 322 | # 323 | # Note that parents must be declared via Builder#belongs_to. 324 | def parent_model 325 | parent_class_name.nil? ? nil : parent_class_name.constantize 326 | end 327 | 328 | # Returns the current parent object for the current object. 329 | # For example, in HatsController where Person has_many :hats, 330 | # if params[:person_id] is given, 331 | # 332 | # parent_object #=> Person.find(params[:person_id]) 333 | # 334 | # Note that parents must be declared via Builder#belongs_to. 335 | # 336 | # Note also that the results of this method are cached 337 | # so that multiple calls don't result in multiple SQL queries. 338 | def parent_object 339 | @parent_object ||= parent_model.nil? ? nil : parent_model.find(params["#{parent_name}_id"]) 340 | end 341 | 342 | # Assigns the current parent object, as given by parent_objects, 343 | # to its proper instance variable, as given by parent_name. 344 | # 345 | # This is automatically added as a before_action. 346 | # You shouldn't need to use it directly unless you're creating a new action. 347 | def load_parent_object 348 | instance_variable_set("@#{parent_name}", parent_object) if parent? 349 | instance_variable_set("@#{polymorphic_parent_name}", parent_object) if polymorphic_parent? 350 | end 351 | 352 | # Renders a 422 error if no parent id is given. 353 | # This is meant to be used with before_action 354 | # to ensure that some actions are only called with a parent id. 355 | # For example: 356 | # 357 | # before_action :ensure_parent_exists, :only => [:create, :update] 358 | # 359 | def ensure_parent_exists 360 | return true if parent? 361 | render :text => 'No parent id given', :status => 422 362 | return false 363 | end 364 | 365 | # Returns whether or not the database update in the +create+, +update+, and +destroy+ 366 | # was completed successfully. 367 | def save_succeeded? 368 | @save_succeeded 369 | end 370 | 371 | # Declares that the current databse update was completed successfully. 372 | # Causes subsequent calls to save_succeeded? to return +true+. 373 | # 374 | # This is mostly meant to be used by the default actions, 375 | # but it can be used by user-defined actions as well. 376 | def save_succeeded! 377 | @save_succeeded = true 378 | end 379 | 380 | # Declares that the current databse update was not completed successfully. 381 | # Causes subsequent calls to save_succeeded? to return +false+. 382 | # 383 | # This is mostly meant to be used by the default actions, 384 | # but it can be used by user-defined actions as well. 385 | def save_failed! 386 | @save_succeeded = false 387 | end 388 | 389 | # Returns whether or not the current action acts upon multiple objects. 390 | # By default, the only such action is +index+. 391 | def plural_action? 392 | PLURAL_ACTIONS.include?(params[:action].to_sym) 393 | end 394 | 395 | # Returns whether or not the current action acts upon a single object. 396 | # By default, this is the case for all actions but +index+. 397 | def singular_action? 398 | !plural_action? 399 | end 400 | 401 | # Returns whether the controller is a singleton, 402 | # implying that there is only one such resource for each parent resource. 403 | # 404 | # Note that the way this is determined is based on the singularity of the controller name, 405 | # so it may yield false positives for oddly-named controllers and need to be overridden. 406 | # 407 | # TODO: maybe we can define plural? and singular? as class_methods, 408 | # so they are not visible to the world 409 | def singular? 410 | instance_variable_name.singularize == instance_variable_name 411 | end 412 | 413 | # Returns whether the controller is a normal plural controller, 414 | # implying that there are multiple resources for each parent resource. 415 | # 416 | # Note that the way this is determined is based on the singularity of the controller name, 417 | # so it may yield false negatives for oddly-named controllers. 418 | # If this is the case, the singular? method should be overridden. 419 | # 420 | # TODO: maybe we can define plural? and singular? as class_methods, 421 | # so they are not visible to the world 422 | def plural? 423 | !singular? 424 | end 425 | end 426 | end 427 | end 428 | -------------------------------------------------------------------------------- /lib/resourceful/default/actions.rb: -------------------------------------------------------------------------------- 1 | module Resourceful 2 | module Default 3 | # Contains the definitions of the default resourceful actions. 4 | # These are made available with the Builder#actions method. 5 | # 6 | # These methods are very compact, 7 | # so the best way to understand them is just to look at their source. 8 | # Check out Resourceful::Accessors and Resourceful::Callbacks 9 | # for the documentation of the methods called within the actions. 10 | # 11 | # Along with each action is listed the RESTful method 12 | # which corresponds to the action. 13 | # The controller in the examples is FoosController, 14 | # and the id for single-object actions is 12. 15 | module Actions 16 | # GET /foos 17 | def index 18 | #load_objects 19 | before :index 20 | response_for :index 21 | end 22 | 23 | # GET /foos/12 24 | def show 25 | # NOTE - Moved this call to a more generic place 26 | #load_object 27 | before :show 28 | response_for :show 29 | rescue 30 | response_for :show_fails 31 | end 32 | 33 | # POST /foos 34 | def create 35 | build_object 36 | load_object 37 | before :create 38 | if current_object.save 39 | save_succeeded! 40 | after :create 41 | response_for :create 42 | else 43 | save_failed! 44 | after :create_fails 45 | response_for :create_fails 46 | end 47 | end 48 | 49 | # PUT /foos/12 50 | def update 51 | #load_object 52 | before :update 53 | 54 | begin 55 | result = current_object.update_attributes object_parameters 56 | rescue ActiveRecord::StaleObjectError 57 | current_object.reload 58 | result = false 59 | end 60 | 61 | if result 62 | save_succeeded! 63 | after :update 64 | response_for :update 65 | else 66 | save_failed! 67 | after :update_fails 68 | response_for :update_fails 69 | end 70 | end 71 | 72 | # GET /foos/new 73 | def new 74 | build_object 75 | load_object 76 | before :new 77 | response_for :new 78 | end 79 | 80 | # GET /foos/12/edit 81 | def edit 82 | #load_object 83 | before :edit 84 | response_for :edit 85 | end 86 | 87 | # DELETE /foos/12 88 | def destroy 89 | #load_object 90 | before :destroy 91 | if current_object.destroy 92 | after :destroy 93 | response_for :destroy 94 | else 95 | after :destroy_fails 96 | response_for :destroy_fails 97 | end 98 | end 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/resourceful/default/callbacks.rb: -------------------------------------------------------------------------------- 1 | require 'resourceful/builder' 2 | 3 | module Resourceful 4 | module Default 5 | # This module is mostly meant to be used by the make_resourceful default actions. 6 | # It provides various methods that declare where callbacks set in the +make_resourceful+ block, 7 | # like Builder#before and Builder#response_for, 8 | # should be called. 9 | module Callbacks 10 | # Calls any +before+ callbacks set in the +make_resourceful+ block for the given event. 11 | def before(event) 12 | resourceful_fire(:before, event.to_sym) 13 | end 14 | 15 | # Calls any +after+ callbacks set in the +make_resourceful+ block for the given event. 16 | def after(event) 17 | resourceful_fire(:after, event.to_sym) 18 | end 19 | 20 | # Calls any +response_for+ callbacks set in the +make_resourceful+ block for the given event. 21 | # Note that these aren't called directly, 22 | # but instead passed along to Rails' respond_to method. 23 | def response_for(event) 24 | if responses = self.class.resourceful_responses[event.to_sym] 25 | respond_to do |format| 26 | responses.each do |key, value| 27 | format.send(key, &scope(value)) 28 | end 29 | end 30 | end 31 | end 32 | 33 | # Returns a block identical to the given block, 34 | # but in the context of the current controller. 35 | # The returned block accepts no arguments, 36 | # even if the given block accepted them. 37 | def scope(block) 38 | proc do 39 | instance_eval(&(block || proc {})) 40 | end 41 | end 42 | 43 | private 44 | 45 | def resourceful_fire(type, name) 46 | callbacks = self.class.resourceful_callbacks[type][name] || [] 47 | callbacks.each { |callback| scope(callback).call } 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/resourceful/default/responses.rb: -------------------------------------------------------------------------------- 1 | module Resourceful 2 | module Default 3 | module Responses 4 | # Sets the default flash message. 5 | # This message can be overridden by passing in 6 | # an HTTP parameter of the form "_flash[type]" via POST or GET. 7 | # 8 | # You can use this to easily have multiple forms 9 | # post to the same create/edit/destroy actions 10 | # but display different flash notices - 11 | # without modifying the controller code at all. 12 | # 13 | # By default, the flash types are +notice+ when the database operation completes successfully 14 | # and +error+ when it fails. 15 | # 16 | #-- 17 | # TODO: Move this out of here 18 | #++ 19 | def set_default_flash(type, message) 20 | flash[type] ||= (params[:_flash] && params[:_flash][type]) || message 21 | end 22 | 23 | # Sets the default redirect 24 | # (the argument passed to +redirect_to+). 25 | # This message can be overridden by passing in 26 | # an HTTP parameter of the form "_redirect_on[status]" via POST or GET. 27 | # 28 | # You can use this to easily have multiple forms 29 | # post to the same create/edit/destroy actions 30 | # but redirect to different URLs - 31 | # without modifying the controller code at all. 32 | # 33 | # By default, the redirect statuses are +success+ when the database operation completes successfully 34 | # and +failure+ when it fails. 35 | # Use the :status option to specify which status to run the redirect for. 36 | # For example: 37 | # 38 | # set_default_redirect "/posts", :status => :failure 39 | # 40 | # This will run redirect_to params[:_redirect_on][:failure] if the parameter exists, 41 | # or redirect_to "/posts" otherwise. 42 | # 43 | #-- 44 | # TODO: Move this out of here 45 | #++ 46 | def set_default_redirect(to, options = {}) 47 | status = options[:status] || :success 48 | redirect_to (params[:_redirect_on] && params[:_redirect_on][status]) || to 49 | end 50 | 51 | # This method is automatically run when this module is included in Resourceful::Base. 52 | # It sets up the default responses for the default actions. 53 | def self.included(base) 54 | base.made_resourceful do 55 | response_for(:show, :index, :edit, :new) do |format| 56 | format.html 57 | format.js 58 | end 59 | 60 | response_for(:show_fails) do |format| 61 | not_found = Proc.new { render :text => I18n.t('make_resourceful.show.fails', :default => "No item found"), :status => 404 } 62 | format.html ¬_found 63 | format.js ¬_found 64 | format.xml ¬_found 65 | end 66 | 67 | response_for(:create) do |format| 68 | format.html do 69 | set_default_flash :notice, I18n.t('make_resourceful.create.success', :default => "Create successful!") 70 | set_default_redirect object_path 71 | end 72 | format.js 73 | end 74 | 75 | response_for(:create_fails) do |format| 76 | format.html do 77 | set_default_flash :error, I18n.t('make_resourceful.create.fails', :default => "There was a problem!") 78 | render :action => :new, :status => 422 79 | end 80 | format.js 81 | end 82 | 83 | response_for(:update) do |format| 84 | format.html do 85 | set_default_flash :notice, I18n.t('make_resourceful.update.success', :default => "Save successful!") 86 | set_default_redirect object_path 87 | end 88 | format.js 89 | end 90 | 91 | response_for(:update_fails) do |format| 92 | format.html do 93 | set_default_flash :error, I18n.t('make_resourceful.update.fails', :default => "There was a problem saving!") 94 | render :action => :edit, :status => 422 95 | end 96 | format.js 97 | end 98 | 99 | response_for(:destroy) do |format| 100 | format.html do 101 | set_default_flash :notice, I18n.t('make_resourceful.destroy.success', :default => "Record deleted!") 102 | set_default_redirect objects_path 103 | end 104 | format.js 105 | end 106 | 107 | response_for(:destroy_fails) do |format| 108 | format.html do 109 | set_default_flash :error, I18n.t('make_resourceful.destroy.fails', :default => "There was a problem deleting!") 110 | set_default_redirect :back, :status => :failure 111 | end 112 | format.js 113 | end 114 | end 115 | end 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /lib/resourceful/default/urls.rb: -------------------------------------------------------------------------------- 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 params[:person_id] == 42, 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 params[:person_id] == 42, 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 params[:person_id] == 42, 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 admin_content_page_path(current_object). 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 137 | -------------------------------------------------------------------------------- /lib/resourceful/generators/resourceful_scaffold/resourceful_scaffold_generator.rb: -------------------------------------------------------------------------------- 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 88 | -------------------------------------------------------------------------------- /lib/resourceful/generators/resourceful_scaffold/templates/controller.rb: -------------------------------------------------------------------------------- 1 | class <%= controller_class_name %>Controller < ApplicationController 2 | make_resourceful do 3 | actions :all 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/resourceful/generators/resourceful_scaffold/templates/fixtures.yml: -------------------------------------------------------------------------------- 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 -%> 11 | -------------------------------------------------------------------------------- /lib/resourceful/generators/resourceful_scaffold/templates/functional_test.rb: -------------------------------------------------------------------------------- 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 51 | -------------------------------------------------------------------------------- /lib/resourceful/generators/resourceful_scaffold/templates/helper.rb: -------------------------------------------------------------------------------- 1 | module <%= controller_class_name %>Helper 2 | end 3 | -------------------------------------------------------------------------------- /lib/resourceful/generators/resourceful_scaffold/templates/migration.rb: -------------------------------------------------------------------------------- 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 14 | -------------------------------------------------------------------------------- /lib/resourceful/generators/resourceful_scaffold/templates/model.rb: -------------------------------------------------------------------------------- 1 | class <%= class_name %> < ActiveRecord::Base 2 | end 3 | -------------------------------------------------------------------------------- /lib/resourceful/generators/resourceful_scaffold/templates/unit_test.rb: -------------------------------------------------------------------------------- 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 8 | -------------------------------------------------------------------------------- /lib/resourceful/generators/resourceful_scaffold/templates/view__form.haml: -------------------------------------------------------------------------------- 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 -%> 6 | -------------------------------------------------------------------------------- /lib/resourceful/generators/resourceful_scaffold/templates/view_edit.haml: -------------------------------------------------------------------------------- 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 12 | -------------------------------------------------------------------------------- /lib/resourceful/generators/resourceful_scaffold/templates/view_index.haml: -------------------------------------------------------------------------------- 1 | %h1 Listing <%= plural_name %> 2 | 3 | = render :partial => '<%= singular_name %>', :collection => current_objects 4 | 5 | = link_to 'New <%= singular_name %>', new_object_path 6 | -------------------------------------------------------------------------------- /lib/resourceful/generators/resourceful_scaffold/templates/view_new.haml: -------------------------------------------------------------------------------- 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 10 | -------------------------------------------------------------------------------- /lib/resourceful/generators/resourceful_scaffold/templates/view_partial.haml: -------------------------------------------------------------------------------- 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 13 | -------------------------------------------------------------------------------- /lib/resourceful/generators/resourceful_scaffold/templates/view_show.haml: -------------------------------------------------------------------------------- 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 15 | -------------------------------------------------------------------------------- /lib/resourceful/maker.rb: -------------------------------------------------------------------------------- 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 :parent_controllers 15 | base.class_attribute :shallow_parent 16 | base.class_attribute :model_namespace 17 | base.class_attribute :made_resourceful 18 | base.class_attribute :permitted_params 19 | 20 | base.resourceful_callbacks = {} 21 | base.resourceful_responses = {} 22 | base.parent_controllers = [] 23 | base.model_namespace = nil 24 | base.made_resourceful = false 25 | end 26 | 27 | # :call-seq: 28 | # make_resourceful(options = {}) { ... } 29 | # 30 | # This is the central method, and namesake, of make_resourceful. 31 | # It takes a block and evaluates it in the context of a Builder, 32 | # allowing the controller to be customized extensively. 33 | # 34 | # See Resourceful::Builder for documentation on the methods available 35 | # in the context of the block. 36 | # 37 | # The only option currently available is :include. 38 | # It takes an object that responds to to_proc 39 | # (or an array of such objects) 40 | # and evaluates that proc in the same context as the block. 41 | # For example: 42 | # 43 | # make_resourceful :include => proc { actions :all } do 44 | # before :show do 45 | # current_object.current_user = current_user 46 | # end 47 | # end 48 | # 49 | # This is the same as: 50 | # 51 | # make_resourceful do 52 | # actions :all 53 | # before :show do 54 | # current_object.current_user = current_user 55 | # end 56 | # end 57 | # 58 | def make_resourceful(options = {}, &block) 59 | # :stopdoc: 60 | include Resourceful::Base 61 | # :startdoc: 62 | 63 | builder = Resourceful::Builder.new(self) 64 | unless builder.inherited? 65 | Resourceful::Base.made_resourceful.each { |proc| builder.instance_eval(&proc) } 66 | end 67 | Array(options[:include]).each { |proc| builder.instance_eval(&proc) } 68 | builder.instance_eval(&block) 69 | 70 | builder.apply 71 | 72 | add_helpers 73 | end 74 | 75 | # Returns whether or not make_resourceful has been called 76 | # on this controller or any controllers it inherits from. 77 | def made_resourceful? 78 | self.class.made_resourceful 79 | end 80 | 81 | private 82 | 83 | def add_helpers 84 | helper_method(:object_path, :objects_path, :new_object_path, :edit_object_path, 85 | :object_url, :objects_url, :new_object_url, :edit_object_url, 86 | :parent_path, :parent_url, 87 | :nested_object_path, :nested_object_url, 88 | :current_objects, :current_object, :current_model, :current_model_name, 89 | :namespaces, :instance_variable_name, :parent_names, :parent_name, 90 | :parent?, :parent_model, :parent_object, :save_succeeded?) 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /lib/resourceful/response.rb: -------------------------------------------------------------------------------- 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, #], [:xml, #], [:js, #]] 14 | # 15 | # Note that the :js response is the empty proc - 16 | # the same as proc {}. 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 34 | -------------------------------------------------------------------------------- /lib/resourceful/serialize.rb: -------------------------------------------------------------------------------- 1 | require 'resourceful/builder' 2 | 3 | module Resourceful 4 | # This module contains mixin modules 5 | # used to implement the object serialization 6 | # used for the Builder#publish method. 7 | # They can also be used to get serialized representations of objects 8 | # in other contexts. 9 | # 10 | # Serialization makes use of duck typing. 11 | # Each class that can be serialized 12 | # (just Array and ActiveRecord::Base by default) 13 | # implements the +serialize+ and +to_serializable+ methods. 14 | # These methods are implemented differently by the different classes, 15 | # but the semantics of the implementations are consistent, 16 | # so they can be used consistently. 17 | # 18 | # +to_serializable+ returns an object that can be directly serialized 19 | # with a call to +to_xml+, +to_yaml+, or +to_json+. 20 | # This object is either a hash or an array, 21 | # and all the elements are either values, like strings and integers, 22 | # or other serializable objects. 23 | # This is useful for getting a model into a simple data structure format. 24 | # The +attributes+ argument uses the same semantics 25 | # as the :attributes option for Builder#publish. 26 | # For example: 27 | # 28 | # c = Cake.new(:flavor => 'chocolate', :text => 'Happy birthday, Chris!') 29 | # c.recipient = User.new(:name => 'Chris', :password => 'not very secure') 30 | # c.to_serializable [ 31 | # :flavor, :text, 32 | # :recipient => :name 33 | # ] 34 | # 35 | # This would return the Ruby hash 36 | # 37 | # { :flavor => 'chocolate', :text => 'Happy birthday, Chris!', 38 | # :user => {:name => 'Chris'} } 39 | # 40 | # +serialize+ takes a format (:xml, :yaml, or :json - see New Formats below) 41 | # and a hash of options. 42 | # The only option currently recognized is :attributes, 43 | # which has the same semantics 44 | # as the :attributes option for Builder#publish. 45 | # +serialize+ returns a string containing the target 46 | # serialized in the given format. 47 | # For example: 48 | # 49 | # c = CandyBag.new(:title => 'jellybag') 50 | # c.candies << Candy.new(:type => 'jellybean', :flavor => 'root beer') 51 | # c.candies << Candy.new(:type => 'jellybean', :flavor => 'pear') 52 | # c.candies << Candy.new(:type => 'licorice', :flavor => 'anisey') 53 | # c.serialize :xml, :attributes => [:title, {:candies => [:type, :flavor]}] 54 | # 55 | # This would return a Ruby string containing 56 | # 57 | # 58 | # 59 | # jellybag 60 | # 61 | # 62 | # jellybean 63 | # root beer 64 | # 65 | # 66 | # jellybean 67 | # pear 68 | # 69 | # 70 | # licorice 71 | # anisey 72 | # 73 | # 74 | # 75 | # 76 | module Serialize 77 | 78 | # Takes an attributes option in the form passed to Builder#publish 79 | # and returns a hash (or nil, if attributes is nil) 80 | # containing the same data, 81 | # but in a more consistent format. 82 | # All keys are converted to symbols, 83 | # and all lists are converted to hashes. 84 | # For example: 85 | # 86 | # Resourceful::Serialize.normalize_attributes([:foo, :bar, {"baz" => ["boom"]}]) 87 | # #=> {"baz"=>["boom"], :foo=>nil, :bar=>nil} 88 | # 89 | def self.normalize_attributes(attributes) # :nodoc: 90 | return nil if attributes.nil? 91 | return {attributes.to_sym => nil} if String === attributes 92 | return {attributes => nil} if !attributes.respond_to?(:inject) 93 | 94 | attributes.inject({}) do |hash, attr| 95 | if Array === attr 96 | hash[attr[0]] = attr[1] 97 | hash 98 | else 99 | hash.merge normalize_attributes(attr) 100 | end 101 | end 102 | end 103 | 104 | # This module contains the definitions of +serialize+ and +to_serializable+ 105 | # that are included in ActiveRecord::Base. 106 | module Model 107 | # :call-seq: 108 | # serialize format, options = {}, :attributes => [ ... ] 109 | # 110 | # See the module documentation for Serialize for details. 111 | def serialize(format, options) 112 | raise "Must specify :attributes option" unless options[:attributes] 113 | hash = self.to_serializable(options[:attributes]) 114 | root = self.class.to_s.underscore 115 | if format == :xml 116 | hash.send("to_#{format}", :root => root) 117 | else 118 | {root => hash}.send("to_#{format}") 119 | end 120 | end 121 | 122 | # See the module documentation for Serialize for details. 123 | def to_serializable(attributes) 124 | raise "Must specify attributes for #{self.inspect}.to_serializable" if attributes.nil? 125 | 126 | Serialize.normalize_attributes(attributes).inject({}) do |hash, (key, value)| 127 | hash[key.to_s] = attr_hash_value(self.send(key), value) 128 | hash 129 | end 130 | end 131 | 132 | private 133 | 134 | # Given an attribute value 135 | # and a normalized (see above) attribute hash, 136 | # returns the serializable form of that attribute. 137 | def attr_hash_value(attr, sub_attributes) 138 | if attr.respond_to?(:to_serializable) 139 | attr.to_serializable(sub_attributes) 140 | else 141 | attr 142 | end 143 | end 144 | end 145 | 146 | # This module contains the definitions of +serialize+ and +to_serializable+ 147 | # that are included in ActiveRecord::Base. 148 | module Array 149 | # :call-seq: 150 | # serialize format, options = {}, :attributes => [ ... ] 151 | # 152 | # See the module documentation for Serialize for details. 153 | def serialize(format, options) 154 | raise "Not all elements respond to to_serializable" unless all? { |e| e.respond_to? :to_serializable } 155 | raise "Must specify :attributes option" unless options[:attributes] 156 | 157 | serialized = map { |e| e.to_serializable(options[:attributes]) } 158 | root = first.class.to_s.pluralize.underscore 159 | 160 | if format == :xml 161 | serialized.send("to_#{format}", :root => root) 162 | else 163 | {root => serialized}.send("to_#{format}") 164 | end 165 | end 166 | 167 | # See the module documentation for Serialize for details. 168 | def to_serializable(attributes) 169 | if first.respond_to?(:to_serializable) 170 | attributes = Serialize.normalize_attributes(attributes) 171 | map { |e| e.to_serializable(attributes) } 172 | else 173 | self 174 | end 175 | end 176 | end 177 | end 178 | end 179 | 180 | if defined? ActiveModel 181 | class ActiveModel::Base; include Resourceful::Serialize::Model; end 182 | else 183 | class ActiveRecord::Base; include Resourceful::Serialize::Model; end 184 | end 185 | class Array; include Resourceful::Serialize::Array; end 186 | -------------------------------------------------------------------------------- /make_resourceful.gemspec: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | HAML_GEMSPEC = Gem::Specification.new do |spec| 4 | spec.name = 'make_resourceful' 5 | spec.summary = "An elegant, structured way to build ActionPack Controllers" 6 | spec.version = File.read(File.dirname(__FILE__) + '/VERSION').strip 7 | spec.authors = ['Hampton Catlin'] 8 | spec.email = 'hcatlin@gmail.com' 9 | spec.description = <<-END 10 | Take back control of your Controllers. Make them awesome. Make them sleek. Make them resourceful. 11 | END 12 | 13 | spec.executables = [] 14 | spec.files = Dir['lib/**/*', 'Rakefile', "Readme.rdoc", "VERSION"] 15 | spec.homepage = 'http://github.com/hcatlin/make_resourceful' 16 | spec.test_files = Dir['spec/**/*_spec.rb'] 17 | end 18 | -------------------------------------------------------------------------------- /spec/accessors_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper' 2 | 3 | describe Resourceful::Default::Accessors, "#current_objects" do 4 | include ControllerMocks 5 | before :each do 6 | mock_controller Resourceful::Default::Accessors 7 | @objects = stub_list 5, 'object' 8 | @model = stub 9 | @controller.stubs(:current_model).returns(@model) 10 | end 11 | 12 | it "should look up all objects in the current model" do 13 | @model.expects(:find).with(:all).returns(@objects) 14 | @controller.current_objects.should == @objects 15 | end 16 | 17 | it "should cache the result, so subsequent calls won't run multiple queries" do 18 | @model.expects(:find).once.returns(@objects) 19 | @controller.current_objects 20 | @controller.current_objects 21 | end 22 | 23 | it "shouldn't run a query if @current_objects is set" do 24 | @controller.instance_variable_set('@current_objects', @objects) 25 | @model.expects(:find).never 26 | @controller.current_objects.should == @objects 27 | end 28 | end 29 | 30 | describe Resourceful::Default::Accessors, "#load_objects" do 31 | include ControllerMocks 32 | before :each do 33 | mock_controller Resourceful::Default::Accessors 34 | @objects = stub_list 5, 'object' 35 | @controller.stubs(:current_objects).returns(@objects) 36 | @controller.stubs(:instance_variable_name).returns("posts") 37 | end 38 | 39 | it "should set the current instance variable to the object collection" do 40 | @controller.load_objects 41 | @controller.instance_variable_get('@posts').should == @objects 42 | end 43 | end 44 | 45 | describe Resourceful::Default::Accessors, "#current_object on a plural controller" do 46 | include ControllerMocks 47 | before :each do 48 | mock_controller Resourceful::Default::Accessors 49 | @controller.stubs(:plural?).returns(true) 50 | @controller.stubs(:params).returns(:id => "12") 51 | 52 | @object = stub 53 | @model = stub 54 | @controller.stubs(:current_model).returns(@model) 55 | end 56 | 57 | it "should look up the object specified by the :id parameter in the current model" do 58 | @model.expects(:find).with('12').returns(@object) 59 | @controller.current_object.should == @object 60 | end 61 | 62 | it "should cache the result, so subsequent calls won't run multiple queries" do 63 | @model.expects(:find).once.returns(@object) 64 | @controller.current_object 65 | @controller.current_object 66 | end 67 | 68 | it "shouldn't run a query if @current_object is set" do 69 | @controller.instance_variable_set('@current_object', @object) 70 | @model.expects(:find).never 71 | @controller.current_object.should == @object 72 | end 73 | end 74 | 75 | describe Resourceful::Default::Accessors, "#current_object on a singular controller" do 76 | include ControllerMocks 77 | before :each do 78 | mock_controller Resourceful::Default::Accessors 79 | @controller.stubs(:plural?).returns(false) 80 | @controller.stubs(:controller_name).returns("posts") 81 | 82 | @parent = stub('parent') 83 | @controller.stubs(:parent_object).returns(@parent) 84 | @controller.stubs(:parent?).returns(true) 85 | 86 | @object = stub 87 | end 88 | 89 | it "should return the instance object from parent object" do 90 | @parent.expects(:post).returns(@object) 91 | @controller.current_object.should == @object 92 | end 93 | end 94 | 95 | describe Resourceful::Default::Accessors, "#load_object" do 96 | include ControllerMocks 97 | before :each do 98 | mock_controller Resourceful::Default::Accessors 99 | @object = stub 100 | @controller.stubs(:current_object).returns(@object) 101 | @controller.stubs(:instance_variable_name).returns("posts") 102 | end 103 | 104 | it "should set the current singular instance variable to the current object" do 105 | @controller.load_object 106 | @controller.instance_variable_get('@post').should == @object 107 | end 108 | end 109 | 110 | describe Resourceful::Default::Accessors, "#build_object with a #build-able model" do 111 | include ControllerMocks 112 | before :each do 113 | mock_controller Resourceful::Default::Accessors 114 | @params = {:name => "Bob", :password => "hideously insecure"} 115 | @controller.stubs(:object_parameters).returns(@params) 116 | 117 | @object = stub 118 | @model = stub 119 | @controller.stubs(:current_model).returns(@model) 120 | 121 | @model.stubs(:build).returns(@object) 122 | end 123 | 124 | it "should return a new object built with current_model from the object parameters" do 125 | @model.expects(:build).with(@params).returns(@object) 126 | @controller.build_object.should == @object 127 | end 128 | 129 | it "should make current_object return the newly built object" do 130 | @controller.build_object 131 | @controller.current_object.should == @object 132 | end 133 | end 134 | 135 | describe Resourceful::Default::Accessors, "#build_object with a non-#build-able model" do 136 | include ControllerMocks 137 | before :each do 138 | mock_controller Resourceful::Default::Accessors 139 | @params = {:name => "Bob", :password => "hideously insecure"} 140 | @controller.stubs(:object_parameters).returns(@params) 141 | 142 | @controller.stubs(:singular?).returns(false) 143 | @controller.stubs(:parent?).returns(false) 144 | 145 | @object = stub 146 | @model = stub 147 | @controller.stubs(:current_model).returns(@model) 148 | 149 | @model.stubs(:new).returns(@object) 150 | end 151 | 152 | it "should return a new instance of the current_model built with the object parameters" do 153 | @model.expects(:new).with(@params).returns(@object) 154 | @controller.build_object.should == @object 155 | end 156 | end 157 | 158 | describe Resourceful::Default::Accessors, "#current_model_name" do 159 | include ControllerMocks 160 | before :each do 161 | mock_controller Resourceful::Default::Accessors 162 | @controller.stubs(:controller_name).returns("funky_posts") 163 | end 164 | 165 | it "should return the controller's name, singularized and camel-cased" do 166 | @controller.current_model_name.should == "FunkyPost" 167 | end 168 | end 169 | 170 | describe Resourceful::Default::Accessors, "#namespaces" do 171 | include ControllerMocks 172 | before :each do 173 | mock_controller Resourceful::Default::Accessors 174 | @kontroller.stubs(:name).returns("FunkyStuff::Admin::Posts") 175 | end 176 | 177 | it "should return an array of underscored symbols representing the namespaces of the controller class" do 178 | @controller.namespaces.should == [:funky_stuff, :admin] 179 | end 180 | 181 | it "should cache the result, so subsequent calls won't run multiple computations" do 182 | @kontroller.expects(:name).once.returns("Posts") 183 | @controller.namespaces 184 | @controller.namespaces 185 | end 186 | end 187 | 188 | describe Resourceful::Default::Accessors, "#instance_variable_name" do 189 | include ControllerMocks 190 | before :each do 191 | mock_controller Resourceful::Default::Accessors 192 | @controller.stubs(:controller_name).returns("posts") 193 | end 194 | 195 | it "should return controller_name" do 196 | @controller.instance_variable_name == "posts" 197 | end 198 | end 199 | 200 | describe Resourceful::Default::Accessors, "#current_model for a singular controller" do 201 | include ControllerMocks 202 | before :each do 203 | mock_controller Resourceful::Default::Accessors 204 | stub_const :Post 205 | @controller.stubs(:singular?).returns(true) 206 | @controller.stubs(:current_model_name).returns("Post") 207 | 208 | @parent = stub('parent') 209 | @controller.stubs(:parent_object).returns(@parent) 210 | @controller.stubs(:parent?).returns(true) 211 | end 212 | 213 | it "should return the constant named by current_model_name" do 214 | @controller.current_model.should == Post 215 | end 216 | end 217 | 218 | describe Resourceful::Default::Accessors, "#current_model for a plural controller with no parent" do 219 | include ControllerMocks 220 | before :each do 221 | mock_controller Resourceful::Default::Accessors 222 | stub_const :Post 223 | @controller.stubs(:singular?).returns(false) 224 | @controller.stubs(:current_model_name).returns("Post") 225 | @controller.stubs(:parent?).returns(false) 226 | end 227 | 228 | it "should return the constant named by current_model_name" do 229 | @controller.current_model.should == Post 230 | end 231 | end 232 | 233 | describe Resourceful::Default::Accessors, "#object_parameters" do 234 | include ControllerMocks 235 | before :each do 236 | mock_controller Resourceful::Default::Accessors 237 | @params = {"crazy_user" => {:name => "Hampton", :location => "Canada"}} 238 | @controller.stubs(:params).returns(@params) 239 | @controller.stubs(:current_model_name).returns("CrazyUser") 240 | end 241 | 242 | it "should return the element of the params hash with the name of the model" do 243 | @controller.object_parameters.should == @params["crazy_user"] 244 | end 245 | end 246 | 247 | describe Resourceful::Default::Accessors, " with two parent classes set on the controller class and one parent parameter supplied" do 248 | include ControllerMocks 249 | before :each do 250 | mock_controller Resourceful::Default::Accessors 251 | @parents = %w{post comment} 252 | @models = @parents.map(&:camelize).map(&method(:stub_const)) 253 | @kontroller.parents = @parents 254 | @controller.stubs(:singular?).returns(false) 255 | @controller.stubs(:instance_variable_name).returns('lines') 256 | 257 | @params = HashWithIndifferentAccess.new :post_id => 12 258 | @controller.stubs(:params).returns(@params) 259 | 260 | @post = stub('Post') 261 | Post.stubs(:find).returns(@post) 262 | 263 | @model = stub 264 | end 265 | 266 | it "should return true for #parent?" do 267 | @controller.parent?.should be_true 268 | end 269 | 270 | it "should return the string names of all the parents for #parent_names" do 271 | @controller.parent_names.should == @parents 272 | end 273 | 274 | it "should return the string name of the current parent for #parent_name" do 275 | @controller.parent_name.should == 'post' 276 | end 277 | 278 | it "should return the model class for #parent_model" do 279 | @controller.parent_model.should == Post 280 | end 281 | 282 | it "should return the parent object for #parent_object" do 283 | Post.expects(:find).with(12).returns(@post) 284 | @controller.parent_object.should == @post 285 | end 286 | 287 | it "should cache the value of #parent_object so multiple calls won't cause multiple queries" do 288 | Post.expects(:find).returns(@post).once 289 | @controller.parent_object 290 | @controller.parent_object 291 | end 292 | 293 | it "should bind the parent object its proper instance variable" do 294 | @controller.load_parent_object 295 | @controller.instance_variable_get('@post').should == @post 296 | end 297 | 298 | it "should return the parent-scoped model for #current_model" do 299 | @post.stubs(:lines).returns(@model) 300 | @controller.current_model.should == @model 301 | end 302 | 303 | it "should return true for #ensure_parent_exists" do 304 | @controller.expects(:render).never 305 | @controller.ensure_parent_exists.should be_true 306 | end 307 | end 308 | 309 | describe Resourceful::Default::Accessors, " with two parent classes set on the controller class but no parent parameter supplied" do 310 | include ControllerMocks 311 | before :each do 312 | mock_controller Resourceful::Default::Accessors 313 | @parents = %w{post comment} 314 | @models = @parents.map(&:camelize).map(&method(:stub_const)) 315 | @kontroller.parents = @parents 316 | @controller.stubs(:params).returns({}) 317 | @controller.stubs(:controller_name).returns('line') 318 | stub_const('Line') 319 | end 320 | 321 | it "should return false for #parent?" do 322 | @controller.parent?.should be_false 323 | end 324 | 325 | it "should return nil for #parent_name" do 326 | @controller.parent_name.should be_nil 327 | end 328 | 329 | it "should return the unscoped model for #current_model" do 330 | @controller.current_model.should == Line 331 | end 332 | 333 | it "should return false and render a 422 error for #ensure_parent_exists" do 334 | @controller.expects(:render).with(has_entry(:status, 422)) 335 | @controller.ensure_parent_exists.should be_false 336 | end 337 | end 338 | 339 | describe Resourceful::Default::Accessors, " with no parents" do 340 | include ControllerMocks 341 | before :each do 342 | mock_controller Resourceful::Default::Accessors 343 | @controller.stubs(:parents).returns([]) 344 | @controller.stubs(:current_model_name).returns('Line') 345 | @controller.stubs(:params).returns({}) 346 | stub_const 'Line' 347 | end 348 | 349 | it "should return false for #parent?" do 350 | @controller.parent?.should be_false 351 | end 352 | 353 | it "should return nil for #parent_name" do 354 | @controller.parent_name.should be_nil 355 | end 356 | 357 | it "should return the unscoped model for #current_model" do 358 | @controller.current_model.should == Line 359 | end 360 | end 361 | 362 | describe Resourceful::Default::Accessors, " for a singular controller with a parent" do 363 | include ControllerMocks 364 | before :each do 365 | mock_controller Resourceful::Default::Accessors 366 | @controller.stubs(:singular?).returns(true) 367 | 368 | @model = stub_model('Thing') 369 | @model.send(:attr_accessor, :person_id) 370 | @controller.stubs(:current_model).returns(@model) 371 | 372 | @person = stub_model('Person') 373 | @person.stubs(:id).returns 42 374 | @controller.stubs(:parent_object).returns(@person) 375 | @controller.stubs(:parent_name).returns('person') 376 | @controller.stubs(:parent?).returns(true) 377 | 378 | @controller.stubs(:object_parameters).returns :thinginess => 12, :bacon => true 379 | end 380 | 381 | it "should set assign the parent's id to a newly built object" do 382 | thing = @controller.build_object 383 | thing.thinginess.should == 12 384 | thing.person_id.should == @person.id 385 | end 386 | end 387 | 388 | describe Resourceful::Default::Accessors, "#save_succeeded!" do 389 | include ControllerMocks 390 | before :each do 391 | mock_controller Resourceful::Default::Accessors 392 | @controller.save_succeeded! 393 | end 394 | 395 | it "should make #save_succeeded? return true" do 396 | @controller.save_succeeded?.should be_true 397 | end 398 | end 399 | 400 | describe Resourceful::Default::Accessors, "#save_failed!" do 401 | include ControllerMocks 402 | before :each do 403 | mock_controller Resourceful::Default::Accessors 404 | @controller.save_failed! 405 | end 406 | 407 | it "should make #save_succeeded? return false" do 408 | @controller.save_succeeded?.should be_false 409 | end 410 | end 411 | 412 | describe Resourceful::Default::Accessors, " for a plural action" do 413 | include ControllerMocks 414 | before :each do 415 | mock_controller Resourceful::Default::Accessors 416 | @controller.stubs(:params).returns :action => "index" 417 | end 418 | 419 | it "should know it's a plural action" do 420 | @controller.should be_a_plural_action 421 | end 422 | 423 | it "should know it's not a singular action" do 424 | @controller.should_not be_a_singular_action 425 | end 426 | end 427 | 428 | describe Resourceful::Default::Accessors, " for a singular action" do 429 | include ControllerMocks 430 | before :each do 431 | mock_controller Resourceful::Default::Accessors 432 | @controller.stubs(:params).returns :action => "show" 433 | end 434 | 435 | it "should know it's not a plural action" do 436 | @controller.should_not be_a_plural_action 437 | end 438 | 439 | it "should know it's a singular action" do 440 | @controller.should be_a_singular_action 441 | end 442 | end 443 | 444 | describe Resourceful::Default::Accessors, " for a singular controller" do 445 | include ControllerMocks 446 | before :each do 447 | mock_controller Resourceful::Default::Accessors 448 | @controller.stubs(:instance_variable_name).returns "post" 449 | end 450 | 451 | it "should know it's not plural" do 452 | @controller.should_not be_plural 453 | end 454 | 455 | it "should know it's singular" do 456 | @controller.should be_singular 457 | end 458 | end 459 | 460 | describe Resourceful::Default::Accessors, " for a plural controller" do 461 | include ControllerMocks 462 | before :each do 463 | mock_controller Resourceful::Default::Accessors 464 | @controller.stubs(:instance_variable_name).returns "posts" 465 | end 466 | 467 | it "should know it's plural" do 468 | @controller.should be_plural 469 | end 470 | 471 | it "should know it's not singular" do 472 | @controller.should_not be_singular 473 | end 474 | end 475 | -------------------------------------------------------------------------------- /spec/actions_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper' 2 | 3 | describe Resourceful::Default::Actions, " index action" do 4 | include ControllerMocks 5 | before :each do 6 | mock_controller Resourceful::Default::Actions 7 | [:load_objects, :before, :response_for].each(&@controller.method(:stubs)) 8 | end 9 | 10 | after(:each) { @controller.index } 11 | 12 | it "should load the object collection" do 13 | #@controller.expects(:load_objects) 14 | end 15 | 16 | it "should call the before :index callback" do 17 | @controller.expects(:before).with(:index) 18 | end 19 | 20 | it "should run the response for index" do 21 | @controller.expects(:response_for).with(:index) 22 | end 23 | end 24 | 25 | describe Resourceful::Default::Actions, " show action" do 26 | include ControllerMocks 27 | before :each do 28 | mock_controller Resourceful::Default::Actions 29 | [:load_object, :before, :response_for].each(&@controller.method(:stubs)) 30 | end 31 | 32 | after(:each) { @controller.show } 33 | 34 | it "should load the instance object" do 35 | #@controller.expects(:load_object) 36 | end 37 | 38 | it "should call the before :show callback" do 39 | @controller.expects(:before).with(:show) 40 | end 41 | 42 | it "should run the response for show" do 43 | @controller.expects(:response_for).with(:show) 44 | end 45 | 46 | it "should run the response for show failing if an exception is raised" do 47 | @controller.stubs(:response_for).with(:show).raises("Oh no!") 48 | @controller.expects(:response_for).with(:show_fails) 49 | end 50 | end 51 | 52 | describe Resourceful::Default::Actions, " successful create action" do 53 | include ControllerMocks 54 | before :each do 55 | mock_controller Resourceful::Default::Actions 56 | [:build_object, :load_object, :before, :after, 57 | :save_succeeded!, :response_for].each(&@controller.method(:stubs)) 58 | @object = stub :save => true 59 | @controller.stubs(:current_object).returns(@object) 60 | end 61 | 62 | after(:each) { @controller.create } 63 | 64 | it "should build the object from the POSTed parameters" do 65 | @controller.expects(:build_object) 66 | end 67 | 68 | it "should load the instance object" do 69 | @controller.expects(:load_object) 70 | end 71 | 72 | it "should call the before :create callback" do 73 | @controller.expects(:before).with(:create) 74 | end 75 | 76 | it "should try to save the object" do 77 | @object.expects(:save).returns(true) 78 | end 79 | 80 | it "should record the successful save" do 81 | @controller.expects(:save_succeeded!) 82 | end 83 | 84 | it "should call the after :create callback" do 85 | @controller.expects(:after).with(:create) 86 | end 87 | 88 | it "should run the response for create" do 89 | @controller.expects(:response_for).with(:create) 90 | end 91 | end 92 | 93 | describe Resourceful::Default::Actions, " unsuccessful create action" do 94 | include ControllerMocks 95 | before :each do 96 | mock_controller Resourceful::Default::Actions 97 | [:build_object, :load_object, :before, :after, 98 | :save_failed!, :response_for].each(&@controller.method(:stubs)) 99 | @object = stub :save => false 100 | @controller.stubs(:current_object).returns(@object) 101 | end 102 | 103 | after(:each) { @controller.create } 104 | 105 | it "should record the unsuccessful save" do 106 | @controller.expects(:save_failed!) 107 | end 108 | 109 | it "should call the after :create_fails callback" do 110 | @controller.expects(:after).with(:create_fails) 111 | end 112 | 113 | it "should run the response for create failing" do 114 | @controller.expects(:response_for).with(:create_fails) 115 | end 116 | end 117 | 118 | describe Resourceful::Default::Actions, " successful update action" do 119 | include ControllerMocks 120 | before :each do 121 | mock_controller Resourceful::Default::Actions 122 | [:load_object, :before, :after, :object_parameters, 123 | :save_succeeded!, :response_for].each(&@controller.method(:stubs)) 124 | @object = stub :update_attributes => true 125 | @controller.stubs(:current_object).returns(@object) 126 | end 127 | 128 | after(:each) { @controller.update } 129 | 130 | it "should load the instance object" do 131 | #@controller.expects(:load_object) 132 | end 133 | 134 | it "should call the before :update callback" do 135 | @controller.expects(:before).with(:update) 136 | end 137 | 138 | it "should try to update the object with the POSTed attributes" do 139 | @controller.expects(:object_parameters).returns(:params => "stuff") 140 | @object.expects(:update_attributes).with(:params => "stuff").returns(true) 141 | end 142 | 143 | it "should record the successful save" do 144 | @controller.expects(:save_succeeded!) 145 | end 146 | 147 | it "should call the after :update callback" do 148 | @controller.expects(:after).with(:update) 149 | end 150 | 151 | it "should run the response for update" do 152 | @controller.expects(:response_for).with(:update) 153 | end 154 | end 155 | 156 | describe Resourceful::Default::Actions, " unsuccessful update action" do 157 | include ControllerMocks 158 | before :each do 159 | mock_controller Resourceful::Default::Actions 160 | [:load_object, :before, :after, :object_parameters, 161 | :save_failed!, :response_for].each(&@controller.method(:stubs)) 162 | @object = stub :update_attributes => false 163 | @controller.stubs(:current_object).returns(@object) 164 | end 165 | 166 | after(:each) { @controller.update } 167 | 168 | it "should record the unsuccessful save" do 169 | @controller.expects(:save_failed!) 170 | end 171 | 172 | it "should call the after :update_fails callback" do 173 | @controller.expects(:after).with(:update_fails) 174 | end 175 | 176 | it "should run the response for update failing" do 177 | @controller.expects(:response_for).with(:update_fails) 178 | end 179 | end 180 | 181 | describe Resourceful::Default::Actions, " unsuccessful update action because of StaleObjectError" do 182 | include ControllerMocks 183 | before :each do 184 | mock_controller Resourceful::Default::Actions 185 | [:load_object, :before, :after, :object_parameters, 186 | :save_failed!, :response_for].each(&@controller.method(:stubs)) 187 | @object = stub_model("Thing") 188 | @object.stubs(:update_attributes).raises(ActiveRecord::StaleObjectError) 189 | @object.expects(:reload) 190 | @controller.stubs(:current_object).returns(@object) 191 | end 192 | 193 | after(:each) { @controller.update } 194 | 195 | it "should record the unsuccessful save" do 196 | @controller.expects(:save_failed!) 197 | end 198 | 199 | it "should call the after :update_fails callback" do 200 | @controller.expects(:after).with(:update_fails) 201 | end 202 | 203 | it "should run the response for update failing" do 204 | @controller.expects(:response_for).with(:update_fails) 205 | end 206 | end 207 | 208 | describe Resourceful::Default::Actions, " new action" do 209 | include ControllerMocks 210 | before :each do 211 | mock_controller Resourceful::Default::Actions 212 | [:build_object, :load_object, 213 | :before, :response_for].each(&@controller.method(:stubs)) 214 | end 215 | 216 | after(:each) { @controller.new } 217 | 218 | it "should build the object from the POSTed parameters" do 219 | @controller.expects(:build_object) 220 | end 221 | 222 | it "should load the instance object" do 223 | @controller.expects(:load_object) 224 | end 225 | 226 | it "should call the before :new callback" do 227 | @controller.expects(:before).with(:new) 228 | end 229 | 230 | it "should run the response for new" do 231 | @controller.expects(:response_for).with(:new) 232 | end 233 | end 234 | 235 | describe Resourceful::Default::Actions, " edit action" do 236 | include ControllerMocks 237 | before :each do 238 | mock_controller Resourceful::Default::Actions 239 | [:load_object, :before, :response_for].each(&@controller.method(:stubs)) 240 | end 241 | 242 | after(:each) { @controller.edit } 243 | 244 | it "should load the instance object" do 245 | #@controller.expects(:load_object) 246 | end 247 | 248 | it "should call the before :edit callback" do 249 | @controller.expects(:before).with(:edit) 250 | end 251 | 252 | it "should run the response for edit" do 253 | @controller.expects(:response_for).with(:edit) 254 | end 255 | end 256 | 257 | describe Resourceful::Default::Actions, " successful destroy action" do 258 | include ControllerMocks 259 | before :each do 260 | mock_controller Resourceful::Default::Actions 261 | [:load_object, :before, 262 | :after, :response_for].each(&@controller.method(:stubs)) 263 | @object = stub :destroy => true 264 | @controller.stubs(:current_object).returns(@object) 265 | end 266 | 267 | after(:each) { @controller.destroy } 268 | 269 | it "should load the instance object" do 270 | #@controller.expects(:load_object) 271 | end 272 | 273 | it "should call the before :destroy callback" do 274 | @controller.expects(:before).with(:destroy) 275 | end 276 | 277 | it "should try to destroy the object" do 278 | @object.expects(:destroy).returns(true) 279 | end 280 | 281 | it "should call the after :destroy callback" do 282 | @controller.expects(:after).with(:destroy) 283 | end 284 | 285 | it "should run the response for destroy" do 286 | @controller.expects(:response_for).with(:destroy) 287 | end 288 | end 289 | 290 | describe Resourceful::Default::Actions, " unsuccessful destroy action" do 291 | include ControllerMocks 292 | before :each do 293 | mock_controller Resourceful::Default::Actions 294 | [:load_object, :before, 295 | :after, :response_for].each(&@controller.method(:stubs)) 296 | @object = stub :destroy => false 297 | @controller.stubs(:current_object).returns(@object) 298 | end 299 | 300 | after(:each) { @controller.destroy } 301 | 302 | it "should call the after :destroy_fails callback" do 303 | @controller.expects(:after).with(:destroy_fails) 304 | end 305 | 306 | it "should run the response for destroy failing" do 307 | @controller.expects(:response_for).with(:destroy_fails) 308 | end 309 | end 310 | 311 | -------------------------------------------------------------------------------- /spec/base_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper' 2 | 3 | describe Resourceful::Base, ".made_resourceful" do 4 | before(:all) { @original_blocks = Resourceful::Base.made_resourceful.dup } 5 | before(:each) { Resourceful::Base.made_resourceful.replace [] } 6 | after(:all) { Resourceful::Base.made_resourceful.replace @original_blocks } 7 | 8 | it "should store blocks when called with blocks and return them when called without a block" do 9 | 5.times { Resourceful::Base.made_resourceful(&should_be_called) } 10 | Resourceful::Base.made_resourceful.each(&:call) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/builder_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper' 2 | 3 | describe Resourceful::Builder, " applied without any modification" do 4 | include ControllerMocks 5 | before :each do 6 | mock_kontroller 7 | create_builder 8 | end 9 | 10 | it "should remove all resourceful actions" do 11 | @kontroller.expects(:send).with do |name, action_module| 12 | name == :include && (action_module.instance_methods & Resourceful::ACTIONS.map(&:to_s)).empty? 13 | end 14 | @builder.apply 15 | end 16 | 17 | it "shouldn't un-hide any actions" do 18 | @builder.apply 19 | @kontroller.hidden_actions.should == Resourceful::ACTIONS 20 | end 21 | 22 | it "shouldn't set any callbacks" do 23 | @builder.apply 24 | callbacks.should == {:before => {}, :after => {}} 25 | end 26 | 27 | it "shouldn't set any responses" do 28 | @builder.apply 29 | responses.should be_empty 30 | end 31 | 32 | it "shouldn't set any parents" do 33 | @builder.apply 34 | parents.should be_empty 35 | end 36 | 37 | it "should set the controller as made_resourceful" do 38 | @builder.apply 39 | @kontroller.made_resourceful.should be_true 40 | end 41 | 42 | it "should set load_parent_object as a before_action for no actions" do 43 | @kontroller.expects(:before_action).with(:load_parent_object, :only => []) 44 | @builder.apply 45 | end 46 | end 47 | 48 | describe Resourceful::Builder, " with some actions set" do 49 | include ControllerMocks 50 | before :each do 51 | mock_kontroller 52 | create_builder 53 | @actions = [:show, :index, :new, :create] 54 | @builder.actions *@actions 55 | end 56 | 57 | it "should include the given actions" do 58 | @kontroller.expects(:send).with do |name, action_module| 59 | name == :include && (action_module.instance_methods & Resourceful::ACTIONS.map(&:to_s)).sort == 60 | @actions.map(&:to_s).sort 61 | end 62 | @builder.apply 63 | end 64 | 65 | it "should un-hide the given actions" do 66 | @builder.apply 67 | (@kontroller.hidden_actions & @actions).should be_empty 68 | end 69 | 70 | it "should set load_parent_object as a before_action for the given actions" do 71 | @kontroller.expects(:before_action).with(:load_parent_object, :only => [:show, :index, :new, :create]) 72 | @builder.apply 73 | end 74 | end 75 | 76 | describe Resourceful::Builder, " with all actions set for a plural controller" do 77 | include ControllerMocks 78 | before :each do 79 | mock_kontroller 80 | @kontroller.class_eval { def plural?; true; end } 81 | create_builder 82 | @builder.actions :all 83 | end 84 | 85 | it "should include all actions" do 86 | @kontroller.expects(:send).with do |name, action_module| 87 | name == :include && (action_module.instance_methods & Resourceful::ACTIONS.map(&:to_s)).sort == 88 | Resourceful::ACTIONS.map(&:to_s).sort 89 | end 90 | @builder.apply 91 | end 92 | end 93 | 94 | describe Resourceful::Builder, " with all actions set for a singular controller" do 95 | include ControllerMocks 96 | before :each do 97 | mock_kontroller 98 | @kontroller.class_eval { def plural?; false; end } 99 | create_builder 100 | @builder.actions :all 101 | end 102 | 103 | it "should include all singular actions" do 104 | @kontroller.expects(:send).with do |name, action_module| 105 | name == :include && (action_module.instance_methods & Resourceful::ACTIONS.map(&:to_s)).sort == 106 | Resourceful::SINGULAR_ACTIONS.map(&:to_s).sort 107 | end 108 | @builder.apply 109 | end 110 | end 111 | 112 | describe Resourceful::Builder, " with several before and after callbacks set" do 113 | include ControllerMocks 114 | before :each do 115 | mock_kontroller 116 | create_builder 117 | @builder.before(:create, :update, 'destroy', &(should_be_called { times(3) })) 118 | @builder.after('index', &should_be_called) 119 | @builder.after(:update, &should_be_called) 120 | @builder.apply 121 | end 122 | 123 | it "should save the callbacks as the :resourceful_callbacks inheritable_attribute" do 124 | callbacks[:before][:create].each(&:call) 125 | callbacks[:before][:update].each(&:call) 126 | callbacks[:before][:destroy].each(&:call) 127 | callbacks[:after][:index].each(&:call) 128 | callbacks[:after][:update].each(&:call) 129 | end 130 | end 131 | 132 | describe Resourceful::Builder, " with chained before and after callbacks" do 133 | include ControllerMocks 134 | before :each do 135 | mock_kontroller 136 | create_builder 137 | @before_value = '' 138 | @builder.before(:index, &lambda { @before_value += 'A' }) 139 | @builder.before(:index, &lambda { @before_value += 'B' }) 140 | 141 | @after_value = '' 142 | @builder.after(:index, &lambda { @after_value += 'A' }) 143 | @builder.after(:index, &lambda { @after_value += 'B' }) 144 | @builder.apply 145 | end 146 | 147 | it "should save as array in the :resourceful_callbacks inheritable_attribute and execute in order" do 148 | callbacks[:before][:index].each { |callback| callback.call } 149 | @before_value.should == 'AB' 150 | callbacks[:after][:index].each { |callback| callback.call } 151 | @after_value.should == 'AB' 152 | end 153 | end 154 | 155 | describe Resourceful::Builder, " with responses set for several formats" do 156 | include ControllerMocks 157 | before :each do 158 | mock_kontroller 159 | create_builder 160 | @builder.response_for('create') do |f| 161 | f.html(&should_be_called) 162 | f.js(&should_be_called) 163 | f.yaml(&should_be_called) 164 | f.xml(&should_be_called) 165 | f.txt(&should_be_called) 166 | end 167 | @builder.response_for(:remove_failed, 'update') do |f| 168 | f.yaml(&(should_be_called { times(2) })) 169 | f.png(&(should_be_called { times(2) })) 170 | end 171 | @builder.apply 172 | end 173 | 174 | it "should save the responses as the :resourceful_responses inheritable_attribute" do 175 | responses[:create].map(&:first).should == [:html, :js, :yaml, :xml, :txt] 176 | responses[:create].map(&:last).each(&:call) 177 | 178 | responses[:remove_failed].map(&:first).should == [:yaml, :png] 179 | responses[:remove_failed].map(&:last).each(&:call) 180 | 181 | responses[:update].map(&:first).should == [:yaml, :png] 182 | responses[:update].map(&:last).each(&:call) 183 | end 184 | end 185 | 186 | describe Resourceful::Builder, " with a response set for the default format" do 187 | include ControllerMocks 188 | before :each do 189 | mock_kontroller 190 | create_builder 191 | @builder.response_for('index', &should_be_called) 192 | @builder.apply 193 | end 194 | 195 | it "should save the response as a response for HTML in the :resourceful_responses inheritable_attribute" do 196 | responses[:index].map(&:first).should == [:html] 197 | responses[:index].map(&:last).each(&:call) 198 | end 199 | end 200 | 201 | describe Resourceful::Builder, " with a response set for no actions" do 202 | include ControllerMocks 203 | before :each do 204 | mock_kontroller 205 | create_builder 206 | end 207 | 208 | it "should raise an error" do 209 | lambda { @builder.response_for {} }.should raise_error("Must specify one or more actions for response_for.") 210 | end 211 | end 212 | 213 | describe Resourceful::Builder, " publishing without an attributes hash" do 214 | include ControllerMocks 215 | before :each do 216 | mock_kontroller 217 | create_builder 218 | end 219 | 220 | it "should raise an error" do 221 | proc { @builder.publish :xml, :yaml }.should raise_error("Must specify :attributes option") 222 | end 223 | end 224 | 225 | describe Resourceful::Builder, " publishing several formats" do 226 | include ControllerMocks 227 | before :each do 228 | mock_kontroller 229 | create_builder 230 | 231 | @model = stub_model("Thing") 232 | @kontroller.stubs(:current_object).returns(@model) 233 | 234 | @models = (1..5).map { stub_model("Thing") } 235 | @kontroller.stubs(:current_objects).returns(@models) 236 | 237 | @builder.publish :yaml, :json, 'xml', :additional => 'option', :attributes => [:name, :stuff] 238 | @builder.apply 239 | end 240 | 241 | it "should add a list of types as responses for index and show" do 242 | responses[:index].map(&:first).should == [:yaml, :json, :xml] 243 | responses[:show].map(&:first).should == [:yaml, :json, :xml] 244 | end 245 | 246 | it "should respond by rendering the serialized model with the proper type, passing along un-recognized options" do 247 | @model.expects(:serialize).with(:yaml, :additional => 'option', :attributes => [:name, :stuff]).returns('serialized') 248 | @kontroller.expects(:render).with(:text => 'serialized') 249 | @kontroller.instance_eval(&responses[:index].find { |type, _| type == :yaml }[1]) 250 | end 251 | 252 | it "should respond render XML and JSON with the proper action" do 253 | @model.expects(:serialize).with(:xml, :additional => 'option', :attributes => [:name, :stuff]).returns('XML serialized') 254 | @model.expects(:serialize).with(:json, :additional => 'option', :attributes => [:name, :stuff]).returns('JSON serialized') 255 | @kontroller.expects(:render).with(:xml => 'XML serialized') 256 | @kontroller.expects(:render).with(:json => 'JSON serialized') 257 | 258 | @kontroller.instance_eval(&responses[:index].find { |type, _| type == :xml }[1]) 259 | @kontroller.instance_eval(&responses[:index].find { |type, _| type == :json }[1]) 260 | end 261 | 262 | it "should render current_objects if the action is plural" do 263 | @kontroller.stubs(:plural_action?).returns(true) 264 | @models.expects(:serialize).with(:yaml, :additional => 'option', :attributes => [:name, :stuff]).returns('serialized') 265 | @kontroller.expects(:render).with(:text => 'serialized') 266 | @kontroller.instance_eval(&responses[:index].find { |type, _| type == :yaml }[1]) 267 | end 268 | end 269 | 270 | describe Resourceful::Builder, " publishing only to #show" do 271 | include ControllerMocks 272 | before :each do 273 | mock_kontroller 274 | create_builder 275 | 276 | @model = stub_model("Thing") 277 | @kontroller.stubs(:current_object).returns(@model) 278 | 279 | @builder.publish :json, :yaml, :only => :show, :attributes => [:name, :stuff] 280 | @builder.apply 281 | end 282 | 283 | it "should add responses for show" do 284 | responses[:show].map(&:first).should == [:json, :yaml] 285 | end 286 | 287 | it "shouldn't add responses for index" do 288 | responses[:index].should be_nil 289 | end 290 | 291 | it "shouldn't pass the :only option to the serialize call" do 292 | @model.expects(:serialize).with(:yaml, :attributes => [:name, :stuff]) 293 | @kontroller.stubs(:render) 294 | @kontroller.instance_eval(&responses[:show].find { |type, _| type == :yaml }[1]) 295 | end 296 | end 297 | 298 | describe Resourceful::Builder, " publishing in addition to other responses" do 299 | include ControllerMocks 300 | before :each do 301 | mock_kontroller 302 | create_builder 303 | 304 | @builder.response_for(:index) {} 305 | @builder.publish :json, :yaml, :attributes => [:name, :stuff] 306 | @builder.response_for :show do |f| 307 | f.html {} 308 | f.js {} 309 | end 310 | @builder.apply 311 | end 312 | 313 | it "should add published responses in addition to pre-existing ones" do 314 | responses[:show].map(&:first).should == [:html, :js, :json, :yaml] 315 | responses[:index].map(&:first).should == [:html, :json, :yaml] 316 | end 317 | end 318 | 319 | describe Resourceful::Builder, " belonging to several parents" do 320 | include ControllerMocks 321 | before :each do 322 | mock_kontroller 323 | create_builder 324 | 325 | @builder.belongs_to :post, :blat, :stang 326 | @builder.apply 327 | end 328 | 329 | it "should save the parents as the :parents inheritable_attribute" do 330 | parents.should == ['post', 'blat', 'stang'] 331 | end 332 | end 333 | -------------------------------------------------------------------------------- /spec/callbacks_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper' 2 | 3 | describe Resourceful::Default::Callbacks, " with a few callbacks" do 4 | include ControllerMocks 5 | before :each do 6 | mock_controller Resourceful::Default::Callbacks 7 | end 8 | 9 | it "should fire the :before callback with the given name when #before is called" do 10 | callbacks[:before] = { :create => [ should_be_called ] } 11 | @controller.before(:create) 12 | end 13 | 14 | it "should fire the :after callback with the given name when #after is called" do 15 | callbacks[:after] = { :index => [ should_be_called ] } 16 | @controller.after("index") 17 | end 18 | end 19 | 20 | describe Resourceful::Default::Callbacks, " with a few responses" do 21 | include ControllerMocks 22 | before :each do 23 | mock_controller Resourceful::Default::Callbacks 24 | responses[:create_failed] = [[:html, nil], [:js, nil]] 25 | responses[:create] = [[:html, proc { "create html" }], [:xml, proc { @xml }]] 26 | @controller.instance_variable_set('@xml', 'create XML') 27 | @response = Resourceful::Response.new 28 | end 29 | 30 | it "should respond to each format with a call to the given block when #response_for is called" do 31 | @controller.expects(:respond_to).yields(@response) 32 | @controller.response_for(:create_failed) 33 | @response.formats[0][0].should == :html 34 | @response.formats[0][1].call.should be_nil 35 | 36 | @response.formats[1][0].should == :js 37 | @response.formats[1][1].call.should be_nil 38 | end 39 | 40 | it "should properly scope blocks when #response_for is called" do 41 | @controller.expects(:respond_to).yields(@response) 42 | @controller.response_for(:create) 43 | @response.formats[0][0].should == :html 44 | @response.formats[0][1].call.should == "create html" 45 | 46 | @response.formats[1][0].should == :xml 47 | 48 | # This value comes from the instance variable in @controller. 49 | # Having it be "create XML" ensures that the block was properly scoped. 50 | @response.formats[1][1].call.should == "create XML" 51 | end 52 | end 53 | 54 | describe Resourceful::Default::Callbacks, "#scope" do 55 | include ControllerMocks 56 | before(:each) { mock_controller Resourceful::Default::Callbacks } 57 | 58 | it "should re-bind the block to the controller's context" do 59 | block = proc { @var } 60 | @controller.instance_variable_set('@var', 'value') 61 | 62 | block.call.should == nil 63 | @controller.scope(block).call.should == 'value' 64 | end 65 | 66 | it "should make the block empty if it's passed in as nil" do 67 | @controller.scope(nil).call.should == nil 68 | end 69 | end 70 | 71 | 72 | -------------------------------------------------------------------------------- /spec/integration_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper' 2 | 3 | describe "ThingsController", "with all the resourceful actions" do 4 | 5 | before :each do 6 | mock_resourceful do 7 | actions :all 8 | end 9 | @objects = stub_list(5, 'Thing') do |t| 10 | [:destroy, :save, :update_attributes].each { |m| t.stubs(m).returns(true) } 11 | t.stubs(:to_param).returns('12') 12 | end 13 | @object = @objects.first 14 | Thing.stubs(:find).returns(@object) 15 | Thing.stubs(:new).returns(@object) 16 | end 17 | 18 | ## Default responses 19 | 20 | (Resourceful::ACTIONS - Resourceful::MODIFYING_ACTIONS).each(&method(:should_render_html)) 21 | Resourceful::ACTIONS.each(&method(:should_render_js)) 22 | Resourceful::ACTIONS.each(&method(:shouldnt_render_xml)) 23 | 24 | ## Specs for #index 25 | 26 | it "should find all records on GET /things" do 27 | Thing.expects(:find).with(:all).returns(@objects) 28 | get :index 29 | end 30 | 31 | it "should return a list of objects for #current_objects after GET /things" do 32 | Thing.stubs(:find).returns(@objects) 33 | get :index 34 | current_objects.should == @objects 35 | end 36 | 37 | it "should assign @things to a list of objects for GET /things" do 38 | Thing.stubs(:find).returns(@objects) 39 | get :index 40 | assigns(:things).should == @objects 41 | end 42 | 43 | ## Specs for #show 44 | 45 | it "should find the record with id 12 on GET /things/12" do 46 | Thing.expects(:find).with('12').returns(@object) 47 | get :show, :id => 12 48 | end 49 | 50 | it "should return an object for #current_object after GET /things/12" do 51 | Thing.stubs(:find).returns(@object) 52 | get :show, :id => 12 53 | current_object.should == @object 54 | end 55 | 56 | it "should assign @thing to an object for GET /things/12" do 57 | Thing.stubs(:find).returns(@object) 58 | get :show, :id => 12 59 | assigns(:thing).should == @object 60 | end 61 | 62 | ## Specs for #edit 63 | 64 | it "should find the record with id 12 on GET /things/12/edit" do 65 | Thing.expects(:find).with('12').returns(@object) 66 | get :edit, :id => 12 67 | end 68 | 69 | it "should return an object for #current_object after GET /things/12/edit" do 70 | Thing.stubs(:find).returns(@object) 71 | get :edit, :id => 12 72 | current_object.should == @object 73 | end 74 | 75 | it "should assign @thing to an object for GET /things/12/edit" do 76 | Thing.stubs(:find).returns(@object) 77 | get :edit, :id => 12 78 | assigns(:thing).should == @object 79 | end 80 | 81 | ## Specs for #new 82 | 83 | it "should create a new object from params[:thing] for GET /things/new" do 84 | Thing.expects(:new).with('name' => "Herbert the thing").returns(@object) 85 | get :new, :thing => {:name => "Herbert the thing"} 86 | end 87 | 88 | it "should create a new object even if there aren't any params for GET /things/new" do 89 | Thing.expects(:new).with(nil).returns(@object) 90 | get :new 91 | end 92 | 93 | it "should return the new object for #current_object after GET /things/new" do 94 | Thing.stubs(:new).returns(@object) 95 | get :new 96 | current_object.should == @object 97 | end 98 | 99 | it "should assign @thing to the new object for GET /things/new" do 100 | Thing.stubs(:new).returns(@object) 101 | get :new 102 | assigns(:thing).should == @object 103 | end 104 | 105 | ## Specs for #create 106 | 107 | it "should create a new object from params[:thing] for POST /things" do 108 | Thing.expects(:new).with('name' => "Herbert the thing").returns(@object) 109 | post :create, :thing => {:name => "Herbert the thing"} 110 | end 111 | 112 | it "should create a new object even if there aren't any params for POST /things" do 113 | Thing.expects(:new).with(nil).returns(@object) 114 | post :create 115 | end 116 | 117 | it "should return the new object for #current_object after POST /things" do 118 | Thing.stubs(:new).returns(@object) 119 | post :create 120 | current_object.should == @object 121 | end 122 | 123 | it "should assign @thing to the new object for POST /things" do 124 | Thing.stubs(:new).returns(@object) 125 | post :create 126 | assigns(:thing).should == @object 127 | end 128 | 129 | it "should save the new object for POST /things" do 130 | Thing.stubs(:new).returns(@object) 131 | @object.expects(:save) 132 | post :create 133 | end 134 | 135 | it "should set an appropriate flash notice for a successful POST /things" do 136 | Thing.stubs(:new).returns(@object) 137 | post :create 138 | flash[:notice].should == "Create successful!" 139 | end 140 | 141 | it "should redirect to the new object for a successful POST /things" do 142 | Thing.stubs(:new).returns(@object) 143 | post :create 144 | response.should redirect_to('/things/12') 145 | end 146 | 147 | it "should set an appropriate flash error for an unsuccessful POST /things" do 148 | Thing.stubs(:new).returns(@object) 149 | @object.stubs(:save).returns(false) 150 | post :create 151 | flash[:error].should == "There was a problem!" 152 | end 153 | 154 | it "should give a failing response for an unsuccessful POST /things" do 155 | Thing.stubs(:new).returns(@object) 156 | @object.stubs(:save).returns(false) 157 | post :create 158 | response.should_not be_success 159 | response.code.should == '422' 160 | end 161 | 162 | it "should render the #new template for an unsuccessful POST /things" do 163 | Thing.stubs(:new).returns(@object) 164 | @object.stubs(:save).returns(false) 165 | post :create 166 | response.body.should include('New object') 167 | end 168 | 169 | ## Specs for #update 170 | 171 | it "should find the record with id 12 on PUT /things/12" do 172 | Thing.expects(:find).with('12').returns(@object) 173 | put :update, :id => 12 174 | end 175 | 176 | it "should return an object for #current_object after PUT /things/12" do 177 | Thing.stubs(:find).returns(@object) 178 | put :update, :id => 12 179 | current_object.should == @object 180 | end 181 | 182 | it "should assign @thing to an object for PUT /things/12" do 183 | Thing.stubs(:find).returns(@object) 184 | put :update, :id => 12 185 | assigns(:thing).should == @object 186 | end 187 | 188 | it "should update the new object for PUT /things/12" do 189 | Thing.stubs(:find).returns(@object) 190 | @object.expects(:update_attributes).with('name' => "Jorje") 191 | put :update, :id => 12, :thing => {:name => "Jorje"} 192 | end 193 | 194 | it "should set an appropriate flash notice for a successful PUT /things/12" do 195 | Thing.stubs(:find).returns(@object) 196 | put :update, :id => 12 197 | flash[:notice].should == "Save successful!" 198 | end 199 | 200 | it "should redirect to the updated object for a successful PUT /things/12" do 201 | Thing.stubs(:find).returns(@object) 202 | put :update, :id => 12 203 | response.should redirect_to('/things/12') 204 | end 205 | 206 | it "should set an appropriate flash error for an unsuccessful PUT /things/12" do 207 | Thing.stubs(:find).returns(@object) 208 | @object.stubs(:update_attributes).returns(false) 209 | put :update, :id => 12 210 | flash[:error].should == "There was a problem saving!" 211 | end 212 | 213 | it "should give a failing response for an unsuccessful PUT /things/12" do 214 | Thing.stubs(:find).returns(@object) 215 | @object.stubs(:update_attributes).returns(false) 216 | put :update, :id => 12 217 | response.should_not be_success 218 | response.code.should == '422' 219 | end 220 | 221 | it "should render the #edit template for an unsuccessful PUT /things/12" do 222 | Thing.stubs(:find).returns(@object) 223 | @object.stubs(:update_attributes).returns(false) 224 | put :update, :id => 12 225 | response.body.should include('Editting object') 226 | end 227 | 228 | ## Specs for #destroy 229 | 230 | it "should find the record with id 12 on DELETE /things/12" do 231 | Thing.expects(:find).with('12').returns(@object) 232 | delete :destroy, :id => 12 233 | end 234 | 235 | it "should return an object for #current_object after DELETE /things/12" do 236 | Thing.stubs(:find).returns(@object) 237 | delete :destroy, :id => 12 238 | current_object.should == @object 239 | end 240 | 241 | it "should assign @thing to an object for DELETE /things/12" do 242 | Thing.stubs(:find).returns(@object) 243 | delete :destroy, :id => 12 244 | assigns(:thing).should == @object 245 | end 246 | 247 | it "should destroy the new object for DELETE /things/12" do 248 | Thing.stubs(:find).returns(@object) 249 | @object.expects(:destroy) 250 | delete :destroy, :id => 12 251 | end 252 | 253 | it "should set an appropriate flash notice for a successful DELETE /things/12" do 254 | Thing.stubs(:find).returns(@object) 255 | delete :destroy, :id => 12 256 | flash[:notice].should == "Record deleted!" 257 | end 258 | 259 | it "should redirect to the object list for a successful DELETE /things/12" do 260 | Thing.stubs(:find).returns(@object) 261 | delete :destroy, :id => 12 262 | response.should redirect_to('/things') 263 | end 264 | 265 | it "should set an appropriate flash error for an unsuccessful DELETE /things/12" do 266 | Thing.stubs(:find).returns(@object) 267 | @object.stubs(:destroy).returns(false) 268 | delete :destroy, :id => 12 269 | flash[:error].should == "There was a problem deleting!" 270 | end 271 | 272 | it "should give a failing response for an unsuccessful DELETE /things/12" do 273 | Thing.stubs(:find).returns(@object) 274 | @object.stubs(:destroy).returns(false) 275 | delete :destroy, :id => 12 276 | response.should_not be_success 277 | end 278 | 279 | it "should redirect to the previous page for an unsuccessful DELETE /things/12" do 280 | Thing.stubs(:find).returns(@object) 281 | @object.stubs(:destroy).returns(false) 282 | delete :destroy, :id => 12 283 | response.should redirect_to(:back) 284 | end 285 | end 286 | 287 | describe "ThingsController", "with several parent objects", :type => :integration do 288 | before :each do 289 | mock_resourceful do 290 | actions :all 291 | belongs_to :person, :category 292 | end 293 | stub_const 'Person' 294 | stub_const 'Category' 295 | 296 | @objects = stub_list(5, 'Thing') do |t| 297 | t.stubs(:save).returns(true) 298 | end 299 | @object = @objects.first 300 | @person = stub('Person') 301 | @category = stub('Category') 302 | @fake_model = stub('parent_object.things') 303 | end 304 | 305 | ## No parent ids 306 | 307 | it "should find all things on GET /things" do 308 | Thing.expects(:find).with(:all).returns(@objects) 309 | get :index 310 | current_objects.should == @objects 311 | end 312 | 313 | it "should find the thing with id 12 regardless of scoping on GET /things/12" do 314 | Thing.expects(:find).with('12').returns(@object) 315 | get :show, :id => 12 316 | current_object.should == @object 317 | end 318 | 319 | it "should create a new thing without a person on POST /things" do 320 | Thing.expects(:new).with('name' => "Lamp").returns(@object) 321 | post :create, :thing => {:name => "Lamp"} 322 | current_object.should == @object 323 | end 324 | 325 | ## Person ids 326 | 327 | it "should assign the proper parent variables and accessors to the person with id 4 for GET /people/4/things" do 328 | Person.stubs(:find).returns(@person) 329 | @person.stubs(:things).returns(@fake_model) 330 | @fake_model.stubs(:find).with(:all).returns(@objects) 331 | get :index, :person_id => 4 332 | controller.instance_eval("parent_object").should == @person 333 | assigns(:person).should == @person 334 | end 335 | 336 | it "should find all the things belonging to the person with id 4 on GET /people/4/things" do 337 | Person.expects(:find).with('4').returns(@person) 338 | @person.expects(:things).at_least_once.returns(@fake_model) 339 | @fake_model.expects(:find).with(:all).returns(@objects) 340 | get :index, :person_id => 4 341 | current_objects.should == @objects 342 | end 343 | 344 | it "should find the thing with id 12 if it belongs to the person with id 4 on GET /person/4/things/12" do 345 | Person.expects(:find).with('4').returns(@person) 346 | @person.expects(:things).at_least_once.returns(@fake_model) 347 | @fake_model.expects(:find).with('12').returns(@object) 348 | get :show, :person_id => 4, :id => 12 349 | current_object.should == @object 350 | end 351 | 352 | it "should create a new thing belonging to the person with id 4 on POST /person/4/things" do 353 | Person.expects(:find).with('4').returns(@person) 354 | @person.expects(:things).at_least_once.returns(@fake_model) 355 | @fake_model.expects(:build).with('name' => 'Lamp').returns(@object) 356 | post :create, :person_id => 4, :thing => {:name => "Lamp"} 357 | current_object.should == @object 358 | end 359 | 360 | ## Category ids 361 | 362 | it "should assign the proper parent variables and accessors to the category with id 4 for GET /people/4/things" do 363 | Category.stubs(:find).returns(@category) 364 | @category.stubs(:things).returns(@fake_model) 365 | @fake_model.stubs(:find).with(:all).returns(@objects) 366 | get :index, :category_id => 4 367 | controller.instance_eval("parent_object").should == @category 368 | assigns(:category).should == @category 369 | end 370 | 371 | it "should find all the things belonging to the category with id 4 on GET /people/4/things" do 372 | Category.expects(:find).with('4').returns(@category) 373 | @category.expects(:things).at_least_once.returns(@fake_model) 374 | @fake_model.expects(:find).with(:all).returns(@objects) 375 | get :index, :category_id => 4 376 | current_objects.should == @objects 377 | end 378 | 379 | it "should find the thing with id 12 if it belongs to the category with id 4 on GET /category/4/things/12" do 380 | Category.expects(:find).with('4').returns(@category) 381 | @category.expects(:things).at_least_once.returns(@fake_model) 382 | @fake_model.expects(:find).with('12').returns(@object) 383 | get :show, :category_id => 4, :id => 12 384 | current_object.should == @object 385 | end 386 | 387 | it "should create a new thing belonging to the category with id 4 on POST /category/4/things" do 388 | Category.expects(:find).with('4').returns(@category) 389 | @category.expects(:things).at_least_once.returns(@fake_model) 390 | @fake_model.expects(:build).with('name' => 'Lamp').returns(@object) 391 | post :create, :category_id => 4, :thing => {:name => "Lamp"} 392 | current_object.should == @object 393 | end 394 | end 395 | -------------------------------------------------------------------------------- /spec/maker_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper' 2 | 3 | describe Resourceful::Maker, "when extended" do 4 | include ControllerMocks 5 | before(:each) { mock_kontroller } 6 | 7 | it "should create an empty, inheritable callbacks hash" do 8 | @kontroller.resourceful_callbacks.should == {} 9 | end 10 | 11 | it "should create an empty, inheritable responses hash" do 12 | @kontroller.resourceful_responses.should == {} 13 | end 14 | 15 | it "should create an empty, inheritable parents array" do 16 | @kontroller.parents.should == [] 17 | end 18 | 19 | it "should create a made_resourceful variable set to false" do 20 | @kontroller.made_resourceful.should be_false 21 | end 22 | 23 | it "should create a made_resourceful? method on the controller that returns the variable" do 24 | @kontroller.should_not be_made_resourceful 25 | @kontroller.made_resourceful = true 26 | @kontroller.should be_made_resourceful 27 | end 28 | end 29 | 30 | describe Resourceful::Maker, "when made_resourceful" do 31 | include ControllerMocks 32 | before(:each) do 33 | mock_kontroller 34 | mock_builder 35 | end 36 | 37 | it "should include Resourceful::Base" do 38 | @kontroller.expects(:include).with(Resourceful::Base) 39 | @kontroller.make_resourceful {} 40 | end 41 | 42 | it "should use Resourceful::Builder to build the controller" do 43 | Resourceful::Builder.expects(:new).with(@kontroller).returns(@builder) 44 | @kontroller.make_resourceful {} 45 | end 46 | 47 | it "should evaluate the made_resourceful callbacks in the context of the builder" do 48 | procs = (1..5).map { should_be_called { with(@builder) } } 49 | Resourceful::Base.stubs(:made_resourceful).returns(procs) 50 | @kontroller.make_resourceful {} 51 | end 52 | 53 | it "should evaluate the :include callback in the context of the builder" do 54 | @kontroller.make_resourceful(:include => should_be_called { with(@builder) }) {} 55 | end 56 | 57 | it "should evaluate the given block in the context of the builder" do 58 | @kontroller.make_resourceful(&(should_be_called { with(@builder) })) 59 | end 60 | end 61 | 62 | describe Resourceful::Maker, "when made_resourceful with an inherited controller" do 63 | include ControllerMocks 64 | before(:each) do 65 | mock_kontroller 66 | mock_builder :inherited 67 | end 68 | 69 | it "should include Resourceful::Base" do 70 | @kontroller.expects(:include).with(Resourceful::Base) 71 | @kontroller.make_resourceful {} 72 | end 73 | 74 | it "should use Resourceful::Builder to build the controller" do 75 | Resourceful::Builder.expects(:new).with(@kontroller).returns(@builder) 76 | @kontroller.make_resourceful {} 77 | end 78 | 79 | it "should not evaluate the made_resourceful callbacks in the context of the builder" do 80 | Resourceful::Base.expects(:made_resourceful).never 81 | @kontroller.make_resourceful {} 82 | end 83 | 84 | it "should evaluate the :include callback in the context of the builder" do 85 | @kontroller.make_resourceful(:include => should_be_called { with(@builder) }) {} 86 | end 87 | 88 | it "should evaluate the given block in the context of the builder" do 89 | @kontroller.make_resourceful(&(should_be_called { with(@builder) })) 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /spec/response_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper' 2 | 3 | describe Resourceful::Response, "when first created" do 4 | before(:each) { @response = Resourceful::Response.new } 5 | 6 | it "should have an empty formats array" do 7 | @response.formats.should == [] 8 | end 9 | end 10 | 11 | describe Resourceful::Response, "with a few formats" do 12 | before :each do 13 | @response = Resourceful::Response.new 14 | @response.html 15 | @response.js {'javascript'} 16 | @response.xml {'xml'} 17 | end 18 | 19 | it "should store the formats and blocks" do 20 | @response.formats.should have_any {|f,p| f == :js && p.call == 'javascript'} 21 | @response.formats.should have_any {|f,p| f == :xml && p.call == 'xml'} 22 | end 23 | 24 | it "should give formats without a block an empty block" do 25 | @response.formats.should have_any {|f,p| f == :html && Proc === p && p.call.nil?} 26 | end 27 | 28 | it "shouldn't allow duplicate formats" do 29 | @response.js {'not javascript'} 30 | @response.formats.should have_any {|f,p| f == :js && p.call == 'javascript'} 31 | @response.formats.should_not have_any {|f,p| f == :js && p.call == 'not javascript'} 32 | end 33 | 34 | it "should keep the formats in sorted order" do 35 | @response.formats.map(&:first).should == [:html, :js, :xml] 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/responses_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper' 2 | 3 | describe 'Resourceful::Default::Responses', " with a _flash parameter for :error" do 4 | include ControllerMocks 5 | before :each do 6 | mock_controller Resourceful::Default::Responses 7 | @flash = {} 8 | @controller.stubs(:flash).returns(@flash) 9 | @params = {:_flash => {:error => 'Oh no, an error!'}} 10 | @controller.stubs(:params).returns(@params) 11 | end 12 | 13 | it "should set the flash for :error to the parameter's value when set_default_flash is called on :error" do 14 | @controller.set_default_flash(:error, "Aw there's no error!") 15 | @flash[:error].should == 'Oh no, an error!' 16 | end 17 | 18 | it "should set the flash for :message to the default value when set_default_flash is called on :message" do 19 | @controller.set_default_flash(:message, "All jim dandy!") 20 | @flash[:message].should == 'All jim dandy!' 21 | end 22 | 23 | it "shouldn't set the flash for :error when set_default_flash is called on :message" do 24 | @controller.set_default_flash(:message, "All jim dandy!") 25 | @flash[:error].should be_nil 26 | end 27 | end 28 | 29 | describe 'Resourceful::Default::Responses', " with a _redirect parameter on :failure" do 30 | include ControllerMocks 31 | before :each do 32 | mock_controller Resourceful::Default::Responses 33 | @params = {:_redirect_on => {:failure => 'http://hamptoncatlin.com/'}} 34 | @controller.stubs(:params).returns(@params) 35 | end 36 | 37 | it "should set the redirect for :failure to the parameter's value when set_default_redirect is called on :failure" do 38 | @controller.expects(:redirect_to).with('http://hamptoncatlin.com/') 39 | @controller.set_default_redirect(:back, :status => :failure) 40 | end 41 | 42 | it "should set the redirect for :success to the default value when set_default_redirect is called on :success" do 43 | @controller.expects(:redirect_to).with(:back) 44 | @controller.set_default_redirect(:back, :status => :success) 45 | end 46 | 47 | it "shouldn't set the redirect for :failure when set_default_redirect is called on :success" do 48 | @controller.expects(:redirect_to).with(:back) 49 | @controller.expects(:redirect_to).with('http://hamptoncatlin.com/').never 50 | @controller.set_default_redirect(:back, :status => :success) 51 | end 52 | 53 | it "should set the default redirect for :success by default" do 54 | @controller.expects(:redirect_to).with(:back) 55 | @controller.set_default_redirect(:back) 56 | end 57 | end 58 | 59 | describe 'Resourceful::Default::Responses', ' for show' do 60 | include ControllerMocks 61 | before :each do 62 | mock_kontroller 63 | create_builder 64 | made_resourceful(Resourceful::Default::Responses) 65 | @builder.apply 66 | end 67 | 68 | it "should have an empty HTML response" do 69 | responses[:show].find { |f, p| f == :html }[1].call.should == nil 70 | end 71 | 72 | it "should have an empty JS response" do 73 | responses[:show].find { |f, p| f == :js }[1].call.should == nil 74 | end 75 | end 76 | 77 | describe 'Resourceful::Default::Responses', ' for index' do 78 | include ControllerMocks 79 | before :each do 80 | mock_kontroller 81 | create_builder 82 | made_resourceful(Resourceful::Default::Responses) 83 | @builder.apply 84 | end 85 | 86 | it "should have an empty HTML response" do 87 | responses[:index].find { |f, p| f == :html }[1].call.should == nil 88 | end 89 | 90 | it "should have an empty JS response" do 91 | responses[:index].find { |f, p| f == :js }[1].call.should == nil 92 | end 93 | end 94 | 95 | describe 'Resourceful::Default::Responses', ' for edit' do 96 | include ControllerMocks 97 | before :each do 98 | mock_kontroller 99 | create_builder 100 | made_resourceful(Resourceful::Default::Responses) 101 | @builder.apply 102 | end 103 | 104 | it "should have an empty HTML response" do 105 | responses[:edit].find { |f, p| f == :html }[1].call.should == nil 106 | end 107 | 108 | it "should have an empty JS response" do 109 | responses[:edit].find { |f, p| f == :js }[1].call.should == nil 110 | end 111 | end 112 | 113 | describe 'Resourceful::Default::Responses', ' for new' do 114 | include ControllerMocks 115 | before :each do 116 | mock_kontroller 117 | create_builder 118 | made_resourceful(Resourceful::Default::Responses) 119 | @builder.apply 120 | end 121 | 122 | it "should have an empty HTML response" do 123 | responses[:new].find { |f, p| f == :html }[1].call.should == nil 124 | end 125 | 126 | it "should have an empty JS response" do 127 | responses[:new].find { |f, p| f == :js }[1].call.should == nil 128 | end 129 | end 130 | 131 | describe 'Resourceful::Default::Responses', ' for show_fails' do 132 | include ControllerMocks 133 | before :each do 134 | mock_controller Resourceful::Default::Callbacks 135 | create_builder 136 | made_resourceful(Resourceful::Default::Responses) 137 | @builder.apply 138 | end 139 | 140 | it "should give a 404 error for HTML" do 141 | @controller.expects(:render).with(:text => "No item found", :status => 404) 142 | @controller.scope(responses[:show_fails].find { |f, p| f == :html }[1]).call 143 | end 144 | 145 | it "should give a 404 error for JS" do 146 | @controller.expects(:render).with(:text => "No item found", :status => 404) 147 | @controller.scope(responses[:show_fails].find { |f, p| f == :js }[1]).call 148 | end 149 | 150 | it "should give a 404 error for XML" do 151 | @controller.expects(:render).with(:text => "No item found", :status => 404) 152 | @controller.scope(responses[:show_fails].find { |f, p| f == :xml }[1]).call 153 | end 154 | end 155 | 156 | describe 'Resourceful::Default::Responses', ' for create' do 157 | include ControllerMocks 158 | before :each do 159 | mock_controller Resourceful::Default::Callbacks 160 | create_builder 161 | made_resourceful(Resourceful::Default::Responses) 162 | @builder.apply 163 | 164 | [:set_default_flash, :set_default_redirect, :object_path].each(&@controller.method(:stubs)) 165 | end 166 | 167 | it "should have an empty JS response" do 168 | responses[:create].find { |f, p| f == :js }[1].call.should == nil 169 | end 170 | 171 | it "should flash a success message to :notice by default for HTML" do 172 | @controller.expects(:set_default_flash).with(:notice, "Create successful!") 173 | @controller.scope(responses[:create].find { |f, p| f == :html }[1]).call 174 | end 175 | 176 | it "should redirect to object_path by default for HTML" do 177 | @controller.stubs(:object_path).returns("/posts/12") 178 | @controller.expects(:set_default_redirect).with("/posts/12") 179 | @controller.scope(responses[:create].find { |f, p| f == :html }[1]).call 180 | end 181 | end 182 | 183 | describe 'Resourceful::Default::Responses', ' for create_fails' do 184 | include ControllerMocks 185 | before :each do 186 | mock_controller Resourceful::Default::Callbacks 187 | create_builder 188 | made_resourceful(Resourceful::Default::Responses) 189 | @builder.apply 190 | 191 | [:set_default_flash, :render].each(&@controller.method(:stubs)) 192 | end 193 | 194 | it "should have an empty JS response" do 195 | responses[:create_fails].find { |f, p| f == :js }[1].call.should == nil 196 | end 197 | 198 | it "should flash a failure message to :error by default for HTML" do 199 | @controller.expects(:set_default_flash).with(:error, "There was a problem!") 200 | @controller.scope(responses[:create_fails].find { |f, p| f == :html }[1]).call 201 | end 202 | 203 | it "should render new with a 422 error for HTML" do 204 | @controller.expects(:render).with(:action => :new, :status => 422) 205 | @controller.scope(responses[:create_fails].find { |f, p| f == :html }[1]).call 206 | end 207 | end 208 | 209 | describe 'Resourceful::Default::Responses', ' for update' do 210 | include ControllerMocks 211 | before :each do 212 | mock_controller Resourceful::Default::Callbacks 213 | create_builder 214 | made_resourceful(Resourceful::Default::Responses) 215 | @builder.apply 216 | 217 | [:set_default_flash, :set_default_redirect, :object_path].each(&@controller.method(:stubs)) 218 | end 219 | 220 | it "should have an empty JS response" do 221 | responses[:update].find { |f, p| f == :js }[1].call.should == nil 222 | end 223 | 224 | it "should flash a success message to :notice by default for HTML" do 225 | @controller.expects(:set_default_flash).with(:notice, "Save successful!") 226 | @controller.scope(responses[:update].find { |f, p| f == :html }[1]).call 227 | end 228 | 229 | it "should redirect to object_path by default for HTML" do 230 | @controller.stubs(:object_path).returns("/posts/12") 231 | @controller.expects(:set_default_redirect).with("/posts/12") 232 | @controller.scope(responses[:update].find { |f, p| f == :html }[1]).call 233 | end 234 | end 235 | 236 | describe 'Resourceful::Default::Responses', ' for update_fails' do 237 | include ControllerMocks 238 | before :each do 239 | mock_controller Resourceful::Default::Callbacks 240 | create_builder 241 | made_resourceful(Resourceful::Default::Responses) 242 | @builder.apply 243 | 244 | [:set_default_flash, :render].each(&@controller.method(:stubs)) 245 | end 246 | 247 | it "should have an empty JS response" do 248 | responses[:update_fails].find { |f, p| f == :js }[1].call.should == nil 249 | end 250 | 251 | it "should flash a failure message to :error by default for HTML" do 252 | @controller.expects(:set_default_flash).with(:error, "There was a problem saving!") 253 | @controller.scope(responses[:update_fails].find { |f, p| f == :html }[1]).call 254 | end 255 | 256 | it "should render edit with a 422 error for HTML" do 257 | @controller.expects(:render).with(:action => :edit, :status => 422) 258 | @controller.scope(responses[:update_fails].find { |f, p| f == :html }[1]).call 259 | end 260 | end 261 | 262 | 263 | describe 'Resourceful::Default::Responses', ' for destroy' do 264 | include ControllerMocks 265 | before :each do 266 | mock_controller Resourceful::Default::Callbacks 267 | create_builder 268 | made_resourceful(Resourceful::Default::Responses) 269 | @builder.apply 270 | 271 | [:set_default_flash, :set_default_redirect, :objects_path].each(&@controller.method(:stubs)) 272 | end 273 | 274 | it "should have an empty JS response" do 275 | responses[:destroy].find { |f, p| f == :js }[1].call.should == nil 276 | end 277 | 278 | it "should flash a success message to :notice by default for HTML" do 279 | @controller.expects(:set_default_flash).with(:notice, "Record deleted!") 280 | @controller.scope(responses[:destroy].find { |f, p| f == :html }[1]).call 281 | end 282 | 283 | it "should redirect to objects_path by default for HTML" do 284 | @controller.stubs(:objects_path).returns("/posts") 285 | @controller.expects(:set_default_redirect).with("/posts") 286 | @controller.scope(responses[:destroy].find { |f, p| f == :html }[1]).call 287 | end 288 | end 289 | 290 | describe 'Resourceful::Default::Responses', ' for destroy_fails' do 291 | include ControllerMocks 292 | before :each do 293 | mock_controller Resourceful::Default::Callbacks 294 | create_builder 295 | made_resourceful(Resourceful::Default::Responses) 296 | @builder.apply 297 | 298 | [:set_default_flash, :set_default_redirect, :render].each(&@controller.method(:stubs)) 299 | end 300 | 301 | it "should have an empty JS response" do 302 | responses[:destroy_fails].find { |f, p| f == :js }[1].call.should == nil 303 | end 304 | 305 | it "should flash a failure message to :error by default for HTML" do 306 | @controller.expects(:set_default_flash).with(:error, "There was a problem deleting!") 307 | @controller.scope(responses[:destroy_fails].find { |f, p| f == :html }[1]).call 308 | end 309 | 310 | it "should redirect back on failure by default for HTML" do 311 | @controller.expects(:set_default_redirect).with(:back, :status => :failure) 312 | @controller.scope(responses[:destroy_fails].find { |f, p| f == :html }[1]).call 313 | end 314 | end 315 | -------------------------------------------------------------------------------- /spec/rspec-rails/LICENSE: -------------------------------------------------------------------------------- 1 | All the code in this directory comes from the rspec-rails plugin. 2 | We've pilfered it as needed to make the make_resourceful specs easier to write. 3 | It was made available by its authors under the following license terms: 4 | 5 | (The MIT License) 6 | 7 | ==================================================================== 8 | ==== RSpec, RSpec-Rails 9 | Copyright (c) 2005-2008 The RSpec Development Team 10 | ==================================================================== 11 | ==== ARTS 12 | Copyright (c) 2006 Kevin Clark, Jake Howerton 13 | ==================================================================== 14 | ==== ZenTest 15 | Copyright (c) 2001-2006 Ryan Davis, Eric Hodel, Zen Spider Software 16 | ==================================================================== 17 | ==== AssertSelect 18 | Copyright (c) 2006 Assaf Arkin 19 | ==================================================================== 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy of 22 | this software and associated documentation files (the "Software"), to deal in 23 | the Software without restriction, including without limitation the rights to 24 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 25 | of the Software, and to permit persons to whom the Software is furnished to do 26 | so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | -------------------------------------------------------------------------------- /spec/rspec-rails/redirect_to.rb: -------------------------------------------------------------------------------- 1 | module Spec 2 | module Rails 3 | module Matchers 4 | 5 | class RedirectTo #:nodoc: 6 | 7 | def initialize(request, expected) 8 | @expected = expected 9 | @request = request 10 | end 11 | 12 | def matches?(response) 13 | @redirected = response.redirect? 14 | @actual = response.redirect_url 15 | return false unless @redirected 16 | if @expected.instance_of? Hash 17 | return false unless @actual =~ %r{^\w+://#{@request.host}} 18 | return false unless actual_redirect_to_valid_route 19 | return actual_hash == expected_hash 20 | else 21 | return @actual == expected_url 22 | end 23 | end 24 | 25 | def actual_hash 26 | hash_from_url @actual 27 | end 28 | 29 | def expected_hash 30 | hash_from_url expected_url 31 | end 32 | 33 | def actual_redirect_to_valid_route 34 | actual_hash 35 | end 36 | 37 | def hash_from_url(url) 38 | query_hash(url).merge(path_hash(url)).with_indifferent_access 39 | end 40 | 41 | def path_hash(url) 42 | path = url.sub(%r{^\w+://#{@request.host}(?::\d+)?}, "").split("?", 2)[0] 43 | ActionController::Routing::Routes.recognize_path path 44 | end 45 | 46 | def query_hash(url) 47 | query = url.split("?", 2)[1] || "" 48 | QueryParameterParser.parse_query_parameters(query, @request) 49 | end 50 | 51 | def expected_url 52 | case @expected 53 | when Hash 54 | return ActionController::UrlRewriter.new(@request, {}).rewrite(@expected) 55 | when :back 56 | return @request.env['HTTP_REFERER'] 57 | when %r{^\w+://.*} 58 | return @expected 59 | else 60 | return "http://#{@request.host}" + (@expected.split('')[0] == '/' ? '' : '/') + @expected 61 | end 62 | end 63 | 64 | def failure_message 65 | if @redirected 66 | return %Q{expected redirect to #{@expected.inspect}, got redirect to #{@actual.inspect}} 67 | else 68 | return %Q{expected redirect to #{@expected.inspect}, got no redirect} 69 | end 70 | end 71 | 72 | def negative_failure_message 73 | return %Q{expected not to be redirected to #{@expected.inspect}, but was} if @redirected 74 | end 75 | 76 | def description 77 | "redirect to #{@actual.inspect}" 78 | end 79 | 80 | class QueryParameterParser 81 | def self.parse_query_parameters(query, request) 82 | if defined?(CGIMethods) 83 | CGIMethods.parse_query_parameters(query) 84 | else 85 | request.class.parse_query_parameters(query) 86 | end 87 | end 88 | end 89 | end 90 | 91 | # :call-seq: 92 | # response.should redirect_to(url) 93 | # response.should redirect_to(:action => action_name) 94 | # response.should redirect_to(:controller => controller_name, :action => action_name) 95 | # response.should_not redirect_to(url) 96 | # response.should_not redirect_to(:action => action_name) 97 | # response.should_not redirect_to(:controller => controller_name, :action => action_name) 98 | # 99 | # Passes if the response is a redirect to the url, action or controller/action. 100 | # Useful in controller specs (integration or isolation mode). 101 | # 102 | # == Examples 103 | # 104 | # response.should redirect_to("path/to/action") 105 | # response.should redirect_to("http://test.host/path/to/action") 106 | # response.should redirect_to(:action => 'list') 107 | def redirect_to(opts) 108 | RedirectTo.new(request, opts) 109 | end 110 | end 111 | 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /spec/rspec-rails/render_template.rb: -------------------------------------------------------------------------------- 1 | module Spec 2 | module Rails 3 | module Matchers 4 | 5 | class RenderTemplate #:nodoc: 6 | 7 | def initialize(expected, controller) 8 | @controller = controller 9 | @expected = expected 10 | end 11 | 12 | def matches?(response) 13 | 14 | if response.respond_to?(:rendered_file) 15 | @actual = response.rendered_file 16 | else 17 | @actual = response.rendered_template.to_s 18 | end 19 | return false if @actual.blank? 20 | given_controller_path, given_file = path_and_file(@actual) 21 | expected_controller_path, expected_file = path_and_file(@expected) 22 | given_controller_path == expected_controller_path && given_file.match(expected_file) 23 | end 24 | 25 | def failure_message 26 | "expected #{@expected.inspect}, got #{@actual.inspect}" 27 | end 28 | 29 | def negative_failure_message 30 | "expected not to render #{@expected.inspect}, but did" 31 | end 32 | 33 | def description 34 | "render template #{@expected.inspect}" 35 | end 36 | 37 | private 38 | def path_and_file(path) 39 | parts = path.split('/') 40 | file = parts.pop 41 | controller = parts.empty? ? current_controller_path : parts.join('/') 42 | return controller, file 43 | end 44 | 45 | def controller_path_from(path) 46 | parts = path.split('/') 47 | parts.pop 48 | parts.join('/') 49 | end 50 | 51 | def current_controller_path 52 | @controller.class.to_s.underscore.gsub(/_controller$/,'') 53 | end 54 | 55 | end 56 | 57 | # :call-seq: 58 | # response.should render_template(path) 59 | # response.should_not render_template(path) 60 | # 61 | # Passes if the specified template is rendered by the response. 62 | # Useful in controller specs (integration or isolation mode). 63 | # 64 | # path can include the controller path or not. It 65 | # can also include an optional extension (no extension assumes .rhtml). 66 | # 67 | # Note that partials must be spelled with the preceding underscore. 68 | # 69 | # == Examples 70 | # 71 | # response.should render_template('list') 72 | # response.should render_template('same_controller/list') 73 | # response.should render_template('other_controller/list') 74 | # 75 | # #rjs 76 | # response.should render_template('list.rjs') 77 | # response.should render_template('same_controller/list.rjs') 78 | # response.should render_template('other_controller/list.rjs') 79 | # 80 | # #partials 81 | # response.should render_template('_a_partial') 82 | # response.should render_template('same_controller/_a_partial') 83 | # response.should render_template('other_controller/_a_partial') 84 | def render_template(path) 85 | RenderTemplate.new(path.to_s, @controller) 86 | end 87 | 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /spec/serialize_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative './spec_helper' 2 | 3 | describe Resourceful::Serialize, ".normalize_attributes" do 4 | def expect_normalize(object) 5 | expect(Resourceful::Serialize.normalize_attributes(object)) 6 | end 7 | 8 | it "should return nil if given nil" do 9 | expect_normalize(nil).to be_nil 10 | end 11 | 12 | it "should return a basic hash if given a non-injectable attribute" do 13 | expect_normalize(:foo).to eq({:foo => nil}) 14 | expect_normalize(12).to eq({12 => nil}) 15 | end 16 | 17 | it "should return a basic hash with a symbol key if given a string attribute" do 18 | expect_normalize("foo").to eq({:foo => nil}) 19 | end 20 | 21 | it "should preserve hashes" do 22 | expect_normalize({:foo => nil, :bar => nil, :baz => nil}).to eq({:foo => nil, :bar => nil, :baz => nil}) 23 | expect_normalize({:foo => 3, :bar => 1, :baz => 4}).to eq({:foo => 3, :bar => 1, :baz => 4}) 24 | expect_normalize({:foo => 3, :bar => 1, :baz => [:foo, :bar]}).to eq({:foo => 3, :bar => 1, :baz => [:foo, :bar]}) 25 | end 26 | 27 | it "should merge injectable attributes into one big hash" do 28 | expect_normalize([:foo, :bar, :baz]).to eq({:foo => nil, :bar => nil, :baz => nil}) 29 | expect_normalize( 30 | [:foo, :bar, {:baz => nil}, :boom, {:bop => nil, :blat => nil}] 31 | ).to eq( 32 | {:foo => nil, :bar => nil, :baz => nil, :boom => nil, :bop => nil, :blat => nil} 33 | ) 34 | expect_normalize( 35 | [:foo, :bar, {:baz => 12}, :boom, {:bop => "foo", :blat => [:fee, :fi, :fo]}] 36 | ).to eq( 37 | {:foo => nil, :bar => nil, :baz => 12, :boom => nil, :bop => "foo", :blat => [:fee, :fi, :fo]} 38 | ) 39 | end 40 | end 41 | 42 | describe Array, " of non-serializable objects" do 43 | before :each do 44 | @array = [1, 2, 3, 4, "foo"] 45 | end 46 | 47 | it "should return itself for #to_serializable" do 48 | @array.to_serializable(nil).should == @array 49 | end 50 | 51 | it "should raise an error for #serialize" do 52 | lambda { @array.serialize(:yaml, :attributes => [:foo]) }.should raise_error("Not all elements respond to to_serializable") 53 | end 54 | end 55 | 56 | describe Array, " of serializable objects" do 57 | before :each do 58 | @cat = stub_model("Cat") 59 | @dog = stub_model("Dog") 60 | @array = %w{brown yellow green}.zip(%w{rex rover fido}). 61 | map { |c, d| @cat.new(:fur => c, :friend => @dog.new(:name => d)) } 62 | end 63 | 64 | it "should return an array of serializable hashes for #to_serializable" do 65 | @array.to_serializable([:fur]).should == [{'fur' => 'brown'}, {'fur' => 'yellow'}, {'fur' => 'green'}] 66 | end 67 | 68 | it "should follow deep attributes for #to_serializable" do 69 | @array.to_serializable([:fur, {:friend => :name}]).should == 70 | [{'fur' => 'brown', 'friend' => {'name' => 'rex'}}, 71 | {'fur' => 'yellow', 'friend' => {'name' => 'rover'}}, 72 | {'fur' => 'green', 'friend' => {'name' => 'fido'}}] 73 | end 74 | 75 | it "should raise an error if #serialize is called without the :attributes option" do 76 | lambda { @array.serialize(:yaml, {}) }.should raise_error("Must specify :attributes option") 77 | end 78 | 79 | it "should serialize to a hash with a pluralized root for #serialize" do 80 | YAML.load(@array.serialize(:yaml, :attributes => [:fur, {:friend => :name}])).should == 81 | {"cats" => [{'fur' => 'brown', 'friend' => {'name' => 'rex'}}, 82 | {'fur' => 'yellow', 'friend' => {'name' => 'rover'}}, 83 | {'fur' => 'green', 'friend' => {'name' => 'fido'}}]} 84 | end 85 | 86 | it "should serialize to an XML document with a pluralized root for #serialize(:xml, ...)" do 87 | doc = REXML::Document.new(@array.serialize(:xml, :attributes => [:fur, {:friend => :name}]), 88 | :ignore_whitespace_nodes => :all) 89 | doc.root.name.should == "cats" 90 | cats = doc.get_elements('/cats/cat') 91 | cats.size.should == 3 92 | cats.zip(%w{brown yellow green}, %w{rex rover fido}) do |cat, fur, dog| 93 | cat.children.find { |e| e.name == "fur" }.text.should == fur 94 | cat.children.find { |e| e.name == "friend" }.children[0].text.should == dog 95 | end 96 | end 97 | end 98 | 99 | describe ActiveModel::Base, " with a few attributes and an association" do 100 | before :each do 101 | @person = stub_model("Person") 102 | @party_hat = stub_model("PartyHat") 103 | @model = @person.new(:name => "joe", :eye_color => "blue", :hairs => 567, 104 | :party_hat => @party_hat.new(:color => 'blue', :size => 12, :pattern => 'stripey')) 105 | end 106 | 107 | it "should raise an error if #to_serializable is called without attributes" do 108 | lambda { @model.to_serializable(nil) }.should raise_error("Must specify attributes for #.to_serializable") 109 | end 110 | 111 | it "should return an attributes hash for #to_serializable" do 112 | @model.to_serializable([:name, :hairs, {:party_hat => [:color, :size]}]).should == 113 | {'name' => 'joe', 'hairs' => 567, 'party_hat' => { 114 | 'color' => 'blue', 'size' => 12 115 | }} 116 | end 117 | 118 | it "should raise an error if #serialize is called without the :attributes option" do 119 | lambda { @model.serialize(:yaml, {}) }.should raise_error("Must specify :attributes option") 120 | end 121 | 122 | it "should serialize to a hash for #serialize" do 123 | YAML.load(@model.serialize(:yaml, :attributes => [:hairs, :eye_color, {:party_hat => :size}])).should == 124 | {"person" => {'hairs' => 567, 'eye_color' => 'blue', 'party_hat' => {'size' => 12}}} 125 | end 126 | 127 | it "should serialize to an XML document for #serialize(:xml, ...)" do 128 | doc = REXML::Document.new(@model.serialize(:xml, :attributes => [:name, :eye_color, {:party_hat => :pattern}]), 129 | :ignore_whitespace_nodes => :all) 130 | doc.root.name.should == "person" 131 | doc.root.children.find { |e| e.name == "name" }.text.should == "joe" 132 | doc.root.children.find { |e| e.name == "eye-color" }.text.should == "blue" 133 | 134 | hat = doc.root.children.find { |e| e.name == "party-hat" } 135 | hat.children.find { |e| e.name == "pattern" }.text.should == "stripey" 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $: << File.dirname(__FILE__) + '/../lib' 2 | 3 | 4 | require 'rubygems' 5 | 6 | require 'rack' 7 | require "rails/all" 8 | require 'rack/test' 9 | require 'rails/test_help' 10 | require 'rspec/rails' 11 | require 'active_model' 12 | require 'support/helper_methods' 13 | require 'support/integration_helpers' 14 | require_relative '../lib/make_resourceful' 15 | require_relative '../lib/resourceful/base' 16 | 17 | RSpec.configure do |config| 18 | config.mock_with :mocha 19 | config.include HelperMethods 20 | config.include IntegrationHelpers 21 | end 22 | 23 | module MetaClass 24 | def metaclass 25 | class << self; self; end 26 | end 27 | end 28 | 29 | def should_be_called(&block) 30 | pstub = stub 31 | pstub.expects(:call).instance_eval(&(block || proc {})) 32 | proc { |*args| pstub.call(*args) } 33 | end 34 | 35 | def stub_model(name) 36 | model = Class.new do 37 | include Resourceful::Serialize::Model 38 | 39 | def self.to_s 40 | @name 41 | end 42 | 43 | def initialize(attrs = {}) 44 | attrs.each do |k, v| 45 | self.stubs(k).returns(v) 46 | end 47 | end 48 | 49 | def inspect 50 | "#<#{self.class.send(:instance_variable_get, '@name')}>" 51 | end 52 | end 53 | model.send(:instance_variable_set, '@name', name) 54 | model 55 | end 56 | 57 | def stub_const(name) 58 | unless Object.const_defined?(name) 59 | obj = Object.new 60 | obj.extend MetaClass 61 | obj.metaclass.send(:define_method, :to_s) { name.to_s } 62 | obj.metaclass.send(:alias_method, :inspect, :to_s) 63 | Object.const_set(name, obj) 64 | end 65 | Object.const_get(name) 66 | end 67 | 68 | def stub_list(size, name = nil, &block) 69 | list = Array.new(size) { |i| name ? stub("#{name}_#{i}") : stub } 70 | list.each(&block) if block 71 | list 72 | end 73 | 74 | module Spec::Matchers 75 | def have_any(&proc) 76 | satisfy { |a| a.any?(&proc) } 77 | end 78 | end 79 | 80 | module ControllerMocks 81 | def mock_kontroller(*to_extend) 82 | options = to_extend.last.is_a?(Hash) ? to_extend.slice!(-1) : {} 83 | @kontroller = Class.new 84 | @kontroller.extend Resourceful::Maker 85 | to_extend.each(&@kontroller.method(:extend)) 86 | 87 | @hidden_actions = Resourceful::ACTIONS.dup 88 | @kontroller.stubs(:hidden_actions).returns(@hidden_actions) 89 | @kontroller.stubs(:plural_action?).returns(false) 90 | @kontroller.stubs(:include) 91 | @kontroller.stubs(:before_action) 92 | @kontroller.stubs(:helper_method) 93 | end 94 | 95 | def mock_controller(*to_extend) 96 | mock_kontroller 97 | @controller = @kontroller.new 98 | to_extend.each(&@controller.method(:extend)) 99 | end 100 | 101 | def mock_builder(inherited = false) 102 | @builder = stub 103 | @builder.stubs(:response_for) 104 | @builder.stubs(:apply) 105 | @builder.stubs(:instance_eval).yields(@buildercc ) 106 | @builder.stubs(:inherited?).returns(inherited) 107 | Resourceful::Base.stubs(:made_resourceful).returns([]) 108 | Resourceful::Builder.stubs(:new).returns(@builder) 109 | end 110 | 111 | def create_builder 112 | @builder = Resourceful::Builder.new(@kontroller) 113 | class << @builder 114 | alias_method :made_resourceful, :instance_eval 115 | end 116 | end 117 | 118 | def responses 119 | @kontroller.resourceful_responses 120 | end 121 | 122 | def callbacks 123 | @kontroller.resourceful_callbacks 124 | end 125 | 126 | def parents 127 | @kontroller.parents 128 | end 129 | 130 | # Evaluates the made_resourceful block of mod (a module) 131 | # in the context of @builder. 132 | # @builder should be initialized via create_builder. 133 | def made_resourceful(mod) 134 | mod.included(@builder) 135 | end 136 | end 137 | 138 | module RailsMocks 139 | attr_reader :response, :request, :controller, :kontroller 140 | 141 | def included(mod) 142 | require 'ruby-debug' 143 | debugger 144 | end 145 | 146 | def mock_resourceful(options = {}, &block) 147 | options = { 148 | :name => "things" 149 | }.merge options 150 | 151 | init_kontroller options 152 | init_routes options 153 | 154 | stub_const(options[:name].singularize.camelize) 155 | kontroller.make_resourceful(&block) 156 | 157 | init_controller options 158 | end 159 | 160 | def assigns(name) 161 | controller.instance_variable_get("@#{name}") 162 | end 163 | 164 | def redirect_to(opts) 165 | Spec::Rails::Matchers::RedirectTo.new(request, opts) 166 | end 167 | 168 | def render_template(path) 169 | Spec::Rails::Matchers::RenderTemplate.new(path.to_s, @controller) 170 | end 171 | 172 | private 173 | 174 | def init_kontroller(options) 175 | @kontroller = Class.new ActionController::Base 176 | @kontroller.extend Resourceful::Maker 177 | @kontroller.extend MetaClass 178 | 179 | @kontroller.metaclass.send(:define_method, :controller_name) { options[:name] } 180 | @kontroller.metaclass.send(:define_method, :controller_path) { options[:name] } 181 | @kontroller.metaclass.send(:define_method, :inspect) { "#{options[:name].camelize}Controller" } 182 | @kontroller.metaclass.send(:alias_method, :to_s, :inspect) 183 | 184 | @kontroller.send(:define_method, :controller_name) { options[:name] } 185 | @kontroller.send(:define_method, :controller_path) { options[:name] } 186 | @kontroller.send(:define_method, :inspect) { "#<#{options[:name].camelize}Controller>" } 187 | @kontroller.send(:alias_method, :to_s, :inspect) 188 | @kontroller.send(:include, ControllerMethods) 189 | @kontroller.send(:view_paths=, [File.join(File.dirname(__FILE__), 'views')]) 190 | 191 | @kontroller 192 | end 193 | 194 | def init_routes(options) 195 | ActionController::Routing::Routes.clear! 196 | route_block = options[:routes] || proc { |map| map.resources options[:name] } 197 | ActionController::Routing::Routes.draw(&route_block) 198 | end 199 | 200 | def init_controller(options) 201 | @controller = kontroller.new 202 | @request = ActionController::TestRequest.new 203 | @response = ActionController::TestResponse.new 204 | 205 | @controller.request = @request 206 | @controller.response = @response 207 | @request.accept = '*/*' 208 | @request.env['HTTP_REFERER'] = 'http://test.host' 209 | 210 | @controller 211 | end 212 | 213 | def action_params(action, params = {}) 214 | params.merge case action 215 | when :show, :edit, :destroy, {:id => 12} 216 | when :update, {:id => 12, :thing => {}} 217 | when :create, {:thing => {}} 218 | else {} 219 | end 220 | end 221 | 222 | def action_method(action) 223 | method case action 224 | when :index, :show, :edit, :new, :get 225 | when :update, :put 226 | when :create, :post 227 | when :destroy, :delete 228 | end 229 | end 230 | 231 | module ControllerMethods 232 | # From rspec-rails ControllerExampleGroup 233 | 234 | def render(options=nil, deprecated_status_or_extra_options=nil, &block) 235 | if ::Rails::VERSION::STRING >= '2.0.0' && deprecated_status_or_extra_options.nil? 236 | deprecated_status_or_extra_options = {} 237 | end 238 | 239 | unless block_given? 240 | if @template.respond_to?(:finder) 241 | (class << @template.finder; self; end).class_eval do 242 | define_method :file_exists? do; true; end 243 | end 244 | else 245 | (class << @template; self; end).class_eval do 246 | define_method :file_exists? do; true; end 247 | end 248 | end 249 | (class << @template; self; end).class_eval do 250 | define_method :render_file do |*args| 251 | @first_render ||= args[0] unless args[0] =~ /^layouts/ 252 | @_first_render ||= args[0] unless args[0] =~ /^layouts/ 253 | end 254 | 255 | define_method :_pick_template do |*args| 256 | @_first_render ||= args[0] unless args[0] =~ /^layouts/ 257 | PickedTemplate.new 258 | end 259 | end 260 | end 261 | 262 | super(options, deprecated_status_or_extra_options, &block) 263 | end 264 | 265 | class PickedTemplate 266 | def render_template(*ignore_args); end 267 | def render_partial(*ignore_args); end 268 | end 269 | end 270 | 271 | end 272 | 273 | -------------------------------------------------------------------------------- /spec/support/helper_methods.rb: -------------------------------------------------------------------------------- 1 | module HelperMethods 2 | def should_render_html(action) 3 | it "should render HTML by default for #{action_string(action)}" do 4 | action_method(action)[action, action_params(action)] 5 | response.body.should include("as HTML") 6 | response.content_type.should == 'text/html' 7 | end 8 | end 9 | 10 | def should_render_js(action) 11 | it "should render JS for #{action_string(action)}" do 12 | action_method(action)[action, action_params(action, :format => 'js')] 13 | response.body.should include("insert(\"#{action}") 14 | response.should be_success 15 | response.content_type.should == 'text/javascript' 16 | end 17 | end 18 | 19 | def shouldnt_render_xml(action) 20 | it "shouldn't render XML for #{action_string(action)}" do 21 | action_method(action)[action, action_params(action, :format => 'xml')] 22 | response.should_not be_success 23 | response.code.should == '406' 24 | end 25 | end 26 | 27 | def action_string(action) 28 | case action 29 | when :index, "GET /things" 30 | when :show, "GET /things/12" 31 | when :edit, "GET /things/12/edit" 32 | when :update, "PUT /things/12" 33 | when :create, "POST /things" 34 | when :new, "GET /things/new" 35 | when :destroy, "DELETE /things/12" 36 | end 37 | end 38 | end -------------------------------------------------------------------------------- /spec/support/integration_helpers.rb: -------------------------------------------------------------------------------- 1 | module IntegrationHelpers 2 | # Need this helper, because we made current_objects private 3 | def current_objects 4 | controller.instance_eval("current_objects") 5 | end 6 | 7 | # Need this helper, because we made current_object private 8 | def current_object 9 | controller.instance_eval("current_object") 10 | end 11 | end -------------------------------------------------------------------------------- /spec/urls_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper' 2 | 3 | describe Resourceful::Default::URLs, " for a controller with no parents or namespaces" do 4 | include ControllerMocks 5 | before :each do 6 | mock_controller Resourceful::Default::URLs 7 | @object = stub_model('Thing') 8 | @controller.stubs(:current_object).returns(@object) 9 | 10 | @controller.stubs(:current_model_name).returns('Thing') 11 | @controller.stubs(:parent?).returns(false) 12 | @controller.stubs(:namespaces).returns([]) 13 | end 14 | 15 | it "should return nil for #url_helper_prefix" do 16 | @controller.url_helper_prefix.should be_nil 17 | end 18 | 19 | it "should return the empty string for #collection_url_prefix" do 20 | @controller.collection_url_prefix.should == "" 21 | end 22 | 23 | it "should get the path of current_object with #object_path" do 24 | @controller.expects(:send).with('thing_path', @object) 25 | @controller.object_path 26 | end 27 | 28 | it "should get the url of current_object with #object_url" do 29 | @controller.expects(:send).with('thing_url', @object) 30 | @controller.object_url 31 | end 32 | 33 | it "should get the path of the passed object with #object_path" do 34 | model = stub_model('Thing') 35 | @controller.expects(:send).with('thing_path', model) 36 | @controller.object_path(model) 37 | end 38 | 39 | it "should get the url of the passed object with #object_url" do 40 | model = stub_model('Thing') 41 | @controller.expects(:send).with('thing_url', model) 42 | @controller.object_url(model) 43 | end 44 | 45 | it "should get the path of current_object with #nested_object_path" do 46 | @controller.expects(:send).with('thing_path', @object) 47 | @controller.nested_object_path 48 | end 49 | 50 | it "should get the url of current_object with #nested_object_url" do 51 | @controller.expects(:send).with('thing_url', @object) 52 | @controller.nested_object_url 53 | end 54 | 55 | it "should get the path of the passed object with #nested_object_path" do 56 | model = stub_model('Thing') 57 | @controller.expects(:send).with('thing_path', model) 58 | @controller.nested_object_path(model) 59 | end 60 | 61 | it "should get the url of the passed object with #nested_object_url" do 62 | model = stub_model('Thing') 63 | @controller.expects(:send).with('thing_url', model) 64 | @controller.nested_object_url(model) 65 | end 66 | 67 | it "should get the edit path of current_object with #edit_object_path" do 68 | @controller.expects(:send).with('edit_thing_path', @object) 69 | @controller.edit_object_path 70 | end 71 | 72 | it "should get the edit url of current_object with #edit_object_url" do 73 | @controller.expects(:send).with('edit_thing_url', @object) 74 | @controller.edit_object_url 75 | end 76 | 77 | it "should get the edit path of the passed object with #edit_object_path" do 78 | model = stub_model('Thing') 79 | @controller.expects(:send).with('edit_thing_path', model) 80 | @controller.edit_object_path(model) 81 | end 82 | 83 | it "should get the edit url of the passed object with #edit_object_url" do 84 | model = stub_model('Thing') 85 | @controller.expects(:send).with('edit_thing_url', model) 86 | @controller.edit_object_url(model) 87 | end 88 | 89 | it "should get the plural path of the current model with #objects_path" do 90 | @controller.expects(:send).with('things_path') 91 | @controller.objects_path 92 | end 93 | 94 | it "should get the plural url of the current model with #objects_url" do 95 | @controller.expects(:send).with('things_url') 96 | @controller.objects_url 97 | end 98 | 99 | it "should get the new path of the current model with #new_object_path" do 100 | @controller.expects(:send).with('new_thing_path') 101 | @controller.new_object_path 102 | end 103 | 104 | it "should get the new url of the current model with #new_object_url" do 105 | @controller.expects(:send).with('new_thing_url') 106 | @controller.new_object_url 107 | end 108 | end 109 | 110 | describe Resourceful::Default::URLs, " for a controller with a parent object" do 111 | include ControllerMocks 112 | before :each do 113 | mock_controller Resourceful::Default::URLs 114 | @object = stub_model('Thing') 115 | @controller.stubs(:current_object).returns(@object) 116 | 117 | @controller.stubs(:current_model_name).returns('Thing') 118 | 119 | @person = stub_model('Person') 120 | @controller.stubs(:parent_object).returns(@person) 121 | @controller.stubs(:parent_name).returns('person') 122 | @controller.stubs(:parent?).returns(true) 123 | @controller.stubs(:parent_class_name).returns('Person') 124 | @controller.stubs(:namespaces).returns([]) 125 | end 126 | 127 | it "should return nil for #url_helper_prefix" do 128 | @controller.url_helper_prefix.should be_nil 129 | end 130 | 131 | it "should return the underscored parent name for #collection_url_prefix" do 132 | @controller.collection_url_prefix.should == "person_" 133 | end 134 | 135 | it "should get the path of current_object with #object_path" do 136 | @controller.expects(:send).with('person_thing_path', @person, @object) 137 | @controller.object_path 138 | end 139 | 140 | it "should get the nested path of current_object with #nested_object_path" do 141 | @controller.expects(:send).with('person_thing_path', @person, @object) 142 | @controller.nested_object_path 143 | end 144 | 145 | it "should get the nested url of current_object with #nested_object_url" do 146 | @controller.expects(:send).with('person_thing_url', @person, @object) 147 | @controller.nested_object_url 148 | end 149 | 150 | it "should get the nested path of the passed object with #nested_object_path" do 151 | object = stub_model('Thing') 152 | @controller.expects(:send).with('person_thing_path', @person, object) 153 | @controller.nested_object_path object 154 | end 155 | 156 | it "should get the nested url of the passed object with #nested_object_url" do 157 | object = stub_model('Thing') 158 | @controller.expects(:send).with('person_thing_url', @person, object) 159 | @controller.nested_object_url object 160 | end 161 | 162 | it "should get the plural path of the current model and its parent with #objects_path" do 163 | @controller.expects(:send).with('person_things_path', @person) 164 | @controller.objects_path 165 | end 166 | 167 | it "should get the edit path of the current model with #edit_object_path" do 168 | @controller.expects(:send).with('edit_person_thing_path', @person, @object) 169 | @controller.edit_object_path 170 | end 171 | 172 | it "should get the new path of the current model and its parent with #new_object_path" do 173 | @controller.expects(:send).with('new_person_thing_path', @person) 174 | @controller.new_object_path 175 | end 176 | 177 | it "should get the path of the parent_object with #parent_path" do 178 | pending 179 | @controller.expects(:send).with('person_path', @person) 180 | @controller.parent_path 181 | end 182 | 183 | it "should get the url of the parent_object with #parent_url" do 184 | pending 185 | @controller.expects(:send).with('person_url', @person) 186 | @controller.parent_url 187 | end 188 | 189 | it "should get the path of the passed object with #parent_path" do 190 | pending 191 | model = stub_model('Person') 192 | @controller.expects(:send).with('person_path', model) 193 | @controller.parent_path model 194 | end 195 | 196 | it "should get the url of the passed object with #parent_url" do 197 | pending 198 | model = stub_model('Person') 199 | @controller.expects(:send).with('person_url', model) 200 | @controller.parent_url model 201 | end 202 | end 203 | 204 | describe Resourceful::Default::URLs, " for a controller within a namespace" do 205 | include ControllerMocks 206 | before :each do 207 | mock_controller Resourceful::Default::URLs 208 | @object = stub_model('Thing') 209 | @controller.stubs(:current_object).returns(@object) 210 | 211 | @controller.stubs(:current_model_name).returns('Thing') 212 | 213 | @controller.stubs(:parent?).returns(false) 214 | @controller.stubs(:namespaces).returns([:admin, :main]) 215 | end 216 | 217 | it "should return the underscored list of namespaces for #url_helper_prefix" do 218 | @controller.url_helper_prefix.should == "admin_main_" 219 | end 220 | 221 | it "should get the namespaced path of current_object with #object_path" do 222 | @controller.expects(:send).with('admin_main_thing_path', @object) 223 | @controller.object_path 224 | end 225 | 226 | it "should get the namespaced plural path of the current model with #objects_path" do 227 | @controller.expects(:send).with('admin_main_things_path') 228 | @controller.objects_path 229 | end 230 | 231 | it "should get the edit path of the current model with #edit_object_path" do 232 | @controller.expects(:send).with('edit_admin_main_thing_path', @object) 233 | @controller.edit_object_path 234 | end 235 | 236 | it "should get the new path of the current model with #new_object_path" do 237 | @controller.expects(:send).with('new_admin_main_thing_path') 238 | @controller.new_object_path 239 | end 240 | end 241 | 242 | describe Resourceful::Default::URLs, " for a controller with a parent object and within a namespace" do 243 | include ControllerMocks 244 | before :each do 245 | mock_controller Resourceful::Default::URLs 246 | @object = stub_model('Thing') 247 | @controller.stubs(:current_object).returns(@object) 248 | 249 | @controller.stubs(:current_model_name).returns('Thing') 250 | 251 | @person = stub_model('Person') 252 | @controller.stubs(:parent_object).returns(@person) 253 | @controller.stubs(:parent_name).returns('person') 254 | @controller.stubs(:parent?).returns(true) 255 | @controller.stubs(:parent_class_name).returns('Person') 256 | @controller.stubs(:namespaces).returns([:admin, :main]) 257 | end 258 | 259 | it "should return the underscored list of namespaces for #url_helper_prefix" do 260 | @controller.url_helper_prefix.should == "admin_main_" 261 | end 262 | 263 | it "should get the namespaced path of current_object with #object_path" do 264 | @controller.expects(:send).with('admin_main_person_thing_path', @person, @object) 265 | @controller.object_path 266 | end 267 | 268 | it "should get the namespaced plural path of the current model and its parent with #objects_path" do 269 | @controller.expects(:send).with('admin_main_person_things_path', @person) 270 | @controller.objects_path 271 | end 272 | 273 | it "should get the edit path of the current model with #edit_object_path" do 274 | @controller.expects(:send).with('edit_admin_main_person_thing_path', @person, @object) 275 | @controller.edit_object_path 276 | end 277 | 278 | it "should get the new path of the current model and its parent with #new_object_path" do 279 | @controller.expects(:send).with('new_admin_main_person_thing_path', @person) 280 | @controller.new_object_path 281 | end 282 | end 283 | -------------------------------------------------------------------------------- /spec/views/things/create.rjs: -------------------------------------------------------------------------------- 1 | page.insert_html 'test', 'create' -------------------------------------------------------------------------------- /spec/views/things/destroy.rjs: -------------------------------------------------------------------------------- 1 | page.insert_html 'test', 'destroy' -------------------------------------------------------------------------------- /spec/views/things/edit.html.erb: -------------------------------------------------------------------------------- 1 | Notice: <%= flash[:notice]%> 2 | Error: <%= flash[:error]%> 3 | 4 | Editting object as HTML -------------------------------------------------------------------------------- /spec/views/things/edit.rjs: -------------------------------------------------------------------------------- 1 | page.insert_html 'test', 'edit' -------------------------------------------------------------------------------- /spec/views/things/index.html.erb: -------------------------------------------------------------------------------- 1 | Notice: <%= flash[:notice]%> 2 | Error: <%= flash[:error]%> 3 | 4 | Objects as HTML -------------------------------------------------------------------------------- /spec/views/things/index.rjs: -------------------------------------------------------------------------------- 1 | page.insert_html 'test', 'index' -------------------------------------------------------------------------------- /spec/views/things/new.html.erb: -------------------------------------------------------------------------------- 1 | Notice: <%= flash[:notice]%> 2 | Error: <%= flash[:error]%> 3 | 4 | New object as HTML -------------------------------------------------------------------------------- /spec/views/things/new.rjs: -------------------------------------------------------------------------------- 1 | page.insert_html 'test', 'new' -------------------------------------------------------------------------------- /spec/views/things/show.html.erb: -------------------------------------------------------------------------------- 1 | Notice: <%= flash[:notice]%> 2 | Error: <%= flash[:error]%> 3 | 4 | Showing object as HTML -------------------------------------------------------------------------------- /spec/views/things/show.rjs: -------------------------------------------------------------------------------- 1 | page.insert_html 'test', 'show' -------------------------------------------------------------------------------- /spec/views/things/update.rjs: -------------------------------------------------------------------------------- 1 | page.insert_html 'test', 'update' --------------------------------------------------------------------------------