CanCan Resource Conditions and special routing with friendly_id slugs - ruby-on-rails-3

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.

Related

Unpermitted parameters although controller has them whitelisted when creating user

In migrating a rails application from 3.2 to 4.1, I am hitting some issues with user creation. As there is a need to distinguish the current_user form a local_user. The controller create action
def create
#local_user = User.new(user_params)
respond_to do |format|
if #local_user.save
if params[:user][:avatar].present?
format.html { render :crop }
else
format.html { redirect_to(admin_user_path(#local_user), :notice => 'User was successfully created.') }
end
else
format.html { render :action => "new" }
end
Generates a console error: Unpermitted parameters: name, surname, pen_name[...], yet the User controller defines them:
def user_params
params.require(:user).permit(:name, :surname, :pen_name, [...])
end
The form call is:
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :method => :post, :validate => true) do |f| %>
This is necessarily a devise issue for a user can be created with the scaffolding for the User class with the exact same variables. There is some logic that devise is going through which does not pull all the controller logic ; what am I missing?
Needs a devise specific initializer, as per this demo, where all variables can be entered as an array.

respond_with location ignored on GET request

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

Rails3 and Rspec2 controller testing with a namespace

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])

No Route matches on Form with nested atributes but not default action

I have a form to update an item called post, but i need a different method than update because there are 2 ways to update the post, i have tried this in a lot of ways but i only get this error
No route matches "/topics/1/posts/35/completed"
the controller:
def completed
#post.download_remote_image
respond_to do |format|
if #post.save
format.html { redirect_to topic_path(#topic), :notice => t('.post_created') }
else
format.html { render :action => :edit }
end
end
end
the view part:
= form_for [#topic, #post], :url => completed_topic_post_path(#topic, #post) do |f|
the routes:
resources :topics do
resources :posts do
get 'complete', :as => :complete
post 'completed', :as => :completed
end
end
Thanks !!
:url => completed_topic_post_path(#topic, #post) needs to be :url => topic_post_completed_path(#topic, #post)

Rails 3 - Nested Resources Routing - One to One relationship

Having some trouble with some nested resources routing. What I'm trying to do is link to a user's profile page for editing purposes. In my view it is written as:
<%= link_to "Edit Profile", edit_user_profile_path(current_user) %>
Which errors out with:
No route matches {:action=>"edit", :controller=>"profiles", :user_id=>#<User id: 1, email: "EDITEDOUT", hashed_password: "EDITEDOUT", created_at: "2011-01-20 18:30:44", updated_at: "2011-01-20 18:30:44">}
In my routes.rb file, it looks like so:
resources :users do
resources :profiles, :controller => "profiles"
end
I checked my Rake routes, and it gave me this as a valid option:
edit_user_profile GET /users/:user_id/profiles/:id/edit(.:format) {:action=>"edit", :controller=>"profiles"}
Which I am able to manually navigate to. For good measures, here's proof of my controller:
class ProfilesController < ApplicationController
def edit
#user = current_user
#profile = current_user.profile
end
def update
#user = current_user
#profile = current_user.profile
respond_to do |format|
if #profile.update_attributes(params[:profile])
format.html { redirect_to(orders_path, :notice => "Your profile has been updated.") }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #profile.errors, :status => :unprocessable_entity }
end
end
end
end
Anyway, I've been having some problem tracking this down. Any pointers would help. For my DB design Profiles belong to Users in a one-to-one relationship. I'm hoping it's just something newbish I'm not noticing a new set of eyes might help.
If you look closely at your route, you'll see that it expects both a :user_id and an :id. The latter, in this case, refers to the user profile.
In order to tell Rails that you want that particular profile, you'll have to specify both the user and the profile in your link, like this:
edit_user_profile_path(current_user, #profile)
Now, Rails will use the first argument (current_user) for the :user_id part of the route, and the second argument (#profile) for the :id.