I am trying to find an elegant way to handle some shared validations between two controllers.
Example:
I have two Accounts controllers. One to process accounts associations to a user synchronously (using, for instance, a PORO that contains the logic for this case), and another for treating the association asynchronously with a worker. Please assume that the logic differs in each scenario and the fact that being sync/async isn't the only difference.
Then I have this two controllers:
module Accounts
class AssociationsController < ApplicationController
def create
return already_associated_account_error if user_has_some_account_associated?
# action = call some account association PORO
render json: action.response, status: action.status_code
end
private
def user_has_some_account_associated?
params[:accounts].any? { |account_number| user_account_associated?(account_number) }
end
def user_account_associated?(account_number)
current_user.accounts.exists?(number: account_number)
end
def already_associated_account_error
render json: 'You already have associated one or more accounts', status: :unprocessable_entity
end
end
end
Now I have another controller in which I'd want to apply the same validation:
module Accounts
class AsyncAssociationsController < ApplicationController
def create
return already_associated_account_error if user_has_some_account_associated?
# Perform asynchronously some account association WORKER
render json: 'Your request is being processed', status: :ok
end
private
def user_has_some_account_associated?
params[:accounts].any? { |account_number| user_account_associated?(account_number) }
end
def user_account_associated?(account_number)
current_user.accounts.exists?(number: account_number)
end
def already_associated_account_error
render json: 'You already have associated one or more accounts', status: :unprocessable_entity
end
end
end
...
HOW and WHERE could I place the validation logic in ONLY ONE SPOT and use it in both controllers? I think in extracting to a concern at first, but I'm not sure if they are intended for this cases of validation logic only.
For this you should use concerns. It's what's they are designed for.
Under the controllers directory make a concerns directory (if it isn't already there) and inside that make the file association_concern.rb with this content:
module AssociationConcern
extend ActiveSupport::Concern
private
def user_has_some_account_associated?
params[:accounts].any? { |account_number| user_account_associated?(account_number) }
end
def user_account_associated?(account_number)
current_user.accounts.exists?(number: account_number)
end
def already_associated_account_error
render json: 'You already have associated one or more accounts', status: :unprocessable_entity
end
end
Anything that is common to the controllers can go in the concern
Then in your controllers simply include AssociationConcern
class AssociationsController < ApplicationController
include AssociationConcern
def create
return already_associated_account_error if user_has_some_account_associated?
# action = call some account association PORO
render json: action.response, status: action.status_code
end
end
Make them inherit from some new controller and add a before_action, like this:
module Accounts
class AbstractAssociationsController < ApplicationController
before_action :check_for_associated_account, only: [:create]
def check_for_associated_account
if user_has_associated_account?
render json: 'You already have associated one or more accounts', status: :unprocessable_entity
end
end
end
end
module Accounts
class AssociationsController < AbstractAssociationsController
def create
# action = call some account association PORO
render json: action.response, status: action.status_code
end
end
end
Then, depending on whether the logic is really different, you can define the user_has_associated_account? in either this abstract controller, separate controllers or delegate it to some PORO class.
Related
I am working on rails and trying to make a simple blog site and its working the way i want to on my local machine but when pushed to production its being blocked by the callback functions.
My before_action :authorized_user? callback is being called and it prompts for logging if not logged in for performing any method on the blog , and if logged in all methods create, update and destroy methods are working perfectly in my development environment but in production even after the user is logged in also and when the create method is being called it asks for to log in . I am unable to understand from where or what code is causing this to happen because the same is working perfectly fine on local machine.
Any help will he highly appreciated.
My blog_controller.rb file is
class BlogsController < ApplicationController
before_action :set_blog, only: [:show, :update, :destroy, :lock_blog, :pin_blog]
before_action :authorized_user?, except: [:index, :show]
def index
#blogs = Blog.all
render json: { blogs: #blogs },status: :ok
end
def show
comments = #blog.comments.select("comments.*, users.username").joins(:user).by_created_at
render status: :ok, json: { blog: #blog, blog_creator: #blog.user, comments: comments }
end
def create
#blog = Blog.new(blog_params.merge(user_id: #current_user.id))
if authorized?
if #blog.save
render status: :ok,
json: {blog: #blog , notice: "Blog Successfully created"}
else
errors = #blog.errors.full_messages.to_sentence
render status: :unprocessable_entity, json: {error:errors}
end
end
end
def update
if authorized?
if #blog.update(blog_params)
render status: :ok,
json: {blog: #blog, notice:"Blog successfully updated"}
else
render status: :unprocessable_entity,
json: {errors: #blog.errors.full_messages.to_sentence}
end
else
handle_unauthorized
end
end
def destroy
if authorized?
if #blog.destroy
render status: :ok,
json: {notice:'Blog deleted'}
else
render status: :unprocessable_entity,
json: {errors: #blog.errors.full_messages.to_sentence}
end
else
handle_unauthorized
end
end
private
def set_blog
#blog = Blog.find(params[:id])
end
def blog_params
params.require(:blog).permit(:title,:body,:image,:is_pinned, :is_locked)
end
def authorized?
#blog.user_id == #current_user.id || #current_user.admin_level >= 1
end
def handle_unauthorized
unless authorized?
render json:{notice:"Not authorized to perform this task"}, status:401
end
end
end
and application_controller.rb file is
class ApplicationController < ActionController::Base
skip_before_action :verify_authenticity_token
include CurrentUserConcern
include ExceptionHandlerConcern
include TokenGenerator
def authorized_user?
render json: { notice: 'Please log in to continue' }, status: :unauthorized unless #current_user
end
def authorized_admin?
authorized_user?
render json: {errors: 'Insufficient Administrative Rights'}, status: 401
end
private
end
current_user_concern.rb file
module CurrentUserConcern
extend ActiveSupport::Concern
included do
before_action :set_current_user
end
def set_current_user
if session[:token]
#current_user = User.find_by(token: session[:token])
end
end
end
Its generally recommended to use libraries for authentication and authorization instead of reinventing the wheel unless its for learning purposes. They have many eyes looking for bugs and insecurites and are battle hardened by tons of users. Home-rolled authentication systems are a very common source of security breaches which could lead to very expensive consequences.
If you're going to roll your own authorization and authentication solution I would suggest you take a page from the libraries like Devise, Pundit and CanCanCan and raise an error when a user is not authorized or authenticated so that you immediately halt whatever the controller is doing and stop the callback chain from executing further.
# app/errors/authentication_error.rb
class AuthenticationError < StandardError; end
# app/errors/authorization_error.rb
class AuthorizationError < StandardError; end
# app/controllers/concerns/
module Authenticable
extend ActiveSupport::Concern
included do
helper_method :current_user, :user_signed_in?
before_action :authenticate_user
rescue_from AuthenticationError, with: :handle_unauthorized
end
def current_user
#current_user ||= find_user_from_token if session[:token].present?
end
def find_user_from_token
User.find_by(token: session[:token])
end
def user_signed_in?
current_user.present?
end
def authenticate_user
raise AuthenticationError.new('Please log in to continue') unless user_signed_in?
end
def handle_unauthenticated(error)
render json: {
notice: error.message
},
status: :unauthorized
end
end
end
# app/controllers/concerns/authorizable.rb
module Authorizable
extend ActiveSupport::Concern
included do
rescue_from AuthenticationError, with: :handle_unauthorized
end
def authorize_admin
raise UserAuthenticationError.new('Insufficient Administrative Rights') unless current_user.admin?
end
def handle_unauthorized(error)
render json:{
notice: error.message
}, status: :unauthorized
end
end
class ApplicationController < ActionController::Base
skip_before_action :verify_authenticity_token
include Authenticable
include Authorizable
# Should you really be mixing this into the controller? Seperate the responsibilites!
include TokenGenerator
end
It also makes debugging much easier as you can disable rescue_from in testing so that you get an exception instead of just a cryptic failure message.
You should also setup your authorization system so that it always authenticates (and authorizes) unless you explicitly opt out. This is a best practice that reduces the possible of security breaches simply due to programmer omission. You opt out by calling skip_before_action :authorize_user.
Instead of your set_current_user use a memoized getter method (current_user) to remove issues caused by the ordering of callbacks. ||= is conditional assignment and will prevent it from querying the database again if you have already fetched the user. This should be the ONLY method in the system that knows how the user is stored. Do not access #current_user directly to avoid leaking the implementation details into the rest of the application.
Methods ending with ? are by convention predicate methods in Ruby and should be expected to return a boolean. Name your modules by what their responsibility is and not what code they contain - avoid the postfix Concern as it tells you nothing about what it does.
I have a SessionsController which all other controllers in my application inherit from. I want to test both base controller methods.
The create rspec works fine, I call "post :create" because there is a route setup. The 2nd method "require_session" is a different story, there is no route. Instead we call this method as before action. What are some approaches I can take to test the require_session method?
class SessionsController < ApplicationController
before_action :require_session
def create
#...
end
def require_session
#...
end
def not_authorized
render json: "Not Authorized", status: 401
end
def server_error
render json: "Server Error (code 1)", status: 500
end
end
How about invoking SessionsController and calling it on that:
sc = SessionsController.new
sc.require_session
I am currently using Clearance from Throughbot for my authentication. I am needing to add an API to my product and can't seem to find docs about using Clearance with an API. Is there a certain Header I can set that Clearance will check automatically and if not what can I use? I think I may be able to use this.
To get around this I ended up overriding the authenticate methods on the ApplicationController and the User model. It looks something like this:
class ApplicationController < ActionController::Base
include Clearance::Controller
include Clearance::Authentication
def authenticate(params)
if request.headers['AUTH-TOKEN']
return nil unless user = User.where(remember_token: request.headers['AUTH-TOKEN']).first
sign_in user
else
User.authenticate(params[:session][:email], params[:session][:password])
end
end
#rest of class omitted for bevity
end
Then I subclasses SessionsController to override the create method like so:
class SessionsController < Clearance::SessionsController
def create
#user = authenticate(params)
sign_in(#user) do |status|
respond_to do |format|
if status.success?
format.html { redirect_back_or url_after_create }
format.json { render json: #user, status: :ok }
else
format.html do
flash.now.notice = status.failure_message
render template: 'sessions/new', status: :unauthorized
end
format.json { render json: [errors: status.failure_message], status: :unauthorized }
end
end
end
end
#rest of class omitted for bevity
end
Then all you have to do to test or use is set the requests AUTH-TOKEN header to the users remember token and you're all set. I chose to use the remember token because it is updated whenever the user logs out. You may not want this to happen and could instead generate a auth_token field on your model and change the where to use the new field.
I'm stuck at defining a custom validation method that's purpose is to verify uniqueness of a property across two models
I realize this is bad code, but i wanted to get the test passing before refactor
here is a model with the custom validation to check another model property, error undefined local variable or method `params' (be gentle I'm still trying to figure out RoR)
class Widget < ActiveRecord::Base
include Slugable
validates :name, presence: true
validate :uniqueness_of_a_slug_across_models
def uniqueness_of_a_slug_across_models
#sprocket = Sprocket.where(slug: params[:widget_slug]).first
if #sprocket.present?
errors.add(:uniqueness_of_a_slug_across_models, "can't be shared slug")
end
end
end
You don't have access to params in a model. It belongs to controller and view. What you could do is to call custom method in widgets controller (instead of regular save) in order to pass params to a model:
class WidgetsController < ActionController::Base
def create
#widget = Widget.new(widget_params)
if #widget.save_with_slug_validation(params)
redirect_to widgets_path
else
render :new
end
end
end
and define it:
class Widget < ActiveRecord::Base
# ...
def save_with_slug_validation(params)
sprocket = Sprocket.find_by(slug: params[:widget_slug])
if sprocket
errors.add(:uniqueness_of_a_slug_across_models, "can't be shared slug")
end
save
end
end
I didn't test it but it should work.
P.S. Rails 4 style is used.
UPD
I should have tested it, sorry. Please use another approach.
Widgets controller:
# POST /widgets
# POST /widgets.json
def create
#widget = widget.new(widget_params)
#widget.has_sprocket! if Sprocket.find_by(slug: params[:widget_slug])
respond_to do |format|
if #widget.save
format.html { redirect_to [:admin, #widget], notice: 'widget was successfully created.' }
format.json { render action: 'show', status: :created, location: #widget }
else
format.html { render action: 'new' }
format.json { render json: #widget.errors, status: :unprocessable_entity }
end
end
end
Widget model:
class Widget < ActiveRecord::Base
include Slugable
validates :name, presence: true
validate :uniqueness_of_a_slug_across_models, if: 'has_sprocket?'
def uniqueness_of_a_slug_across_models
errors.add(:uniqueness_of_a_slug_across_models, "can't be shared slug")
end
def has_sprocket!
#has_sprocket = true
end
def has_sprocket?
!!#has_sprocket
end
end
It would be better to move has_sprocket! and has_sprocket? methods and maybe validation itself to Slugable concern.
I am currently working on a web application built on Rails 3 that heavily uses Ajax/REST for the client side. Thus, I often find myself writing controller actions like this:
def create
if !params[:name]
respond_to do |format|
format.html { render json: {}, status: :not_found }
format.json { render json: {}, status: :not_found }
end
return
end
account = ...
respond_to do |format|
format.html { render json: account }
format.json { render json: account }
end
end
Nearly all of my actions are returning a json object in a success case or an error code. However, I always have to write this verbose respond_to block and a return, if I want the action to return earlier.
Instead I would like to use something like this instead, or a similar alternative:
def create
if !params[:name]
throw :not_found
end
account = ...
return account
end
How can this be done with Rails 3+ ?
Have a look into inherited_resources. This will allow you to rewrite your controller as:
class SomeController < ApplicationController
inherit_resources
respond_to :html, :js, :json
end
That is it. All of your create/read/update/delete methods will be accessible as usual. You can, as I have in the past, inherit from a master resources controller which uses inherited_resources, and then you can tweak the responses in a more general way.
class ResourcesController < ApplicationController
inherit_resources
respond_to :html, :js
def create
create! do |format|
format.js do
# generic code here for managing all create methods initiated via js
# current model is avialbe via 'resource'
# e.g 'resource.errors'
end
end
end
Then simply inherit from that controller:
class SomeController < ResourcesController
end
This abstraction can be overkill for most purposes, but it has come in extremely handy when working 30 or 40 models which all require similar controllers.
Inherited_resources offers many helpers for accessing the current model (referred to as resource) to facilitate dynamic references, so you can, for example, return relevant forms, or partials based on resource/model name.
To give you an idea of how to use this, you could return forms for the current controller by using the controller name in the parameters. Should be noted that malformed controller names will not reach this method (as it will return 404), so it is safe to use:
format.js do
render "#{params[:controller]}/form"
end
Best of all, you can override any of the methods yourself by defining them in a particular controller.
If your are always returning json, you can ommit the respond_to block and write it like :
def create
if !params[:name]
render json: {}, status: :not_found
return
end
account = ...
render json: account
end