Rails - Troubles in getting attributes from relations - ruby-on-rails

Those are my models:
class Person < ActiveRecord::Base
has_many :honors, :dependent => :destroy, :foreign_key => "honored_id"
has_many :honor_creators, :through => :honors, :source => :person, :foreign_key => "person_id"
class Group
has_many :honorss,:foreign_key => "group_id"
class Honor
belongs_to :person, :class_name => 'Person', :foreign_key => "person_id"
belongs_to :honored, :class_name => 'Person', :foreign_key => "honored_id"
belongs_to :group, :class_name => 'Group', :foreign_key => "group_id"
Since the honors are shown at the person#show page, here is my controller:
def show
...
#honors = #person.honors.paginate(:page => params[:page], :per_page => Honor.per_page)
end
And my view:
<% unless #honors.empty? %>
<% #honors.each do |ho| %>
My question is: using the ho I get all the attributes from the honor, but I want to get the creater of the honor and the group that it belongs. How can I do that?
Thanks!

You can do that by accessing
ho.person
or
ho.group

Note:
belongs_to :group, :class_name => 'Group', :foreign_key => "group_id"
and
belongs_to :group
will do the same thing because Rails will assume that class and foreign key when you give it :group.
To get ho.honor_creator to work (If I'm assuming correctly), you'll want:
class Person < Model
has_many :honors, :foreign_key => :creator_id
end
class Honor < Model
belongs_to :creator, :class_name => "Person"
end
That way, you can set honor.creator_id = params[:user_id] and then access the Person with ho.creator.
In other words, I'd redo your models like this:
class Person < ActiveRecord::Base
belongs_to :honor, :foreign_key => :recipient_id
has_many :honors, :foreign_key => :creator_id
class Group
has_many :honors
class Honor
has_many :recipients, :class_name => 'Person' # recipient_id is assumed
belongs_to :creator, :class_name => 'Person' # creator_id is assumed
belongs_to :group # group_id is assumed
I renamed some columns to hopefully make better sense, and this is done off the top of my head, so I'll look at some reference to ensure my accuracy.
This will give you access to honor.creator and honor.group.
<% #honors.each do |honor| %>
Honor Creator: <%= honor.creator.name %>
Honor Group: <%= honor.group.name %>
Honor Recipients: <% honor.recipients.each {|recipient| puts recipient} %>
<% end %>
Might have messed up the recipient association, but I'll go look.

Related

Multiple association types between 2 models

The following are my three models: many users can each have many products (and vice versa) through an associations model.
class Product < ActiveRecord::Base
has_many :associations
has_many :users, :through => :associations
end
class User < ActiveRecord::Base
has_many :associations
has_many :products, :through => :associations
end
class Association < ActiveRecord::Base
belongs_to :user
belongs_to :product
end
This works great. But now I want to add 2 different association types, a 'strong-association' and 'weak-association' on top of the original association.
What is the best way to go about this?
So far I've considered 1) adding a type column to my association table to specify whether it is a strong/medium/weak association 2) adding a strong-association and also a weak-association record. Both methods seem troublesome when I want to display only a particular association type in my rails view.
I also want to be able to easily change the association type in my project.
You should definitely add a new field to your associations join table: this is the correct way to store this relationship. There's various things you could do after that.
You could add some new has_many associations:
class Product < ActiveRecord::Base
has_many :associations
has_many :users, :through => :associations
has_many :weak_associated_users, :class_name => "User", :through => :associations, :source => :user, :conditions => ["associations.strength = ?", "weak"]
has_many :medium_associated_users, :class_name => "User", :through => :associations, :source => :user, :conditions => ["associations.strength = ?", "medium"]
has_many :strong_associated_users, :class_name => "User", :through => :associations, :source => :user, :conditions => ["associations.strength = ?", "strong"]
end
class User < ActiveRecord::Base
has_many :associations
has_many :products, :through => :associations
has_many :weak_associated_products, :class_name => "Product", :through => :associations, :source => :product, :conditions => ["associations.strength = ?", "weak"]
has_many :medium_associated_products, :class_name => "Product", :through => :associations, :source => :product, :conditions => ["associations.strength = ?", "medium"]
has_many :strong_associated_products, :class_name => "Product", :through => :associations, :source => :product, :conditions => ["associations.strength = ?", "strong"]
end
#fields: user_id, product_id, strength
class Association < ActiveRecord::Base
belongs_to :user
belongs_to :product
end
And then do something like (on the page)
<h2>Strongly association users</h2>
<% #product.strong_associated_users.each do |user| %>
...show user info here
<% end %>
Or, you could not bother with the new has_many associations, and just split the association records up on the page:
<% grouped = #product.associations.find(:all, :include => [:user]).group_by(&:strength) %>
<% ["weak", "medium", "strong"].each do |strength| %>
<% if associations = grouped[strength] %>
<h2><%= strength %> associations</h2>#
<% associations.each do |association| %>
<% user = association.user %>
...show user info here
<% end %>
<% end %>
<% end %>
If you want to change the association type I would recommend the extra column for your association table. You can filter out what kinds of records you want to show in your controller action by first gathering up all the associations and then calling a method like #select. For example, to get all the 'weak' associations you could use the following code in your controller action (I'm just using a "show" action for an example. I'm not sure what your situation calls for):
def show
#products = current_user.products.select! { |p| p.association_type == "weak" }
#associations scoped through current_user
#also, asociation_type is just a placeholder name for whatever you name that extra column
render :show ##products are all products with "weak" association
end
You could change what select looks for according to what type of association you want.

AR joined class in parent class

i have Author entity which belongs_to User. User has_many posts. Please advice how can i show recent_posts on Author entity from User.
class User < ActiveRecord::Base
has_many :posts, :foreign_key => "author_id"
end
class Post < ActiveRecord::Base
attr_accessible :title, :content
belongs_to :author, :class_name => "User"
end
class Author < ActiveRecord::Base
belongs_to :user
has_many :recent_posts, :through => :user,
:class_name => "Post",
:limit => 3,
:order => "updated_at desc"
end
How recent_post should be done? Raw sql?
You want the :source option to has_many, which you use to specify the association on the other model, like so:
has_many :recent_posts, :through => :user, :source => :posts, :limit => 3, :order => 'updated_at desc'

Ruby on Rails has_many_through with validation and logic to ensure only one record in the join table which is updated as required

I have the Course model which has a number of has many through associations with the User model with join table CourseUser. The join table has an attribute type_str which specifies which role the user takes on. I have added validation to ensure that only one record is present in the join table for each course, user pair. The problem is ensuring that this record is updated if it is already present, rather than adding a new one which of course makes validation fail.
User class:
class User < ActiveRecord::Base
...
has_many :courses_enrolled_on, :through => :course_enrollees, :source => :course, :conditions => { :course_users => { :type_str => "enrollee" } }
has_many :course_users
has_many :courses, :through => :course_users, :source => :course, :readonly => true
end
Course class
class Course < ActiveRecord::Base
has_many :course_enrollees, :conditions => { :type_str => "enrollee" }, :class_name => CourseUser
has_many :enrollees, :through => :course_enrollees, :source => :user
has_many :course_users
has_many :users, :through => :course_users, :source => :user, :readonly => true
end
Course class:
class CourseUser < ActiveRecord::Base
belongs_to :course
belongs_to :user
validates_uniqueness_of :course_id, :scope => :user_id
end

Polymorphic associations in Rails 3

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?

Rails Model has_many with multiple foreign_keys

Relatively new to rails and trying to model a very simple family "tree" with a single Person model that has a name, gender, father_id and mother_id (2 parents). Below is basically what I want to do, but obviously I can't repeat the :children in a has_many (the first gets overwritten).
class Person < ActiveRecord::Base
belongs_to :father, :class_name => 'Person'
belongs_to :mother, :class_name => 'Person'
has_many :children, :class_name => 'Person', :foreign_key => 'mother_id'
has_many :children, :class_name => 'Person', :foreign_key => 'father_id'
end
Is there a simple way to use has_many with 2 foreign keys, or maybe change the foreign key based on the object's gender? Or is there another/better way altogether?
Thanks!
Found a simple answer on IRC that seems to work (thanks to Radar):
class Person < ActiveRecord::Base
belongs_to :father, :class_name => 'Person'
belongs_to :mother, :class_name => 'Person'
has_many :children_of_father, :class_name => 'Person', :foreign_key => 'father_id'
has_many :children_of_mother, :class_name => 'Person', :foreign_key => 'mother_id'
def children
children_of_mother + children_of_father
end
end
To improve on Kenzie's answer, you can achieve an ActiveRecord Relation by defining Person#children like:
def children
children_of_mother.merge(children_of_father)
end
see this answer for more details
Used named_scopes over the Person model
do this:
class Person < ActiveRecord::Base
def children
Person.with_parent(id)
end
named_scope :with_parent, lambda{ |pid|
{ :conditions=>["father_id = ? or mother_id=?", pid, pid]}
}
end
I believe you can achieve the relationships you want using :has_one.
class Person < ActiveRecord::Base
has_one :father, :class_name => 'Person', :foreign_key => 'father_id'
has_one :mother, :class_name => 'Person', :foreign_key => 'mother_id'
has_many :children, :class_name => 'Person'
end
I'll confirm and edit this answer after work ; )
My answer to Associations and (multiple) foreign keys in rails (3.2) : how to describe them in the model, and write up migrations is just for you!
As for your code,here are my modifications
class Person < ActiveRecord::Base
belongs_to :father, :class_name => 'Person'
belongs_to :mother, :class_name => 'Person'
has_many :children, ->(person) { unscope(where: :person_id).where("father_id = ? OR mother_id = ?", person.id, person.id) }, class_name: 'Person'
end
So any questions?
I prefer to use scopes for this issue. Like this:
class Person < ActiveRecord::Base
belongs_to :father, :class_name => 'Person'
belongs_to :mother, :class_name => 'Person'
has_many :children_of_father, :class_name => 'Person', :foreign_key => 'father_id'
has_many :children_of_mother, :class_name => 'Person', :foreign_key => 'mother_id'
scope :children_for, lambda {|father_id, mother_id| where('father_id = ? AND mother_id = ?', father_id, mother_id) }
end
This trick make it easy to get children without use instances:
Person.children_for father_id, mother_id
Not a solution to the general question as stated ("has_many with multiple foreign keys"), but, given a person can either be a mother or a father, but not both, I would add a gender column and go with
has_many :children_of_father, :class_name => 'Person', :foreign_key => 'father_id'
has_many :children_of_mother, :class_name => 'Person', :foreign_key => 'mother_id'
def children
gender == "male" ? children_of_father : children_of_mother
end
I was looking for the same feature, if you don't want to return an array but a ActiveRecord::AssociationRelation, you can use << instead of +.
(See the ActiveRecord documentation)
class Person < ActiveRecord::Base
belongs_to :father, :class_name => 'Person'
belongs_to :mother, :class_name => 'Person'
has_many :children_of_father, :class_name => 'Person', :foreign_key => 'father_id'
has_many :children_of_mother, :class_name => 'Person', :foreign_key => 'mother_id'
def children
children_of_mother << children_of_father
end
end

Resources