Pergunta

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

Não consigo ver nenhum motivo para ter subclasses nessa situação. O comportamento dos objetos de localização é idêntico, o único ponto deles é facilitar as associações. Eu também preferiria armazenar os dados como uma string e não uma string, pois eles permitirão que os índices de banco de dados sejam menores.

Eu imagino algo como o seguinte, mas não consigo completar o pensamento:

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

Com este código, o seguinte falha, basicamente o tornando inútil:

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

location.type # => nil

Isso é óbvio, porque não há como traduzir as opções: Condições na declaração Has_One no tipo igual a 1.

Eu poderia incorporar o ID na visão em qualquer lugar que esses campos apareçam, mas isso também parece errado:

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

Existe alguma maneira de evitar as subclasses extras ou fazer com que a abordagem de localização_types funcione?

Em nosso caso específico, o aplicativo está muito ciente do local e os objetos podem ter muitos tipos diferentes de locais. Estou apenas sendo estranho não querendo todas essas subclasses?

Alguma sugestão que você tem é apreciada, diga -me que estou louco, se você quiser, mas gostaria de ver mais de 10 modelos de localização diferentes flutuando em torno de aplicativos/modelos?

Foi útil?

Solução

Até onde eu posso ver, um local é um local é um local. As diferentes "subclasses" a que você está se referindo (ideallocation, local de nascimento) parecem estar apenas descrevendo o relacionamento do local com um usuário em particular. Pare -me se eu tiver essa parte errada.

Sabendo disso, posso ver duas soluções para isso.

O primeiro é tratar os locais como objetos de valor em vez de entidades. (Para mais informações sobre os termos: Valor vs entidade Objetos (design orientado ao domínio)). No exemplo acima, você parece estar definindo o local para "Cincinnati, Oh", em vez de encontrar um objeto "Cincinnati, OH" do banco de dados. Nesse caso, se muitos usuários diferentes existissem em Cincinnati, você teria tantos locais idênticos "Cincinnati, OH" no seu banco de dados, embora exista apenas um Cincinnati, OH. Para mim, isso é um sinal claro de que você está trabalhando com um objeto de valor, não uma entidade.

Como seria essa solução? Provavelmente usando um objeto de localização simples como este:

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"

A outra solução que posso ver é usar o seu modelo de ActiveRecord existente, mas simplesmente usar o relacionamento com o usuário para definir o relacionamento "tipo", assim:

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

Tudo o que você precisa fazer para fazer esse trabalho é incluir atributos Location_ID, ideal_location_id e birthplace_id no seu modelo de usuário.

Outras dicas

Por que não usar o nome do nome?

Algo como:

class User
  has_many :locations
end

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

Então no seu código:

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

Você ainda teria que lidar com definir o tipo de criação, eu acho.

Tente adicionar ganchos antes_save

class Location
  def before_save
    self.type = 1
  end
end

E da mesma forma para os outros tipos de localização

Você pode encapsular o comportamento dos objetos de localização usando módulos e usar alguma macro para criar o relacionamento:

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

Você pode usar algo assim no seu módulo:

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-atts-como-method-to-attive-registro

Talvez eu esteja perdendo algo importante aqui, mas pensei que você poderia nomear seus relacionamentos assim:

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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top