Каков наилучший способ получить одинаковое поведение от нескольких объектов AR, на которые ссылаются полиморфно? - PullRequest
0 голосов
/ 08 декабря 2010

Это приложение Rails 3.

У меня есть изображения, которые можно привязать либо к продукту, либо к бренду. У продукта есть идентификатор, а у бренда есть имя.

Полиморфные отношения из Image называются «связываемыми».

Если я хочу перечислить элементы, с которыми связано определенное изображение, я хочу избежать условного представления в виде, подобном этому:

<% for image in Image.all %>
  <% if image.linkable.class.name=="Product" %>
    <%= image.linkable.identifier %>
  <% elsif image.linkable.class.name=="Brand" %>
    <%= image.linkable.name %>
  <% end %>
<% end %>

Конечно, я мог бы просто поместить метод внутри Brand под названием «identifier» и использовать его для вызова «name». Но это не расширяется, если я хочу добавить больше объектов, с которыми может быть связано изображение. 8 лет назад в программировании на Java я мог поручить классу реализовать интерфейс, но я не знаю, смогу ли я сделать что-то подобное в Ruby. Я ценю любые рекомендации, которые может предложить каждый.

Ответы [ 4 ]

2 голосов
/ 08 декабря 2010

Хотя мне нравится ответ, который вы приняли, позвольте мне переписать ваш пример, чтобы он был немного более читабельным, используя более идиоматический ruby:

<% Image.each do |image| %>
  <%= case image.linkable.class.name
        when "Product"
           image.linkable.identifier
        when "Brand"
           image.linkable.name
      end %>
<% end %>

Вы также можете легко извлечь этот оператор case в вспомогательную функцию, котораяможет быть хорошим промежуточным решением, если вы не хотите создавать модуль и расширять его.

в файле помощника:

def link_name(image)
  case image.linkable.class.name
    when "Product"
       image.linkable.identifier
    when "Brand"
       image.linkable.name
  end
end

и тогда ваши представления становятся:

<% Image.each do |image| %>
  <%= link_name(image) %>
<% end %>
1 голос
/ 08 декабря 2010

Добавление метода внутри Brand (или Product) - хороший способ. Поскольку этот метод идентификатора представляет контракт с объектом, с которым может быть связано изображение. Вы можете объединить его для всех, скажем image_identifier и добавить этот метод ко всем классам, на которые ссылается изображение.

Конечно, добавление метода к Brand означает не только определение его внутри класса. Это можно сделать (скорее, следует) через модуль, который расширяется связываемыми элементами.

Вот как я это попробовал:

class Brand < ActiveRecord::Base
  extend Linkable
  linkable 'identifier'
end

class Product < ActiveRecord::Base
  extend Linkable
  linkable 'name'
end

class Image < ActiveRecord::Base
  belongs_to :linkable, :polymorphic => true
end

module Linkable
  def linkable(identifier_name = 'name')
    has_many :images, :as => :linkable
    instance_eval do
      define_method :link_identifier do
        send identifier_name.to_sym
      end
    end
  end
end

>> Product
=> Product(id: integer, name: string, created_at: datetime, updated_at: datetime)
>> Brand
=> Brand(id: integer, identifier: string, created_at: datetime, updated_at: datetime)
>> Brand.create :identifier => 'Foo'
=> #<Brand id: 1, identifier: "Foo", created_at: "2010-12-08 16:00:11", updated_at: "2010-12-08 16:00:11">
>> Product.create :name => 'Bar'
=> #<Product id: 1, name: "Bar", created_at: "2010-12-08 16:00:23", updated_at: "2010-12-08 16:00:23">
>> i = Image.new
=> #<Image id: nil, linkable_type: nil, linkable_id: nil, title: nil, created_at: nil, updated_at: nil>
>> i.linkable = Product.first
=> #<Product id: 1, name: "Bar", created_at: "2010-12-08 16:00:23", updated_at: "2010-12-08 16:00:23">
>> i.save
>> i = Image.new
=> #<Image id: nil, linkable_type: nil, linkable_id: nil, title: nil, created_at: nil, updated_at: nil>
>> i.linkable = Brand.first
=> #<Brand id: 1, identifier: "Foo", created_at: "2010-12-08 16:00:11", updated_at: "2010-12-08 16:00:11">
>> i.save
=> true

>> Image.first.link_identifier
=> "Bar"
>> Image.last.link_identifier
=> "Foo"
1 голос
/ 08 декабря 2010

Я сделал это с простым sql

Image.find_by_sql("SELECT * from images INNER JOIN products on (images.linkable_id = products.id AND images.linkable_type = "product");")
1 голос
/ 08 декабря 2010

Вы можете создать модуль с именем Linkable и создать в нем методы поведения. Затем вы расширяете модуль в классах, где вы хотите добавить эти поведения. Таким образом, вам не нужно беспокоиться о наследовании от всего, что вы можете просто добавить в поведение.

Это стандартный способ Ruby для добавления общих функций к нескольким классам без наследования. По соглашению вы также можете назвать свой модуль, используя прилагательное на основе глагола вместо глагола; Linkable против Link.

Например:

module Linkable
  def link
    puts "Look, I'm linked!"
  end
end

class Product < ActiveRecord
  extend Linkable
end

class Brand < ActiveRecord
  extend Linkable
end

Конечно, ваши классы и модуль будут иметь реальную функциональность.

...