2

I'm working on a new Rails 4 application and having a hard time getting all the pieces to work together correctly. I have a model (sample) with a belongs_to association with another model (lat_long) and am trying to do CRUD operations on both from the sample views (editing the default generated views). The problem is that lat_long can be blank; everything works fine if it isn't but when it is it's hitting the validation in the model, so I think it's trying to save a blank lat_long instead of setting it to nil. update and edit are the problems.

Here are the relevant pieces from the code:
Models:

class Sample < ActiveRecord::Base
  belongs_to :lat_long
  accepts_nested_attributes_for :lat_long 
end

class LatLong < ActiveRecord::Base
  validates_each :west_longitude do |record, attr, value|
    record.errors.add attr, "must be within range." if (value < 110.0 or value > 115.0)   
  end   
  validates_each :north_latitude do |record, attr, value|
    record.errors.add attr, "must be within range." if (value < 38.0 or value > 42.0)   
  end 
end

samples/_form.html.erb used in edit and new:

<%= form_for(@sample) do |f| %>
    <%= f.fields_for :lat_long do |g| %>
        <div class="field">
          <%= g.label :north_latitude %><br>
          <%= g.text_field :north_latitude %>
        </div>
        <div class="field">
          <%= g.label :west_longitude %><br>
          <%= g.text_field :west_longitude %>
        </div>
    <% end %>
<% end %>

samples_controller.rb:

class SamplesController < ApplicationController
 # GET /samples/new
  def new
    @sample = Sample.new
    @sample.build_lat_long
    @sample.build_trap
  end
  # GET /samples/1/edit
  def edit
    @sample.lat_long || @sample.build_lat_long
    @sample.trap || @sample.build_trap
  end
  # POST /samples
  # POST /samples.json
  def create
    @sample = Sample.new(sample_params)
    puts sample_params
    respond_to do |format|
      if @sample.save
        format.html { redirect_to @sample, notice: 'Sample was successfully created.' }
        format.json { render :show, status: :created, location: @sample }
      else
        format.html { render :new }
        format.json { render json: @sample.errors, status: :unprocessable_entity }
      end
    end
  end
  # PATCH/PUT /samples/1
  # PATCH/PUT /samples/1.json
  def update
    respond_to do |format|
      if @sample.update(sample_params)
        format.html { redirect_to @sample, notice: 'Sample was successfully updated.' }
        format.json { render :show, status: :ok, location: @sample }
      else
        format.html { render :edit }
        format.json { render json: @sample.errors, status: :unprocessable_entity }
      end
    end
  end
  def sample_params
    params.require(:sample).permit(...,
                                   lat_long_attributes: [:id,
                                   :west_longitude, :north_latitude])
  end
end

I can do a check in the controller before updating of course, but is that the right thing to do? It "feels" like there ought to be a way to have Rails take care of this, but looking at lots of StackOverflow questions and the documentation haven't given me any ideas. Most examples used has_one or has_many, so I don't know if that's the problem? According to this question it's newish that belongs_to works with accepts_nested_attributes_for so perhaps it's just plain not set up for this.

I would like to know what the best practice is for this situation, the most "Railsy" thing to do. Oh and I think I've included all the relevant bits of code but if there's anything else you need to see let me know.

Community
  • 1
  • 1
Maltiriel
  • 793
  • 2
  • 11
  • 28

1 Answers1

1

I'd do something like:

class Sample < ActiveRecord::Base
  belongs_to :lat_long
  accepts_nested_attributes_for :lat_long, reject_if: proc { |attributes| attributes['west_longitude'].blank? && attributes['north_latitude'].blank? }
end

class LatLong < ActiveRecord::Base
  validates :west_longitude, numericality: { greater_than_or_equal_to: 110,
                             less_than_or_equal_to: 115,
                             message: "must be within range" },
                             allow_nil: true

  validates :north_latitude, numericality: { greater_than_or_equal_to: 38,
                             less_than_or_equal_to: 42,
                             message: "must be within range" },
                             allow_nil: true
end
Rodrigo Martinez
  • 611
  • 5
  • 14
  • This results in a blank lat_long (empty except for id, updated_at, created_at) being saved. I want sample.lat_long itself to be nil (and thus sample.lat_long_id in the database to be NULL). – Maltiriel Apr 08 '16 at 20:03
  • @Maltiriel Just modified the answer, see if that helps. I guess you could also strip the `:allow_nil => true` option of validation since no empty values will reach the model this way. – Rodrigo Martinez Apr 08 '16 at 20:30
  • Yes, I imagine something like the second part of your answer will work, but I don't know if it's the best design. Thanks for the answer though. As far as your comment about stripping :allow_nil => true, I'm not sure what you're going for there. If that line isn't in there or if it's :allow_nil => false then the view requires those values. They are allowed to be blank, I just don't want a blank object being saved to the database. – Maltiriel Apr 08 '16 at 20:39
  • 1
    @Maltiriel I updated the answer again with your comments...I completely forgot about `reject_if`, but that's the way to go. – Rodrigo Martinez Apr 08 '16 at 20:43