rails 3 model relation with has_and_belongs_to_many - ruby-on-rails-3

I got these three models:
Projects, Organisms and Sequences
Each sequence is unique and belongs to an organism. An organism can have many sequences. So far, it works fine.
The problem I am struggling with is the association in my projects model:
I need to select one organism and one of the to the organism associated sequences to the project. But also, an organism can have many projects.
I was following these instructions : has_and_belongs_to_many-associations-in-ruby-on-rails. But it fails, when I try save my form data. When I try to "build" it in the console, it even fails:
ree-1.8.7-2011.03 :001 > project = Project.new
=> #<Project id: nil, name: nil, organism_id: nil, sequence_id: nil, created_at: nil, updated_at: nil>
ree-1.8.7-2011.03 :002 > project.organism.build
NoMethodError: undefined method `organism' for #<Project:0x56117c8>
What's wrong with what I'm doing?
Maybe i am following the wrong path here. im not very good in database design, so i need some help obviously :) What i'd like to have is something like project.organism and project.sequence as well as sequence.organism and origanism.sequences. You see, the three models are close connected to each other. The background is, that a rake task will update the list of the organisms and the associated sequences regulari form NCBI database. Therefore i cant just "save" the data in the projects model, i have to link them to other tables.
EDIT: my Project Model looks like this:
class Project < ActiveRecord::Base
has_and_belongs_to_many :organisms
#has_one :sequence
end
note: i still have no idea, how to make the sequence available to the project.
and the migration:
class CreateProjects < ActiveRecord::Migration
def change
create_table :projects do |t|
t.string :name
t.integer :organism_id
t.integer :sequence_id
t.timestamps
end
end
end
thank you four your help,
Adrian
EDIT 2:
i think i have solved it. I just rethought about that, and this is the more simple solution:
class Project < ActiveRecord::Base
belongs_to :organism
belongs_to :sequence
end
class Sequence < ActiveRecord::Base
belongs_to :organism
end
class Organism < ActiveRecord::Base
has_many :projects
has_many :sequences
end
The problem with this is only that i cant go back from sequences to projects like sequence.projects

try
project.organisms.build
instead of
project.organism.build
You did called the association organisms after all :)

Related

Undefined Method on has_many :through

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.

Rails STI association with subclasses

I'm getting some strange behaviour when fetching collections from a has_many association with rails 3 when using STI. I have:
class Branch < ActiveRecord::Base
has_many :employees, class_name: 'User::Employee'
has_many :admins, class_name: 'User::BranchAdmin'
end
class User < ActiveRecord::Base
end
class User::Employee < User
belongs_to :branch
end
class User::BranchAdmin < User::Employee
end
The desired behaviour is that branch.employees returns all employees including branch admins. The branch admins only seem to be 'loaded' under this collection when they have been accessed by branch.admins, this is output from the console:
Branch.first.employees.count
=> 2
Branch.first.admins.count
=> 1
Branch.first.employees.count
=> 3
This can be seen in the generated SQL, the first time:
SELECT COUNT(*) FROM "users" WHERE "users"."type" IN ('User::Employee') AND "users"."branch_id" = 1
and the second time:
SELECT COUNT(*) FROM "users" WHERE "users"."type" IN ('User::Employee', 'User::BranchAdmin') AND "users"."branch_id" = 1
I could solve this problem by just specifying:
class Branch < ActiveRecord::Base
has_many :employees, class_name: 'User'
has_many :admins, class_name: 'User::BranchAdmin'
end
since they all be found from their branch_id but this creates problems in the controller if I want to do branch.employees.build then the class will default to User and I have to hack at the type column somewhere. I have got around this for now with:
has_many :employees, class_name: 'User::Employee',
finder_sql: Proc.new{
%Q(SELECT users.* FROM users WHERE users.type IN ('User::Employee','User::BranchAdmin') AND users.branch_id = #{id})
},
counter_sql: Proc.new{
%Q(SELECT COUNT(*) FROM "users" WHERE "users"."type" IN ('User::Employee', 'User::BranchAdmin') AND "users"."branch_id" = #{id})
}
but I would really like to avoid this if possible. Anyone, any ideas?
EDIT:
The finder_sql and counter_sql haven't really solved it for me because it seems that parent associations don't use this and so organisation.employees that has_many :employees, through: :branches will again only include the User::Employee class in the selection.
Basically, the problem only exists in the development environment where classes are loaded as needed. (In production, classes are loaded and kept available.)
The problem comes in due to the interpreter not having seen yet that Admins are a type of Employee when you first run the Employee.find, etc. call.
(Notice that it later uses IN ('User::Employee', 'User::BranchAdmin'))
This happens with every use of model classes that are more than one level deep, but only in dev-mode.
Subclasses always autoload their parent hierarchy. Base classes don't autoload their child hierachies.
Hack-fix:
You can force the correct behaviour in dev-mode by explicitly requiring all your child classes from the base class rb file.
Can you use :conditions?
class Branch < ActiveRecord::Base
has_many :employees, class_name: 'User::Employee', :conditions => {:type => "User::Employee"}
has_many :admins, class_name: 'User::BranchAdmin', :conditions => {:type => "User::BranchAdmin"}
end
This would be my preferred method. One other way to do it might be to add a default scope to the polymorphic models.
class User::BranchAdmin < User::Employee
default_scope where("type = ?", name)
end
A similar problem continues to exist in Rails 6.
This link outlines the issue and workaround. It contains the following explanation and code snippet:
Active Record needs to have STI hierarchies fully loaded in order to generate correct SQL. Preloading in Zeitwerk was designed for this use case:
By preloading the leaves of the tree, autoloading will take care of the entire hierarchy upwards following superclasses.
These files are going to be preloaded on boot, and on each reload.
# config/initializers/preload_vehicle_sti.rb
autoloader = Rails.autoloaders.main
sti_leaves = %w(car motorbike truck)
sti_leaves.each do |leaf|
autoloader.preload("#{Rails.root}/app/models/#{leaf}.rb")
end
You may require a spring stop for the configuration changes to take.
Indeed, that was the plan in the early days of the gem, but it was abandoned soon (in 2019, before Rails 6 was out). Preloading has been deprecated for a long time, and has been deleted in the forthcoming Zeitwerk 2.5.
In a Rails application you can do it this way:
# config/initializers/preload_vehicle_sti.rb
Rails.application.config.to_prepare do
Car
Motorbike
Truck
end
That is, you "preload" just by using the constants in a to_prepare block.

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?

referencing attributes in models with belongs_to relationships through a nested namespace

Ok, so I thought I understood how the relationship specifications work in rails but I've been struggling with this for a day now.
Some context, I have two models Cars & Model Names (e.g. Impala, Charger, etc), where Cars are instances of Model Names, and Model Names really is nothing more than a lookup table of Model Names and some other model level attributes. The Model Name controller is nested within the admin namespace as only admins can CRUD Model Names. End users can add instances of cars to the Cars model.
So, in routes.rb I have:
resources :cars
namespace :admin do resources :model_names end
The Model's are defined as:
class Admin::ModelName < ActiveRecord::Base
end
class Car < ActiveRecord::Base
belongs_to :admin_model_name
end
The Migrations are:
class CreateCars < ActiveRecord::Migration
def self.up
create_table :cars do |t|
t.string :chassis_number
t.string :description
t.references :admin_model_name
t.timestamps
end
end
class CreateAdminModelNames < ActiveRecord::Migration
def self.up
create_table :admin_model_names do |t|
t.string :model
t.integer :sort_index
#...additional attributes removed
t.timestamps
end
The admin CRUD of ModelName all work great. The problem is in the Car views. I think I should be referencing a particular cars model name like such:
<%= #car.admin_model_names.Model =>
But I get the error:
undefined method `admin_model_names' for #<Car:0x000001040e2478>
I've tried the attr_accessible on the ModelNames model but to no avail. The underlying data is referenced correctly. I have also have HABTMT relationship between Cars & Users and that all worked fine referencing each others attributes from the different entities views. But haven't been able to get this to work. Is it due to the nested resource & admin control inheritance?
The only referencing method I found that works is:
<%= Admin::ModelName.find(#car.admin_model_name_id).model %>
But this really seems to be too much code (and expense of a find) to get to that attribute. Is there a Rails way?
Thanks in advance.
Scott
Have you tried:
class Car < ActiveRecord::Base
belongs_to :admin_model_name, :class_name => "Admin::ModelName"
end
as stated in
http://guides.rubyonrails.org/association_basics.html
section 3.4?
you may also need to set the :foreign_key => "admin_model_name_id" attribute to specify the referencing model.
Hope it helps.
Did you try
class Car < ActiveRecord::Base
belongs_to :admin_model_name, :class_name => 'Admin::ModelName'
end
and if necessary add :foreign_key => '' and add this column to your cars table.