Rails 3 set default actions for response - ruby-on-rails

In my application i need to set some deafult actions for all format.js and format.htm responses. At this moment I have something like this in all controllers:
def index
#users = User.all
respond_to do |format|
format.html {html_response}
format.js {js_response}
end
end
But I think that it isn't a good solution. What can I do?

Make a private method in your ApplicationController and call it from wherever required
class ApplicationController < ActionController::Base
…
private
def default_responses
respond_to do |format|
format.html {html_response}
format.js {js_response}
end
end
end
class SomethingsController < ApplicationController
def index
#somethings = Something.all
default_responses
end
end

Related

What is the DRY way to restrict an entire controller with Pundit in Rails?

I'm using Pundit with Rails, and I have a controller that I need to completely restrict from a specific user role. My roles are "Staff" and "Consumer." The staff should have full access to the controller, but the consumers should have no access.
Is there a way to do this that is more DRY than restricting each action one-by-one?
For instance, here is my policy:
class MaterialPolicy < ApplicationPolicy
attr_reader :user, :material
def initialize(user, material)
#user = user
#material = material
end
def index?
user.staff?
end
def show?
index?
end
def new?
index?
end
def edit?
index?
end
def create?
index?
end
def update?
create?
end
def destroy?
update?
end
end
And my controller:
class MaterialsController < ApplicationController
before_action :set_material, only: [:show, :edit, :update, :destroy]
# GET /materials
def index
#materials = Material.all
authorize #materials
end
# GET /materials/1
def show
authorize #material
end
# GET /materials/new
def new
#material = Material.new
authorize #material
end
# GET /materials/1/edit
def edit
authorize #material
end
# POST /materials
def create
#material = Material.new(material_params)
authorize #material
respond_to do |format|
if #material.save
format.html { redirect_to #material, notice: 'Material was successfully created.' }
else
format.html { render :new }
end
end
end
# PATCH/PUT /materials/1
def update
authorize #material
respond_to do |format|
if #material.update(material_params)
format.html { redirect_to #material, notice: 'Material was successfully updated.' }
else
format.html { render :edit }
end
end
end
# DELETE /materials/1
def destroy
authorize #material
#material.destroy
respond_to do |format|
format.html { redirect_to materials_url, notice: 'Material was successfully destroyed.' }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_material
#material = Material.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def material_params
params.require(:material).permit(:name)
end
end
Is there a way to do this that I'm not understanding, or is that how Pundit is designed, to require you to be explicit?
The first step is just to move the call to authorize to your callback:
def set_material
#material = Material.find(params[:id])
authorize #material
end
You can also write #material = authorize Material.find(params[:id]) if your Pundit version is up to date (previous versions returned true/false instead of the record).
Pundit has a huge amount of flexibility in how you choose to use it. You could for example create a separate headless policy:
class StaffPolicy < ApplicationPolicy
# the second argument is just a symbol (:staff) and is not actually used
def initialize(user, symbol)
#user = user
end
def access?
user.staff?
end
end
And then use this in a callback to authorize the entire controller:
class MaterialsController < ApplicationController
before_action :authorize_staff
# ...
def authorize_staff
authorize :staff, :access?
end
end
Or you can just use inheritance or mixins to dry your policy class:
class StaffPolicy < ApplicationPolicy
%i[ show? index? new? create? edit? update? delete? ].each do |name|
define_method name do
user.staff?
end
end
end
class MaterialPolicy < StaffPolicy
# this is how you would add additional restraints in a subclass
def show?
super && some_other_condition
end
end
Pundit is after all just plain old Ruby OOP.
Pundit doesn't require you to be explicit, but it allows it. If the index? method in your policy wasn't duplicated, you'd want the ability to be explicit.
You can start by looking at moving some of the authorization checks into the set_material method, that cuts down over half of the checks.
The other half could be abstracted out into other private methods if you wanted, but I think they're fine as-is.
You could also look at adding a before_action callback to call the authorizer based on the action name, after you've memoized #material via your other callback, but readability is likely to suffer.
Use the second argument for the authorize method. Eg:
authorize #material, :index?
You can now remove all the other methods that just calls index?

Basic rails crud in ajax with devise

I have a basic CRUD in rails and ajax, but I need to integrate Devise gem, when adding devise gem to my project and test the CRUD, I get this error:
the controller code in rails 4.2:
class ProductsController < ApplicationController
before_action :authenticate_user!
before_action :product_find, only: [:show, :update, :destroy, :edit]
def index
#products = Product.all.order('created_at DESC')
end
def show
end
def new
#product = Product.new
respond_to do |format|
format.html { render layout: false }
format.json { render json: #product }
format.js
end
end
def create
#product = current_user.products.build(product_params)
respond_to do |format|
if #product.save
format.json { render :show, status: :created, location: #product}
format.js
else
format.html { render :new }
format.json { render json: #product.errors, status: :unprocessable_entity }
format.js
end
end
end
def edit
end
def update
#product = Product.update(params[:id], product_params)
end
def destroy
#product.destroy
end
private
def product_find
#product = Product.where(id: params[:id]).first
end
def product_params
params.require(:product).permit(:name, :quantity, :price)
end
end
The user can connect without problems, the error occurs when I create a new product with the registered user
what am I doing wrong?
I think the error may be in the CREATE and NEW method
thank!
Have you set up the associations correctly for your models? The error undefined method 'products' for <User> suggests that you haven't.
# user.rb
has_many :products
# product.rb
belongs_to :user
Make sure you also have a user_id field on the Product model.

Couldn't find Post without an ID POST

I am receiving this error:
ActiveRecord::RecordNotFound in MainController#compare Couldn't find
Post without an ID
Here is my code for main controller:
#job_one = Post.find(params[:j1])
#job_two = Post.find(params[:j2])
UPDATE #1:
main_controller.rb
class MainController < ApplicationController
skip_before_action :authenticate_user!
def index
#posts = Post.all.order(id: :asc)
end
def dummy
end
def editor
end
def compare
#job_one = Post.find(params[:j1])
#job_two = Post.find(params[:j2])
respond_to do |format|
format.html
format.pdf do
pdf = Prawn::Document.new
pdf.text "Hello World"
send_data pdf.render
end
end
end
end
What I am doing is comparing two posts that are made side by side. So these are the two posts I chose from the index page.
Can anyone off some assistance?

Putting a respond_to method inside a before_action callback

I guess you can consider this a continuation from my previous question. Basically I'm rendering a js.erb partial which enables ajax functionality to like/dislike a restaurant dish. I have four actions that render this partial:
class DishesController < ApplicationController
def like
#dish.liked_by current_user
respond_to do |format|
format.js { render partial: "dishes/shared/vote.js.erb" }
end
end
def unlike
#dish.unliked_by current_user
respond_to do |format|
format.js { render partial: "dishes/shared/vote.js.erb" }
end
end
...
end
To DRY this up I planned to put the respond_to method inside of a before_action callback:
class DishesController < ApplicationController
before_action :render_vote_partial
...
private
...
def render_vote_partial
respond_to do |format|
format.js { render partial: "dishes/shared/vote.js.erb" }
end
end
end
Unfortunately this doesn't render the partial at all. What am I doing wrong?
If you want to dry up the respond_to, just stick it in a method (like you have already) and call that method after each action.
Instead of
before_action :render_vote_partial
just do do:
def like
# do your work here ...
render_vote_partial
end
def unlike
# do your work here ...
render_vote_partial
end

Why redirect_to in around_filter or after_filter won't work?

How to make redirect_to works in those filters?
I'm trying to change
def start
....
redirect_to index
end
def end
...
redirect_to index
end
to
around_filter :around
def around
...
yield
redirect_to index
end
def start
..
end
def stop
...
end
After the action is complete it renders the template automatically, thus you cannot render / redirect after the request is complete. You could solve this by putting the redirect_to at the end of the actions that you need it for. This is not what around_filters were designed to do.
Presumably, your actions already have a redirect_to or render call. You cannot call these methods twice per request.
You can change response.location, which has the same effect as calling redirect_to. An example with an after_filter (the same could be done with around):
after_filter :different_redirect, only:[:create]
def different_redirect
if self.status == 302
response.location = other_thing_path
end
end
def create
#my_thing = MyThing.new(params[:my_thing])
respond_to do |format|
if #my_thing.save
format.html { redirect_to(my_things_path) }
else
format.html { render :action => "new" }
end
end
end

Resources