2010-02-18 83 views
1
class User < ActiveRecord::Base 
    has_one :location, :dependent => :destroy, :as => :locatable 
    has_one :ideal_location, :dependent => :destroy, :as => :locatable 
    has_one :birthplace, :dependent => :destroy, :as => :locatable 
end 

class Location < ActiveRecord::Base 
    belongs_to :locatable, :polymorphic => true 
end 

class IdealLocation < ActiveRecord::Base 
end 

class Birthplace < ActiveRecord::Base 
end 

我看不出有任何理由在这种情况下有子类。位置对象的行为是相同的,它们唯一的一点是使关联变得容易。我还希望将数据存储为int而不是字符串,因为它可以使数据库索引更小。在轨道上避免STI

我想象像下面这样,但我不能完成的想法:

class User < ActiveRecord::Base 
    LOCATION_TYPES = { :location => 1, :ideal_location => 2, :birthplace => 3 } 

    has_one :location, :conditions => ["type = ?", LOCATION_TYPES[:location]], :dependent => :destroy, :as => :locatable 
    has_one :ideal_location, :conditions => ["type = ?", LOCATION_TYPES[:ideal_location]], :dependent => :destroy, :as => :locatable 
    has_one :birthplace, :conditions => ["type = ?", LOCATION_TYPES[:birthplace]], :dependent => :destroy, :as => :locatable 
end 

class Location < ActiveRecord::Base 
    belongs_to :locatable, :polymorphic => true 
end 

这段代码的下面失败,基本上使其失去作用:

user = User.first 
location = user.build_location 
location.city = "Cincinnati" 
location.state = "Ohio" 
location.save! 

location.type # => nil 

这是显而易见的,因为没有办法将has_one声明中的条件选项转换为等于1的类型。

我可以在视图中的任何位置嵌入id这些字段出现,但这似乎也是错误的:

<%= f.hidden_field :type, LOCATION_TYPES[:location] %> 

有什么办法来避免额外的子类或使LOCATION_TYPES方法的工作?

在我们的特殊情况下,应用程序是非常位置感知的,对象可以有多种不同类型的位置。我只是很奇怪,不想要所有这些子类?

任何建议,你有赞赏,告诉我我疯了,如果你想,但你想看看10 +不同的位置模型漂浮在应用程序/模型?

回答

1

据我所见,位置是位置是位置。您所指的不同“子类”(IdealLocation,Birthplace)似乎只是描述了位置与特定用户的关系。如果我弄错那部分,就阻止我。

知道了,我可以看到两个解决方案。

首先是将位置视为价值对象而不是实体。 (更多关于条款:Value vs Entity objects (Domain Driven Design))。在上面的例子中,您似乎将位置设置为“俄亥俄州辛辛那提”,而不是从数据库中找到“辛辛那提,俄亥俄州”对象。在那种情况下,如果辛辛那提有许多不同的用户,那么尽管只有一个辛辛那提,俄亥俄州,你的数据库中只有同样多的“辛辛那提,俄亥俄州”位置。对我而言,这是一个明确的信号,表明您正在使用值对象,而不是实体。

该解决方案将如何看待?有可能使用一个简单的定位对象是这样的:

class Location 
    attr_accessor :city, :state 

    def initialize(options={}) 
    @city = options[:city] 
    @state = options[:state] 
    end 
end 

class User < ActiveRecord::Base 
    serialize :location 
    serialize :ideal_location 
    serialize :birthplace 
end 

@user.ideal_location = Location.new(:city => "Cincinnati", :state => "OH") 
@user.birthplace = Location.new(:city => "Detroit", :state => "MI") 
@user.save! 

@user.ideal_location.state # => "OH" 

其他解决方案,我可以看到的是使用您现有的位置ActiveRecord的模型,只是使用与用户的关系,定义关系“型”,像这样:

class User < ActiveRecord::Base 
    belongs_to :location, :dependent => :destroy 
    belongs_to :ideal_location, :class_name => "Location", :dependent => :destroy 
    belongs_to :birthplace, :class_name => "Location", :dependent => :destroy 
end 

class Location < ActiveRecord::Base 
end 

您需要做的工作是在用户模型中包含location_id,ideal_location_id和birthplace_id属性。

+0

您是对的,子类仅描述位置关系给用户。 感谢您指向有关值对象与实体的文章。你是对的,用我现在的解决方案,我将最终复制位置表中的很多行。 不幸的是,价值对象的情况将不会解决,因为位置模型实际上有更多的行为,需要通过SQL访问。 您的belongs_to方法似乎在头部受到了攻击,我不确定为什么我首先通过使用has_one来避免它。 – 2010-02-19 14:06:33

0

尝试增加before_save钩

class Location 
    def before_save 
    self.type = 1 
    end 
end 

,同样对于其他类型的位置

+0

,我不希望有其他类型的位置是点。感谢您阅读我发布的内容。 – 2010-02-18 13:56:26

+0

我试图建议如何让你失败的例子(与location.type =>零)的工作。 – dan 2010-02-18 16:08:03

0

可以使用模块封装定位对象的行为,并使用一些宏创建的关系:

has_one <location_class>,: conditions => [ "type =?" LOCATION_TYPES [: location]],: dependent =>: destroy,: as =>: locatable 

你可以在你的模块中使用类似这样的东西:

module Orders 
    def self.included(base) 
    base.extend(ClassMethods) 
    end 

    module ClassMethods 
    def some_class_method(param) 
    end 

    def some_other_class_method(param) 
    end 

    module InstanceMethods 
     def some_instance_method 
     end 
    end 
    end 
end 

Rails guides: add-an-acts-as-method-to-active-record

+0

模块的一些要点示例: Without acts_as:http://gist.github.com/307290 With acts_as:http://gist.github.com/307296 – nanda 2010-02-18 03:15:22

2

为什么不使用named_scopes?

喜欢的东西:

class User 
    has_many :locations 
end 

class Location 
    named_scope :ideal, :conditions => "type = 'ideal'" 
    named_scope :birthplace, :conditions => "type = 'birthplace" # or whatever 
end 

然后在你的代码:

user.locations.ideal => # list of ideal locations 
user.locations.birthplace => # list of birthplace locations 

你最好还是要处理的设置上创造的类型,我想。

0

也许我失去了一些重要的东西在这里,但我想你能说出你的关系是这样的:

class User < ActiveRecord::Base 

    has_one :location, :dependent => :destroy 
    #ideal_location_id 
    has_one :ideal_location, :class_name => "Location", :dependent => :destroy 
    #birthplace_id 
    has_one :birthplace, :class_name => "Location", :dependent => :destroy 

end 

class Location < ActiveRecord::Base 
    belongs_to :user # user_id 
end 
+0

从Rails API:has_one指定与另一个类的一对一关联。只有在其他类包含外键时才应使用此方法。如果当前类包含外键,则应该使用belongs_to。 这样的事情可能会使用belongs_to来代替,chrisdinn只是发布了类似的东西,但与belongs_to,我将不得不评估他的职位。谢谢。 – 2010-02-19 13:59:05