The Ruby On Rials Gudie -- Active Record Associations

这一章讲的是model间的关系,这个和数据库表间关系有点相似,但是更加的像一个真实世界的关系,比如谁has_many谁,谁belong谁,这样的话,我们进行一些操作时是很简单的。就像下面的一样

class Customer < ActiveRecord::Base
 has_many :orders, dependent: :destroy
end
 
class Order < ActiveRecord::Base
 belongs_to :customer
end

Customer有很多order,每个order仅能属于一个Customer,当一个Customer 进行destroy时,附属的Order也将被删除。

对于上面的例子,我们新建order时,要用它属于的Customer创建。

@order = @customer.orders.create(order_date: Time.now)

下面我们将简单说下rails支持的association有哪些

belongs_to

has_one

has_many

has_many :through

has_one :through

has_and_belongs_to_many

下面我们一个个的说说什么意思

belongs_to

belongs_to建立了一个对另一个model的one-to-one连接,简单来说就是 Abelongs_to B 那么A就属于B,一个A只能对应一个B,而一个B可以拥有一个或多个A。看下面的例子

class Order < ActiveRecord::Base
  belongs_to :customer
end


注意,belongs_to后面跟的并不是:customers 而是:customer,也就是会所belongs_to跟着单数形式。其实很好理解,每个order(一个model就是一条记录)只能属于一个customer的,所以是单数啦。

相应的migration应该是这样子的

class CreateOrders <ActiveRecord::Migration
  defchange
   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

has_one 同样是建立了一个one-to-one的联系,但有一点不同的是,这里不说的那么专业,简单而言 A has_one B 那么 A 将有一个 B。看下面的一个例子

class Supplier < ActiveRecord::Base
  has_one :account
end


它和belongs_to最大的区别是那个外键的位置,可以仔细比较下两张图,即可发现。

它的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

has_many建立了一个one-to-many的连接,一般我们在 A中写了 Abelongs_to B,在B中我们就写 B has_many A(或者B has_one A),表明了B 有零个或者多个 A,例子


class Customer < ActiveRecord::Base
  has_many :orders
end

注意,has_many是跟着复数形式的


它的migration类似于这样(对比这belongs_to看)

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

has_many :through

这个联系是指 A 通过 C 与B 建立了many-to-many的联系。比如病人挂号看病,每个病人可以挂一个号,每个号可以看一个医生,但是病人通过挂号就可以看很多个医生,同时医生也可以挂号为多个病人服务,这里 A 就是病人(Patient)B就是预约号(Appointment)C就是医生(Physician)上述用代码实现的话就像下面

注意,其实through是一个参数,其实调用的还是has_many这个函数。


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

这个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

这样 physician.patients= patients rails就可以识别了。

其实,这个函数更加强大,你只要记住一点,A has_many  B , through: C , 那么A 就有很多C,同时有很多B,A中不必写has_manyC ,C也不必写belongs_to A。类似的,我们可以把A认为一篇文章,B为段落,C为句子。假如我们希望 @document.paragraphs 那么下面的写法就比较好,简单来说就是A有很多B, B有很多C。C仅属于一个B,B仅属于一个A.

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

has_one :through

和has_many :through类似,只不过是建立的one-to-one关系。直接说例子,比如说每个供应商有一个账号,每个账号有一个账号历史,那么就应该类似的实现


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


migration如下

    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

这个是建立直接的many-to-many联系,假如A has_and_blongs_to_many B,那么他们将生成一个新表叫As_Bs,直接看例子,组件和零件的关系,每个零件属于多个组件,一个组件包含多个零件。所以

class Assembly < ActiveRecord::Base
 has_and_belongs_to_many :parts
end
 
class Part < ActiveRecord::Base
 has_and_belongs_to_many :assemblies
end

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

多态连接关系(polymorphic association)

假如我们的model A 可能与B或者C有关系,那么我们就要采用多态连接了。主要想法是开启多态,然后把连接指向一个B和C公用的name上,然后B和C再重命名为该name。

就比如下面的例子,employee 和 product 都有image。实现如下

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

对于Picture 它belongs_to一个叫imageable的model,而这个model是多态的。对于Employee和Product他们重命名为imageable然后has_many Picture,所以,@employee.pictures.或者@product.pictures.可以使用。假如我们想通过picture访问它的拥有者,可以通过 @picture.imageable访问,这个需要求改migration来使他工作,其实就是加一个外键,加上id和type(id就是id,type是说是employee还是product),修改后的migration如下

class CreatePictures <ActiveRecord::Migration
  defchange
   create_table :pictures do |t|
     t.string :name
     t.references :imageable, polymorphic: true #声明了外键,并且说明是多态的
     t.timestamps
   end
  end
end

主要用法就如上面所说的了,下面我们讲一些使用的小技巧

我们所执行的操作都是基于缓存的,假如我们需要重新载入缓存该怎么办呢?这个是有可能用到的,因为假如我们的数据库数据更改了但是缓存中数据并没有更改,那么基于该缓存的一切操作将不再正确了。其实重载也很简单,只要传递一个true参数就行了

假如原来的

customer.orders                 # retrieves orders from the database
customer.orders.size            # uses the cached copy of orders
customer.orders.empty?          # uses the cached copy of orders
假如传递了true

customer.orders                 # retrieves orders from the database
customer.orders.size            # uses the cached copy of orders
customer.orders(true).empty?    # discards the cached copy of orders and goes back to the database

最后,我们仔细说说每个部分association将产生的函数和可接受参数

一、belongs_to

看例子

classOrder < ActiveRecord::Base
  belongs_to:customer
end

那么,他将产生以下函数(customer是根据model名字不同变化的,其余部分不变)

customer

customer=

build_customer

create_customer

为了更加通用,我们将这样讲,A belongs_to : association,一般形式是这样的,然后依托上面的例子将

所以它产生了下面四个通用函数。

association(force_reload = false)

association=(associate)

build_association(attributes = {})

create_association(attributes = {})

1. association(force_reload = false)

访问A belongs_to 的association,其中的参数决定是否重新从数据库中加载缓存。例如下面的

@customer = @order.customer

2. association=(associate)

利用已经存在的association 修改A belongs_to的association,例如

@order.customer = @customer 

3. build_association(attributes = {})

新建一个A belongs_to 的association,但是相当于执行了new,但是没有save,参数就是new association需要的参数。例如

@customer = @order.build_customer(customer_number: 123, customer_name: "John Doe")

4. create_association(attributes = {})

与build_association类似,不过他相当于执行了create,也就是save了。

@customer = @order.create_customer(customer_number: 123,  customer_name: "John Doe")

belongs_to的参数

belongs_to 的参数有下面的几个

1. :autosave #设置为true,那么rails将自动保存加载的model,并且自动消除

2. :class_name #用于指定model,假如两个不属于同一个model,那么就在class_name以字符串形式写出

3. :counter_cache #避免每次查询还要遍历数据库计数。你可以设置为true

class Order < ActiveRecord::Base
  belongs_to :customer, counter_cache: true
end
class Customer < ActiveRecord::Base
  has_many :orders
end

你还可以将这个信息存到数据库中,但是这个会给你增加一列,他后面跟着列的名字,如下

class Order < ActiveRecord::Base
  belongs_to :customer, counter_cache: :count_of_orders
end
class Customer < ActiveRecord::Base
  has_many :orders
end

4. :dependent

dependent说的是当A has_many B时,假如我们删除掉A,与A对应的B应该如何处置。主要有一下三种

:destroy 假如删除 A ,那么将通过调用B的destroy函数来删除B

:delete 假如删除A, 那么将不调用B的destroy函数,而是直接从数据库删除

:restrict 假如删除A,只要A有关联的B,那么就将抛出一个ActiveRecord::DeleteRestrictionError错误

5. :foreign_key 指向一个string,你可以使用它自己直接指定外键列的名称

6. :inverse_of 显示的指出与该联系相反的model名,他与:polymorphic 不共存

class Customer < ActiveRecord::Base
  has_many :orders, inverse_of: :customer
end
 
class Order < ActiveRecord::Base
  belongs_to :customer, inverse_of: :orders
end

7. :polymorphic 参照上面说的多态连接

8. :touch 首先,你可以设置该参数为true,这样对于下面的例子,每次对orders进行save或者destroy什么的操作,同样也会更新customer的时间戳,也就是更新updated_at 或者updated_on 列。

class Order < ActiveRecord::Base
 belongs_to :customer, touch: true
end
 
class Customer < ActiveRecord::Base
 has_many :orders
end

同样,你可以指定列进行更新,如下

class Order < ActiveRecord::Base
  belongs_to :customer, touch: :orders_updated_at #orders是表名字,而updated_at是列名。
end

9. :validate #设置为true,那么每次操作就要进行validation验证,默认为false

二、has_one

他与belongs_to有很多相似之处,所以相似的东西我们就不在赘述,仅列出。

 

四个通用函数

association(force_reload = false)

association=(associate)

build_association(attributes = {})

create_association(attributes = {})

参数

:as

:autosave

:class_name

:dependent

:foreign_key

:inverse_of

:primary_key 自定义主键

:source

:source_type

:through

:validate

只是新加了一个source 和 source_type

source的理解可以参考下面的,讲的很清楚(source_type应该类似,不过暂时没找到资料):

壹、

Sometimes, you want to use different namesfor different associations. If the name you want to use for an association onthe model isn't the same as the assocation on the :through model, youcan use:source to specify it.

I don't think the above paragraph is much clearerthan the one in the docs, so here's an example. Let's assume we have threemodels, Pet, Dog and Dog::Breed.

class Pet < ActiveRecord::Base
  has_many :dogs
end

class Dog < ActiveRecord::Base
  belongs_to :pet
  has_many :breeds
end

class Dog::Breed < ActiveRecord::Base
  belongs_to :dog
end

In this case, we've chosen to namespacethe Dog::Breed, because we want to accessDog.find(123).breeds as anice and convenient association.

Now, if we now want to createa has_many :dog_breeds, :through => :dogs association on Pet,we suddenly have a problem. Rails won't be able to finda :dog_breeds association on Dog, so Rails can't possiblyknow which Dog association you want to use. Enter :source:

class Pet < ActiveRecord::Base
  has_many :dogs
  has_many :dog_breeds, :through => :dogs, :source => :breeds
end

With :source, we're telling Rails tolook for an association called :breeds on the Dog model (asthat's the model used for :dogs), and use that.

贰、

Let me expand on that example:

class User
  has_many :subscriptions
  has_many :newsletters, :through => :subscriptions
end

class Newsletter
  has_many :subscriptions
  has_many :users, :through => :subscriptions
end

class Subscription
  belongs_to :newsletter
  belongs_to :user
end

With this code, you can do somethinglike Newsletter.find(id).users to get a list of the newsletter'ssubscribers. But if you want to be clearer and be able totypeNewsletter.find(id).subscribers instead, you must change theNewsletter class to this:

class Newsletter
  has_many :subscriptions
  has_many :subscribers, :through => :subscriptions, :source => :user
end

You are renamingthe users association to subscribers. If you don't providethe :source, Rails will look for an associationcalled subscriber in the Subscription class. You have to tell it touse theuser association in the Subscription class to make the list ofsubscribers.

 

二、has_many

当你为你的class添加上has_many约束时,它将自动添加如下方法

collection(force_reload = false)

collection<<(object, ...)

collection.delete(object, ...)

collection.destroy(object, ...)

collection=objects

collection_singular_ids

collection_singular_ids=ids

collection.clear

collection.empty?

collection.size

collection.find(...)

collection.where(...)

collection.exists?(...)

collection.build(attributes = {}, ...)

collection.create(attributes = {})

其中,collection是has_many 后面的第一个参数的符号就是会出现 has_many :collection这样的约束,注意,之前我们已经提过,has_many后面跟的应该是复数形式。collection_singular 是指collection的单数形式。有点抽象,举个例子,比如我们添加了如下的约束

class Customer < ActiveRecord::Base
  has_many :orders
end

那么他将产生如下的几个函数

orders(force_reload = false)

       orders<<(object, ...)

       orders.delete(object, ...)

       orders.destroy(object, ...)

       orders=objects

       order_ids

       order_ids=ids

       orders.clear

       orders.empty?

       orders.size

       orders.find(...)

       orders.where(...)

       orders.exists?(...)

       orders.build(attributes = {}, ...)

       orders.create(attributes = {})


接下来我们将仔细讲解这些函数。

 

 1. collection(force_reload= false)

他将返回一个数组,这个数组中存的是所有相关的记录。参数force_reload 是决定是否重新从数据库加载。

@orders = @customer.orders

2. collection<<(object, ...)

添加一条或多条记录

@customer.orders << @order1

3. collection.delete(object, ...)

删除一条或多条记录,它并不执行destroy函数,而是将他们的外键设为null,是否从数据库中删除,是由dependent关系决定的。这个在上面我们讲过。


@customer.orders.delete(@order1)

4. collection.destroy(object, ...)

删除一条或多条记录,它通过执行destroy函数执行,它会忽略dependent关系,直接从数据库中删除记录

@customer.orders.destroy(@order1)

5. collection=objects

修改为objects,它是通过添加和删除来保持合适的。

6. collection_singular_ids

返回一个array,里面存了collection中object的id

@order_ids = @customer.order_ids

7. collection_singular_ids=ids

使collection中只存在主键为ids中出现的记录,它是通过增加删除来实现的

8. collection.clear

清除记录,数据库中是否删除记录,决定于dependent关系。

9. collection.empty?

10. collection.size

11. collection.find(...)

查找,后面我们会仔细讲它的使用语法

@open_orders = @customer.orders.find(1)

12. collection.where(...)

这个方法根据提供的条件查找记录,但他并不是立刻查找,而是在真正使用时才进行查找。

@open_orders=@customer.orders.where(open: true) # No query yet
@open_order= @open_orders.first # Now the database will be queried

13. collection.exists?(...)

这个函数判断是否有满足条件的记录存在。

14. collection.build(attributes= {}, ...)

这个函数将返回一个或多个associated的新object,这些object是由传递的参数新建的,同时,外键联系也将被建立,但是他们还没有save。

@order=@customer.orders.build(order_date: Time.now, order_number: "A12345")

15. collection.create(attributes= {})

与上一个函数类似,只不过它会进行validation测试,假如通过了就会save。

 

has_many的参数

has_many参数有一下几个,上面讲过的就不在赘述

·        1. :as

    它用于多态association,as是将has_many那个连接命名为polymorphic对应的连接。可以参考上面的。

·       2. :autosave

·       3. :class_name

·       4. :dependent

当它们的所有者被destroyed的时候决定该如何做,这个和上面的参数有点不同,比如A has_many B,我们说的将是A执行destroy的时候。但要注意,当你使用了:through的时候,这个选项就被忽略了。

    5: :destroy 所有相关的B也将destroy,它是通过执行destroy函数执行的,也就是说它会经过回调函数

    5.1 :delete_all直接从数据库中将相关的B删除,他并不通过回调函数。

    5.2 :nullify 仅将外键联系设置为null,并不删除,也不通过回调函数。、

      5.3 :restrict_with_exception 将抛出异常

        5.4 :restrict_with_error 将抛出错误 

·       6.:foreign_key

·       7. :inverse_of

·       8. :primary_key

·       9. :source

·       10. :source_type

·       11. :through

·       12. :validate

 

三、has_and_belongs_to_many 

这个联系创建了一个多对多的联系。它是通过新建一个中间表来实现的。

它新加的函数如下,与has_many类似

·        collection(force_reload=false)

·        collection<<(object,...)

·        collection.delete(object,...)

·        collection.destroy(object,...)

·        collection=objects

·        collection_singular_ids

·        collection_singular_ids=ids

·        collection.clear

·        collection.empty?

·        collection.size

·        collection.find(...)

·        collection.where(...)

·        collection.exists?(...)

·        collection.build(attributes={})

·        collection.create(attributes={})

 

它的参数如下  A has_and_belongs_to_many B

·        :association_foreign_key 直接指定B的外键的名字

·        :autosave

·        :class_name

·        :foreign_key 直接指定A的外键名字

·        :join_table 指定join table的名字,也就是存放A和B外键表的名字

·        :validate

·        :readonly

Association Callbacks

Association 也是支持回调函数的,用法和之前讲的callback类似。

它支持的回调函数如下

·        before_add

·        after_add

·        before_remove

·        after_remove

四个的用法相同,类似于下面的形式


classCustomer <ActiveRecord::Base
  has_many :orders, before_add: :check_credit_limit
 
  defcheck_credit_limit(order)
    ...
  end
end

你也可以采用流水式作业法,也就是说你可以在before_add前一次执行两个函数,分别进行加工,这样的逻辑性更强。用法很简单,你只要把两个函数名放在一个array中他们就会依次执行了。就如下面的例子

classCustomer < ActiveRecord::Base
  has_many :orders,
    before_add: [:check_credit_limit,:calculate_shipping_charges]
 
  defcheck_credit_limit(order)
    ...
  end
 
  defcalculate_shipping_charges(order)
    ...
  end
end




 






















   
   



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值