Undefined Method on has_many :through - ruby-on-rails-3

I have three models:
Class Project < ActiveRecord::Base
has_many :tasks
has_many :tags, :through => :tasks
end
Class Tasks < ActiveRecord::Base
belongs_to :project
has_and_belongs_to_many :tags
end
Class Tags < ActiveRecord::Base
has_and_belongs_to_many :tasks
has_many :projects, :through => :tasks
When I open up console, I can get my Project and Task information as expected:
Tag.find(1).projects
Tag.find(1).tasks
If I want, I can get all the tasks for each project regardless of the tag:
Project.find(1).tasks
For whatever reason, I can't access tasks if I get projects by tag...
something = Tag.find(1).projects
something.tasks
...I get the error:
undefined method `tasks' for #<ActiveRecord::Relation:0x007feae4af0e70>
I've looked for a couple hours and can't find anything that corrects this problem. Based on everything I've found, it should be working... but it's not.
I'm using Rails 3.2.3.

Shouldn't Tag.find(1).tasks give you the same result?
Anyway, the problem you're facing is that you're trying to retrieve an association from a Relation object instead of an instance of your model. Relations can be used to chain query conditions, but you can't directly reference associations from them. So, to get your example working, you'd need to do
p = Tag.find(1).projects.includes(:tasks)
Then reference tasks like this: p[0].tasks.
However I'd just make sure that Tag.find(1).tasks will generate the same SQL and ultimately return the same collection of tasks.

Related

Return two tables, nested together, with one request

I have a project management app with Projects, Tasks and Assignments.
Project
has_many :tasks
Task
belongs_to :project
has_many :assignments
Assignment
belongs_to :task
belongs_to :user
I want to be able to GET all of a Project's Tasks and Assignments with just one request.
Currently I have to request each Task's Assignments, which could be very inefficient if a user has more than just a few Tasks.
If you do something like this, Rails will figure out the best queries to run
# Assuming params[:id] is the project id.
#project = Project.includes(:tasks => :assignments).find(params[:id])
So when you ask for #project.tasks and the loop through each task with task.assignments, the associations are already loaded.
Add a has many through to the Project model like so:
has_many :tasks
has_many :assignments, through: :tasks
Now you'll be able to call #project.assignments to get ALL assignments of that project or #project.assignments.uniq to list unique assignments (that don't repeat) in the event that an assignment would be under two tasks if you expand your associations to include that functionality.

Rails 3: Object chaining with has_many :through associations

I'm trying to get a grasp of how to work with associations in Rails, specifically, when and when not to write explicit SQL code.
In my application, I have four models, which are defined as follows:
class User < ActiveRecord::Base
has_many :comments
has_many :geographies
has_many :communities, through: :geographies
class Comment < ActiveRecord::Base
belongs_to :user
class Community < ActiveRecord::Base
has_many :geographies
has_many :users
class Geography < ActiveRecord::Base
belongs_to :user
belongs_to :community
Users can post comments, and are associated to one or more communities through the geography table (the geography table stores user_id and community_id).
I have an index action listing all comments, and I would like to filter by community. Given a comment object, I can get the user object via comment.user, but I can't chain beyond that (i.e., something like comment.user.geography(0).community doesn't work).
It seems this object chaining is a key feature of rails, but does it work with has_many :through associations? Given my example, is it possible to get the community object from the comment object by using object chaining, or would I need to write the SQL to get anything other than the user when given the comment object?
Since User is associated with multiple communities, you will need to tell ActiveRecord (or raw SQL) which community you want:
comment.user.communities #=> should give you all the communities
If you don't particularly care for getting all communities and just want to get any community
comment.user.communities.first #=> should give you the first community
But, generally you will be interested in one particular community, based on a condition.
comment.user.communities.where(name: 'Europe') #=> should give you the European community.
I don't think you need the geographies table.
Try
class Community < ActiveRecord::Base
has_many :users
end
class User < ActiveRecord::Base
belongs_to :community
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :user
end
Then you can access a comment's user's community like
#comment.user.community

Implementing a has_many :through

I am thinking of creating the following models using 'has_many :through':
class Contract < AR::Base
has_many :clientlines
has_many :codelines
has_many :clients, :through => :clientlines
has_many :codes, :through => :codelines
end
class clientlines < AR::Base
belongs_to :contract
belongs_to :client
end
class Client < AR::Base
has_many :clientlines
has_many :contracts, :through => :clientlines
end
class codeline < AR::Base
belongs_to :contract
belongs_to :code
units_alloc -------**I would like to add this attribute after this intermediate
end has been created?
class Code < AR::Base
has_many :codelines
has_many :contracts, :through => :codelines
end
Do I first create the models with 'rails generate model Contract authnum:string, client_id:integer, st_date:date, end_date:date' for example.
Then fill in all of the associations before the migrations?.
Also, my understanding is that all of the join tables are created automatically by rails when using the has_many :through association. When does that happen?
Lastly, as indicated by the **, can I have this attribute in codelines, and do I create a 'rails generate migration add_units_alloc_to_codelines units_alloc:number' in order to add this attribute to the join table? I was also wondering how I declare the number to be too two decimal places?
If you have the time and inclination could you please comment on my proposed design for my database?
Thanks.
by using has_many :through you use a third model that makes the connection between the other two, so rails doesn't automatically build that model, you build it yourself and reference the foreign keys for the other two models.
don't name your models at their plural, always singular. if you can't name them at singular, you're doing it wrong
The order in which you create your models shouldn't matter too much (rails generates some migrations which you can easily modify later)
That units_alloc attribute, just add it to the model when you create it, simple as that!
for 2 decimals use something like t.decimal :amount, :precision => 6, :scale => 2 in your migration (that example gives you 6 digits and 2 decimals)
Read the Rails Guides, it will really help you get out a lot of trouble

Rails 3 - associations

I print in my view a number that tell me, how many people read my article. It looks something like a:
<%=article.hits.count%>
As is possible to see, I created a simple association.
Now I am trying to get the information, if the user who is log in on my page, so if he is already had read this article. In my table that contains hits is column user_id.
But I can't still find the way, how to get...
I tried something like:
<% if session[:login_user_id].hits.user_id == session[:login_user_id]%>
Have you read it already.
<% end %>
But the example above doesn't work me... Could anyone help me please, how to do?
EDIT: The models:
class Article < ActiveRecord::Base
has_many :hits
end
class Hits < ActiveRecord::Base
belongs_to :article, :class_name => "DataHit", :foreign_key => "article_id"
has_many :users
end
class User < ActiveRecord::Base
belongs_to :hit
end
Thanks in advance
Let's first talk about the model you like to receive. For me, it sounds like:
Every article can be visited / read by many users.
Every user can read / visit many articles.
This is a classical n:m-association which is normally implemented by a has-many-through association.
If this is the intention, it should be implemented like:
class Article < ActiveRecord::Base
has_many :hits
has_many :users, :through => :hits
end
class Hits < ActiveRecord::Base
belongs_to :article, :class_name => "DataHit", :foreign_key => "article_id"
belongs_to :user
end
class User < ActiveRecord::Base
has_many :hits
has_many :articles, :through => :hits
end
Of course, you have to add migrations that ensure that the final DB model is like that:
Hit has article_id and user_id to ensure that users may find the articles they have read
If you have that model implemented, it should be more easy. Then you have operations available like: #article.users.contains(User.find(user_id)). Have a look at the tutorial at Ruby on Rails Guides which explain what the has-many-through relation is and which advantages they have.
It would be helpful if you try the things first in the console of Rails. To do that, start with:
Start the rails console in the root directory of your application: rails c
Enter there e.g.: art = Article.find(1) to get the article with the id.
Try which methods are available: art.methods.sort to see all methods that could be used. If there is no method users, you have did something wrong with the assocication.
Try the call: us = art.users and look at the result. It should be a rails specific object, an object that behaves like a collection and understands how to add and remove users to that collection (with the whole life cycle of rails). The error your currently have could mean different things:
Your database model does not match your associations defined in Rails (I suspect that).
Some minor tweak (misspelling somewhere) which hinders Rails.
I hope this gives you some clues what to do next, I don't think that we can fix the problem here once and for all times.

Filter based on model attribute has_many relationship through, rails 3?

I have a simple question, but can't seem to find any solution, though I have found things that are similar, but just not exactly what I am looking for.
I have an application where a User has many Assets through the class UserAsset. I want to be able to do current_user.user_assets , but I only want to return records that have an Asset with a specified field value of "active".
This post is similar but I need to use the main model not the join model as a filter.
class UserAsset < ActiveRecord::Base
belongs_to :asset
belongs_to :user
end
class Asset < ActiveRecord::Base
has_many :user_assets
has_many :users, :through => :user_assets
end
class User < ActiveRecord::Base
has_many :user_assets
has_many :assets, :through => :user_assets
end
I tried setting the default scope on Asset, and also some conditions on the has many (user_assets) relationship, but rails is failing to consider the join on the Assets table. ie Unknown column 'asset.live' in 'where clause'. Trying to achieve the following:
#active_user_assets = current_user.user_assets #only where assets.active = true
So how do I use conditions or scopes to achieve this? I need the user_asset object because it contains info about the relationship that is relevant.
Thanks in advance!
You want current_user.assets, then your scopes should work.
Oh, but you want the user_assets. Hmm. I think you need the :include clause to find() but where to put it, I can't be arsed to think of right now.
Perhaps
current_user.user_assets.find(:all, :include => :assets).where('asset.live=?', true)
(I'm not on Rails 3 yet, so that's going to be mangled)
Are you using :through when you really want a HABTM?