Rails Model and foreign keys - ruby-on-rails-3

Sorry if this has been asked before but I've tried searching and can't find exactly what I'm looking for.
I have two tables, one is called users and has a user_id field, the second is called users_friends and only has two fields, user_id and friends_id
I've spent the entire day trying many different variations and cannot get it to pull back the friends as user objects like I'm expecting.
The entire contents of my UsersFriends model is
belongs_to :user, :class_name => "User", :foreign_key => "user_id"
has_one :friends, :class_name => "User", :foreign_key => "id"
Thanks in advance,
J

I got a feeling that you do not understand how associations work. Please refer to: http://guides.rubyonrails.org/association_basics.html
Also you might find reading github page and/or code of acts_as_network plugin quite helpful and inspiring for building your "friends" feature. Please have a look:
https://github.com/sjlombardo/acts_as_network

Related

Structuring Rails models for different kinds of data

I need an opinion about how to structure my models for a rails 4 app I'm doodling on. The architecture shouldn't be too hard but I've been going in circles for hours so I'll throw it out to the community.
I have a model called "checkin" that has_many "fields". Each field can have many values that can be one of several data types that may increase in number over time but for starters will be just float and string. Once a field is added to a checkin its data type cannot change.
Think of a checkin for losing weight where some people would want to log just weight. Others might want to log weight, %bodyfat. Others might want extra fields for other metrics or a text field for what mood you're in.
So what I've got so far is:
class Checkin < ActiveRecord::Base
has_many :fields
class Field < ActiveRecord::Base
belongs_to :checkin
And then I was going to do
class DataFloat < ActiveRecord::Base
belongs_to :field
class DataString < ActiveRecord::Base
belongs_to :string
etc.
The schema is simple with just references for now. DataFloat has a float and DataString has a string (as you'd expect)
Then I'll use something like ActiveRecord::Base.descendants a (as per THIS) to create a select box so that you can choose what a field is when you add it to the checkin.
My question for all those MVC experts out there is whether or not this is the best way to do this. Would I be better off having a central object "Data" that is extended by DataFloat and DataString? Is there a third, better way I haven't thought of?
Anyone...... bueller?
I managed to solve it but it took a long time to find the correct term for what I was trying to do. It's basically polymorphic associations in reverse.
There's a fantastic tutorial by Rune Madsen here:
https://gist.github.com/runemadsen/1242485
basically I do it this way:
class Field < ActiveRecord::Base
has_many :field_datas
has_many :data_ints, :through => :field_datas, :source => :data_object, :source_type => 'DataInt'
has_many :data_floats, :through => :field_datas, :source => :data_object, :source_type => 'DataFloat'
.... etc.....
Then I do:
class FieldData < ActiveRecord::Base
belongs_to :field
belongs_to :data_object, :polymorphic => true
end
And then finally the actual data which is a table with an id and one column:
class DataInt < ActiveRecord::Base
has_one :field_data, :as =>:data_object
has_one :field, :through => :datas
And then there would be a DataType class for each data type.
I think I will probably need to write special handlers to make sure everything gets destroyed and created properly but overall I'm pretty pleased with it.
I would love to know if anyone has an opinion about doing it this way.

Rails 3 has_many :through accessing attributes

I am working with a has_many through for the first time, and despite a lot of reading here and in the guide I am not understanding the correct way to access attributes on the through table. My tables are the same as this example from another post.
class Product < ActiveRecord::Base
has_many :collaborators
has_many :users, :through => :collaborators
end
class User < ActiveRecord::Base
has_many :collaborators
has_many :products, :through => :collaborators
end
class Collaborator < ActiveRecord::Base
belongs_to :product
belongs_to :user
end
Assuming that the collaborators table has additional attributes, say hours_spent, what is the correct way to find the hours_spent from the collaborator table for a particular user and product?
When I have found my users via the product, and am iterating over them as in
#product.users.each do |user|
This seems to work
user.collaborator[0].hours_spent
I get the correct value, but since there should only be one collaborator record for each User/Product pair, the index is throwing me off, making me think I’m doing something wrong.
Thank you for reading!
EDIT
Perhaps I am not getting the has_many through concept. Maybe a MySQL example would help.
What I was thinking is that if I did
SELECT * FROM collaborators where user_id = 1;
I would expect a set (zero or more) as the result. Similarly
SELECT * FROM collaborators where product_id = 1;
would also give me a set, but
SELECT * FROM collaborators where user_id = 1 and product_id = 1;
would give at most 1 row.
If I am understanding properly, all 3 queries return a set. So I guess I need some kind of uniqueness constraint, but that would have to be a compound key of sorts, on both of the belongs to keys. Is that even possible? Is there a structure that better models this?
Thanks so much for the quick and helpful responses!
There may be a single database row per pair, but when considering a single user, that user can be associated to many products, so a user can have many rows in the collaborators table. Similarly, when considering a single product, that product can be associated to many users, so a product can have many rows in the collaborators table.
Also, instead of using user.collaborators[0].hours_spent, use user.collaborators.first.try(:hours_spent) (which may return null), if you only want the first collaborator's hours spent.
If a single user can only have one single product and a single product can only have a single user, then switch the has_many's to has_one's for everything.
Update: The preceding is the answer to the original question which has since been clarified via comments. See comments for detail and see comments on other answer by Peter.
Perhaps you should use has_and_belongs_to_many. If your Collaborator is used only to make link between User and Product without having more fields.
class Product < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :products
end
The beetween migration would be:
class CreateUsersProducts < ActiveRecord::Migration
def change
create_table "users_products", :id => false do |t|
t.integer :user_id
t.integer :product_id
end
end
end
After implementing this, what I found was that I think I had the correct relationships setup, I had to use the has_many :though as users could have many products, and it needed to be :through because there are additional attributes on the collaborator table. The sticking point was how to get there to only be a single Collaborator record for each user/product pair, and then how do I guarantee I got it. And to this point the answer I've found is it has to be done in code.
To make sure there is only a single record for each pair, I used
class Collaborator < ActiveRecord::Base
validates :product_id, :presence => true, :uniqueness => {:scope => [:user_id], :message => "This is a duplicate join"}
And then to make doubly sure I'm finding the right record, I have a scope
scope :collaboration_instance, lambda {|p_id, u_id| where("collaborations.product_id = ? && collaborations.user_id = ?", p_id, u_id)}
If someone has a more elegant solution, or just wants to improve this one, please post and I will change yours to the selected answer.

Retrieving sublist 3 level deep in Rails

I have a datamodel that contains a Project, which contains a list of Suggestions, and each Suggestion is created by a User. Is there a way that I can create a list of all distinct Users that made Suggestions within a Project?
I'm using Mongoid 3. I was thinking something like this, but it doesn't work:
#project = Project.find(params[:id])
#users = Array.new
#users.push(#project.suggestions.user) <-- this doesn't work
Any ideas? Here's my model structure:
class Project
include Mongoid::Document
has_many :suggestions, :dependent => :destroy
...
end
class Suggestion
include Mongoid::Document
belongs_to :author, class_name: "User", :inverse_of => :suggestions
belongs_to :project
...
end
class User
include Mongoid::Document
has_many :suggestions, :inverse_of => :author
...
end
While Mongoid can give MongoDB the semblance of relationships, and MongoDB can hold foreign key fields, there's no underlying support for these relationships. Here are a few options that might help you get the solution you were looking for:
Option 1: Denormalize the data relevant to your patterns of access
In other words, duplicate some of the data to help you make your frequent types of queries efficient. You could do this in one of a few ways.
One way would be to add a new array field to User perhaps called suggested_project_ids. You could alternatively add a new array field to Project called suggesting_user_ids. In either case, you would have to make sure you update this array of ObjectIds whenever a Suggestion is made. MongoDB makes this easier with $addToSet. Querying from Mongoid then looks something like this:
User.where(suggested_project_ids: some_project_id)
Option 2: Denormalize the data (similar to Option 1), but let Mongoid manage the relationships
class Project
has_and_belongs_to_many :suggesting_users, class_name: "User", inverse_of: :suggested_projects
end
class User
has_and_belongs_to_many :suggested_projects, class_name: "Project", inverse_of: :suggesting_users
end
From here, you would still need to manage the addition of suggesting users to the projects when new suggestions are made, but you can do so with the objects themselves. Mongoid will handle the set logic under the hood. Afterwards, finding the unique set of users making suggestions on projects looks like this:
some_project.suggesting_users
Option 3: Perform two queries to get your result
Depending on the number of users that make suggestions on each project, you might be able to get away without performing any denormalization, but instead just make two queries.
First, get the list of user ids that made suggestions on a project.
author_ids = some_project.suggestions.map(&:author_id)
users = User.find(author_ids)
In your Project class add this :
has_many :users, :through => :suggestions
You'll then be able to do :
#users.push(#project.users)
More info on :through here :
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many
For mongoid, take a look at this answer :
How to implement has_many :through relationships with Mongoid and mongodb?

putting a condition on an includes

I have the following relationships:
Category has_many :posts
Post has_many :comments
Post has_many :commenters, :through => :comments
I have the following eager load, giving me posts, comments and commenters (note that I need all 3, and hence the includes as opposed to joins)
category.posts.includes(:comments, :commenters)
However, I'd like to limit comments (and if possible commenters) to only those created in the past two weeks while still returning the same set of posts. Initially I thought I could specify a condition on the includes:
category.posts.includes(:comments, :commenters).where("comments.created_at > ?", 2.weeks.ago)
But found that this returns only the posts that meet the condition. I'm thinking that I may need to do something like performing a subquery on comments and then performing a join. Is there an easy way to do this with AR of would I be better off doing this with sql?
Finally managed to figure this out from reading this page:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
I simply needed to create an association in my Post model like:
Post has_many :recent_comments, :class_name = 'Comment', :conditions => ["created_at > ?", 2.weeks.ago]
Then I could do the following to get the desired ActiveRecord::Association object:
category.posts.includes(:recent_comments => :commenters)
There was also a suggestion of doing this by using a scope on a model. However, I read somewhere (I think it was on SO) that scopes are on their way out and that ARel has taken their place so I decided to do this without scopes.
Try :
category.posts.all(:includes => {:comments =>:commenters}, :conditions => ["comments.created_at = ? AND commenters.created_at = ?", 2.weeks.ago, 2.weeks.ago]

Ruby on Rails: has_many referential --which model objects does it own?

I am new to Rails and finished Michael Hartl's "Ruby on Rails 3 Tutorial". Although the book teaches me a lot, I find this puzzle I don't understand.
To preview the puzzle, that is, I don't understand, inside User model,
has_many :following, :through=>:relationship, :source=>:followed
how this piece of code link "user.following" to an array of User instances.
And below is the whole puzzle.
First of all, I have the Relationship model, which records followed_id and follower_id infos. Inside Relationship model, the association is simple as
class Relationship < ActiveRecord::Base
attr_accessible :followed_id
belongs_to :follower, :class_name => "User"
belongs_to :followed, :class_name => "User"
end
Then, inside the User model, a user will assume the role of follower, and collect all its following rows in relationships table through relationships association.
class User < ActiveRecord::Base
.
.
.
has_many :relationships, :foreign_key => "follower_id", :dependent => :destroy
.
Until now, I got it.
But confusion came at the next line, where through user.following it can assemble all that user's following(User instances). Like so,
has_many :following, :through=>:relationships, :source=>:followed
I understand that :source=>:followed will overwrite the default, and let find all followed_ids associated with that user.
But, how can Rails recognize followed_id to link to User object? The label name doesn't match users, nor is there :class_name specified. I just don't get how Rails do this underlying work, or I missed out some hints.
Thank you! :)
But, how can Rails recognize followed_id to link to User object? The
label name doesn't match users, nor is there :class_name specified. I
just don't get how Rails do this underlying work, or I missed out some
hints.
Rails recognize that is an user object because it is set in Relationship's belongs_to. What Rails does here is to follow the relationship class through the foreign key "follower_id" and returning every User that has a relationship with the current user as followed. Of course Rails do that in a single SQL statement like this:
SELECT `users`.* FROM `users` INNER JOIN `relationships` ON `relationships`.followed_id = `users`.id WHERE ((`relationships`.follower_id = <the current user id> ))
has_many :following, :through=>:relationships, :source=>:followed
This explains to Rails that following is the inverse relationship of following and that users has many following and followed through his relationships.
The way Rails knows that followed_id is linked to User is that it is defined in your Relationship model.
Hope you've understood ! Good luck :)