Skip to content

Instantly share code, notes, and snippets.

@oojikoo-gist
Last active February 4, 2016 22:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oojikoo-gist/73f69f8dcac56e15e805 to your computer and use it in GitHub Desktop.
Save oojikoo-gist/73f69f8dcac56e15e805 to your computer and use it in GitHub Desktop.
rails: models cheetsheet

Rails Models

more advanced

Generating models

$ rails g model User

Associations

belongs_to
has_one
has_many
has_many :through
has_one :through
has_and_belongs_to_many

belongs_to :author,
  class_name: 'User',
  dependent: :destroy  // delete this

Has many

belongs_to :parent, :foreign_key => 'parent_id' class_name: 'Folder'
has_many :folders, :foreign_key => 'parent_id', class_name: 'Folder'

has_many :comments,    :order      => "posted_on"
has_many :comments,    :include    => :author
has_many :people,      :class_name => "Person"
has_many :people,      :conditions => "deleted = 0"
has_many :tracks,      :order      => "position"
has_many :comments,    :dependent  => :nullify
has_many :comments,    :dependent  => :destroy
has_many :tags,        :as         => :taggable
has_many :reports,     :readonly   => true
has_many :subscribers, :through    => :subscriptions, class_name: "User", :source => :user
has_many :subscribers, :finder_sql =>
    'SELECT DISTINCT people.* ' +
    'FROM people p, post_subscriptions ps ' +
    'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
    'ORDER BY p.first_name'

belongs to

belongs_to :author,
  :dependent      => :destroy    # or :delete

  :class_name     => "Person"
  :select         => "*"
  :counter_cache  => true
  :counter_cache  => :custom_counter
  :include        => "Book"
  :readonly       => true

  :conditions     => 'published = true'

  :touch          => true
  :touch          => :authors_last_updated_at

  :primary_key    => "name"
  :foreign_key    => "author_name"

Many-to-many

If you have a join model:

class Programmer < ActiveRecord::Base
  has_many :assignments
  has_many :projects, :through => :assignments
end

class Project < ActiveRecord::Base
  has_many :assignments
  has_many :programmers, :through => :assignments
end

class Assignment
  belongs_to :project
  belongs_to :programmer
end

Or HABTM:

has_and_belongs_to_many :projects
has_and_belongs_to_many :projects, :include => [ :milestones, :manager ]
has_and_belongs_to_many :nations, :class_name => "Country"
has_and_belongs_to_many :categories, :join_table => "prods_cats"
has_and_belongs_to_many :categories, :readonly => true
has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
"DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}"

Polymorphic associations

class Post
  has_many :attachments, :as => :parent
end

class Image
  belongs_to :parent, :polymorphic => true
end

And in migrations:

create_table :images do |t|
  t.references :post, :polymorphic => true
end

Dynamic attribute-based finders

# Returns one record
Person.find_by_name(name)
Person.find_last_by_name(name)
Person.find_or_create_by_name(name)
Person.find_or_initialize_by_name(name)

# Returns a list of recordns
Person.find_all_by_name(name)

# Add a bang to make it raise an exception
Person.find_by_name!(name)

# You may use `scoped` instead of `find`
Person.scoped_by_user_name

Migrations

Run migrations

$ rake db:migrate

Migrations

create_table :users do |t|
  t.string :name
  t.text   :description

  t.primary_key :id
  t.string
  t.text
  t.integer
  t.float
  t.decimal
  t.datetime
  t.timestamp
  t.time
  t.date
  t.binary
  t.boolean
end

options:
  :null (boolean)
  :limit (integer)
  :default
  :precision (integer)
  :scale (integer)

Tasks

create_table
change_table
drop_table
add_column
change_column
rename_column
remove_column
add_index
remove_index

Associations

t.references :category   # kinda same as t.integer :category_id

# Can have different types
t.references :category, polymorphic: true

Add/remove columns

$ rails generate migration RemovePartNumberFromProducts part_number:string

class RemovePartNumberFromProducts < ActiveRecord::Migration
  def up
    remove_column :products, :part_number
  end
 
  def down
    add_column :products, :part_number, :string
  end
end

Validation

class Person < ActiveRecord::Base

  validates :name,     presence: true

  validates :terms,    acceptance: true

  validates :email,    confirmation: true

  validates :slug,     uniqueness: true
  validates :slug,     uniqueness: { case_sensitive: false }
  validates :holiday,  uniqueness: { scope: :year, :message => "only once a year" }

  validates :code,     format: /regex/
  validates :code,     format: { with: /regex/ }

  validates :name,     length: { minimum: 2 }
  validates :bio,      length: { maximum: 500 }
  validates :password, length: { in: => 6..20 }
  validates :number,   length: { is: => 6 }

  validates :gender,   inclusion: %w(male female)
  validates :gender,   inclusion: { in: %w(male female) }
  validates :lol,      exclusion: %w(xyz)

  validates :points,   numericality: true
  validates :played,   numericality: { only_integer: true }
  # ... greater_than, greater_than_or_equal_to,
  # ... less_than, less_than_or_equal_to
  # ... odd, even, equal_to

  # Validate the associated records to ensure they're valid as well
  has_many :books
  validates_associated :books

  # Length (full enchalada)
  validates :content, length: {
    minimum:   300,
    maximum:   400,
    tokenizer: lambda { |str| str.scan(/\w+/) },
    too_short: "must have at least %{count} words",
    too_long:  "must have at most %{count} words" }

  # Multiple
  validates :login, :email, presence: true

  # Conditional
  validates :description, presence: true, if: :published?
  validates :description, presence: true, if: lambda { |obj| .. }

  validates :title, presence: true, on: :save   # :save | :create | :update
end

Validate checkboxes

class Person < ActiveRecord::Base
  validates :terms_of_service, :acceptance => true
end

Validate associated records

class Library < ActiveRecord::Base
  has_many :books
  validates_associated :books
end

Confirmations (like passwords)

class Person < ActiveRecord::Base
  validates :email, :confirmation => true
end

Validate format

class Product < ActiveRecord::Base
  validates :legacy_code, :format => { :with => /\A[a-zA-Z]+\z/,
    :message => "Only letters allowed" }
end

Validate length

class Person < ActiveRecord::Base
  validates :name, :length => { :minimum => 2 }
  validates :bio, :length => { :maximum => 500 }
  validates :password, :length => { :in => 6..20 }
  validates :registration_number, :length => { :is => 6 }

  validates :content, :length => {
    :minimum   => 300,
    :maximum   => 400,
    :tokenizer => lambda { |str| str.scan(/\w+/) },
    :too_short => "must have at least %{count} words",
    :too_long  => "must have at most %{count} words"
  }
end

Numeric

class Player < ActiveRecord::Base
  validates :points, :numericality => true
  validates :games_played, :numericality => { :only_integer => true }
end

Non-empty

class Person < ActiveRecord::Base
  validates :name, :login, :email, :presence => true
end

custom

class Person < ActiveRecord::Base
  validate :foo_cant_be_nil

  def foo_cant_be_nil
    errors.add(:foo, 'cant be nil')  if foo.nil?
  end
end

Error

record.errors.valid?      #=> false
record.errors             #=> { :name => ["can't be blank"] }
record.errors.messages    #=> { :name => ["can't be blank"] }

record.errors[:name].any?

API

items = Model.find_by_email(email)
items = Model.where(first_name: "Harvey")

item = Model.find(id)

item.serialize_hash
item.new_record?

item.create     # Same an #new then #save
item.create!    # Same as above, but raises an Exception

item.save
item.save!      # Same as above, but raises an Exception

item.update
item.update_attributes
item.update_attributes!

item.valid?
item.invalid?

Callbacks

Guides: callbacks

Mass updates

# Updates person id 15
Person.update 15, name: "John", age: 24
Person.update [1,2], [{name: "John"}, {name: "foo"}]

Joining

Student.joins(:schools).where(:schools => { :type => 'public' })
Student.joins(:schools).where('schools.type' => 'public' )

Where interpolation

where("name = ?", "John")
where(["name = :name", { name: "John" }])

Serialize

class User < ActiveRecord::Base
  serialize :preferences
end

user = User.create(:preferences => { "background" => "black", "display" => large })

You can also specify a class option as the second parameter that’ll raise an exception if a serialized object is retrieved as a descendant of a class not in the hierarchy.

class User < ActiveRecord::Base
  serialize :preferences, Hash
end

user = User.create(:preferences => %w( one two three ))
User.find(user.id).preferences    # raises SerializationTypeMismatch

Overriding accessors

class Song < ActiveRecord::Base
  # Uses an integer of seconds to hold the length of the song

  def length=(minutes)
    write_attribute(:length, minutes.to_i * 60)
  end

  def length
    read_attribute(:length) / 60
  end
end

Callbacks

after_create
after_initialize
after_validation
after_save
after_commit

QueryMethods

items = Model
  .where(first_name: 'Harvey')
  .where('id = 3')
  .where('id = ?', 3)

  .order(:title)
  .order(title: :desc)
  .order("title DESC")

  .reorder(:title)  # discards other .order's
  .rewhere(...)     # discards other .where's

  .limit(2)
  .offset(1)
  .uniq

Advanced:

  .select(:id)
  .select([:id, :name])

  .group(:name)   # GROUP BY name
  .group('name AS grouped_name, age')
  .having('SUM(price) > 30')  # needs to be chained with .group

  .includes(:user)
  .includes(user: [:articles])

  .references(:posts)
  # .where("posts.name = 'foo'").references(:posts)

FinderMethods

    item = Model.find(id)
    item = Model.find_by_email(email)
    item = Model.where(email: email).first

    Model
      .exists?(5)
      .exists?(name: "David")

      .first
      .last
      .find_nth(4, [offset])

Persistence

    item.new_record?
    item.persisted?
    item.destroyed?

    item.serialize_hash

    item.save
    item.save!      # Same as above, but raises an Exception

    item.update  name: 'John'  # Saves immediately
    item.update! name: 'John'


    item.update_column  :name, 'John'  # skips validations and callbacks
    item.update_columns  name: 'John'
    item.update_columns! name: 'John'

    item.touch                 # updates :updated_at
    item.touch :published_at

    item.destroy
    item.delete  # skips callbacks


    Model.create     # Same an #new then #save
    Model.create!    # Same as above, but raises an Exception

AttributeAssignment

    item.attributes                      # #<Hash>

    item.attributes = { name: 'John' }   # Merges attributes in. Doesn't save.
    item.assign_attributes name: 'John'  # Same as above

Dirty

    item.changed?
    item.changed             # ['name']
    item.changed_attributes  # { 'name' => 'Bob' } - original values
    item.changes             # { 'name' => ['Bob', 'Robert'] }
    item.previous_changes    # available after #save
    item.restore_attributes

    item.name = 'Robert'
    item.name_was         # 'Bob'
    item.name_change      # [ 'Bob', 'Robert' ]
    item.name_changed?    # true
    item.name_changed?(from: 'Bob', to: 'Robert')

Validations

    item.valid?
    item.invalid?

Calculations

    Person.count
    Person.count(:age)    # counts non-nil's

    Person.average(:age)
    Person.maximum(:age)
    Person.minimum(:age)
    Person.sum('2 * age')

    Person.calculate(:count, :all)

Advanced:

    Person.distinct.count
    Person.group(:city).count
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment