Mongoid: embeds_one polymphic

Teraz mam coś takiego:

[code=ruby]class MediaObject
include Mongoid::Document
belongs_to :resource, polymorphic: true
end

class Media::Text
include Mongoid::Document
has_one :media_object, as: :resource
end

class Media::Image
include Mongoid::Document
has_one :media_object, as: :resource
end[/code]
i wszystko bangla, tylko powoduje n+1 zapytań kiedy chce pobrać n MediaObject. Chciałem zamienić to na relację embeds_one (nie potrzebuję mieć dostępu do Media::[Text|Image…] “z zewnątrz”, tylko przed MediaObject) ale nijak nie idzie :stuck_out_tongue:

Tzn. jak zamienię po prostu belongs_to -> embeds_one to mogę dostać się do MediaObject przed Media::Text, a nie na odwrót :smiley:

jak to sprytnie ugryźć?

Ciężko zrozumieć o co Ci chodzi. Przede wszystkim jakie to N+1 zapytań? Poza tym, jeśli masz tego typu asocjacji dużo to może jakaś baza SQL? Dodatkowo może zamiast asocjacji zrób dziedziczenie, jeśli tam będzie i tak has_one.

Ok, późno było :stuck_out_tongue: Jeszcze raz :slight_smile:

[code=ruby]def index
@media_objects = MediaObject.all
end

  • @media_object.each do |media_object|
    .title= media_object.title
    .content= media_object.resource.content[/code]
    Powoduje n+1 zapytań - 1 dla pobrania wszystkich MediaObjects i n dla każdego resource (Media::[Text|Image])

Jak uda mi się to dobrze zaimplementować, to SQL będzie zupełnie niepotrzebne - nie potrzebuję dostępu do dokumentów Media::[Text|Image] “z zewnątrz” ale przez MediaObject, czyli właśnie jako embedded document (dlatego nie chcę has_one)

Dlaczego w ogóle takie kombinacje a nie trzymanie tych informacji bezpośrednio w MediaObject? Ano dlatego że embeddowane dokumenty mogą mieć różne pola (content, url etc), dlatego za każdym ma stać inny model :slight_smile:

Ok, poradziłem sobie tak:

[code=ruby]class MediaObject
include Mongoid::Document
embeds_one :resource, class_name: ‘Media::Resource’
end

class Media::Resource
include Mongoid::Document
embedded_in :media_object
end

class Media::Text < Media::Resource
end[/code]
Trochę chciałem uniknąć STI (ileż to się człowiek naczytał jakie to złooo) ale chyba inaczej się nie da :slight_smile:

Dlaczego STI to zło?

Też nigdy do końca tego nie rozumiałem :wink:

@zlw: zauważ też, że w mongo STI jest bardziej naturalne, nie masz jakichś nadmiarowych pól, których nie używasz, bo nie musisz w ogóle definiować pól.

Ponieważ położenie indeksu na pole tekstowe z nazwą klasy (główny bottleneck wydajnościowy STI) przekracza umiejętności wielu “programistów” :wink:

Tak naprawdę to nie musi być pole tekstowe, ale to też pewnie przekracza możliwości wielu programistów :wink:

Kiedyś po prostu trafiłem na kilka blogów gdzie odradzano STI np. tu.

No ta, to tak to można łamać wszystkie Railsowe konwencje żeby sobie udowodnić że nie przekracza to moich możliwości :smiley: Ale prawda, że w mongo nie ma problemu nadmiarowych pól :slight_smile:

Nie chodziło mi akurat o żadne wydajnościowe problemy. Rzeczywiście

[code=ruby]class Foobar
include Mongoid::Document

index :type

end[/code]
specjalnie trudne nie jest :smiley:

Dla mongo ma znaczenie tylko ten ostatni argument, ale to tylko wydajność, więc jeżeli w tym przypadku Ci na tym nie zależy to tym bardziej nie powinieneś się przejmować.

Zależy nie zależy, innego wyjścia żeby ładnie to rozwiązać nie ma :smiley:

Nie chcę zakładać nowego tematu, wiec napiszę tu:

Mam problem z walidacją powyższych modeli.

[code=ruby]params = { media_object: { resource: { content: “foo” }, title: “foobar”, type: :text }}

media = MediaObject.new(params[:media_object])
media.valid? #=> false

media.errors #=> #<ActiveModel::Errors:0x007f809d249aa8 @messages={:resource=>[“jest nieprawidłowe”]}>
media.resource.errors #=> #<ActiveModel::Errors:0x007f809d4c52f0 @messages={:content=>[“jest za krótkie (minimalnie 5 znaków)”]}>[/code]
jak zmusić MediaObject, żeby w errors wyświetlał pełne wiadomości errorów z resource? Teraz nijak nie chce działać wyświetlanie błędów w formularzu:

[code=haml]= semantic_form_for @media_object do |f|
= f.semantic_errors
= f.input :title
= f.input :type, as: :hidden, value: @media_object.type

= f.semantic_fields_for :resource do |r|
= r.input :content, as: :text

= f.buttons[/code]

[code=ruby]# MediaObject
class MediaObject
include Mongoid::Document

#== Resource relation
embeds_one :resource, class_name: ‘Media::Resource’

after_initialize :build_proper_resource

def build_proper_resource
if type
self.resource = Media::Resource.build_proper_resource(type, self)
end
end
end

Media::Resource

class Media::Resource
include Mongoid::Document

#== MediaObject relation
embedded_in :media_object

def self.build_proper_resource(type, media_object)
klass = media_object.class.media_types[type].constantize

media_params, resource_params = media_object.attributes, (media_object.resource.try(:attributes) || {})
klass_params = media_params.merge(resource_params).symbolize_keys.slice(*klass.allowed_fields.map(&:to_sym))
      
klass.new(klass_params)

end
end[/code]
media_types to hash:

{ text: 'Media::Text', image: 'Media::Image' }

class Media::Text < Media::Resource field :content, type: String validates :content, presence: true, length: { minimum: 5 } end
po wysłaniu formularza, pojawiają się tylko błędy tytułu:
http://img607.imageshack.us/img607/180/zrzutekranu2012012o1534.png