So I have these two actions in my RelationshipController:
class RelationshipsController < ApplicationController
before_filter :authenticate_user!
respond_to :json
def create
user = User.find(params[:user_to_follow_id])
relationship = current_user.follow!(user)
respond_with user
end
def destroy
user = User.find(params[:id])
current_user.unfollow!(user)
logger.debug user.id
respond_with user
end
end
Now when I use the create action, I get a response back with the respond_with user method which is also nicely formatted with ActiveModel::Serializer.
When I use the destroy method, I get a response back which is completely blank. It does seems to work with render json: {user: user, status: 201} but I am wondering why am I not able to use respond_with for both of them? The user they send back has the same type of data in both cases.
Unfollow method:
def unfollow!(user)
$redis.multi do
$redis.srem(self.redis_key(:following), user.id)
$redis.srem(user.redis_key(:followers), self.id)
end
end
Looks like you're bumping into the default response for action_controller / responder.rb
# File actionpack/lib/action_controller/metal/responder.rb, line 203
def api_behavior(error)
raise error unless resourceful?
raise MissingRenderer.new(format) unless has_renderer?
if get?
display resource
elsif post?
display resource, :status => :created, :location => api_location
else
head :no_content
end
end
If you're responding to a get or post, then some content is expected back, otherwise just a head and no content. By default, you just deleted a relationship, so there's nothing to return. If you want to send back a user in response to a relationship deletion, then I think you'll have to craft your own response (your render son statement).
Related
I'm working on my first rails api server.
I've got a controller for my User model that looks as such:
class UsersController < ApplicationController
def index
if current_user.admin?
#users = User.all
render json: #users
else
render json: { message: 'You do not have the appropriate permissions to access this resource' }, status: 401
end
end
def show
if User.exists?(#id)
#id = params[:id]
if current_user.id.to_s == #id || current_user.admin?
#user = User.find(#id)
render json: #user
else
render json: { message: 'You do not have the appropriate permissions to access this resource' }, status: 401
end
else
render json: { message: 'Requested resource not found' }, status: 404
end
end
end
What I want and currently have for these two controller methods is:
/users fetch all users only if the authenticated user making the request is of role admin
/users/:id fetch a user by id only if the authenticated user making the request has a matching id or is of role admin
The current implementation breaks the DRY philosophy. The reasoning is that the logic for handling whether or not the requesting user has the permissions to access the requested resource(s) is repeated across both controller methods. Furthermore, any model's controller method for show will repeat the logic for checking whether or not the requested resource exists. I also feel like this kind of implementation makes for fat controllers, where I'd rather them be skinny.
What I want to know from the community and from those that have solved this problem before; what is the best way to go about this in order to conform to the DRY philosophy and to keep controllers skinny.
Good to know: I'm using devise and devise-token-auth for authentication.
You need to use some kind of Authorization gem like cancancan. It is exactly what you need. Also it's else not elsif. elsif is followed by condition.
You can use github.com/varvet/pundit instead, for authorization.
It matches with the controller, instead of putting the authorization in the controller, you can use this to move out the authorization to another class.
I have used this across multiple Rails/Rails-API projects and didn't encounter a problem so far.
Instead of writing the code above. You can do this instead.
Also, prioritize early returns over nested ifs for readability.
In your controller.
class UsersController < ApplicationController
def index
authorize User # This will call the policy that matches this controller since this is UsersController it will call `UserPolicy`
#users = User.all
render :json => #users
end
def show
#user = User.find_by :id => params[:id] # Instead of using exists which query the data from db then finding it again, you can use find_by which will return nil if no records found.
if #user.blank?
return render :json => {:message => 'User not found.'}, :status => 404
end
authorize #user # This will call the policy that matches this controller since this is UsersController it will call `UserPolicy`
render :json => #user
end
end
In your Policy
class UserPolicy < ApplicationPolicy
def index?
#user.admin? # The policy is called in controller then this will check if the user is admin if not it will raise Pundit::NotAuthorizedError
end
def show?
#user.admin? || #record == #user # The policy is called in controller then this will check if the user is admin or the user is the same as the record he is accessing if not it will raise Pundit::NotAuthorizedError
end
end
In your ApplicationController
class ApplicationController < ActionController::API
include Pundit
rescue_from Pundit::NotAuthorizedError, :with => :show_forbidden
private
def show_forbidden exception
return render :json => {
:message => 'You are not authorized to perform this action.'
}, :status => 403
end
end
I'm working with validations in rails, stuff like:
validates_presence_of :some_field
I've noticed that if the validation fails, all changes are overwritten with existing values from the database. This makes some sense, as the page is basically being reloaded (as I gather from my development log), however this increases the risk of user error/frustration, as a single error in one field will require the hapless fellow to re-enter the changes he made to all fields.
My question: How can I get rails to reload the data that was just submitted if validation fails? That way, the user can correct the mistake without needing to re-enter the rest of his revisions.
Thanks for any advice.
Edit:
My update method, as requested, is as follows:
def update
#incorporation = Incorporation.find(params[:id])
#company = #incorporation.company
begin
#company.name="#{params[:company][:names_attributes].values.first["name_string"]} #{params[:company][:names_attributes].values.first["suffix"]}"
rescue NoMethodError
#company.name="Company #{#company.id} (Untitled)"
end
if #company.update(company_params)
redirect_to incorporations_index_path
else
redirect_to edit_incorporation_path(#incorporation)
end
end
Full disclosure regarding my controller: the above update is from my incorporations_controller even though I'm updating my Company model. Company has_one :incorporation. I did this because, in the larger context of my app, it made my associations much cleaner.
Update your controller to this
def update
#incorporation = Incorporation.find(params[:id])
#company = #incorporation.company
begin
#company.name="#{params[:company][:names_attributes].values.first["name_string"]} #{params[:company][:names_attributes].values.first["suffix"]}"
rescue NoMethodError
#company.name="Company #{#company.id} (Untitled)"
end
respond_to do |format|
if #company.update(company_params)
format.html { redirect_to({:action => "index"})}
else
format.html{render :edit}
format.json { render json: #incorporation.errors, status: :unprocessable_entity }
end
end
end
To add to the correct answer, you can clean up your code quite a bit:
def update
#incorporation = Incorporation.find params[:id]
respond_to do |format|
if #incorporation.update company_params
format.html { redirect_to({:action => "index"})}
else
format.html { render :edit }
format.json { render json: #incorporation.errors, status: :unprocessable_entity }
end
end
end
If you're using accepts_nested_attributes_for, you definitely should not hack the associated objects on the front-end.
You should look up fat model, skinny controller (let the model do the work):
#app/models/company.rb
class Company < ActiveRecord::Base
before_update :set_name
attr_accessor :name_string, :name_suffix
private
def set_name
if name_string && name_suffix
self[:name] = "#{name_string} #{name_suffix}"
else
self[:name] = "Company #{id} (Untitled)"
end
end
end
This will allow you to populate the name of the `company. To edit your nested/associated objects directly is an antipattern; a hack which will later come back to haunt you.
The key from the answer is: render :edit
Rendering the edit view means that your current #company / #incorporation data is maintained.
Redirecting will invoke a new instance of the controller, overriding the #incorporation, hence what you see on your front-end.
I'm new to wicked form and I was following the railcast episode on wicked forms but I keep receiving this error "Couldn't find Company with 'id'=info". So I know that the problem is clearly in my controllers somewhere. I know it's something super simple that I'm just racking my brain on so I know you guys will be a giant help. Here is the code, any and all help appreciated!
Code for companies Controller:
def create
#company = Company.new(company_params)
respond_to do |format|
if #company.save
#object = #company.id
format.html { redirect_to(company_steps_path(#company)) }
format.json { render :show, status: :created, location: #company }
else
format.html { render :new }
format.json { render json: #company.errors, status: :unprocessable_entity }
end
end
end
Code for company_steps Controller:
class CompanyStepsController < ApplicationController
include Wicked::Wizard
steps :info, :address, :quote
def show
#company = Company.find(params[:id])
render_wizard
end
def update
#company = Company.where(id: params[:id])
#company.attributes = params[:company]
render_wizard #company
end
end
When you use #find and the record is not found ActiveRecord raise a ActiveRecord::RecordNotFound with a message like "Couldn't find Company with id='somevalue'".
I assume your id column is of type integer and you pass a string.
In your #show method params[:id] == 'info'.
Check your link_to, redirect_to and routes.
At some point you generate this url http://localhost:3000/company_steps/info (probably in a view).
You do a GET request on it, which match GET "/company_steps/:id" company_steps#show.
The method #show is call in the controller CompanyStepsController with params[:id] == 'info'.
As we see previously you get a ActiveRecord::RecordNotFound exception because ActiveRecord can't find the record with a id 'info'.
The error is raise in your controller, but the problem is probably in your views or in a redirect. You need a id and you pass a string.
EDIT: as discussed in comments
Ok params[:id] == 'info' is generated by wicked.
They use id to control the flow of steps.
You need to use nested routes to have rails generate something like params[:company_id].
resources :companies do
resources :steps, controller: 'companies/steps'
end
So rake routes should give you:
/companies/:company_id/steps/:id
in the controller
params[:company_id] == 42
params[:id] == 'info'
https://github.com/schneems/wicked/wiki/Building-Partial-Objects-Step-by-Step
It's the first time I'm using this gem and it's driving me crazy with something as simple as authorize the showaction only for the resource owner.
I tried different ways, configuring the controller mapping and actions, but always get the unauthorized message for show, other actions work as they should.
It seems that showis not getting it's way to the ApplicationAuthorizer.
This is how it's configured:
class EnterpriseAuthorizer < ApplicationAuthorizer
# This works
def self.creatable_by?(user)
user.is_enterpriser?
end
# This doesn't
def readable_by?(user)
true # Just for testing
end
end
class EnterprisesController < ApplicationController
authorize_actions_for Enterprise
def show
#enterprise = Enterprise.find(params[:id])
respond_to do |format|
format.html
format.json { render json: #enterprise }
end
end
I have include Authority::UserAbilities in User and include Authority::Abilities in the Enterprise model. And User has_one :enterprise
Any idea? Thinking seriously about rolling back to cancan.
Thanks in advance.
Authority has different ways of checking permissions. For collection-based actions (e.g. new, create, index), you use authorize_actions_for Model.
For instance-based actions (e.g. edit, update, show, delete), you must call authorize_action_for #instance.
Change your code to this and it should work.
class EnterprisesController < ApplicationController
authorize_actions_for Enterprise
def show
#enterprise = Enterprise.find(params[:id])
authorize_action_for #enterprise
respond_to do |format|
format.html
format.json { render json: #enterprise }
end
end
end
If you want a less messy way to do this, put the
#enterprise = Enterprise.find(params[:id])
authorize_action_for #enterprise
into a before filter that's called by each instance action.
Rails 3.0.3
ruby 1.9.2p0
The Problem:
I have a Users table which has many items, the item(s) in turn therefore belongs to the Users.
In my model item.rb i attempt to save the item along with the value for the user.id so i have:
self.User_ID = #user.id
this however give me the error
Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
this is causing some confusion that it can't find this as in the show.html.erb that 'wraps' this page <%= #user.id %> displays the correct ID on the page
Many thanks in advance
** EDIT **
The Shorten action is the action upon which i want to parameter to be passed
class ItemsController < ApplicationController
def redirect
#item = Item.find_by_shortened(params[:shortened])
if #item
#redirect_to #item.original
redirect_to #item.original
else
redirect_to :shorten
end
end
def shorten
#host = request.host_with_port
#user = current_user
You need to load the #user model in every action that will require access to it. Having it render properly in the show action will not guarantee it is loaded in the update action.
Usually you need to have something like this in your controller:
class UsersController < ApplicationController
before_filter :load_user, :except => [ :index, :new, :create ]
# ...
protected
def load_user
#user = User.find(params[:user_id] || params[:id])
rescue ActiveRecord::RecordNotFound
render(:text => 'Record not found')
end
end