Search code examples
ruby-on-railsnested-resources

Creating model relationships/Nested resourses


Hi I am trying to create a relationship between two models Establishments and OpenClosed I have added

class Establishment < ApplicationRecord
  has_many :open_closed
end

class OpenClosed < ApplicationRecord
  belongs_to :establishment
end

I have added establishment_id field to open_closed table and then ran rails db:migrate.Next I've nested the resources

resources :establishments do
  resources :open_closeds
end 

Then I modified the OpenClosed controller #new line comment where I've added new code

def new
 @establishment = Establishment.find(params[:establishment_id]) #new line added
 @open_closed = OpenClosed.new
end

 # GET /open_closeds/1/edit
def edit
end

# POST /open_closeds
# POST /open_closeds.json
def create
 @establishment = Establishment.find(params[:establishment_id]) #new line added
 @open_closed = OpenClosed.new(open_closed_params)
 @open_closed.Establishment = @establishments #new line added

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

This is the error I'm getting when I submit form in open_closed view:

undefined method `Establishment=' for # Did you mean? establishment= establishment establishment_id= establishment_id establishment_id?

Can not for the life of me work out where I'm going wrong here could someone please point me in the right direction with this one many thanks in advance.


Solution

  • First, rewrite the OpenClosed#new action to look like this:

    <h1>New Open Closed</h1>
    
    <%= render 'form', establishment: @establishment, open_closed: @open_closed %>
    <%= link_to 'Back', establishment_open_closeds_path %>
    

    This means we are now passing in an Establishment object and a OpenClosed object to the form partial.

    Next, change the <%= form_for ... %> declaration at

    `app/views/open_closeds/_form.html.erb`
    

    to look like this:

    <%= form_for [establishment, open_closed] do |f| %>
    

    We've just told our form to use both the Establishment and OpenClosed object.

    Next, rewrite the open_closed controller to look like this:

    def create
      @establishment = Establishment.find(params[:establishment_id])
      @open_closed = OpenClosed.new(open_closed_params)
      @open_closed.Establishment = @establishment #new line added
    
      respond_to do |format|
        if @open_closed.save
          format.html { redirect_to [@establishment, @open_closed], notice: 'Open closed was successfully created.' }
          format.json { render :show, status: :created, location: @open_closed }
        else
          format.html { render :new }
          format.json { render json: @open_closed.errors, status: :unprocessable_entity }
        end
      end
    end
    

    The two modifications I made were:

    changing the line

    @open_closed.Establishment = @establishments
    

    to

    @open_closed.establishment = @establishment
    

    Note that the 'e' in establishment is now lowercase and I removed the 's' at the end of the line (that was probably just a typo). If you look at your db/schema.rb file, you should see

    t.integer  "establishment_id"
    

    under the open_closeds table. In ActiveRecord, you access the methods of your class using the same name as the row in your database table. However, for foreign keys like establishment_id, rails is smart enough to know that when you say @open_closed.establishment, you really just want the Establishment that belongs to your OpenClosed object.

    The second thing I changed in the above code is this line:

    redirect_to [@establishment, @open_closed] ...    
    

    You need to provide both the establishment and open_closed objects in order for the route to be valid. Since the redirect statement is sending you to the show method of the OpenClosed class, you can run rake routes to see what the route for OpenClosed#show looks like. I ran rake routes on your route file and got this for the show method of OpenClosed

    establishment_open_closed GET    /establishments/:establishment_id/open_closeds/:id(.:format)      open_closeds#show
    

    So you can see that OpenClosed#show is expecting both an Establishment object and an OpenClosed object. You simply need to provide both objects in an array as the first parameter to redirect_to

    NOTE:

    You will likely still get an error when you try this because many of the routes in the application are incorrect and need to changed. For example, to actually render the OpenClosed#show view (which is where you're redirected after a successful call to OpenClosed#create), you will need to make the following modifications to the show template of the OpenClosed controller:

    <p id="notice"><%= notice %></p>
    
    <%= link_to 'Edit', edit_establishment_open_closed_path(@open_closed) %> |
    <%= link_to 'Back', establishment_open_closeds_path %>
    

    As a tip for the future, you can add

    <%= debug(params) if Rails.env.development? %> in your app/views/layouts/application.html.erb file directly after the yield. This will print out server response information which can useful when debugging.

    Lastly, there are going to be other parts of you application that won't work until you fix the routes, but hopefully you have enough info to do that now.

    For more information on nested routes, check out the official rails guids

    Happing coding!