How to write custom method in RoR? - ruby-on-rails

How do I write this method in RoR?
if item is nil or item.user!='my_value' redirect_to "/"
Want to call it like so:
#item = Item.find(params[:id])
method_described_earlier(#item)
Or other way.
Want to see how rails pro would do it.

You should use a before_filter, which will run before any set of controller actions you indicate. For example:
class SomeController < ApplicationController
before_filter :require_special_item, only: [:myaction, :other_action, ...]
def myaction
# do something with #item
end
private
def require_special_item
#item = Item.find(params[:id])
if #item is nil or #item.user!='my_value'
redirect_to "/"
end
end
end
The method require_special_item will always run before myaction and any other actions you indicate, redirecting the user if the found item doesn't meet your requirements.

Related

Ruby on Rails Controller Instance Variable not shared

My 'new' action generates the cart object #cart out of a session. When I call the 'update' action via AJAX the #cart object doesn't exist. Why is it not shared across the controller?
cart_controller.rb
def new
#cart = Cart.new(session[:cart])
end
def update
logger.debug #cart.present? # false
end
#cart is an instance variable and it is not persisted between requests. And session is accessible between requests.
Basically if you have set some data into session then you can use that data between requests. As it was mentioned you can setup a before_filter and preset #cart instance variable before executing the update action.
class MyController < ApplicationController
before_action :instantiate_cart, only: [:update] #is the list of actions you want to affect with this `before_action` method
...
private
def instantiate_cart
#cart = Cart.new(session[:cart])
end
end
Instance variables (starting with #) are not shared between requests (or across controller actions). You can denote a method in order to get cart. Here is an example:
def new
cart
end
def update
logger.debug cart.present?
end
private
def cart
#cart ||= Cart.new(session[:cart])
end
The instance variables can't be shared across the controller. They are available to the actions where they are defined. So you can't use #cart in update action since you didn't define it.
def new
#cart = Cart.new(session[:cart])
end
def update
#cart = Cart.new(session[:cart])
logger.debug #cart.present?
end
To DRY the code, use before_action to set the cart
before_action :set_cart, only: [:new, :update]
def new
end
def update
logger.debug #cart.present?
end
private
def set_cart
#cart = Cart.new(session[:cart])
end
TL;DR: controller instance variables are not shared across different HTTP requests as each request create a new instance of the controller.
Conceptually what you are expecting should have been correct! You are defining an instance variable and you should have access to it everywhere across the class.
The problem is that on every HTTP request, a new instance of the class is being created.
So when you hit the new action an instance of the controller will be initiated, new method will be called and #cart will be created and assigned. Something like:
# HTTP request /new
controller = MyController.new # an object of your controller is created
controller.new # the requested action is called and #cart is assigned
But when you make a new HTTP request to update a new instance of the controller will be initiated, update method will be called and it has no #cart!
# HTTP request /update
controller1 = MyController.new # an object of your controller is created
controller1.new # the requested action is called and #cart is not assigned 😱
As you can see controller and controller1 are two different objects initiated from MyController as this took place in two different HTTP requests (different contexts).
To fix your issue you need to create #cart for each action when it's needed something like:
def new
cart
end
def update
logger.debug cart.present?
end
private
def cart
#cart ||= Cart.new(session[:cart])
end
I would suggest using before_action to create the instance of #cart, in the case the #cart instance variable will be visible to new and update actions.
before_action :get_cart, only: [:new, :update]
private
def get_cart
#cart = Cart.new(session[:cart])
end
If you don't want to use action callbacks, another alternative is calling get_cart method directly to new and update actions. Since, get_cart returns the instance #cart. As reference to this you can see this link

How to auto-assign value to the permitted params in Rails controller create method

I have Ruby On Rails application. I have created method in my CommentsController and I want to always auto-assign value to one of the permitted fields (in my case its :commenter)"
class CommentsController < ApplicationController
def create
#meme = Meme.find(params[:meme_id])
#comment = #meme.comments.create(comment_params)
redirect_to meme_path(#meme)
end
private
def comment_params
params.require(:comment).permit(:commenter, :body)
end
end
How can I achieve this?
At first, you should authenticate a user. Since the current user is authenticated there is no need to pass him in parameters. You can do something like this:
class CommentsController < ApplicationController
before_action :authenticate_user!
def create
#meme = Meme.find(params[:meme_id])
#comment = #meme.comments.create(comment_params)
redirect_to meme_path(#meme)
end
private
def comment_params
params.require(:comment).permit(:body).merge(commenter: current_user)
end
end
Merging it along with the comment_params, you can add more params that you want as key and values in the second hash
def comment_params
params.require(:comment).permit(:body).merge!({commenter: 'default_value'})
end
You can use tap:
def comment_params
params.require(:comment).permit(:commenter, :body).tap do |comment|
comment[:commenter] = "some value here"
comment[:any_other_key] = "some other value"
end
end
Could you add t.string :commenter, default: "default_commenter" in your migrations. That way if you are working on the console then you won't get blank values. It will also clean up the controller code.

How to render json for all actions from the after_action filter in ApplicationController?

Is it possible to create an after_filter method in the Rails ApplicationController that runs on every action and renders to JSON? I'm scaffolding out an API, and I'd like to render output to JSON for every action in the controller.
clients_controller.rb
def index
#response = Client.all
end
application_controller.rb
...
after_action :render_json
def render_json
render json: #response
end
The after_action is never executed, and the code aborts with:
Template is missing. Missing template clients/index, ...
If the render json: #response is moved into the controller action, it works correctly.
Is there a filter that will allow me to DRY up the controllers and move the render calls to the base controller?
You can't render after_action/ after_filter. The callback after_action is for doing stuff after rendering. So rendering in after_action is too late.
But your exception is just because you miss the JSON template. I recommend using RABL (which offers a lot of flexibility to your JSON responses and there is also a Railscast about it). Then your controller could look like:
class ClientsController < ApplicationController
def index
#clients = Client.all
end
def show
#client = Client.find params[:id]
end
end
And don't forget to create your rabl templates.
e.g. clients/index.rabl:
collection #clients, :object_root => false
attributes :id
node(:fancy_client_name) { |attribute| attribute.client_method_generating_a_fancy_name }
But in the case you still want to be more declarative you can take advantage of the ActionController::MimeResponds.respond_to like:
class ClientsController < ApplicationController
respond_to :json, :html
def index
#clients = Client.all
respond_with(#clients)
end
def show
#client = Client.find params[:id]
respond_with(#client)
end
end
Btw. beware if you put code in an after_action, this will delay the whole request.

how to make clean code in controller rails

how to make this code clean in rails?
profiles_controller.rb :
class ProfilesController < ApplicationController
before_action :find_profile, only: [:edit, :update]
def index
#profiles = Profile.all
end
def new
#profile = Profile.new
end
def create
profile, message = Profile.create_object(params["profile"], current_user)
flash[:notice] = message
redirect_to profile_url
end
def edit
end
def update
profile, message = #profile.update_object(params["profile"])
flash[:notice] = message
redirect_to profile_url
end
private
def find_profile
#profile = Profile.friendly.find(params["id"])
end
end
i look flash[:notice] and redirct_to profile_url is duplicate in my code, how to make the code to clean and dry?
How about moving the repetitive code to a separate method and call that method inside the actions.
def flash_redirect # you can come up with a better name
flash[:notice] = message
redirect_to profile_url
end
then in update action:
def update
profile, message = #profile.update_object(params["profile"])
flash_redirect
end
do the same thing for create action
UPDATE:
in case you are wondering about usingafter_action, you can't use it to redirect as the call-back is appended after the action runs out its course. see this answer
Take a look at Inherited Resources. It's based on the fact that many CRUD controllers in Rails have the exact same general structure. It does most of the work for you and is fully customisable in case things are done a little different in your controllers.
Using this gem, your code would look like this:
class ProfilesController < InheritedResources::Base
def create
redirect_to_profile(*Profile.create_object(params[:profile], current_user))
end
def update
redirect_to_profile(*#profile.update_object(params[:profile]))
end
private
def redirect_to_profile(profile, message)
redirect_to(profile_url, notice: message)
end
def resource
#profile ||= Profile.friendly.find(params[:id])
end
end
The create and update methods return multiple values, so I used the splat operator to DRY this up.
create_object and update_object don't follow the Rails default, so we need to implement those actions for Inherited Resources instead. Currently they don't seem to be handling validation errors. If you can, refactor them to use ActiveRecord's save and update, it would make everything even easier and DRYer.

Rails if conditional in controller error

I'm wondering how can I print on the index of my project only the rooms with the :is_available column or the rooms table with the :true value (is boolean).
I can't figure out how to achieve this (Sorry but I'm new with Rails). Any advice will be very appreciate!
I've this error with my current code:
"ActiveRecord::RecordNotFound in RoomsController#home
Couldn't find Room without an ID"
Here is my rooms_controller code:
class RoomsController < ApplicationController
before_action :get_room, only: [:index, :home]
def index
end
def show
#room = Room.find(params[:id])
end
def home
if params[:set_locale]
redirect_to root_url(locale: params[:set_locale])
else
puts #rooms if Room.all(params[:is_available => :true])
end
end
def get_room
#rooms = Room.all
end
end
You already have got #rooms = Room.all, you just need to precise your query (from all to your is_available restriction).
def home
if params[:set_locale]
redirect_to root_url(locale: params[:set_locale])
else
puts #rooms.where(is_available: true)
end
end
Also, you should avoid using puts in your controller logic. Either pass variable to the view (you can change #rooms value or create new variable #available_rooms), respond_with it or log it using Rails.logger if you use puts as a debugging solution.
def index
end
def home
if params[:set_locale]
redirect_to root_url(locale: params[:set_locale])
elsif params[:is_available]
puts #rooms
end
end
def get_room
#rooms = Room.where(is_available: true)
end
Using puts in controller - not a good idea.Use view to show the data.
There are several issues you may have:
Routes
Your index method looks empty. I presume you're using "home" as a substitute
In this case, you have to know what type of action this is - a member or collection action? The reason this is important is that when you define your routes, you have to ensure you define the route in the right way. For your home route, I'd have done this:
#config/routes.rb
resources :rooms do
get "home", action: "home"
end
Scopes
You can use a scope to bring back all the values with :is_available present. This lives in the model like this:
#app/models/room.rb
Class Room < ActiveRecord::Base
scope :is_available?, -> { where(is_available: true) }
end
This will allow you to call
#room = Room.is_available?
Code
Although you've not given us any context of the error (when it happens, what you do to make it happen), this is what I would do to help fix it:
#app/controllers/rooms_controller.rb
def home
if params[:set_locale]
redirect_to root_url(locale: params[:set_locale])
else
puts Room.is_available?
end
end
This may change depending the params you send & how you send them
def home
if params[:set_locale]
redirect_to root_url(locale: params[:set_locale])
else
puts #rooms if params[:is_available] && Room.where(is_available: true)
end
end
should work.

Resources