user9454269
user9454269

Reputation:

Ruby on Rails - Linking a user and product controller together, is not working

UPDATE 2

What I have done so far was rails generate migration add_user_reference_to_products

Then I did

class AddUserReferenceToProducts < ActiveRecord::Migration[5.1] def change add_reference :products, :user, foreign_key: true end end

But I am obtaining the PG::DuplicateColumn: ERROR: column "user_id" of relation "request_items" already exists.

When I reference this (as @products.user_id) in the view, it's blank. I've tried creating a new product, but it isn't working. What should I be doing, as I just can't , for the life of me now, figure out what to do now.

UPDATE

I want to do something like this @products.users.username (If there is another way of doing this, please tell me) to display a user's username, for the product they have created.

Currently, I have not modified the code as outlined below, but if any more information is required please tell me. Also, if you are able to provide a step by step process, I would appreciate that.

The answer provided Clyde doesn't work for me (undefined method "username").

QUESTION: How can I link the user controller with the product controller, despite already defining this relationship in the model? (A user can create many products, and a product belongs to a user)

I have a website, where users can create products. Now, these users also have a profile page. I want to know if it's possible to have it so when the user creates the product ,and I click the product itself, it will show which user created it and when I click the user name, it directs to their profile page.

Currently, the only thing that isn't working is the user name is not appearing on the product they have created, the routes and everything else is working.

For users, relevant code:

class UsersController < ApplicationController
  def index
    @users = User.all
  end

  def show
    @users = User.find(params[:id])
  end
end

Schema

  create_table "users", force: :cascade do |t|
    t.string   "email"
    t.string   "username"
    t.string   "encrypted_password"
    t.string   "description"
    t.string   "address"
    t.string   "phone_number"
 end

For the products:

class ProductsController < ApplicationController
  def index
    @products = Product.all
  end

  def new
    @products = Product.new
  end

  def create
      params[:products][:user_id] = current_user.id
      @products = products.new(products_params)
  end

  def edit
    @products = Product.find(params[:id])
  end

  def update
    @products = Products.find(params[:id])
    @products.update_attributes!(products_params)
    flash[:notice] = "#{@products.name} has been succesfully updated."
    redirect_to show_my_products_path
  end

  def destroy
    @products = Product.find(params[:id])
    @products.destroy
    flash[:notice] = "Your product '#{@products.name}' has been deleted."
    redirect_to show_my_products_path
  end

  def show
    @products = Product.find(params[:id])
  end

end

Schema

  create_table "products", force: :cascade do |t|
    t.string   "name",                default: "", null: false
    t.string   "description"
    t.string   "quantity"
    t.datetime "created_at",                       null: false
    t.datetime "updated_at",                       null: false
    t.integer  "user_id"
  end

For the show products page, it essentially displays the product information. One thing I want to add is this line:

<%= link_to "#{user.username}",  users_show_path_url(user), class: 'link_to'   %>

Where, if you click this link on the product page, that a user has created, it directs to their profile page. However, I do get the error, when in the show.html.erb page for products:

undefined method `username' for nil:NilClass

OR

Couldn't find User with 'id'= .... 

I guess, I am not too sure how I can link the user who has created their product and display their username on that particular product. I've tried adding:

@users = User.all
@users = User.find(params[:id])

To the show product controller.

Also, this is the model:

class User < ActiveRecord::Base
  has_many :products
end

class Product < ActiveRecord::Base
  belongs_to :user
end

I've intentionally left out some code and selected the most relevant code, but if you require more code please tell me.

What I want is when someone clicks the product, they can see which user created it, and see their username and therefore click that to go to the user profile. The only link between the product and users is the "user_id" in the product table. So when a product is created, a user_id is tied to that item.

Upvotes: 3

Views: 841

Answers (4)

Mohammad Shahnawaz
Mohammad Shahnawaz

Reputation: 876

First of all, I will create the devise User

rails generate devise User

Then I will create a Task

rails g scaffold Task name:string description:text user:references

Then I will modify the task controller

class TasksController < ApplicationController
  before_action :set_task, only: [:show, :edit, :update, :destroy]
  before_action :authenticate_user!, except: [ :show, :index]
  # GET /tasks
  # GET /tasks.json
  def index
    @tasks = Task.all
  end

  # GET /tasks/1
  # GET /tasks/1.json
  def show
  end

  # GET /tasks/new
  def new
    @task = current_user.tasks.new
  end

  # GET /tasks/1/edit
  def edit
  end

  # POST /tasks
  # POST /tasks.json
  def create
    @task = current_user.tasks.new(task_params)

    respond_to do |format|
      if @task.save
        format.html { redirect_to @task, notice: 'Task was successfully created.' }
        format.json { render :show, status: :created, location: @task }
      else
        format.html { render :new }
        format.json { render json: @task.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /tasks/1
  # PATCH/PUT /tasks/1.json
  def update
    respond_to do |format|
      if @task.update(task_params)
        format.html { redirect_to @task, notice: 'Task was successfully updated.' }
        format.json { render :show, status: :ok, location: @task }
      else
        format.html { render :edit }
        format.json { render json: @task.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /tasks/1
  # DELETE /tasks/1.json
  def destroy
    @task.destroy
    respond_to do |format|
      format.html { redirect_to tasks_url, notice: 'Task was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_task
      @task = Task.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def task_params
      params.require(:task).permit(:name, :description, :user_id)
    end
end

Then I will add the new column in the User table.

rails generate migration add_username_to_users username:string

Then I will run the migration command

rake db:migrate

Then I will add the username field in signup form #/views/devise/registerations/new.html.erb

<div class="field">
  <%= f.label :username %><br />
  <%= f.text_field :username, autofocus: true, autocomplete: "username" %>
</div>

Then I will allow the username in devise controller

class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:username])
  end
end

And then I will add the validation for username in #user.rb

  validates :username, uniqueness: true, presence: true

Then I will add the reference in user model

has_many :tasks

Now You can show Your username with the task In views everywhere

<%= task.user.username %>

Here is the working example Association link sample for username display

Upvotes: 2

Hay.P
Hay.P

Reputation: 48

I'm not sure your create function in the Products controller is working as intended.

First, when calling Product.new, Product needs a capital P. https://apidock.com/rails/v3.2.13/ActiveRecord/Base/new/class

Second, I don't think you are associating a user correctly.

Third, you need to save the new instance to the database.

Depending on your product_params, potentially you could do something like:

def create
  attributes = product_params.merge(user_id: current_user.id)
  @product = Product.new(attributes)

  if @product.save
    redirect_to show_my_products_path, notice: 'Product was successfully created.'
  else
    render :new
  end
end

In this case, as you are not passing user_id through on params you won't need user_id permitted in your product_params.

Then in the correct view you should be able to call

<%= link_to @product.user.username, users_show_path_url(user), class: 'link_to' %> |

I also see you calling Products.find in other methods where they should be singular. (Potentially a separate problem) https://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find

It's also best practise to call your instance methods singular if there is only one so apart from in the index they would all be @product not @products but this is purely for readability.


General advice - It is best to try to scaffold your application as much as possible and rails will take care of a lot of work for you.

For example, from scratch I ran

rails g scaffold user username

rails g scaffold products name user:references

rails db:migrate

I don't have all the attributes as this was just an example but this generated me all the code I needed to get everything working as intended (except the has_many in my model). It's an extremely useful thing to learn about :)

Upvotes: 0

CaTs
CaTs

Reputation: 1323

In order to use the variable user, you need to assign it and I don't see where this is being done in the show product action.

Instead try accessing the user through the product:

<%= link_to "#{@products.user.username}",  users_show_path_url(@products.user), class: 'link_to' %>

You will also need to ensure that the product you are viewing actually has a user otherwise it will cause the same kinds of errors.

Upvotes: 0

Clyde T
Clyde T

Reputation: 141

Try :

   <%= link_to user_path(@product.user), title: @product.user.username do %>
     <%= @product.user.username %>
   <% end %>

Upvotes: 0

Related Questions