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.



Nenhum comentário:

Postar um comentário