Accessing one controller variable in another contr

2020-05-27 13:36发布

I have a C programming background and I am learning Ruby/Rails for a side project. Currently I am struggling to understand how the scoping of variables works. Here is an example of a problem I am hitting. I have a User and a Product model. In the Product DB I store different products and one of the fields is a userid field that matches the userid stored in the User table. After the user logs in - I display the user's name, email etc (this is stored in @current_user) and then have a link to show different products owned by the user. I do this via the following code in users/show.html.erb like this: ...

Name: <%= @current_user.name %>

... <%= link_to "Products", products_path, id: "products" %>

So far so good. When I click the "Products" link it takes me to products/index.html.erb and the following code gets executed (I want to filter this display to show only products owned by @current_user.userid. How do I do this? The following code gives me a runtime error as @current_user evaluates to nil I believe. If I remove the "if" clause then all products ar displayed which is not what I want. Would be grateful for any tips.

<tbody>
<% @products.each do |product| %>
  <tr>
    <td><%= product.userid if product.userid == @current_user.userid %></td>
    <td><%= product.productName if product.userid == @current_user.userid %></td>
    <td><%= product.productLink if product.userid == @current_user.userid %></td>

Thanks,

3条回答
一夜七次
2楼-- · 2020-05-27 14:16

You have to initialize @current_user on each request, not only after the user logs in. Controller instance variables do not persist across requests.

Here's a common way to do this:

  1. Store the user ID in the session.
  2. Create a "before" filter in ApplicationController that retrieves the user ID from the session and initializes the instance variable.
查看更多
forever°为你锁心
3楼-- · 2020-05-27 14:27

I was going to make this a comment, but I think it is worthy of an answer. The thing to understand about not just rails, but the entire internets, is that HTTP is stateless. This means that in your controller you grab the user's information, build them a webpage with all of their stuff from the database, and send it out to them. Then they do something on the page and send a request in for another page or more data, and your rails app is like "Oh hai! Who are you?" Because all those variables that you filled up with nice information are just gone. Every time your app receives a request, it makes a brand new instance of the controller it is using, and then gets rid of it afterwards, along with everything inside it. And this is important, because there might be thousands, or millions of people currently logged into your ultimate frisbee league website trying to figure out who is playing where on Saturday. And you want to give the right info to the right person, you can't assume that the person you last sent information to is the same person asking you for new info now.

So we need to use the session, some information that gets passed to the client browser every time you send out a page, and the browser sends it back to you with every request. You put enough info in the session so that you can use it to rebuild what you need from the server when you get a response. I'm going to make this real simple and then refer you to the Rails Security Guide where you can get some more important information. Actually, I'll just use their example:

You can add the user id to the session this way:

session[:user_id] = @current_user.id

And then get it back this way:

@current_user = User.find(session[:user_id])

And then you use your user model (in this case) to get the specific info you need from the database. Here's another link to the Rails guides because it's so nice, I'm linking it twice: http://guides.rubyonrails.org/security.html#what-are-sessions-questionmark

If you are doing something that requires a password you can use a gem like devise, instructions here, but be warned, it totally hijacks your user model, your sessions controller, some views, basically all things related to logging in and identity. It isn't easy to customize, but it makes all this stuff and things much more complicated very simple to implement. If you don't need any usernames or passwords and you are doing something real simple just skip the special gems and use the session yourself.

查看更多
干净又极端
4楼-- · 2020-05-27 14:33

An instance variable set in one action can't be accessed in another action. As mentioned, you will need to store the user_id in the session hash. In the controller that handles your user sign_in, you will have to do something like this:

#in SessionsController(for example)

def create
  user = User.find_by(email: params[:session][:email].downcase)
  if user && user.authenticate # logic for authentication
    session[:user_id] = user.id
    redirect_to root_path, notice: "Successful sign in."
  else
    flash.now[:error] = "Invalid email/password combination"
    render :new
  end
end

Take note of assigning the user_id to the session key :user_id in the session hash. Now, in application_helper or sessions_helper define current_user so that it's accessible to all views:

def current_user
  User.find(session[:user_id]) if session[:user_id]
end

Now to iterate through all of a certain user's products:

<tbody>
<% current_user.products.each do |product| %>
  <tr>
    <td><%= product.userid %></td>
    <td><%= product.product_name %></td>
    <td><%= product.product_link %></td>

As long as you have a has_many :products association in User and Product model has belongs_to :user, you should be able to call all of a user's products by just doing user.products like above. Notice too that I used 'snake_case' for the methods/attributes of product. It's the convention in Ruby to use snake_case for method names and variables. If you named your columns productName and productLink, you'll have to rename them though to be able to use 'snake_case'. In Ruby, CamelCase is used for Class and ModuleNames.

Hope that helps!

查看更多
登录 后发表回答