Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save andresgutgon/559597 to your computer and use it in GitHub Desktop.
Save andresgutgon/559597 to your computer and use it in GitHub Desktop.
# PROBLEMA:
# ¿Cómo hacer que N tipos diferentes de objetos compartan atributos/campos comunes?
# Para resolver el problema se me ocurren 3 opciones
# A) CON RELACIONES POLIMORFICAS
# B) STI
# C) TODO EN UNA UNICA TABLA
#
# -----------------------------------------------------------------------------------------------------------
# A) CON RELACIONES POLIMORFICAS:
# NOTA: No estoy seguro, pero creo que esto mismo funcionaria si fuera has_one en lugar de has_many.
# Que en nuestro caso tiene mas sentido.
# class Journal
# has_many :publications, :as => :publicable
# end
#
# class Book
# has_many :publications, :as => :publicable
# end
#
# class Publication
# belongs_to :publicable, :polymorphic => true
# end
# De esta manera, cuando creemos un Book o un Journal tendremos asociada la tabla publicaciones.
# Ventaja:
# 1. Escala: Todos los tipo de publicacion que quieras
# 2. Clara separacion de rutas (site.com/books y site.com/journals,...)
# Desventajas:
# 1. Estas haciendo relaciones mas complejas para gestionar las publicaciones de la pagina.
# Muchas tablas, modelos, controladores y vistas
# -----------------------------------------------------------------------------------------------------------
# B) STI
# Este articulo explica bien el concepto de STI
# http://jumpstartlab.com/resources/rails-jumpstart/jscontact/i3-remodeling-people-into-contacts-with-sti/
# Leetelo y entiendelo entero, y habras aprendido STI en Rails. A mi me sirvio, lo que pasa es que me
# parecio mas trabajo que la opción C
# -----------------------------------------------------------------------------------------------------------
# C) TODO EN UNA UNICA TABLA
# Todo en una sola tabla y en un solo modelo (Publication)
# VENTAJAS:
# 1. Solo tienes que gestionar un modelo
# DESVENTAJAS:
# 1. Es un poco lio para entender
# 2. Mas de cuatro tipos supone bastante lio en las validaciones y en los formularios. Si solo vas a tener tres o cuatro puedes probarlo.
# OBJETIVO: Quiero poder crear y editar publicaciones, pero de tres tipos distintos, y cada tipo tendra
# campos diferentes
# Al hacerlo asi te encontraras con que los registros de un tipo dejan campos vacios Porque a la hora de
# crearlos no los validaste como necesarios. En fin, basurilla en la BBDD, pero se puede vivir con ello.
# Alla voy.
# 1. Haz un selector de tipo de publicación
# Cuando quieras crear una publicacion debes apuntar a esta URL
< %= link_to "Crea publicacion", select_pub_type_path % >
# routes.rb
map.resources :publications, :member => { :select_pub_type => :get}
# publications_controller.rb
def select_pub_type
@pre_publication = PrePublication.new
end
# Lo que hay que contar aqui es que me he creado un modelo que no apunta a una tabla de la BBDD, solo me
# sirve como paso intermedio para almacenar el tipo de publicacion antes de que cree la verdadera publicacion
# app/models/pre_publication.rb
# Nota que heredo de Tableless porque estoy en Rails 2.3.8 y las validaciones no estan accesibles si no heredo de ActiveRecord
# Si trabajas con Rails3 lo tendras mas facil :)
# Tableles es un plugin que trae las validaciones de rails a una clase que no hereda de ActiveRecord
class PrePublication < Tableless
column :pub_type, :string
column :published, :boolean
PUB_TYPES = { :books => "Libros", :journals => "Revistas",....}
validates_presence_of :pub_type
end
# app/views/publications/select_pub_type.html.erb
# Con el objeto @pre_publication creado, mostramos los tipos de publicaciones que ofrecemos al usuario
# NOTA: LA sintaxis del form no es la de Rails porque yo uso Formtastic, pero funciona igual.
<% semantic_form_for @pre_publication, :url => redirect_to_new_publication_user_path do |form| %>
<% form.inputs do %>
<%= form.input :pub_type, :as => :radio, :value_as_class => true, :collection => PUB_TYPES %>
<%= form.input :published, :checked => true %>
<% end %>
<% form.buttons do %>
<%= form.button :next %>
<% end %>
<% end %>
# De este archivo hay que destacar dos cosas
# 1. La URL donde apunta el form => redirect_to_new_publication_path
# 2. De donde saco el tipo de publicaciones que permito. Esto sale de la variable que he creado en app/models/pre_publication.rb
# 1. Lo primero es un metodo que me redirige al metodo new de la publicacion, pero con el tipo
# de publicacion seteado
# routes.rb
map.resources :publications, :member => { :redirect_to_new_publication => :post}
# publications_controller.rb
# NOTA: Si el objeto @pre_publication es valido deacuerdo con las validaciones hechas en app/models/pre_publication.rb
# lo redirijo a new, pero con el tipo de publicacion como param de la URL ej.: site.com?type=book
# Si no lo mando de vuelta a select_pub_type
def redirect_to_new_publication
@pre_publication = PrePublication.new(params[:pre_publication])
if @pre_publication.valid?
redirect_to new_publication_path(@user,:pub_type => @pre_publication.pub_type)
else
# request.post?
render :template => "publications/select_pub_type"
end
end
# publications_controller.rb
def new
if params[:pub_type]
pub_type = params[:pub_type]
published = params[:published] ? true : false
if PrePublication::PUB_TYPES.include?(pub_type)
@publication = @publication.new(:pub_type => pub_type, :published => published)
else
flash[:error] = "Elige un tipo de publicacion permitido"
redirect_to select_pub_type_path
end
else
flash[:error] = "Primero elige el tipo de publicacion"
redirect_to select_pub_type_path
end
end
# app/views/publications/new.html.erb
# Este archivo es muy complicado, por eso esta idea solo es buena si solo tienes tres o cuatro tipos diferentes de publicaciones
# Para que te hagas una idea, a la hora de mostrar los campos lo que hago es mostrar lo comunes (nombre, autor,...) como en cualquier
# otro formulario.
# Pero para los campos que son propios solo de una o dos tipos de publicaciones hago esto:
# En new instamcio un objeto del tipo Publication, por lo que todos los metodos publicos de la clase publication.rb los
# tengo accesibles en la vista. Asi es como miro si para ese tipo de publicacion le tengo que enseñar al usuario ese campo.
<% if @publication.have_editors? % >
<%= form.input :editors % >
<% end % >
# Nota el metodo .have_editors? este tipo de metodos los coloco en el modelo publication.rb
# En este archivo esta jugo por eso te lo pongo casi entero y miras como valido o no los campos en funcion del tipo de publicación.
# publication.rb
class Publication < ActiveRecord::Base
# validations
validates_presence_of :title, :abstract
validates_presence_of :pub_pages, :if => Proc.new{|model| model.have_pages?}
validates_presence_of :media_title, :if => Proc.new{|model| model.have_media_title?}
validates_presence_of :publisher, :if => Proc.new{|model| and model.have_publisher?}
validates_format_of :pub_pages, :with => /\A\d{1,5}(-\d{1,5})?\z/,
:if => Proc.new{|model| model.have_pages?}
# Metodos para validar y mostrar en funcion del tipo de publicacion
def have_pages?
["journal", "article_or_chapter_book"].include?(self.pub_type)
end
def have_media_title?
["journal", "article_or_chapter_book"].include?(self.pub_type)
end
def have_publisher?
["book", "article_or_chapter_book"].include?(self.pub_type)
end
def have_editors?
["book", "article_or_chapter_book"].include?(self.pub_type)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment