别名多态关联的名称

时间:2016-10-16 20:02:47

标签: ruby-on-rails activerecord polymorphic-associations

我是第一次尝试在项目中实现多态关联,但是我不喜欢这些关联如何阅读,并且想知道是否有一种混叠它们的方式?

示例:

# app/models/comment.rb
class Comment < ActiveRecord::Base
  belongs_to :commentable, :polymorphic => true
end

# app/models/post.rb
class Post < ActiveRecord::Base
  has_many :comments, :as => :commentable
end

# app/models/picture.rb
class Picture < ActiveRecord::Base
  has_many :comments, :as => :commentable
end

假设我想从给定的评论中检索一个Post实例,terrific_post = Comment.first.commentable在我看来并不好读。有没有办法在注释模型中为不同的关联名称添加别名,并避免依赖于commentable这样的单个名称?我意识到你可以选择一个与你的特定dsl更好地对齐而不是说“可评论”的名称,但是我更愿意继续根据他们的关系引用名称(或变体)的关联,类似于Comment.first.post和`Comment.first.picture'如果可能的话。

在一天结束时,对于多态关联的灵活性而言,这不是一个很大的牺牲。只是好奇是否存在解决方案。

注意:以下示例来自The Odin Project,它可以很好地解释各种类型的关联。

2 个答案:

答案 0 :(得分:2)

您可以像任何其他方法一样为关联设置别名:

class Comment < ActiveRecord::Base
  belongs_to :commentable, :polymorphic => true
  alias_method :post, :commentable
  alias_method :picture, :commentable
end

然后,您可以执行Comment.first.postComment.first.picture

Comment.first.post可以是PostPicture, 所以你应该知道你在做什么。

另一种方法是实现只有在postcommentable时才会返回Post的方法,而picture仅在commentablePicture时才会返回class Comment < ActiveRecord::Base belongs_to :commentable, polymorphic: true def post commentable if commentable.is_a? Post end def picture commentable if commentable.is_a? Picture end end

runs <- 200; size <- 100
sample_mat <- matrix(rnorm(runs*size, mean = 0, sd = 1), ncol = runs)
colMeans(abs(sample_mat))

答案 1 :(得分:1)

更新:为了实验目的,我最终将我的解决方案打包到 gem 中。随意尝试,但不要在生产中使用它。

已弃用:以下解决方案将不起作用,因为 Rails 不再支持用于单一关联的 extend 选项(但它本来会很好)。

您可以使用 extend 选项扩展您的关联:

# lib/association_extensions/polymorphic_aliases_extension.rb
module PolymorphicAliasesExtension
  # Your extension code
end

# config/initializers/association_extensions.rb
Dir["#{Rails.root}/lib/association_extensions/*.rb"].each { |file| require file }

# app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :commentable, polymorphic: true, extend: PolymorphicAliasesExtension
end

在您的扩展程序上,您必须使用 proxy_association 方法来访问关联的内部结构,例如 ownertargetreflection 属性。

请注意,多态 belongs_to 关联会生成以下方法(其中 association 是关联的名称):

record.association
record.association=
record.association_id
record.reload_association

因此,您必须为这些方法取别名。

从那里开始,您几乎可以利用元编程的力量做您想做的事。

我认为这应该有效:

module PolymorphicAliasesExtension
 PREFIXES = %i( reload_ ).freeze
 SUFFIXES = %i( = _id ).freeze

 owner           = proxy_association.owner # Comment record
 target_name     = proxy_association.target.class.model_name.singular # "post" or "picture"
 reflection_name = proxy_association.reflection.name # :commentable

 polymorphic_aliases = ["alias :#{target_name} :#{reflection_name}"]
  
 PREFIXES.each do |prefix|
  polymorphic_aliases << "alias :#{prefix}#{target_name} :#{prefix}#{reflection_name}"
 end
  
 SUFFIXES do |suffix|
  polymorphic_aliases << "alias :#{target_name}#{suffix} :#{reflection_name}#{suffix}"
 end
  
 polymorphic_aliases.each do |polymorphic_alias|
  owner.class_eval <<-CODE, __FILE__, __LINE__ + 1
   #{polymorphic_alias}
  CODE
 end
end

或者干脆:

# lib/association_extensions/polymorphic_aliases_extension.rb
module PolymorphicAliasesExtension
  owner           = proxy_association.owner # Comment record
  target_name     = proxy_association.target.class.model_name.singular # "post" or "picture"
  reflection_name = proxy_association.reflection.name # :commentable

  owner.class_eval <<-CODE, __FILE__, __LINE__ + 1
   alias_attribute :#{target_name} :#{reflection_name}
  CODE
end

您的型号代码:

# app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :commentable, polymorphic: true, extend: PolymorphicAliasesExtension
end

# app/models/post.rb
class Post < ApplicationRecord
  has_many :comments, as: :commentable
end

# app/models/picture.rb
class Picture < ApplicationRecord
  has_many :comments, as: :commentable
end

# When the `target` is a Post:
#
# Comment#post
# Comment#post=
# Comment#post_id
# Comment#reload_post
#
# When the `target` is a Picture:
#
# Comment#picture
# Comment#picture=
# Comment#picture_id
# Comment#reload_picture