I'm trying to test a controller with a name space, following is my controller (/admin/sites_controller.rb):
class Admin::SitesController < AdminController
def create
#site = Site.new(params[:site])
respond_to do |format|
if #site.save
format.html { redirect_to(#site, :notice => 'Site was successfully created.') }
format.xml { render :xml => #site, :status => :created, :location => #site }
else
format.html { render :action => "new" }
format.xml { render :xml => #site.errors, :status => :unprocessable_entity }
end
end
end
end
and following is my routes.rb file
namespace :admin do
resources :sites
end
I'm using rspec2 to test my controller and following is my controller spec
describe Admin::SitesController do
describe "POST create" do
describe "with valid params" do
it "creates a new Site" do
expect {
post :create, :site => valid_attributes
}.to change(Site, :count).by(1)
end
end
end
end
But when I run the spec it gives me the following routing error
Admin::SitesController POST create with valid params creates a new Site
Failure/Error: post :create, :site => valid_attributes
NoMethodError:
undefined method `site_url' for #<Admin::SitesController:0xb5fbe6d0>
# ./app/controllers/admin/sites_controller.rb:47:in `create'
# ./app/controllers/admin/sites_controller.rb:45:in `create'
# ./spec/controllers/admin/sites_controller_spec.rb:78
# ./spec/controllers/admin/sites_controller_spec.rb:77
I guess its because of the 'admin' name space I'm using, but how can I fix that?
I'm using
Rails3
Rspec2
Linux
When you namespace the route, you're creating URL and path helpers that look like this:
HTTP Verb Path action helper
GET /admin/sites index admin_sites_path
GET /admin/sites/new new new_admin_site_path
POST /admin/sites create admin_sites_path
GET /admin/sites/:id show admin_site_path(:id)
GET /admin/sites/:id/edit edit edit_admin_site_path(:id)
PUT /admin/sites/:id update admin_site_path(:id)
DELETE /admin/sites/:id destroy admin_site_path(:id)
So you can either use those directly in your code (i.e. redirect_to admin_site_path(#site) ), or you can do something like:
redirect_to([:admin, #site])
Related
I have a Page Model that has a :name attribute. I have a specific route for the Page Model with the name "home", because I want this specific Page record to be found at the root_url. This works.. but because I'm hard coding the route... I only want users with the role "super_admin" to be able to change the :name attribute, on the Page model, where the name == "home". For example, users with the "admin" role should not be able to change the :name attribute on the "home" Page.
Can I get that fine grained with CanCan?
Should I put this logic in the PageControllers update action?
Should I set the "page#show" route differently (not hard code it)?
Not sure how to do any of these.
Thanks in advance for any advice!
ability.rb
elsif user.role == "admin"
can :manage, :all
cannot :update, Page, ["name == ?", "home"] do |page|
page.name == "home"
end
end
routes.rb (I'm using friendly_id to generate a slug from the :name attribute)
match '/:slug', :to => "pages#show", :as => :slug, :via => :get
root :to => 'pages', :controllers => "pages", :action => "show", :slug => "home"
pages_controller.rb (standard)
def update
#page = Page.find(params[:id])
respond_to do |format|
if #page.update_attributes(params[:page])
format.html { redirect_to #page, notice: 'Page was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #page.errors, status: :unprocessable_entity }
end
end
end
I must admit, I've read your question three times, and I think I have answers for you...
1 - Yes, I believe so. However, I'm not convinced your ability.rb code is correct. I'd aim for something closer to this:
cannot :update, Page do |page|
page.name == "home"
end
2 - If you do load_and_authorize_resource in your controller, that should be all you need, because that will load #page for you.
class PagesController < ApplicationController
load_and_authorize_resource
def update
respond_to do |format|
if #page.update_attributes(params[:page])
format.html { redirect_to #page, notice: 'Page was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #page.errors, status: :unprocessable_entity }
end
end
end
end
3 - To me, your route looks fine. That's likely the way I'd approach it.
I have the following rspec test:
def valid_attributes
{ "product_id" => "1" }
end
describe "POST create" do
describe "with valid params" do
it "creates a new LineItem" do
expect {
post :create, {:line_item => valid_attributes}, valid_session #my valid_session is blank
}.to change(LineItem, :count).by(1)
end
Which fails with this error:
1) LineItemsController POST create with valid params redirects to the created line_item
Failure/Error: post :create, {:line_item => valid_attributes}, valid_session
ActiveRecord::RecordNotFound:
Couldn't find Product without an ID
# ./app/controllers/line_items_controller.rb:44:in `create'
# ./spec/controllers/line_items_controller_spec.rb:87:in `block (4 levels) in <top (required)>'
This is my controller's create action:
def create
#cart = current_cart
product = Product.find(params[:product_id])
#line_item = #cart.line_items.build(:product => product)
respond_to do |format|
if #line_item.save
format.html { redirect_to #line_item.cart, notice: 'Line item was successfully created.' }
format.json { render json: #line_item.cart, status: :created, location: #line_item }
else
format.html { render action: "new" }
format.json { render json: #line_item.errors, status: :unprocessable_entity }
end
end
end
As you can see, my action expects a product_id from the request's params object. How should I work this product_id into my rspec test?
I've tried placing this before statement:
before(:each) do
ApplicationController.any_instance.stub(:product).and_return(#product = mock('product'))
end
. . . but it changes nothing. I am missing some rspec concept here somewhere.
Try like this:
describe "POST create" do
describe "with valid params" do
it "creates a new LineItem" do
expect {
post :create, :product_id => 1
}.to change(LineItem, :count).by(1)
end
Hope it helps.
I ended up resolving my issue by using a fixture instead of attempting to mock the solution as suggested in another answer.
The reason for this is that the controller does the query to get information from the database: product = Product.find(params[:product_id]) and I found a fixture-based solution was quicker to resolve my problem than one using a mock and I could not figure out how to stub the query quickly (the fixtures also help with another test on the controller so it eventually helped anyway.
For reference:
I referenced my fixture with this line toward the top of the test: fixtures :products
I changed my test to:
describe "POST create" do
describe "with valid params" do
it "creates a new LineItem" do
expect {
post :create, :product_id => products(:one).id
}.to change(LineItem, :count).by(1)
end
And here is my fixture file, products.yml:
one:
name: FirstProduct
price: 1.23
two:
name: SecondProduct
price: 4.56
As it's not currently possible for me to use a json templating engine (jbuilder or rabl) as per Rails3 ActionView Template Handlers doesn't work on Production Server I'm wondering how to best change this controller action to include a custom node with as_json (or something else)
class Mobile::AndroidUsersController < SecureMobileUserController
skip_before_filter :authorize, :only => :create
respond_to :json
# POST /mobile_users
# POST /mobile_users.xml
def create
#mobile_user = AndroidUser.find_by_auth(params[:mobile_user][:auth])
unless #mobile_user
#mobile_user = AndroidUser.new(params[:mobile_user])
else
#mobile_user.attributes = params[:mobile_user]
end
respond_to do |format|
if #mobile_user.save
format.json #Add a custom token node here
else
:unprocessable_entity }
format.json { render json: #mobile_user.errors, status: :unprocessable_entity }
:unprocessable_entity }
end
end
end
end
I just need to add a custom node called token that has a value that I get from calling a method on the MobileUser class
:token => MobileUser.next_token
You can change the call to as_json like this:
format.json {render :json => #mobile_user.as_json(:methods => [:next_token])}
At each step in my checkout process, an order is updated via a PUT request. However, one of the states has a form that submits to a third party which redirects back to my site, calling the update method with GET (no control over this).
Why does my respond_with code appear to be totally ignored and I get a Missing Template checkout/update error? It should be hitting #edit.
CheckoutController.rb
before_filter :load_order
def update
if #order.update_attributes(params[:order])
#order.next
end
respond_with(#order, :location => checkout_state_url(#order.state))
end
routes.rb
match '/checkout/update/:state' => 'checkout#update', :as => :update_checkout
match '/checkout/:state' => 'checkout#edit', :as => :checkout_state
match '/checkout' => 'checkout#edit', :state => 'client_details', :as => :checkout
It looks like respond_with does different things depending upon the HTTP verb and whether the resource has errors. See here and here.
The following code worked for me:
def update
if #order.update_attributes(params[:order]) && #order.next
respond_with(#order) { |format| format.html { redirect_to checkout_state_url(#order.state) } }
else
respond_with(#order) { |format| format.html { render :edit } }
end
end
I have the following routings
PosTracker::Application.routes.draw do
get "home/index"
resources :pos
resources :apis
match 'update_data' => 'home#update', :as => :update, :via => :get
root :to => "home#index"
end
Now, when using the link_to helper method:
link_to "text", pos_path(starbase)
I get the following route /pos.13 instead of /pos/13. Obviously, this won't produce valid output. How can I fix this?
Edit: Relevant controller:
class PosController < ApplicationController
# GET /pos
# GET /pos.xml
def index
#do stuff
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #pos }
end
end
# GET /pos/1
# GET /pos/1.xml
def show
#pos = Pos.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #pos }
end
end
end
It seems to me like Rails is recognizing pos_path as your #index action url helper. Generally it will take the symbol you pass to resources and singularize it for a #show action.
The url helper you want to use would be
link_to "text", po_path(starbase)
You can generally find the name of the helper methods by running
rake routes
Or to get the helper for a specific controller
rake routes CONTROLLER=pos