Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails 4 - Save address as one column in database

I'm new to rails and working on a simple application. I have a model called Client in my ERD and would like to save the address for each client.

My initial thoughts where to save the address as seperate fields i.e:

rails g model Client address_first:string address_second:string city:string ...

but this seems very inefficient. I'm certain there must be a way to save the address s a hash, array or perhaps JSON? I've read around and people keep referring to using "serialise" but I'm uncertain if and how I would implement this for an address field.

Any help would be most appreciated.

Thanks

like image 993
Craig Avatar asked Oct 14 '15 06:10

Craig


2 Answers

It depends on what database you are using - both MySQL and Postgres have JSON column types - Postgres also has a hash type called hstore.

They also have different options for querying inside the hash/json column. While these data types are great for dynamic data - there is no real performance gain (it might actually be slower).

And you can't use all the rails goodness of object mapping, validations etc for attributes stored inside a hstore/json column.

When it comes to addresses everybody naively puts it in there User (or Client or Customer) model only to discover that in the real world you need multiple addresses.

# == Schema Information
#
# Table name: clients
#
#  id         :integer          not null, primary key
#  created_at :datetime         not null
#  updated_at :datetime         not null
#

class Client < ActiveRecord::Base
  has_many :addresses
end


# == Schema Information
#
# Table name: addresses
#
#  id             :integer          not null, primary key
#  address_first  :string
#  address_second :string
#  street         :string
#  city           :string
#  country        :string
#  client_id      :integer
#  created_at     :datetime         not null
#  updated_at     :datetime         not null
#

class Address < ActiveRecord::Base
  belongs_to :client
end
like image 148
max Avatar answered Oct 22 '22 18:10

max


To further Max's suggestion, I'd like to add that you can include address information inside a single model, if you want.

Having multiple models is cool, but if you're sure you only want certain attributes for your Client, you can endeavour to save a hash of options to a column in there.


SQL

To answer your question directly, and to elaborate on Max's recommendation, there are several column types you can use, depending on the variant of SQL you're employing.

PostgreSQL (Heroku uses this) has the hstore and json column types.

Both of these provide you with a column which allows you to store a single serialized object, allowing you to populate this object with serialized data:

enter image description here

I've never used this functionality on PGSQL before, so you'll be best reading this blog post on how to choose between HSTORE and JSON column types.

--

MYSQL, which most people use, also has the capacity to store JSON:

As of MySQL 5.7.8, MySQL supports a native JSON data type that enables efficient access to data in JSON (JavaScript Object Notation) documents. The JSON data type provides these advantages over storing JSON-format strings in a string column:

This seems to work in the same way as its PGSQL brother.


If you do go for one of these (you could use the following setup):

#clients
#id | name | email | address (json) | created_at | updated_at

... it will give you the ability to insert JSON formatted objects into the address column.

Now, as pointed out by Max, this will prevent you from running validations and other attribute-level logic on the address column. It will prevent you from looking up this column through ActiveRecord, and will make it very difficult to use this data in any other capacity than just to store it.

Thus, if you were absolutely sure you wanted to store it in this way, you'd be able to use the following to do it:

#app/models/client.rb
class Client < ActiveRecord::Base
   serialize :address
end

#app/controllers/clients_controller.rb
class ClientsController < ApplicationController
   def new
      @client = Client.new
   end

   def create
      @client = Client.new client_params
      @client.save
   end

   private

   def client_params
     params.require(:client).permit(:name, :email, address:{})
   end
end

#app/views/clients/new.html.erb
<%= form_for @client do |f| %>
   <%= f.text_field :name %>
   <%= f.email_field :email %>
   <%= f.fields_for :address, OpenStruct.new(f.object.address || {}) do |address| %>
      <%= address.text_field :street %>
      <%= address.text_field :other_data %>
   <% end %>
   <%= f.submit %>
<% end %>

fields_for available here: How to edit a Rails serialized field in a form?


Models

The alternative, as pointed out by Max, is to use multiple models.

This, although looking good, is actually a very delicate process. Many people start inserting models for everything, and soon enough your application is full of models which don't do anything.

If you wanted to use models, you'd be best using the following:

#app/models/client.rb
class Client < ActiveRecord::Base
   #columns id | name | email | etc | created_at | updated_at
   has_one :address
   before_create :build_address, unless: Proc.new { |client| client.address }
end

#app/models/address.rb
class Address < ActiveRecord::Base
   #columns id | client_id | your | address | columns | here | created_at | updated_at
   belongs_to :client
end

I would only recommend this if you wanted the address to play a major role in the application (IE if they are going to be shipping to it etc).

If you're using the address in any other non-important form, I'd leave it as the serialized hash.

like image 5
Richard Peck Avatar answered Oct 22 '22 20:10

Richard Peck