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
I can't really see any reason to have subclasses in this situation. The behavior of the location objects are identical, the only point of them is to make the associations easy. I also would prefer to store the data as an int and not a string as it will allow the database indexes to be smaller.
I envision something like the following, but I can't complete the thought:
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
With this code the following fails, basically making it useless:
user = User.first
location = user.build_location
location.city = "Cincinnati"
location.state = "Ohio"
location.save!
location.type # => nil
This is obvious because there is no way to translate the :conditions options on the has_one declaration into the type equaling 1.
I could embed the id in the view anywhere these fields appear, but this seems wrong too:
<%= f.hidden_field :type, LOCATION_TYPES[:location] %>
Is there any way to avoid the extra subclasses or make the LOCATION_TYPES approach work?
In our particular case the application is very location aware and objects can have many different types of locations. Am I just being weird not wanting all those subclasses?
Any suggestions you have are appreciated, tell me I'm crazy if you want, but would you want to see 10+ different location models floating around app/models?
Why not use named_scopes?
Something like:
class User
has_many :locations
end
class Location
named_scope :ideal, :conditions => "type = 'ideal'"
named_scope :birthplace, :conditions => "type = 'birthplace" # or whatever
end
Then in your code:
user.locations.ideal => # list of ideal locations
user.locations.birthplace => # list of birthplace locations
You'd still have to handle setting the type on creation, I think.
As far as I can see it, a Location is a location is a Location. The different "subclasses" you're referring to (IdealLocation, Birthplace) seem to just be describing the location's relationship to a particular User. Stop me if I've got that part wrong.
Knowing that, I can see two solutions to this.
The first is to treat locations as value objects rather than entities. (For more on the terms: Value vs Entity objects (Domain Driven Design)). In the example above, you seem to be setting the location to "Cincinnati, OH", rather than finding a "Cincinnati, OH" object from the database. In that case, if many different users existed in Cincinnati, you'd have just as many identical "Cincinnati, OH" locations in your database, though there's only one Cincinnati, OH. To me, that's a clear sign that you're working with a value object, not an entity.
How would this solution look? Likely using a simple Location object like this:
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"
The other solution I can see is to use your existing Location ActiveRecord model, but simply use the relationship with User to define the relationship "type", like so:
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
All you'd need to do to make this work is include location_id, ideal_location_id, and birthplace_id attributes in your User model.
Try adding before_save hooks
class Location
def before_save
self.type = 1
end
end
and likewise for the other types of location
You can encapsulate the behavior of Location objects using modules, and use some macro to create the relationship:
has_one <location_class>,: conditions => [ "type =?" LOCATION_TYPES [: location]],: dependent =>: destroy,: as =>: locatable
You can use something like this in your module:
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-acts-as-method-to-active-record
Maybe I'm missing something important here, but I thought you could name your relationships like this:
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
Related
Is there a DSL for creating an object in an AR relationship that would be the opposite of :dependent => destroy (in other words create an object so that it always exists). Say, for example, I have the following:
class Item < ActiveRecord::Base
#price
has_one :price, :as => :pricable, :dependent => :destroy
accepts_nested_attributes_for :price
....
class Price < ActiveRecord::Base
belongs_to :pricable, :polymorphic => true
attr_accessible :price, :price_comment
I'm thinking I'd like for a price to be created every time even if we don't specify a price? Is the only (or best) option to do this as a callback or is there a way to do this via a DSL (similar to :denpendent => :destroy)?
No, as there is virtually no use-case for this. If your record cannot exist without an associated record, you should probably be preventing the record from being saved, not shoe-horning in some kind of pseudo-null object to take its place.
The closest approximation would be a before_save callback:
class Item < ActiveRecord::Base
has_one :price, :as => :pricable, :dependent => :destroy
accepts_nested_attributes_for :price
before_save :create_default_price
def create_default_price
self.price ||= create_price
end
end
You should only run this code one time on create and use the convenience method create_price here:
class Item < ActiveRecord::Base
has_one :price, :as => :pricable, :dependent => :destroy
accepts_nested_attributes_for :price
after_validation :create_default_price, :on => :create
def create_default_price
self.create_price
end
end
I'm attempting to build a diverse tournament management system which allows multiple types of tournament bracket backends. The goal is to provide a very simple option for the tournament bracket backend (here, PlasTournament) and the possibility of multiple, more elaborate backends (right now, just Challonge::Tournament, which is from Challonge, using the challonge-api gem via its API).
My models:
class Tournament < ActiveRecord::Base
belongs_to :event
validates :event, :presence => true
has_one :remote, :as => :tournament_bracket, :dependent => :destroy
end
class PlasTournament < ActiveRecord::Base
belongs_to :winner, :class_name => "User"
belongs_to :creator, :class_name => "User"
belongs_to :tournament_bracket, :polymorphic => true
has_and_belongs_to_many :participants, :class_name => "User"
end
There's also Challonge::Tournament, but I'm not sure how to get it to fit this style. Do I need to monkeypatch it? It's an ActiveResource class and I really only need to store in the polymorphic association in the Tournament class its class and id.
I previously had these models:
class Tournament < ActiveRecord::Base
belongs_to :event
validates :event, :presence => true
has_one :remote_tournament, :as => :tournament_bracket, :dependent => :destroy
end
class RemoteTournament < ActiveRecord::Base
belongs_to :tournament_bracket, :polymorphic => true
def tournament_bracket_type_type=(sType)
super(sType.to_s.classify.constantize.base_class.to_s)
end
end
class PlasTournament < RemoteTournament
belongs_to :winner, :class_name => "User"
belongs_to :creator, :class_name => "User"
belongs_to :tournament_bracket, :polymorphic => true
has_and_belongs_to_many :participants, :class_name => "User"
end
or something like that. It didn't work, and I didn't commit it like I did the above.
Ideally, I'd like to be able to do something like this in my controller's create method (and I know some of this could be handled by passing params[:tournament] to #new; I'm just being explicit for this example):
#tournament = Tournament.new
#tournament.event = params[:tournament][:event_id]
#tournament.name = params[:tournament][:name]
#remote = params[:tournament][:remote_type].constantize.new
#set its state
#tournament.remote = #remote
#tournament.save!
and then later on in Tournament#show do something like this:
#tournament = Tournament.find params[:id]
#the only things I need from Tournament are the
#name, description, max_participants, and remote
#remote = #tournament.remote
#^this is primarily for shorthand access
Then, in the view, I can pass off to certain partials or widgets based on the class of the remote.
case remote.class
when PlasTournament
render :partial => 'brackets/plas'
when Challonge::Tournament
render :partial => 'brackets/challonge'
end
Am I off my rocker? This seems fairly straightforward, but I think I'm caught in the implementation details of Rails.
What I'm looking for is essentially a way to have polymorphic tournament bracketing systems hidden behind a tournament class.
I have a weird bug that just popped up with my rails app that I cannot figure out. I recently added a new association to an existing Model and now my previous associations do not want to work properly.
#=> self.user
#=> <# user.id => "1" ...
#=> self.transactions
#=> [<# transaction_id => "1"...
#=> self.credit_plan
#=> nil
So the first two associations work fine via, but for some reason credit_plan returns nil and is crashing all my existing working code. Here is the record associations I have.
class Order < ActiveRecord::Base
belongs_to :user
belongs_to :credit_plan
has_many :transactions, :class_name => "OrderTransaction"
.
class CreditPlan < ActiveRecord::Base
scope :active, where({:is_active => true})
scope :inactive, where({:is_active => false})
has_many :orders, :class_name => "Order"
.
class OrderTransaction < ActiveRecord::Base
belongs_to :order
serialize :params
Alright Guys, I figured it out. If I had posted more context of my files, I'm sure someone would have figured it out and helped me sooner.
So basically, when I was setting up my virtual attributes for the credit card form, I accidentally stomped on my own name space by adding :credit_plan as an attribute, which overrides the association.
class Order < ActiveRecord::Base
belongs_to :user
belongs_to :credit_plan
has_many :transactions, :class_name => "OrderTransaction"
validates_presence_of :credit_plan_id, :user
attr_accessor :first_name, :last_name, :card_type, :credit_card,
:number, :verification_value, :promotional_code, :expires_on,
:credit_plan # << This will override associations, delete to fix.
validate :validate_card, :on => :create
I have started off by following this guide here Rails - Multiple Index Key Association
I have tried to add in :include to speed up the association but im getitng this error:
ActiveRecord::StatementInvalid in ItemsController#show
SQLite3::SQLException: no such column: links.item_id: SELECT "links".* FROM "links" WHERE ("links".item_id = 19)
here's what i have:
item.rb
class Item < ActiveRecord::Base
has_many :links, :dependent => :destroy, :uniq => true
belongs_to :category
belongs_to :user
is_sluggable :name
def links
Link.by_item(self)
end
link.rb
class Link < ActiveRecord::Base
belongs_to :item1, :class_name => 'Item', :foreign_key => :item1_id
belongs_to :item2, :class_name => 'Item', :foreign_key => :item2_id
belongs_to :user
validates_presence_of :item1_id
validates_presence_of :item2_id
validates_uniqueness_of :item1_id, :scope => :item2_id, :message => "This combination already exists!"
def self.by_item(item)
where("item1_id = :item_id OR item2_id = :item_id", :item_id => item.id)
end
end
items_controller.rb
def show
#item = Item.find_using_slug(params[:id], :include => [:category, :user, :links])
It works okay without :links inside :include. But otherwise I get the error.
From what I understand, the item_id is stored in the links table as item1_id or item2_id, which is why it cannot be found. Is there a workaround for this because I will be heavily referencing the links records. I am not so good with the SQL stuff.
Also unsure what's the best way to set up an Index
Willing to try out any advice. Thanks
The problem lies with the has_many :links association setup in Item. By default, this gets converted to a SQL query which looks for all links which have an item_id of the current Item. To override this default behavior, specify the find SQL yourself like this:
has_many :links, :dependent => :destroy, :uniq => true, :finder_sql => 'SELECT DISTINCT(*) FROM links WHERE item1_id = #{id} OR item2_id = #{id}'
I think I'm going crazy.
Let's say I have 3 models: Address, Warehouse, Category:
class Address < ActiveRecord::Base
belongs_to :category
belongs_to :addressable, :polymorphic => true
scope :billing_addresses , where(:categories => {:name => 'billing'}).joins(:category)
scope :shipping_addresses , where(:categories => {:name => 'shipping'}).joins(:category)
end
class Category < ActiveRecord::Base
has_many :addresses
has_many :subcategories, :class_name => "Category", :foreign_key => "category_id"
belongs_to :category, :class_name => "Category"
end
class Warehouse < ActiveRecord::Base
has_many :addresses, :as => :addressable
end
Address is polymorphic, because eventually I'll be using it to store addresses for clients, people, employees etc. Also each address can be of a certain type: billing, shipping, work, home, etc.
I'm trying to pull some information on a page.
#some_warehouse = Warehouse.first
Then in my view:
%b= #some_warehouse.name
%b= #some_warehouse.billing_address.address_line_1
Etc.
I end up doing a lookup for each line of information.
I tried to do things like
Warehouse.includes(:addresses).where(:name => "Ware1")
Warehouse.joins(:addresses).where(:name => "Ware1")
And various variations of that.
No matter what I don' I can't get rails to preload all the tables. What am I doing wrong?
Here are revised models, that do appropriate joins in sql and reduce number of quesries from 16 to 8, one for each piece of info, instead of multiples ones that also do lookup categories, etc.:
class Address < ActiveRecord::Base
belongs_to :category
belongs_to :addressable, :polymorphic => true
scope :billing_addresses , where(:categories => {:name => 'billing'}).includes(:category)
scope :shipping_addresses , where(:categories => {:name => 'shipping'}).includes(:category)
end
class Warehouse < ActiveRecord::Base
has_many :addresses, :as => :addressable, :include => :category, :dependent => :destroy
def billing_address
self.addresses.billing_addresses.first
end
def shipping_address
self.addresses.shipping_addresses.first
end
end
class Category < ActiveRecord::Base
has_many :addresses
has_many :subcategories, :class_name => "Category", :foreign_key => "category_id"
belongs_to :category, :class_name => "Category"
end
Sleep helps. Also not forgetting to reload console from time to time :-)
Maybe you want to use preload_associations?