Polymorphic model

Nie każdy lubi STI (Single Table Inheritance), nie wszędzie “opłaca” się go uzyć.
Lekiem na całe zło może nie jest, ale pragnę Państwu przedstawić :cool::
Polymorphic model

Gem umożliwia na zdefiniowanie odmian danego modelu, można też wykonywać warunkowe walidacje, czy też warunkowe callbacki!

Weźmy taki przykład:

[code=ruby]class Gallery < ActiveRecord::Base
polymorphic_model :with_type_column => :gallery_type
define_type :main, :singleton => true, :autocreate => true
define_type :private

validates_presence_of :title
validates_presence_of :owner, :if => :private?

before_validations_on_create do |gallery|
gallery.title = “Main gallery” if gallery.main?
end
end

użycie:

zawsze zwraca obiekt:

@main_gallery = Gallery.main

dla kolekcji (nie-singletonowych typów) tworzone są named scopy

@private_galleries_owners = Gallery.private.map(&:owner)[/code]
Dodatkowo gem rozszerza rspec-rails, wystarczy dodać w swoim spec_helper.rb:

require 'spec/rails/polymorphic_model'

i już można mockować polimorficzne modele:

[code=ruby] describe Gallery do
describe “main” do
before do
@gallery = mock_polymorphic_model(Gallery, :main)
end

    it "should be main gallery" do
      @gallery.should be_main
      @gallery.should_not be_private
      @gallery.gallery_type.should == "main"
    end
end

end[/code]
Próba mockowania modelu w niezdefiniowanym typie rzuca wyjątek.

W planach jest dodanie bloków do metody define_type tak, by powyzszy przykład można było zapisać tak:

[code=ruby]class Gallery < ActiveRecord::Base
polymorphic_model :with_type_column => :gallery_type
define_type :main, :singleton => true, :autocreate => true do
before_validations_on_create :set_title_for_main_gallery
end

define_type :private do
validates_presence_of :owner
end

validates_presence_of :title

private
def set_title_for_main_gallery
gallery.title = “Main gallery”
end
end[/code]
Co o tym myślicie? :slight_smile:

[quote=sevos]Nie każdy lubi STI (Single Table Inheritance), nie wszędzie “opłaca” się go uzyć.
Lekiem na całe zło może nie jest, ale pragnę Państwu przedstawić :cool::[/quote]
Jakie są zalety Tego rozwiązania w porównaniu do zwykłego dziedziczenia i o jakim ‘źle’ piszesz ??
Może nie do końca wgłębiłem się w temat ale Twój przykład jakoś mnie nie przekonuje,…
czegoś nie zauważyłem ?? :slight_smile:

Samo dziedziczneie jest fajne, jak się patrzy na modele, wręcz piękne w swojej prostocie. Jednak gdy od modelu przejdziemy do kontrolera i widoków przychodzi czas na podjęcie decyzji:

  • osobny kontroler dla każdego pod-typu (wydaje się być zalecane i wspierane przez railsy)
  • jeden nadrzędny kontroler RESTowy dla wszystkich

W przypadku, gdy mam do czynienia z kontrolerami RESTowymi wydaje mi się nadmiarowe tworzenie wielu kontrolerów, przykładowy routes, jaki by powstał:

[code=ruby]map.respurce :main_gallery do |gallery|
gallery.resources :pictures
end

map.resources :simple_galleries do |galleries|
galleries.resources :pictures
end

map.resources :premium_galleries do |galleries|
galleries.resources :pictures
end[/code]
Do tego dodajmy osobne kontrolery i osobne widoki (zakładam użycie resource_controllera)

Można wybrać drugą opcję, którą preferuję: utworzyć jeden kontroler zarządzający galeriami, pojawia sie jednak przy tym kilka problemów:

<%= form_for @gallery do |f| %>

Będzie szukało np. simple_galleries_path, gdy my chcemy użyć galleries_path (można wymusić explicite poprzez podanie URLa ale wtedy trzeba tworzyć alternatywne widoki dla każdego typu).

  • Jak poinformować resource_controller w akcji NEW, jaki model chcemy utworzyć? Można szukać parametru przekazywanego do akcji new, i to jest chyba najprostsze rozwiązanie:

new_action.before do @object = Gallery.new(:gallery_type => params[:gallery][:gallery_type] if params[:gallery] end
w przypadku STI trzeba by było zrobić coś takiego:

new_action.before do @object = params[:gallery][:gallery_type].constantize.new if params[:gallery] end
(znalezione przed chwilą na http://stackoverflow.com/questions/435678/rails-single-table-inheritance-what-is-the-best-way-to-explicitly-set-type)

Zaletą “mojego” rozwiązania jest to, że zawsze jest tworzony typ galerii (w parametrze można przesłać wszystko).

Pozostawię Wam ocenie oba podejscia.

Z tym “złem” może rzeczywiście przesadziłem. Chciałem jedynie zaproponować jakieś inne rozwiązanie. Używam tego gema w dwóch projektach, pracuje mi się z nim przyjemnie i chciałem się podzielić pomysłem.

Update:
Poza tym zachęcam do lektury tego wątku: http://rubyonrails.pl/forum/viewtopic.php?pid=14647#p14647 i udzielania pomocy.