ruby-on-railsrubymongoid

How can I force the correct hash access syntax from my text_field form helper?


I am working on a Rails project, using Mongo. I am faced with an issue in my view form helper, in that (whether this access is through Rails or Mongoid, I am not sure) I am attempting to access a value within a Hash field on my model, but I am getting an undefined method error, because it is attempting to access the hash using dot notation, instead of bracket notation.

The model:

class AgencySetting
  include Mongoid::Document
  include Mongoid::Timestamps

  field :uuid,            type: String
  field :mailing_address, type: Hash
  field :service_address, type: Hash
  field :active,          type: Boolean, default: true

  embeds_many :contracts

  validates :uuid, presence: true, uniqueness: true
end

The form (this is probably not the ideal way to handle a Hash attribute field, there is another f.fields_for attached to service_address that I have excluded for brevity):

      = form_with model: @agency_setting, url: agency_setting_url, method: :post do |f|

        = f.fields_for :mailing_address, @agency_setting.mailing_address do |m_fields|
          = m_fields.text_field :line_1, class: 'form-control my-1', placeholder: 'Contract street line one'
          = m_fields.text_field :line_2, class: 'form-control my-1', placeholder: 'Contract street line two'
          = m_fields.text_field :city, class: 'form-control my-1', placeholder: 'Contract city'
          = m_fields.text_field :state, class: 'form-control my-1', placeholder: 'Contract state'
          = m_fields.text_field :zip, class: 'form-control my-1', placeholder: 'Contract zip'

        = f.submit 'Save settings', class: 'btn btn-primary mt-2'

It throws the error on the first line of m_fields, specifically it gives, undefined method 'line_1' for {"line_1"=>"123", "line_2"=>"123", "city"=>"123", "state"=>"123", "zip"=>"123"}:BSON::Document. Clearly, the reason for this is that it is attempting to access mailing_address with mailing_address.line_1 instead of mailing_address[:line_1] which runs fine in the console. Is it possible to force the helper to use the latter syntax instead?


Solution

  • Is it possible to force the helper to use the latter syntax instead?

    No. But you can provide the value manually:

    = form_with model: @agency_setting, url: agency_setting_url, method: :post do |f|
      = f.fields_for :mailing_address, @agency_setting.mailing_address do |m_fields|
        = m_fields.text_field :line_1, 
                              class: 'form-control my-1', 
                              placeholder: 'Contract street line one',
                              value: m_fields.object[:line_1]
    

    Or you can convert the Hash into a OpenStruct:

    = form_with model: @agency_setting, url: agency_setting_url, method: :post do |f|
      = f.fields_for :mailing_address, OpenStruct.new(@agency_setting.mailing_address) do |m_fields|
        = m_fields.text_field :line_1, 
                              class: 'form-control my-1', 
                              placeholder: 'Contract street line one'
        # ...
    

    But this really just begs the question why you're shooting yourself in the foot instead of just modeling your data better.

    class Address
      include Mongoid::Document
      include Mongoid::Attributes::Dynamic
      include Mongoid::Timestamps
    
      field :line_1,            type: String
      field :line_2,            type: String
      # ...
    end
    
    class AgencySetting
      include Mongoid::Document
      include Mongoid::Timestamps
    
      field :uuid,            type: String
      field :active,          type: Boolean, default: true
    
      embeds_many :contracts
      embeds_one :mailing_address, class_name: 'Address'
      embeds_one :service_address, class_name: 'Address'
    end
    

    Having an actual model will make your life so much easier when it comes to adding validations and dealing with the input.