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
Related
So I am using omniauth-facebook to create a log in using Facebook.
Here is my sessions_controller.rb
class SessionsController < ApplicationController
def create
user = User.from_omniauth(env["omniauth.auth"])
session[:user_id] = user.id
redirect_to "/sessions/menu"
end
def destroy
session[:user_id] = nil
redirect_to root_url
end
def new
end
def menu
puts user.name
end
end
Unfortunately I don't know how to access the user variable in the menu action. How would you guys recommend I do this?
Update
class SessionsController < ApplicationController
def create
#user = User.from_omniauth(env["omniauth.auth"])
session[:user_id] = #user.id
redirect_to "/sessions/menu"
end
def destroy
session[:user_id] = nil
redirect_to root_url
end
def new
end
def menu
puts #user
end
end
Even when I update it like so, it doesn't work
Unfortunately I don't know how to access the user variable in the menu
action. How would you guys recommend I do this?
Every time a request is made in your app for actions in SessionsController, a new instance of the SessionsController is created. So, instance variable set during create action would not be available when request for menu action is called as now you have a new instance of SessionsController which is why #user set in create action would not be available in menu. Also, if you use user (local variable) in this case, then its always local to the method/action in which its defined. So even that would not be available in menu.
By using facebook-omniauth gem you would receive Auth Hash in env["omniauth.auth"] which in turn you would use to create(new user) or initialize(in case of existing user) a user hopefully in from_omniauth method.
NOTE: env is request specific so env["omniauth.auth"] value will be present in create action but not in menu.
To resolve this, i.e., to access the created or initialized facebook user in menu action, you should make use of the user_id that you stored in session as below:
def menu
if session[:user_id]
#user = User.find(session[:user_id])
end
end
Also, if you would like to access the user in other actions as well then I would recommend to reuse the code by using before_action callback:
class SessionsController < ApplicationController
## Pass specific actions to "only" option
before_action :set_user, only: [:menu, :action1, :action2]
#...
def menu
puts #user.name
end
private
def set_user
if session[:user_id]
#user = User.find(session[:user_id])
end
end
end
where you can add specific actions via :only option
I'm making a practice app where I implement a resource (Task) manually instead of using Rails' built-in functionality.
The index page lists tasks and has a form to create a new task. If there is an error when submitting, the index page is rendered and the errors are displayed on the page.
My question is: Does every instance variable assignment in the index action need to be copied over to the create action so that it knows how to render the index page? Or is there some way to pass just the #task instance variable to the index action and let it take care of everything else?
Right now I'm only copying over one line from the index action to the create action:
#tasks = Task.all
but I imagine in more complex apps you may have many instance variables in a single action, so I would guess there is some alternative to copying them over to all actions that render the same page.
In your controller do :
before_filter :set_instances, only: [:index, :create]
private
def set instances
#tasks = Task.all
#task = Task.new
...
end
You can use filters on and call them only on new create e.i
class TasksController < ApplicationController
before_filter :set_tasks,:only => [:index, :new, :create]
def index
...
end
def new
...
end
def create
.....
end
def set_tasks
#tasks = Task.all
#task = Task.new
end
end
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.
Note: I'm using Rails 3.2
I'm trying to implement a simple_form by following this sample code: https://github.com/rafaelfranca/simple_form-bootstrap/blob/master/app/controllers/articles_controller.rb. I have a Summary model instead of an Article model.
I understand most of what's going on, except for the two private methods. I tried basically copying the code except for the two private methods, and what it does is it creates a new Summary, but the attributes are all nil.
1) How are attributes saved? What's the difference between .new and .save?
2) What is available in the create action? When you reach the create action, you've just filled out a form, so something must be available, but I don't know what it is, or how it becomes available.
3) What's going on with the before_action and the two private methods?
Summaries Controller
class SummariesController < ApplicationController
before_filter :set_summary, only: [:show, :edit, :update, :destroy]
def index
#summaries = Summary.all
end
def show
end
def new
#summary = Summary.new
end
def edit
end
def create
#summary = Summary.new(params[:summary])
if #summary.save
redirect_to #summary, notice: 'Summary created.'
else
render :new
end
end
def update
if #summary.update(params[:summary])
redirect_to #summary, notice: 'Summary updated.'
else
render :edit
end
end
def destroy
#summary.destroy
redirect_to summaries_url, notice: 'Summary destroyed'
end
private
# sets #summary to make available for show, edit, update
# and destroy actions so code isn't repetitive
def set_summary
#summary = Summary.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
# def summary_params
# params[:summary].permit!
#end
end
To answer your questions:
1) new is a Ruby method for initializing a new instance of an object. So when you call Article.new, you get a new instance of the Article class.
save is a Rails method for saving a record to the database. If your object instance is instantiated with new, it calls create under the hood. If you loaded the object through a finder (find, all, where, etc.), then it will call update under the hood instead of create.
2) A hash named params is available in all actions (and in the view). In the case of the create action, params[:article], which is the data posted by the form. At the top of create, try calling raise params[:article].to_yaml to see what's going on in there.
Heck, even try raise params.to_yaml to see what's in there.
3) The call to before_action runs the set_article private method before the show, edit, update, and destroy actions. Without that, you'd need to manually call #article = Article.find(params[:id]) in every single one of those actions. So this eliminates quite a bit of repetition!
You'll notice that the first line of the create action calls the article_params method. This is a common way of implementing what are called strong parameters in Rails. Strong parameters are new in Rails 4 but can be added to earlier versions of Rails via the strong_parameters gem.
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.