Nested Attributes in Rails - ruby-on-rails

Imagine 4 models in Rails 3.1
class Student < ActiveRecord::Base
has_many :memberships
has_many :courses, :through => :memberships
has_many :tests, :through => :courses
end
class Membership < ActiveRecord::Base
belongs_to :student
belongs_to :course
end
class Course < ActiveRecod::Base
has_many :tests
has_many :students, :through => :memberships
end
class Test < ActiveRecord::Base
belongs_to :course
end
How can I output a sorted list (ie by date) of a student's upcoming tests
(I'm guessing there is a fairly simple answer, but I've been trying in vain for a while)
My best guess is something like:
#upcomingTests = #currstudent.tests.sort_by &:testDateTime
but it seems to return an empty array

First of all, there is an slight error on your model "Course". It needs "belongs_to :student".
class Course < ActiveRecod::Base
has_many :tests
has_many :students, :through => :memberships
belongs_to :student
end
After you've created and populated a foreign key, you can create a simple named_scope on your test mode:
named_scope :ordered, :order => "created_at DESC"
Then it's just the matter of accessing it from wherever you want:
#ordered_tests = #student.tests.ordered

Related

ActiveRecord Joins result

Doing an ActiveRecord join in RoR seems to work if I look at the generated SQL.
But what I can't figure out is why the result of that SQL isn't returned into the variable.
What I'm doing is:
class Book < ActiveRecord::Base
has_many :readings, dependent: :destroy
has_many :readers, :through => :readings
accepts_nested_attributes_for :readings
end
class Reader < ActiveRecord::Base
has_many :readings, dependent: :destroy
has_many :books, :through => :readings
accepts_nested_attributes_for :books
end
class Reading < ActiveRecord::Base
belongs_to :reader
belongs_to :book
end
Now, when asking:
result = Reading.where(:reader_id => rid, ).joins(:book).select(columns.collect{|c| c[:name]}.join(',')).flatten
It shows the correct generated SQL:
SELECT readings.id,books.title,books.author,readings.when FROM `readings` INNER JOIN `books` ON `books`.`id` = `readings`.`book_id` WHERE `readings`.`reader_id` = 2
BUT: the result variable only contains the values of the Reading record, NOT the fields of the joined table.
What am I missing?
I have made the association changes in question also:-
class Book < ActiveRecord::Base
has_many :readings, dependent: :destroy
has_many :readers, :through => :readings
accepts_nested_attributes_for :readings
end
class Reader < ActiveRecord::Base
has_many :readings, dependent: :destroy
has_many :books, :through => :readings
accepts_nested_attributes_for :books
end
class Reading < ActiveRecord::Base
belongs_to :reader
belongs_to :book
end
Query in this way:-
reader = Reader.find(rid)
result = reader.books.pluck(:name).join(',')
Ultimately, I've rewritten my helper class and fetched the various fields as I needed them. (as krishnar suggested)
Anyways: Thanx you guys for your contributions.

Named many to many relations in Rails

How should I create following model in Rails 3.2? Project can have 1+ owners and 1+ users. Both of them are instances of class Person. I've thought about has_and_belongs_to_many but I don't know how to handle two separate collections of Persons for each Project.
You'll need a join model to represent each has-and-belongs-to-many relationship, and you would access using has-many-through as described here:
class ProjectOwnerLink < ActiveRecord::Base
belongs_to :project
belongs_to :owner, class_name: 'Person'
end
class ProjectUserLink < ActiveRecord::Base
belongs_to :project
belongs_to :user, class_name: 'Person'
end
class Project < ActiveRecord::Base
has_many :project_owner_links
has_many :owners, :through => :project_owner_links
has_many :project_user_links
has_many :users, :through => :project_user_links
end
class Person < ActiveRecord::Base
has_many :project_owner_links
has_many :owned_projects, :through => :project_owner_links, :source => :project
has_many :project_user_links
has_many :used_projects, :through => :project_user_links, :source => :project
end
You could define another model Participation that holds the type of the relationship, i.e. the role of the user. (Untested) code:
class Project < ActiveRecord::Base
has_many :participations
has_many :users, :through => :participations
def with_role(role)
includes(:participations).where('participation.role = ?', role)
end
def owners
users.with_role('owner')
end
def participants
users.with_role('participant')
end
end
 
class User < ActiveRecord::Base
has_many :participations
has_many :projects, :through => :participations
def with_role(role)
includes(:participations).where('participation.role = ?', role)
end
def projects_owned
projects.with_role('owner')
end
def projects_participating_in
projects.with_role('participant')
end
end
 
class Participation < ActiveRecord::Base
# has an attribute 'role'
belongs_to :project
belongs_to :user
end
Below is the demo application.
https://github.com/diatmpravin/habtm-demo.git
Please have a look, Let me know if you have any question?

Namespaced Join Table Refers to Itself

I have the following models:
class User < ActiveRecord::Base
has_many :groups, :through => :memberships
has_many :memberships, :class_name => "User::Group"
end
class Group < ActiveRecord::Base
has_many :users, :through => :members
has_many :members, :class_name => "User::Group"
end
class User::Group < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
I make the following association call:
#user.groups
And it returns an array of User::Groups, instead. Am I overlooking something?
I changed the name of the join table to User::Membership, and now everything works as expected. It seems that naming the last part of the namespace after another model was causing some sort of interference, though I'm not sure why. Does anyone have a more informed explanation?

How do you model "Likes" in rails?

I have 3 models: User, Object, Likes
Currently, I have the model: a user has many Objects. How do I go about modeling:
1) A user can like many objects
2) an Object can have many likes (from different users)
So I want to be able to do something like this:
User.likes = list of objects liked by a user
Objects.liked_by = list of Users liked by object
The model below is definitely wrong...
class User < ActiveRecord::Base
has_many :objects
has_many :objects, :through => :likes
end
class Likes < ActiveRecord::Base
belongs_to :user
belongs_to :object
end
class Objects < ActiveRecord::Base
belongs_to :users
has_many :users, :through => :likes
end
To elaborate further on my comment to Brandon Tilley's answer, I would suggest the following:
class User < ActiveRecord::Base
# your original association
has_many :things
# the like associations
has_many :likes
has_many :liked_things, :through => :likes, :source => :thing
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :thing
end
class Thing < ActiveRecord::Base
# your original association
belongs_to :user
# the like associations
has_many :likes
has_many :liking_users, :through => :likes, :source => :user
end
You are close; to use a :through, relation, you first must set up the relationship you're going through:
class User < ActiveRecord::Base
has_many :likes
has_many :objects, :through => :likes
end
class Likes < ActiveRecord::Base
belongs_to :user
belongs_to :object
end
class Objects < ActiveRecord::Base
has_many :likes
has_many :users, :through => :likes
end
Note that Objects should has_many :likes, so that the foreign key is in the right place. (Also, you should probably use the singular form Like and Object for your models.)
Here is a simple method to achieve this. Basically, you can create as many relationships as needed as long as you specify the proper class name using the :class_name option. However, it is not always a good idea, so make sure only one is used during any given request, to avoid additional queries.
class User < ActiveRecord::Base
has_many :likes, :include => :obj
has_many :objs
has_many :liked, :through => :likes, :class_name => 'Obj'
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :obj
end
class Obj < ActiveRecord::Base
belongs_to :user
has_many :likes, :include => :user
has_many :users, :through => :likes
# having both belongs to and has many for users may be confusing
# so it's better to use a different name
has_many :liked_by, :through => :likes, :class_name => 'User'
end
u = User.find(1)
u.objs # all objects created by u
u.liked # all objects liked by u
u.likes # all likes
u.likes.collect(&:obj) # all objects liked by u
o = Obj.find(1)
o.user # creator
o.users # users who liked o
o.liked_by # users who liked o. same as o.users
o.likes # all likes for o
o.likes.collect(&:user)
Models & associations as per naming conventions of rails modeling
class User < ActiveRecord::Base
has_many :likes
has_many :objects, :through => :likes
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :object
end
class Object < ActiveRecord::Base
belongs_to :user
has_many :likes
has_many :users, :through => :likes
end
Also, you can use of already built-in gems like acts-as-taggable-on to have same functionality without code :)

Trouble with has_many :through => association

Caution: I am a 4 week old at programming. I am having trouble with a has_many :through => relationship between my Neighborhood and Cta_train models.
Here are my models:
class CtaTrain < ActiveRecord::Base
belongs_to :Ctaline
has_and_belongs_to_many :searches
has_many :neighborhoods, :through => :CtaLocation, :foreign_key => :neighborhood_id
has_many :CtaLocations
end
class Neighborhood < ActiveRecord::Base
has_many :geopoints
has_many :listings
has_many :properties
has_and_belongs_to_many :searches
has_many :CtaTrains, :through => :CtaLocation, :foreign_key => :cta_train_id
has_many :CtaLocations
end
class CtaLocation < ActiveRecord::Base
belongs_to :neighborhood
belongs_to :CtaTrain
end
When I try to do this:
neighborhood.CtaTrains
I get this error:
ActiveRecord::HasManyThroughAssociationNotFoundError (Could not find the association :CtaLocation in model Neighborhood):
I have been slogging through this for several hours now....I have tried many iterations of ideas from stackoverflow....what I show above feels like the closest solution, but obviously still not working. Any thoughts would be appreciated!
I think the problem is that you're not following Rails conventions by using lowercase/underscore for your symbols. Class names have to be CamelCase but you should be doing the following everywhere else:
class CtaTrain < ActiveRecord::Base
belongs_to :cta_line
has_and_belongs_to_many :searches
has_many :neighborhoods, :through => :cta_locations, :foreign_key => :neighborhood_id
has_many :cta_locations
end
*Update: You should also be using :cta_locations (plural) in your has many through

Resources