Rails self-referential has_many through with custom naming of join table - ruby-on-rails-3

I have some troubles wrapping my head around the following situation..
I am trying to create a tree structure, where I will be able to give custom names to connections between nodes..
So I want to have Node and Relation models. Each
Node
has_many :relations
Each
Relation
has_many :nodes
Node can be either a parent or a child.. So far everything was easy and there are tons of examples that show how to make a self-referential has_many table... The problem is that I want to be able to give names to relations, so that I can do something like:
relation1 = node1.relations.create(:name => "relation_name", :child => node2)
and in result get something like:
relation1.name == "relation_name"
relation1.parent == node1
relation1.child == node2
All the creations are happening within the model, this activity is not really exposed to user, if that matters.
Thanks!
EDIT2:
Here is how it works now:
class Node < ActiveRecord::Base
belongs_to :sentence
has_one :parent_relation, :foreign_key => "child_id", :class_name => "Relation"
has_many :child_relations, :foreign_key => "parent_id", :class_name => "Relation"
has_one :parent, :through => :parent_relation
has_many :children, :through => :child_relations, :source => :child
has_many :relations, :foreign_key => "child_id"
has_many :relations, :foreign_key => "parent_id"
class Relation < ActiveRecord::Base
has_many :videos, :as => :videoable, :dependent => :destroy
has_many :phrases, :through => :videos
belongs_to :parent, :class_name => "Node"#, :inverse_of => :parent_relation
belongs_to :child, :class_name => "Node"#, :inverse_of => :child_relation

So what you're talking about is more like a Joins Model than a Self-Reference.
Note: I changed your relation association 'labels' because I was having a hard time with your naming, so you don't have to change your 'labels' that was just for me.
So for your Node class you could do something like this
class Node < ActiveRecord::Base
has_one :parent_relation, :foreign_key => "child_id",
:class_name => "Relation"
has_many :child_relations, :foreign_key => "parent_id",
:class_name => "Relation"
has_one :parent, :through => :parent_relation
has_many :children, :through => :child_relations, :source => :child
end
Then for your Relation class you could something like
class Relation < ActiveRecord::Base
belongs_to :parent, :class_name => "Node", :inverse_of => :parent_relation
belongs_to :child, :class_name => "Node", :inverse_of => :child_relations
end
The :inverse_of option should let you build let you build a Node based off the parent and children associations from your Node instances, this is just a caveat from the magic with :through relationships. (Documentation for this is at the bottom of the Joins Model section.)
I don't fully understand your association structure, but I think this should model it correctly. Lemme know if there are any problems though.
Side Note: Since Relation is a constant set in the ActiveRecord module you might consider changing it to something like NodeRelationship. I don't think it will interfere with your program, but it definitively caused some trouble for my thought process.

Related

RSpec testing non validating has_many through relationship

I have a many to many relationship between documents.
Say I have document1 and document2. I have a many to many table where there are parents and children.
document.rb
has_many :child_relationships, :class_name => "DocumentRelationship", :foreign_key => "child_id", :dependent => :destroy
has_many :parents, :through => :child_relationships, :source => :parent
has_many :parent_relationships, :class_name => "DocumentRelationship", :foreign_key => "parent_id", :dependent => :destroy
has_many :children, :through => :parent_relationships, :source => :child
document_relationship.rb
belongs_to :parent, :class_name => "Document", :foreign_key => "parent_id"
belongs_to :child, :class_name => "Document", :foreign_key => "child_id"
validates_uniqueness_of :child_id, :scope => [:parent_id]
validates_presence_of :parent_id
validates_presence_of :child_id
validate :obeys_chronology
def obeys_chronology
errors.add(:child_id, "must be created after its parent") if child_id.to_i < parent_id.to_i
errors.add(:child_id, "cannot be its own parent") if child_id.to_i == parent_id.to_i
end
If I say document2.children << document1 it appropriately fails validation, but I don't know how to write a test for this.
Is there a better way to do this?
Add it to the collection
document2.children << document1
document2.children.contain?(document1).should == false
Then make sure it's not in there.

SQL query for joining data from three tables with reverse relationships

I have three tables: player, games, hide
Here is the Player model
has_many :hides, :foreign_key=> "hider_id",
:dependent => :destroy
has_many :hidees, :through => :hides
Here are the attributes belonging to the Hide Model.
attr_accessible :hidee_id
belongs_to :hider, :class_name => "Player"
belongs_to :hidee, :class_name => "Game"
validates :hider_id, :presence => true
validates :hidee_id, :presence => true
Here is the Games model:
has_many :reverse_hides, :foreign_key => "hidee_id",
:class_name => "Hide",
:dependent => :destroy
has_many :hiders, :through => :reverse_hides
I want write a query that would return all games that were hidden by the current player.
At present I have: Game.joins(:hiders) which returns all games that are marked as hidden, what do i need to do to find the games only hidden by a particular player... for example where('Player.id = ?' hider_id) <-- this did not seem to work.
I appreciate your help and time!
Granted this doesn't take advantage of much Rails magic, but it should work, if I've understood your models properly:
Game.joins('JOIN hides ON hides.hidee_id = games.id').where('hides.hider_id = ?', player.id)

How do I delete a record from all tables it is referred to?

Good morning fellow Overflowers,
Small problem with model associations. I have these model associations:
class Categorization < ActiveRecord::Base
belongs_to :exhibit
belongs_to :category
end
class Category < ActiveRecord::Base
has_many :categorizations
has_many :exhibits, :through => :categorizations
acts_as_indexed :fields => [:title]
validates :title, :presence => true, :uniqueness => true
end
class Exhibit < ActiveRecord::Base
has_many :categorizations
has_many :categories, :through => :categorizations, :source => :category
acts_as_indexed :fields => [:title, :bulb]
validates :title, :presence => true, :uniqueness => true
belongs_to :foto, :class_name => 'Image'
end
So, essentially Categorization ends up with these columns (date/time stamps omitted):
categorization_id, exhibit_id and category_id.
My problem is that when I delete an Exhibit, its reference on the Categorization table is not deleted thus getting a DB error on my view. I have to first unassign the Exhibit from any Category and then delete it safely. Or (given for example that the Exhibit I delete has :exhibit_id=>'1') when I give in the rails console: Categorization.find_by_exhibit_id(1).destroy
Thanks for any help!!
You can set the :dependent options on associations that you want Rails to follow when you delete their parents:
class Exhibit < ActiveRecord::Base
has_many :categorizations, :dependent => :destroy
...
end

Setting up a polymorphic has_many :through relationship

rails g model Article name:string
rails g model Category name:string
rails g model Tag name:string taggable_id:integer taggable_type:string category_id:integer
I have created my models as shown in the preceding code. Articles will be one of many models which can have tags. The category model will contain all categories which may be assigned. The tag model will be a polymorphic join-table which represents tagged relationships.
class Article < ActiveRecord::Base
has_many :tags, :as => :taggable
has_many :categories, :through => :taggable
end
class Category < ActiveRecord::Base
has_many :tags, :as => :taggable
has_many :articles, :through => :taggable
end
class Tag < ActiveRecord::Base
belongs_to :taggable, :polymorphic => true
belongs_to :category
end
I can't seem to get this to work, I can do it non polymorphic, but I must have something wrong with the polymorphic part. Any ideas?
Edit: Still not getting this right:
class Article < ActiveRecord::Base
has_many :taggables, :as => :tag
has_many :categories, :through => :taggables, :source => :tag, :source_type => "Article"
end
class Category < ActiveRecord::Base
has_many :taggables, :as => :tag
has_many :articles, :through => :taggables, :source => :tag, :source_type => "Article"
end
class Tag < ActiveRecord::Base
belongs_to :taggable, :polymorphic => true
belongs_to :category
end
To create a polymorphic has_many :through, you must first create your models. We will use'Article,' 'Category,' and 'Tag' where 'Tag' is the join-model and Article is one of many objects which can be "tagged" with a category.
First you create your 'Article' and 'Category' models. These are basic models which do not need any special attention, just yet:
rails g model Article name:string
rails g model Category name:string
Now, we will create our polymorphic join-table:
rails g model Tag taggable_id:integer taggable_type:string category_id:integer
The join-table joins together two tables, or in our case one table to many others via polymorphic behavior. It does this by storing the ID from two separate tables. This creates a link. Our 'Category' table will always be a 'Category' so we include 'category_id.' The tables it links to vary, so we add an item 'taggable_id' which holds the id of any taggable item. Then, we use 'taggable_type' to complete the link allowing the link to know what it is linked to, such as an article.
Now, we need to set up our models:
class Article < ActiveRecord::Base
has_many :tags, :as => :taggable, :dependent => :destroy
has_many :categories, :through => :tags
end
class Category < ActiveRecord::Base
has_many :tags, :dependent => :destroy
has_many :articles, :through => :tags, :source => :taggable, :source_type => 'Article'
end
class Tag < ActiveRecord::Base
belongs_to :taggable, :polymorphic => true
belongs_to :category
end
After this, setup your database using:
rake db:migrate
That's it! Now, you can setup your database with real data:
Category.create :name => "Food"
Article.create :name => "Picking the right restaurant."
Article.create :name => "The perfect cherry pie!"
Article.create :name => "Foods to avoid when in a hurry!"
Category.create :name => "Kitchen"
Article.create :name => "The buyers guide to great refrigeration units."
Article.create :name => "The best stove for your money."
Category.create :name => "Beverages"
Article.create :name => "How to: Make your own soda."
Article.create :name => "How to: Fermenting fruit."
Now you have a few categories and various articles. They are not categorized using tags, however. So, we will need to do that:
a = Tag.new
a.taggable = Article.find_by_name("Picking the right restaurant.")
a.category = Category.find_by_name("Food")
a.save
You could then repeat this for each, this will link your categories and articles. After doing this you will be able to access each article's categories and each categorie's articles:
Article.first.categories
Category.first.articles
Notes:
1)Whenever you want to delete an item that is linked by a link-model make sure to use "destroy." When you destroy a linked object, it will also destroy the link. This ensures that there are no bad or dead links. This is why we use ':dependent => :destroy'
2)When setting up our 'Article' model, which is one our 'taggable' models, it must be linked using :as. Since in the preceeding example we used 'taggable_type' and 'taggable_id' we use :as => :taggable. This helps rails know how to store the values in the database.
3)When linking categories to articles, we use:
has_many :articles, :through => :tags, :source => :taggable, :source_type => 'Article'
This tells the category model that it should have many :articles through :tags. The source is :taggable, for the same reason as above. The source-type is "Article" because a model will automatically set taggable_type to its own name.
You simply cannot make the join table polymorphic, at least Rails does not support this out of the box. The solution is (taken from Obie's Rails 3 way):
If you really need it, has_many :through is possible with polymorphic associations, but only by specifying exactly what type of polymorphic associations you want. To do so you must use the :source_type option. In most cases you will have to use the :source option, since the association name will not match the interface name used for the polymorphic association:
class User < ActiveRecord::Base
has_many :comments
has_many :commented_timesheets, :through => :comments, :source => :commentable,
:source_type => "Timesheet"
has_many :commented_billable_weeks, :through => :comments, :source => :commentable,
:source_type => "BillableWeek"
It's verbose and the whole scheme loses its elegance if you go this route, but it works:
User.first.commented_timesheets
I hope I helped!

How to write an AREL statement on a self-referencing table

I have written quite a few AREL statements, but I'm tying myself in knots over this one. Here is my situation:
class Product < AR::Base
has_many :parents, :class_name => "ProductLink", :foreign_key => :to_product_id
has_many :children, :class_name => "ProductLink", :foreign_key => :from_product_id
# has an attribute called "identifier"
end
class ProductLink < AR::Base
belongs_to :parent, :class_name => "Product", :foreign_key => :from_product_id
belongs_to :child, :class_name => "Product", :foreign_key => :to_product_id
end
I want to retrieve all of the Products that have a child product with an identifier that matches some value.
I have twisted myself into a pretzel with this, seems easy, but I have been looking at it for too long now. I appreciate any help!
Got it!
brand.products.joins(:children => :child).where(:children => { :child => { :searchable_identifier.matches => "2136" } } )
That works great. See the hashed joins? That's what was throwing me off.