您的位置:首页 > 编程语言 > Ruby

The Ruby On Rials Gudie -- Active Record Associations

2013-11-17 10:03 525 查看
这一章讲的是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


 





                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ruby on rails guide