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'm working on a multi-site CMS that has a notion of cross-publication among sites. Several types of content (Articles, Events, Bios, etc) can be associated with many Sites and Sites can have many pieces of content. The many-to-many association between content pieces and sites must also support a couple common attributes for each content item associated -- the notion of site origination (is this the original site upon which the content appeared?) as well as a notion of "primary" and "secondary" content status for a given piece of content on a given associated site.
My idea has been to create a polymorphic join model called ContentAssociation, but I'm having trouble getting the polymorphic associations to behave as I expect them to, and I'm wondering if perhaps I'm going about this all wrong.
Here's my setup for the join table and the models:
create_table "content_associations", :force => true do |t|
t.string "associable_type"
t.integer "associable_id"
t.integer "site_id"
t.boolean "primary_eligible"
t.boolean "secondary_eligible"
t.boolean "originating_site"
t.datetime "created_at"
t.datetime "updated_at"
end
class ContentAssociation < ActiveRecord::Base
belongs_to :site
belongs_to :associable, :polymorphic => true
belongs_to :primary_site, :class_name => "Site", :foreign_key => "site_id"
belongs_to :secondary_site, :class_name => "Site", :foreign_key => "site_id"
belongs_to :originating_site, :class_name => "Site", :foreign_key => "site_id"
end
class Site < ActiveRecord::Base
has_many :content_associations, :dependent => :destroy
has_many :articles, :through => :content_associations, :source => :associable, :source_type => "Article"
has_many :events, :through => :content_associations, :source => :associable, :source_type => "Event"
has_many :primary_articles, :through => :content_associations,
:source => :associable,
:source_type => "Article",
:conditions => ["content_associations.primary_eligible = ?" true]
has_many :originating_articles, :through => :content_associations,
:source => :associable,
:source_type => "Article",
:conditions => ["content_associations.originating_site = ?" true]
has_many :secondary_articles, :through => :content_associations,
:source => :associable,
:source_type => "Article",
:conditions => ["content_associations.secondary_eligible = ?" true]
end
class Article < ActiveRecord::Base
has_many :content_associations, :as => :associable, :dependent => :destroy
has_one :originating_site, :through => :content_associations,
:source => :associable,
:conditions => ["content_associations.originating_site = ?" true]
has_many :primary_sites, :through => :content_associations,
:source => :associable
:conditions => ["content_associations.primary_eligible = ?" true]
has_many :secondary_sites, :through => :content_associations,
:source => :associable
:conditions => ["content_associations.secondary_eligible = ?" true]
end
I've tried a lot of variations of the above association declarations, but no matter what I do, I can't seem to get the behavior I want
#site = Site.find(2)
#article = Article.find(23)
#article.originating_site = #site
#site.originating_articles #=>[#article]
or this
#site.primary_articles << #article
#article.primary_sites #=> [#site]
Is Rails' built-in polymorphism the wrong mechanism to use to affect these connections between Sites and their various pieces of content? It seems like it would be useful because of the fact that I need to connect multiple different models to a single common model in a many-to-many way, but I've had a hard time finding any examples using it in this manner.
Perhaps part of the complexity is that I need the association in both directions -- i.e. to see all the Sites that a given Article is associated with and see all of the Articles associated with a given Site. I've heard of the plugin has_many_polymorphs, and it looks like it might solve my problems. But I'm trying to use Rails 3 here and not sure that it's supported yet.
Any help is greatly appreciated -- even if it just sheds more light on my imperfect understanding of the uses of polymorphism in this context.
thanks in advance!
If you need the associations to be more extensible than STI would allow, you can try writing your own collection helpers that do extra type-introspection.
Any time you define a relationship with belongs_to, has_many or has_one etc. you can also define helper functions related to that collection:
class Article < ActiveRecord::Base
has_many :associations, :as => :associable, :dependent => :destroy
has_many :sites, :through => :article_associations
scope :originating_site, lambda { joins(:article_associations).where('content_associations.originating_site' => true).first }
scope :primary_sites, lambda { joins(:article_associations).where('content_associations.primary_eligable' => true) }
scope :secondary_sites, lambda { joins(:article_associations).where('content_associations.secondary_eligable' => true) }
end
class Site < ActiveRecord::Base
has_many :content_associations, :as => :associable, :dependent => :destroy do
def articles
collect(&:associable).collect { |a| a.is_a? Article }
end
end
end
class ContentAssociation < ActiveRecord::Base
belongs_to :site
belongs_to :associable, :polymorphic => true
belongs_to :primary_site, :class_name => "Site", :foreign_key => "site_id"
belongs_to :secondary_site, :class_name => "Site", :foreign_key => "site_id"
belongs_to :originating_site, :class_name => "Site", :foreign_key => "site_id"
end
You could move those function defs elsewhere if you need them to be more DRY:
module Content
class Procs
cattr_accessor :associations
##associations = lambda do
def articles
collect(&:associable).collect { |a| a.is_a? Article }
end
def events
collect(&:associable).collect { |e| e.is_a? Event }
end
def bios
collect(&:associable).collect { |b| b.is_a? Bio }
end
end
end
end
class Site < ActiveRecord::Base
has_many :content_associations, :as => :associable, :dependent => :destroy, &Content::Procs.associations
end
And since articles, events & bios in this example are all doing the same thing, we can DRY this even more:
module Content
class Procs
cattr_accessor :associations
##associations = lambda do
%w(articles events bios).each do |type_name|
type = eval type_name.singularize.classify
define_method type_name do
collect(&:associable).collect { |a| a.is_a? type }
end
end
end
end
end
And now it's starting to become more like a generic plugin, rather than application-specific code. Which is good, because you can reuse it easily.
Just a shot, but have you looked at polymorphic has_many :through => relationships? There's a few useful blog posts about - try http://blog.hasmanythrough.com/2006/4/3/polymorphic-through and http://www.inter-sections.net/2007/09/25/polymorphic-has_many-through-join-model/ (there was also a question here). Hope some of that helps a bit, good luck!
In this case I don't think polymorphism is the right way to go, at least from what I understand of your system's design. Here's an example using STI. It's complicated, so forgive me if I'm missing something. I'm also not very strong on the new arel syntax, so can't guarantee this will function without tinkering.
class Article < ActiveRecord::Base
has_many :article_associations, :dependent => :destroy
has_many :sites, :through => :article_associations
scope :originating_site, lambda { joins(:article_associations).where('content_associations.originating_site' => true).first }
scope :primary_sites, lambda { joins(:article_associations).where('content_associations.primary_eligable' => true) }
scope :secondary_sites, lambda { joins(:article_associations).where('content_associations.secondary_eligable' => true) }
end
class Site < ActiveRecord::Base
has_many :content_associations, :dependent => :destroy
has_many :article_associations
has_many :articles, :through => :article_associations
end
class ContentAssociation < ActiveRecord::Base
belongs_to :site
belongs_to :primary_site, :class_name => "Site", :foreign_key => "site_id"
belongs_to :secondary_site, :class_name => "Site", :foreign_key => "site_id"
belongs_to :originating_site, :class_name => "Site", :foreign_key => "site_id"
end
class ArticleAssociation < ContentAssociation
belongs_to :article
end
What I'm doing here is creating a base association model and a separate child association for each data type. So, if you need to access associations by type you'll have access to site.articles but you can also get a list of site.content_assocations with everything together.
The STI feature will need a type:string column to store the datatype. This will be handled automatically unless you're using the ContentAssociation model. Since ArticleAssociation is using article_id you'll also need to add that, and every other column the child models use.
I am trying to find all terms and courses that apply to a contact.
Here are my models
class Register < ActiveRecord::Base
belongs_to :session
belongs_to :contact
end
class Session < ActiveRecord::Base
belongs_to :term
belongs_to :course
has_many :registers
has_many :contacts, :through => :registers
end
Here is the find a wrote
#data = Register.all :joins => {:session =>[:term, :course]} , :conditions => ["contact_id = ?", params[:id]]
When I run the query all I get is the session records not the terms or courses
Thanks
Alex
Try using :include instead of :joins. Something like:
#data = Register.all :include => {:session =>[:term, :course]} , :conditions => ["contact_id = ?", params[:id]]
Is there any way to nest named scopes inside of each other from different models?
Example:
class Company
has_many :employees
named_scope :with_employees, :include => :employees
end
class Employee
belongs_to :company
belongs_to :spouse
named_scope :with_spouse, :include => :spouse
end
class Spouse
has_one :employee
end
Is there any nice way for me to find a company while including employees and spouses like this:
Company.with_employees.with_spouse.find(1)
or is it necessary for me to define another named_scope in Company:
:with_employees_and_spouse, :include => {:employees => :spouse}
In this contrived example, it's not too bad, but the nesting is much deeper in my application, and I'd like it if I didn't have to add un-DRY code redefining the include at each level of the nesting.
You can use default scoping
class Company
default_scope :include => :employees
has_many :employees
end
class Employee
default_scope :include => :spouse
belongs_to :company
belongs_to :spouse
end
class Spouse
has_one :employee
end
Then this should work. I have not tested it though.
Company.find(1) # includes => [:employee => :spouse]
You need define all the time all of your conditions. But you can define some method to combine some named_scope
class Company
has_many :employees
named_scope :with_employees, :include => :employees
named_scope :limit, :lambda{|l| :limit => l }
def with_employees_with_spouse
with_employees.with_spouse
end
def with_employees_with_spouse_and_limit_by(limit)
with_employees_with_spouse.limit(limit)
end
end
class Employee
belongs_to :company
belongs_to :spouse
named_scope :with_spouse, :include => :spouse
end
class Spouse
has_one :employee
end
try this
Company.with_employees.merge( Employees.with_spouse)
Having set up my polymorphic relationship like so:
class Review < ActiveRecord::Base
belongs_to :reviewable, :polymorphic => true
belongs_to :user
end
class Wine < ActiveRecord::Base
has_many :reviews, :as => :reviewable
end
class Beer < ActiveRecord::Base
has_many :reviews, :as => :reviewable
end
I can do Wine.last.reviews and Beer.find(3).reviews etc...
What I'm strugling to do is go in the other direction, i.e. Lets say I want to find the last 10 reviews for Wine and the last 10 reviews for Beer.
The easiest way to do this is probably to add a named scope to your Review model that specifies the reviewable_type column.
Like so:
class Review < ActiveRecord::Base
belongs_to :reviewable, :polymorphic => true
belongs_to :user
named_scope :for_wines, :conditions => { :reviewable_type => 'Wine' }
named_scope :for_beers, :conditions => { :reviewable_type => 'Beer' }
end
That way you have the flexibility of scoping when finding your results...
Review.for_wines.approved.all
Review.for_beers.active.find(:all, :order => 'created_at')
etc