Oliver
Oliver

Reputation: 1181

Find record in table and save id to another table in Rails

I'm developing an app in which users can have a personal food diary.

It's pretty simple: a user can search for a nutrient on a Nutrients table (let's say milk) - currently through a pretty simple search form - and then should be able to save the amount he consumed of this nutrient (together with the nutrient_id) on a second table, which is called Diaries (that holds the nutrient_id as foreign key and an integer field called "amount").

My search works. I can also create new records in Diaries but I have to type in the nutrient_id manually.

My question is now how I can make this easily work? Ideally a user finds a nutrient clicks on it and will be redirected to a page that shows this nutrient together with a field for "amount" and a save button to save both information (nutrient_id and amount) on the Diaries table.

At the end of the day I think the user will be directed to the new action of my diary controller - the question is how my app sets the nutrient_id for this action for the nutrient the user selected before?

Sorry if this is a too simple question but I just started coding few weeks ago.

Thanks a lot for help!

My code looks like as follows:

nutrient.rb

class Nutrient < ActiveRecord::Base
  has_many :diaries

  def self.search(search)
    where("name LIKE ?", "%#{search}%") 
  end

end

diary.rb

class Diary < ActiveRecord::Base
  belongs_to :nutrient
end

nutrients_controller.rb

class NutrientsController < ApplicationController
  before_action :set_nutrient, only: [:show, :edit, :update, :destroy]

  # GET /nutrients
  # GET /nutrients.json
  def index
    @nutrients = Nutrient.all
  end

  def search
    if params[:search]
      @nutrients = Nutrient.search(params[:search]).order("created_at DESC")
      if @nutrients.present?
        @nutrients
      else
        flash[:notice] = "Nutrient not found in database"
      end
    end
  end

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

  # GET /nutrients/new
  def new
    @nutrient = Nutrient.new
  end

  # GET /nutrients/1/edit
  def edit
  end

  # POST /nutrients
  # POST /nutrients.json
  def create
    @nutrient = Nutrient.new(nutrient_params)

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

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

  # DELETE /nutrients/1
  # DELETE /nutrients/1.json
  def destroy
    @nutrient.destroy
    respond_to do |format|
      format.html { redirect_to nutrients_url, notice: 'Nutrient was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

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

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

diaries_controller.rb

class DiariesController < ApplicationController
  before_action :set_diary, only: [:show, :edit, :update, :destroy]

  # GET /diaries
  # GET /diaries.json
  def index
    @diaries = Diary.all
  end

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

  # GET /diaries/new
  def new
    @diary = Diary.new
  end

  # GET /diaries/1/edit
  def edit
  end

  # POST /diaries
  # POST /diaries.json
  def create
    @diary = Diary.new(diary_params)

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

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

  # DELETE /diaries/1
  # DELETE /diaries/1.json
  def destroy
    @diary.destroy
    respond_to do |format|
      format.html { redirect_to diaries_url, notice: 'Diary was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

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

    # Never trust parameters from the scary internet, only allow the white list through.
    def diary_params
      params.require(:diary).permit(:nutrient_id, :amount)
    end
end

routes.rb

Rails.application.routes.draw do

  resources :diaries
  resources :nutrients do
    collection do
      get :search
    end
  end

_form.html.erb (for a new diary record)

<%= form_for(@diary) do |f| %>
  <% if @diary.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@diary.errors.count, "error") %> prohibited this diary from being saved:</h2>

      <ul>
      <% @diary.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :nutrient_id %><br>
    <%= f.number_field :nutrient_id %>
  </div>
  <div class="field">
    <%= f.label :amount %><br>
    <%= f.number_field :amount %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

Upvotes: 0

Views: 212

Answers (1)

oreoluwa
oreoluwa

Reputation: 5633

There are several ways to resolve this, one the quickest and most likely the best approach to me would be to nest the diaries under nutrients, since diaries belongs_to :nutrients

  resources :nutrients do
   resources :diaries
    collection do
      get :search
    end
  end

This way your all your diaries path method would accept a @nutrient argument and your route would be like: /nutrients/4/diaries/1

So in your diaries_controller, you could have:

class DiariesController < ApplicationController
   before_action :set_nutrient

   def new
    @diary = @nutrient.diaries.new
   end

   def create
    @diary = @nutrient.diaries.new(diary_params) # you can safely remove the nutrient_id from the strong params
    ... remaining logic here
   end
...
  private
   def set_nutrient
    @nutrient ||= Nutrient.find(params[:nutrient_id])
   end

   # You cans skip/ignore this method, if you don't want to be too strict
   def set_diary
    @diary ||= @nutrient.diaries.find(params[:id])
   end
end

Then in your view, you could then have:

<%= form_for([@nutrient, @diary]) do |f| %>
  <% if @diary.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@diary.errors.count, "error") %> prohibited this diary from being saved:</h2>

      <ul>
      <% @diary.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>
  <div class="field">
    <%= f.label :amount %><br>
    <%= f.number_field :amount %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

As I mentioned, there are still other ways of doing this, as you could also use hidden_fields however, this seem to be the cleanest way to me. If you don't always want your diaries routes to be nested, you could expect: [the list of actions that should not be nested, eg show], both on your before_action and your routes' resources :diaries. Hope I'm able to help or let me know other confusions you may encounter.

Upvotes: 2

Related Questions