quarta-feira, 27 de novembro de 2013

Ruby on Rails - Active Record Associations

Active Record Associations
=================

   Based on guides.rubyonrails.org

Active Record Associations
     Types of Associations
            belongs_to
            has_one
            has_many
            has_many :through
            has_one :through
            has_and_belongs_to_many     
     belongs_to
            Set a one-to-one conection with another model.  Each instance of this class belongs_to one instance of other instance
            ex:
                        class Order < ActiveRecord::Base
                          belongs_to :customer
                        end
            http://guides.rubyonrails.org/images/belongs_to.png

            Corresponding migration
                        class CreateOrders < ActiveRecord::Migration
                          def change
                            create_table :customers do |t|
                              t.string :name
                              t.timestamps
                            end
                         
                            create_table :orders do |t|
                              t.belongs_to :customer
                              t.datetime :order_date
                              t.timestamps
                            end
                          end
                        end
     has_one
            set a one-to-one connection with another model.  But this association denotes possession of another instance
            ex:
                        class Supplier < ActiveRecord::Base
                          has_one :account
                        end
            http://guides.rubyonrails.org/images/has_one.png
            Corresponding migration
                        class CreateSuppliers < ActiveRecord::Migration
                          def change
                            create_table :suppliers do |t|
                              t.string :name
                              t.timestamps
                            end
                         
                            create_table :accounts do |t|
                              t.belongs_to :supplier
                              t.string :account_number
                              t.timestamps
                            end
                          end
                        end
     has_many
            A instance of a class has many instances of another class
            ex:
                        class Customer < ActiveRecord::Base
                          has_many :orders
                        end
            http://guides.rubyonrails.org/images/has_many.png
            Corresponding migration
                        class CreateCustomers < ActiveRecord::Migration
                          def change
                            create_table :customers do |t|
                              t.string :name
                              t.timestamps
                            end
                         
                            create_table :orders do |t|
                              t.belongs_to :customer
                              t.datetime :order_date
                              t.timestamps
                            end
                          end
                        end
     The has_many :through Association
            To set many-to-many association.  It creates a third model to represent the many-to-many association
            ex:
                        class Physician < ActiveRecord::Base
                          has_many :appointments
                          has_many :patients, through: :appointments
                        end
                         
                        class Appointment < ActiveRecord::Base
                          belongs_to :physician
                          belongs_to :patient
                        end
                         
                        class Patient < ActiveRecord::Base
                          has_many :appointments
                          has_many :physicians, through: :appointments
                        end
            http://guides.rubyonrails.org/images/has_many_through.png
            Corresponding migration
                        class CreateAppointments < ActiveRecord::Migration
                          def change
                            create_table :physicians do |t|
                              t.string :name
                              t.timestamps
                            end
                         
                            create_table :patients do |t|
                              t.string :name
                              t.timestamps
                            end
                         
                            create_table :appointments do |t|
                              t.belongs_to :physician
                              t.belongs_to :patient
                              t.datetime :appointment_date
                              t.timestamps
                            end
                          end
                        end
            It is possible to get values using the reference attribute
                        physician.patients = patients

            It is possible to go through the nest tree of dependencies using :through
            EX:
                        class Document < ActiveRecord::Base
                          has_many :sections
                          has_many :paragraphs, through: :sections
                        end
                         
                        class Section < ActiveRecord::Base
                          belongs_to :document
                          has_many :paragraphs
                        end
                         
                        class Paragraph < ActiveRecord::Base
                          belongs_to :section
                        end

                        @document.paragraphs

     has-one :though
            Set one-to-one relation .  The :though has a similar purpose of above. Access another model through a second.

            ex:
                        class Supplier < ActiveRecord::Base
                          has_one :account
                          has_one :account_history, through: :account
                        end
                         
                        class Account < ActiveRecord::Base
                          belongs_to :supplier
                          has_one :account_history
                        end
                         
                        class AccountHistory < ActiveRecord::Base
                          belongs_to :account
                        end
            Corresponding Migration
                        class CreateAccountHistories < ActiveRecord::Migration
                          def change
                            create_table :suppliers do |t|
                              t.string :name
                              t.timestamps
                            end
                         
                            create_table :accounts do |t|
                              t.belongs_to :supplier
                              t.string :account_number
                              t.timestamps
                            end
                         
                            create_table :account_histories do |t|
                              t.belongs_to :account
                              t.integer :credit_rating
                              t.timestamps
                            end
                          end
                        end
     has_and_belongs_to_many
            Creates a direct many-to-many association between two models
            ex:
                        class Assembly < ActiveRecord::Base
                          has_and_belongs_to_many :parts
                        end
                         
                        class Part < ActiveRecord::Base
                          has_and_belongs_to_many :assemblies
                        end
                        http://guides.rubyonrails.org/images/habtm.png
            Corresponding migration
                        class CreateAssembliesAndParts < ActiveRecord::Migration
                          def change
                            create_table :assemblies do |t|
                              t.string :name
                              t.timestamps
                            end
                         
                            create_table :parts do |t|
                              t.string :part_number
                              t.timestamps
                            end
                         
                            create_table :assemblies_parts do |t|
                              t.belongs_to :assembly
                              t.belongs_to :part
                            end
                          end
                        end
     has_many :through OR has_and_belongs_to_many?
            has_many :through -> If you need the models to be independent entities
                        If you need validations, callbacks, or extra attributes on the join model.
            has_and_belongs_to_many -> if there is a dependency between entities

     Polymorphic Associations
             a model can belong to more than one other model, on a single association.
             Ex: A Picture that belongs to Employee and Product
                        class Picture < ActiveRecord::Base
                          belongs_to :imageable, polymorphic: true
                        end
                         
                        class Employee < ActiveRecord::Base
                          has_many :pictures, as: :imageable
                        end
                         
                        class Product < ActiveRecord::Base
                          has_many :pictures, as: :imageable
                        end
            Then:
                        @employee.pictures.
                        AND
                        @product.pictures.
            Corresponding migration
                        class CreatePictures < ActiveRecord::Migration
                          def change
                            create_table :pictures do |t|
                              t.string :name
                              t.references :imageable, polymorphic: true
                              t.timestamps
                            end
                          end
                        end
                        http://guides.rubyonrails.org/images/polymorphic.png
     Self-joins
            Ex:
                        class Employee < ActiveRecord::Base
                          has_many :subordinates, class_name: "Employee",
                                                  foreign_key: "manager_id"
                         
                          belongs_to :manager, class_name: "Employee"
                        end
     Issues
            Updating the schema
                        belongs_to associations you need to create foreign
                                   ex:
                                               class Order < ActiveRecord::Base
                                                 belongs_to :customer
                                               end

                                               class CreateOrders < ActiveRecord::Migration
                                                 def change
                                                   create_table :orders do |t|
                                                     t.datetime :order_date
                                                     t.string   :order_number
                                                     t.integer  :customer_id
                                                   end
                                                 end
                                               end
                                   has_and_belongs_to_many associations you need to create the appropriate join table.

            Controlling association scope
                        To make associations of classes that are in different modules uses "class_name" and tell all the path
                        module MyApplication
                          module Business
                            class Supplier < ActiveRecord::Base
                               has_one :account,
                                class_name: "MyApplication::Billing::Account"
                            end
                          end
                         
                          module Billing
                            class Account < ActiveRecord::Base
                               belongs_to :supplier,
                                class_name: "MyApplication::Business::Supplier"
                            end
                          end
                        end
            Bi-directional associations
                        To guarantee that the relation is bi-directional use : "inverse_of:"
                        class Customer < ActiveRecord::Base
                          has_many :orders, inverse_of: :customer
                        end
                         
                        class Order < ActiveRecord::Base
                          belongs_to :customer, inverse_of: :orders
                        end
                        ex:
                                   c = Customer.first
                                   o = c.orders.first
                                   c.first_name == o.customer.first_name # => true
                                   c.first_name = 'Manny'
                                   c.first_name == o.customer.first_name # => true
                        Limitations:
                                   Do not work with :through, :polymorphic and :as associations
                                   For belongs_to associations, has_many inverse associations are ignored.       
     belongs_to Association Reference
            association(force_reload = false)
            association=(associate)
            build_association(attributes = {})
            create_association(attributes = {})                    
            Ex:
                        class Order < ActiveRecord::Base
                          belongs_to :customer
                        end

                        association(force_reload = false)
                                   @customer = @order.customer
                        association=(associate)
                                   @order.customer = @customer
                        build_customer
                                   The associated object is NOT saved
                                   @customer = @order.build_customer(customer_number: 123,
                            customer_name: "John Doe")
                        create_customer
                                   The associated object IS SAVED
                                   @customer = @order.create_customer(customer_number: 123,
                            customer_name: "John Doe")          
        Obtions to belongs_to
            To customize the behavior of belongs_to
            :autosave
                        If true, Rails will save or destroy any member as soon as the parent is saved
                        :class_name
                                   Used when the name of class in belongs_to can't be derived from association name.
                                   Or to change the name of the accessed object in belongs_to
                                   ex:
                                               class Order < ActiveRecord::Base
                                                 belongs_to :customer, class_name: "Patron"
                                               end
                        :counter_cache
                                   Make the SIZE more efficient including a counter and the Db not uses "count(*)"
                                   ex:
                                               class Order < ActiveRecord::Base
                                                 belongs_to :customer, counter_cache: true
                                                 #OR
                                                 belongs_to :customer, counter_cache: :count_of_orders
                                               end
                                               class Customer < ActiveRecord::Base
                                                 has_many :orders
                                               end
                        :dependent
                                   :destroy -> Call the destroy of the dependant object when the parent is destroyed
                                               belongs_to :customer, dependent: :destroy
                                   :delete -> remove the associated object, but don't call destroy.
                                               belongs_to :customer, dependent: :delete
                                   :restrict -> A ActiveRecord::DeleteRestrictionError is thrown if try to delete the associated object
                                               belongs_to :customer, dependent: :restrict
                        :foreign_key
                                    Set a name for foreign key
                                   class Order < ActiveRecord::Base
                                     belongs_to :customer, class_name: "Patron",
                                                           foreign_key: "patron_id"
                                   end
                        :inverse_of
                                   Specify the inverse of association
                                   Ex:
                                               class Customer < ActiveRecord::Base
                                                 has_many :orders, inverse_of: :customer
                                               end
                                                
                                               class Order < ActiveRecord::Base
                                                 belongs_to :customer, inverse_of: :orders
                                               end
                         :polymorphic
                                    Indicates that an association is polymorphic
                         :touch
                                    If the object is saved or destroyed the updated_at stamp is set
                                     belongs_to :customer, touch: true
                                     #OR specify the attribute
                                      belongs_to :customer, touch: :orders_updated_at
                        :validate
                                   Whenever you save the object, the associated will be validated as well
                                     belongs_to :customer, validate: true
            Scopes for belongs_to
                        To customize the query used by belongs_to
                        where
                                   belongs_to :customer, -> { where active: true }
                        includes
                                   To specify a second-order association that could eager-loaded.  Increase the efficiency
                                   For first-order, the eager is automatic
                                               class LineItem < ActiveRecord::Base
                                                 belongs_to :order, -> { includes :customer }
                                               end
                                                
                                               class Order < ActiveRecord::Base
                                                 belongs_to :customer
                                                 has_many :line_items
                                               end
                                                
                                               class Customer < ActiveRecord::Base
                                                 has_many :orders
                                               end
                        readonly
                                   If readonly, the associated object will be readonly when retrived via a association.
                        select
                                   lets override the "SQL SELECT" clause
     has_many Association Reference
            Methods
                        collection(force_reload = false)
                                   @orders = @customer.orders
                        collection<<(object, ...)
                                   @customer.orders << @order1
                        collection.delete(object, ...)
                                   - Remove from list, seting foreign key to nil.  If associeated with :destroy it will be destroyed.
                                   @customer.orders.delete(@order1)
                        collection.destroy(object, ...)
                                   - It will ignore the :depend option
                                   @customer.orders.destroy(@order1)
                        collection=objects
                                   @order_ids = @customer.order_ids   (ARRAY)
                        collection.clear
                                   Remove every object from the collection.  Destroy if "depend :destroy", delete if "depend :delete_all" or set foreign key to nil
                        collection.empty?
                                   <% if @customer.orders.empty? %>
                                     No Orders Found
                                   <% end %>
                        collection.size
                                   @order_count = @customer.orders.size
                        collection.find(...)
                                   @open_orders = @customer.orders.find(1)
                        collection.where(...)
                                   @open_orders = @customer.orders.where(open: true) # No query yet
                        collection.exists?(...)
                        collection.build(attributes = {}, ...)
                                   Created but NOT saved
                                   @order = @customer.orders.build(order_date: Time.now,
                                order_number: "A12345")
                        collection.create(attributes = {})
                                   Created and SAVED
                                   @order = @customer.orders.create(order_date: Time.now,
                                 order_number: "A12345")
    options for has_may
            :as
                        Polymorphic association
                        :autosave
                                   Automatically saved the association based on any modification of the object
                        :class_name
                                   Change the class
                                    has_many :orders, class_name: "Transaction"
                        :dependent

                        :foreign_key
                                   has_many :orders, foreign_key: "cust_id"
                        :inverse_of

                        :primary_key
                        :source
                        :source_type
                        :through
                        :validate
                                   If validate option to false the associated object will not be validated when the object is saved.  Default = true.
            Scope for has_many
                        where
                                   Confition to the associated object to be met
                                   class Customer < ActiveRecord::Base
                                     has_many :confirmed_orders, -> { where "confirmed = 1" },
                                       class_name: "Order"
                                   end

                        extending
                        group
                                   Define a group by
                                   has_many :line_items, -> { group 'orders.id' },
                        through: :orders
                        includes
                                   To define a second-order association to eager-load
                                    has_many :orders, -> { includes :line_items }
                        limit
                                   limit the number of objects that will be fetched
                                   has_many :recent_orders, -> { order('order_date desc').limit(100) },    class_name: "Order",
                        offset
                                   Define the offset of a fetch
                                   -> { offset(11) }
                        order
                                   Define the order by
                                   has_many :orders, -> { order "date_confirmed DESC" }
                        readonly
                                   If true the associated objects retrieved are readonly
                        select
                                   To define your own SQL SELEC
                        distinct
                                   Used to keep the collection free from duplication.  Mostly used with :through option
                                   class Person
                                     has_many :readings
                                     has_many :posts, -> { distinct }, through: :readings
                                   end

     has_and_belongs_to_many Association Reference
            Methods
                        Basicaly the same as has_many association plus:
                        :join_table
                                   Override the name of the join_table
            Scope for has_and_belongs_to_many
                        Basicaly the same as has_many association plus:
                         uniq
                                    To remove duplicates from colection

     Association Callbacks
            Callbacks work in some place in the life cicle of a Active Record object.  The are triggered by events in the life cicle of the object.
           
            before_add
            after_add
            before_remove
            after_remove
            ex:
            class Customer < ActiveRecord::Base
              has_many :orders, before_add: :check_credit_limit
             
              def check_credit_limit(order)
                ...
              end
            end
            If an exception is thrown, the operation is not performed.



Ruby on Rails - Active Record Validation

Active Record Validation
=============
   Based on guides.rubyonrails.org

            Validation happens when class.new_record? is called

            Ex:
                        p = Person.new(name: "John Doe")
                        #
                        #p.new_record?
                        #true
                        p.save
                        #true
                        p.new_reccord?
                        false
            Validation acts on:
                        create
                        create!
                        save
                        save!
                        update
                        update!
            These methods skip validation
                        decrement!
                        decrement_counter
                        increment!
                        increment_counter
                        toggle!
                        touch
                        update_all
                        update_attribute
                        update_column
                        update_columns
                        update_counters

                        save(validate: false)
            valid? invalid?
                        Person.create(name: nil).valid? # => false
            errors[]
                        After validation, each error will be stores in a array object.errors
                        Person.new.errors[:name].any?
                        person = Person.new
                        person.valid? # => false
                        person.errors[:name]
                        If no error, return []
            add
                        It is possible to add error
                        errors.add(:name, "cannot contain the characters !@#%*()_-+=")
            error [:base]
                        It is array that always exists, but is empty. Can include error
                                   errors[:base] << "This person is invalid because ..."
                        Messages can be includes as it is an array.  Message will be shown in error message.
            Clear and Empty
                        person.errors.clear
                        person.errors.empty? # => true
            size
                        person.errors.size # => 2
            Each erro
                        person.errors.full_messages.each do |msg|
                                   puts msg
                        end
            When a error occurs a
is created to b

    Validation Helper
                        Pre-defined validation helpers that can be used directly
                        Acceptance
                                   Validates checkbox of acceptance of some company condition for example.
                                   class Person < ActiveRecord::Base
                                               validates :terms_of_service, acceptance: true
                                               #OR
                                               validates :terms_of_service, acceptance: { accept: 'yes' }
                                   end
                                   Message:  "must be accepted"
                        validates_associated
                                   If valid? the main object(ex:Library), all associations(ex:Books) should be validated
                                   class Library < ActiveRecord::Base
                                     has_many :books
                                     validates_associated :books
                                   end
                        confirmation
                                   when two fields should receive the same value
                                   Ex:
                                               class Person < ActiveRecord::Base
                                                 validates :email, confirmation: true
                                                 validates :email_confirmation, presence: true
                                               end
                                   In view appears:
                                               <%= text_field :person, :email %>
                                               <%= text_field :person, :email_confirmation %>
                        exclusion
                                   To exclude a specific value.  use "in:" to specify
                                   class Account < ActiveRecord::Base
                                     validates :subdomain, exclusion: { in: %w(www us ca jp),
                                       message: "Subdomain %{value} is reserved." }
                                   end
                        format
                                   Specify a accepted format
                                   class Product < ActiveRecord::Base
                                     validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/,
                                       message: "Only letters allowed" }
                                   end
                        inclusion
                                   If a value is in a set
                                   class Coffee < ActiveRecord::Base
                                     validates :size, inclusion: { in: %w(small medium large),
                                       message: "%{value} is not a valid size" }
                                   end
                        lenght
                                   Accepted lenght
                                   class Person < ActiveRecord::Base
                                     validates :name, length: { minimum: 2, maximum: 500,
                                     too_short: "must have at least %{count} words",
                          too_long: "must have at most %{count} words" }
                                     validates :password, length: { in: 6..20 }
                                     validates :registration_number, length: { is: 6 }
                                   end
                        numerically
                                   Accept only number
                                   class Player < ActiveRecord::Base
                                     validates :points, numericality: true
                                     validates :games_played, numericality: { only_integer: true } #/\A[+-]?\d+\Z/
                                   end
                                   Other options:
                                               :greater_than, :greater_than_or_equal_to, :equal_to, :less_than, :less_than_or_equal_to, :odd, :even,
                        Presence
                                   Verify the presence of an attribute
                                   class LineItem < ActiveRecord::Base
                                     belongs_to :order
                                     validates :order, presence: true
                                   end
                                   - inverse_of
                                               In line_items, Order is mandatory, uses inverse_of
                                               class Order < ActiveRecord::Base
                                                 has_many :line_items, inverse_of: :order
                                               end
                        absence
                                   If you need some field to be blank or empty
                                   class Person < ActiveRecord::Base
                                     validates :name, :login, :email, absence: true
                                   end
                                   Same as presence to association
                        uniqueness
                                   enforce uniqueness
                                   class Account < ActiveRecord::Base
                                     validates :email, uniqueness: true
                                     validates :name, uniqueness: { case_sensitive: false }
                                   end
                        validates_with
                                   Passed the validation to another class
                                   class Person < ActiveRecord::Base
                                     validates_with GoodnessValidator
                                   end
                                    
                                   class GoodnessValidator < ActiveModel::Validator
                                     def validate(record)
                                       if record.first_name == "Evil"
                                         record.errors[:base] << "This person is evil"
                                       end
                                     end
                                   end                             
                        validates_each
                                   validates attributes in a block
                                   class Person < ActiveRecord::Base
                                     validates_each :name, :surname do |record, attr, value|
                                       record.errors.add(attr, 'must start with upper case') if value =~ /\A[a-z]/
                                     end
                                   end
            Common Validation Options
                        :allow_nil
                        :allow_blank
                        :message
                        :on
                                   Specify when then validation should happen (:create, :update)
                                   # it will be possible to create the record with a non-numerical age
                                   validates :age, numericality: true, on: :update
            Strict Validations

            Conditional Validation
                        Use of :if or :unless
                                   class Order < ActiveRecord::Base
                                     validates :card_number, presence: true, if: :paid_with_card?
                                    
                                     def paid_with_card?
                                       payment_type == "card"
                                     end
                                   end

                                   class Person < ActiveRecord::Base
                                     validates :surname, presence: true, if: "name.nil?"
                                   end
                        Using Proc with :if or :unless
                                   class Account < ActiveRecord::Base
                                     validates :password, confirmation: true,
                                       unless: Proc.new { |a| a.password.blank? }
                                   end
                        Grouping conditions
                                   Uses with_options
                                   class User < ActiveRecord::Base
                                     with_options if: :is_admin? do |admin|
                                       admin.validates :password, length: { minimum: 10 }
                                       admin.validates :email, presence: true
                                     end
                                   end
            Custom Validations
                        Creating a Validator
                                   Extends the class ActiveModel::Validator and call the validator using: validates_with               
                                   class MyValidator < ActiveModel::Validator
                                               def validate(record)
                                               end
                                    end
                                    class Person
                                     include ActiveModel::Validations
                                     validates_with MyValidator
                                   end
                        Creating methods
                                   class Class < ActiveRecord::Base
                                               validate :methodToValidate, on :create

                                               def methodToValidate
                                                   errors.add(:attribute, "can't be in the past")
                                                   end
                                               end
                                   end
            Errors in view
                        <% if @post.errors.any? %>
                          
                            

<%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:

                         
                            
                            <% @post.errors.full_messages.each do |msg| %>
                              

  • <%= msg %>
  •                             <% end %>
                                

                              

                            <% end %>