I am using Rails Inherited_resource gem in my comments controller, and comments is a nested resource so:
resources :projects do
resources :comments do
end
I also have a belongs_to in the comments controller:
belongs_to :project, :finder => :find_by_project_uuid!, :class_name => "Thfz::Project", :polymorphic => true
How can I set the comment's user association to the current_user(user_id) when its created? As user_id is not suppose to be massive assigned.
I tried following:
def begin_of_association_chain
current_user
end
This does set the user id correctly, but I cannot get nested resource working for Project with this.
Same question come when destroy a comment, I will need to find the comment through current_user, so how to achieve this?
So do I have to write my own create and destroy actions?
Thanks :)
Have you tried the following inside comments_controller?
class CommentsController < InheritedResources::Base
before_filter :authenticate_user! # Assuming you are using Devise for authentication
respond_to :html, :xml, :json
belongs_to :project, :finder => :find_by_project_uuid!, :class_name => "Thfz::Project"
def create
#comment = build_resource
#comment.author = current_user
create!
end
end
Related
I am looking to create a new record within a nested resource in rails as according to http://guides.rubyonrails.org/routing.html.
My model is:
class Entry < ApplicationRecord
belongs_to :user
belongs_to :event
class User < ApplicationRecord
has_many :entries, dependent: :destroy
class Event < ApplicationRecord
has_many :entries, dependent: :destroy
And I have declared my route as
Rails.application.routes.draw do
resources :users
resources :events do
resources :entries
end
What is the syntax for the entries controller for me to be able to create an entry on a link like events/2/entries/new. This is what I was trying:
class EntriesController < ApplicationController
def new
#entry = Entry.new
end
def create
#entry = Entry.new(params[:entry])
if #entry.save
redirect_to #user
flash.now[:info] = "Event Created"
else
render '/create'
flash.now[:danger] = "Somthing went wrong"
end
end
def entry_params
params.require(:event_id).permit(:siCard, :course)
end
end
On by new.html.erb I am using
<%= form_for(new_event_entry_path) do |f| %>.
But I cant get it to work as No route matches [POST] "/events/1/entries/new"
Many thanks
The route you've provided in form_for does not accept POSTs. You've provided the route that renders the new page, not the route that accepts new records, which is the create route. For that, you'll want to use event_entries_path(#event) route helper.
With nested resources, the form_for's first argument should be an array: <%= form_for([#event, #entry], .... Rails will intelligently choose the route you need based on whether the #entry is persisted or not.
Have a nested resource as such
class Dealer < ActiveRecord::Base
has_many :vehicles
end
and
class Vehicle < ActiveRecord::Base
belongs_to :dealer
end
below are my routes.
resources :dealers do
resources :vehicles, :except => [:index]
end
resources :vehicles, :only => [:index]
looking at the wiki at the github page for cancan I did the following:
class VehiclesController < ApplicationController
load_and_authorize_resource :dealer
load_and_authorize_resource :vehicle, :through => :dealer
def index
#vehicles = Vehicle.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #vehicles }
end
end
end
but now when the admin tries to go to the index page with the abilities:
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.has_role? :admin
can :manage, :all
end
end
I get
Couldn't find Dealer with id=
what do i need to change for admin to still be able to do all the actions and yet have others be checked before they can do any action.
The problem is not that he is not authorized to this action. The problem is that CanCan tries to fetch an instance of dealer to load all its vehicles and you have not provided a :dealer_id within params[:dealer_id]. Cancan assumes you would be loading only dealer's vehicles in this controller because you used an load_and_authorieze :through. This authorization should be used within Dealers::VehiclesController.
As long as you only loading vehicles just use load_and_authorize_resource :vehicle. And because load_and_authorize will set #vehicles for you within the before filter there is also no need to load the vehicles explicitly with Vehicle.all.
load_and_authorize is just a convenient method and it assumes some defaults. Once you will come to a point where you have some more complex use case. It will be time to throw away this method and just use CanCan's authorize!, can? and a properly configured Vehicle .accessible_by (for listing) methods.
When using load_and_authorize_resource :vehicle, through: :dealer it expects to receive a dealer_id in the request in order to authorize the dealer.
Since you use except: :index in your routes dealer_id will not be automatically included in the request.
If you don't want to authorize the dealer in the index action you can do something like this (taken from Can Can wiki)
class CommentsController < ApplicationController
load_and_authorize_resource :post
load_and_authorize_resource :through => :post
skip_authorize_resource :only => :show
skip_authorize_resource :post, :only => :show
end
In my Rails app, I have the following objects:
Group: has_many users through group_membership
GroupMembership: belongs_to user, belongs_to group
User: has_many groups through group_membership
Users can create groups. When this happens, I want to automatically add the user to the group. In my GroupsController, I have the following (extending InheritedResources):
super do |success, failure|
if success
GroupMembership.create(:user_id => current_user, :group_id => ???)
...
end
The problem is I cannot retrieve the object that super created. Is there a way to do this? Or better, is there a way to change the LockGroup model so that it always performs this association?
When the callback is fired, the controller already has the standard instance variable corresponding to the created group: #group !!
class GroupController < InheritedResources::Base
def create
super do |success, failure|
if success
GroupMembership.create(:user_id => current_user, :group_id => #group.id)
...
end
end
end
I assume the params key given for your group is :group. Then you can used the nested_attributes_for option in the model. Then you can set those in a before filter from the create action:
class User < ActiveRecord::Base
accept_nested_attributes_for :group_membership
end
# on your controller
before_filter :add_user, :on => [:create]
def add_user
params[:group][:group_membership_attributes] = {}
params[:group][:group_membership_attributes][:user] = current_user
end
or you build the group membership on user initialize:
class User < ActiveRecord::Base
def after_initialize
build_group_membership
end
end
# on your controller
before_filter :add_user, :on => [:create]
def add_user
params[:group][:user] = current_user
end
and it should automagically work.
If I have a nested resource like so:
resources :users
resources :posts
end
and a user has_many posts, it is possible to have Rails start numbering based on the parent association in the URL? For example, currently, nesting resources just grabs the ID:
#user.posts.find(params[:id])
This correctly namespaces the posts, only allowing posts from #user... however, is there a way such that the post_id is independent? I.E. I want each user's posts to start at 1, where:
/users/1/posts/1
/users/2/posts/1
Actually refer to two different posts?
It can be quite a bit of work, but basically you can do it with these steps:
Create a migration to add a new attribute to store the specific user-post count. (I used user_post_id)
Override Post's to_param method to use the new value you just created. (It has to be a string.)
to_param is the method that the url and path helpers use.
Create a before_save filter that will actually increment the user_post_id value for each new post.
Change all your controller methods to find on user_post_id
#user = User.find(params[:user_id])
#post = #user.posts.where(:user_post_id => (params[:id])).first
Change all your Views that might not work now
You can see the source here: Custom Nested Resource URL example
Code
migration:
class AddUserPostIdToPosts < ActiveRecord::Migration
def change
add_column :posts, :user_post_id, :integer
end
end
post.rb:
class Post < ActiveRecord::Base
before_save :set_next_user_post_id
belongs_to :user
validates :user_post_id, :uniqueness => {:scope => :user_id}
def to_param
self.user_post_id.to_s
end
private
def set_next_user_post_id
self.user_post_id ||= get_new_user_post_id
end
def get_new_user_post_id
user = self.user
max = user.posts.maximum('user_post_id') || 0
max + 1
end
end
A couple controller methods
posts_controller.rb:
class PostsController < ApplicationController
respond_to :html, :xml
before_filter :find_user
def index
#posts = #user.posts.all
respond_with #posts
end
def show
#post = #user.posts.where(:user_post_id => (params[:id])).first
respond_with [#user, #post]
end
...
end
I have a problem with 3 levels nesting of models in CanCan combined with Inherited Resources. I've read that we should nest everything up to 2 levels, but I had to put everything under account model and now I've tried doing this in CanCan:
load_and_authorize_resource :account
load_and_authorize_resource :project, :through => :account
load_and_authorize_resource :model, :through => :project
That gives me #account variable that has a value of #project, like it is overwriting that. #project is what is supposed to be and #model too. Is that fault of mine, CanCan's, Inherited Resources or just CanCan isn't supporting 3 levels nesting? Also, I do this in IR for the ModelsController.
belongs_to :account, :finder => :find_by_name! do
belongs_to :project, :finder => :find_by_name!
end
Another strange thing is when i remove the part load_and_ from CanCan's definition. It works then, but I've read that it can be dangerous not to use the load part.
Can I use only the authorize_resource or should I do something with CanCan?
Your authorizations have been correct as far as I can say.
The developer of the CanCan gem ryan posted how this should behave: https://github.com/ryanb/cancan/issues/127#issuecomment-364475
That means that your
load_and_authorize_resource :account
load_and_authorize_resource :project, :through => :account
load_and_authorize_resource :model, :through => :project
will end up in an block like this (here: create action. For other actions should the last authorize! and the #model change):
#account = Account.find(params[:account_id])
authorize! :read, #account
#project = #account.projects.find(params[:project_id])
authorize! :read, #project
#model = #project.models.build
authorize! :new, #model
I hope that this answer can help developers looking for nested cancan authorization :-) .
source: https://github.com/ryanb/cancan/issues/127#issuecomment-364475
ps: wrong behavior for /accounts/1/projects/2/models/new:
load_and_authorize_resource :project
load_and_authorize_resource :model, :through => :project
This is kind of a security issue, because this will do
#project = Project.find(params[:project_id])
[...]
, and does not check if the current account is allowed to read the linked account '1'.
And it does not check, if the project '2' is really a project of account '1'.