I have a rails model for a user as:
class Player < ActiveRecord::Base
has_and_belongs_to_many :character_factors
has_and_belongs_to_many :stats_factors
end
And my character factors model is as:
class CharacterFactor < ActiveRecord::Base
has_and_belongs_to_many :players
end
And my stats factors model is:
class StatFactor < ActiveRecord::Base
has_and_belongs_to_many :players
end
Within my Player model I have the following methods:
def self.character_factors(id)
#character_factors = CharacterFactor.joins(:players).where('players.id = ?', id)
end
and another method:
def self.stat_factors(id)
#stat_factors = StatFactor.joins(:players).where('players.id = ?', id)
end
The join on character factors and players looks for the correct table:
players_character_factors and performs the query properly
whereas the join on stat_factors looks for the table:
stat_factors_players and thus errors out. I'm not sure why it looks for two differently formatted table names for the exact same association. Is there something I'm not doing correctly over here?
I found the solution for my question.
In Rails for HABTM associations the table names are generated automatically in the lexical order of the model names unless explicitly specified.
In my case since I didn't specify the name of the join table so the tables which rails was trying to look for were wrong. I fixed it by updating my models as such:
class Player < ActiveRecord::Base
has_and_belongs_to_many :character_factors, :join_table => :players_character_factors
has_and_belongs_to_many :stats_factors, :join_table => :players_stats_factors
end
and similarly for character factors:
class CharacterFactor < ActiveRecord::Base
has_and_belongs_to_many :players, :join_table => :players_character_factors
end
and for stat factors:
class StatFactor < ActiveRecord::Base
has_and_belongs_to_many :players, :join_table => :players_stats_factors
end
Related
I have two models with a has many through association between them like below:
TipoDocumento < ActiveRecord::Base
has_many :dependencias
has_many :TipoRequisitos, :through => :dependencias
...
end
TipoRequisito < ActiveRecord::Base
has_many :dependencias
has_many :TipoDocumentos, :through => :dependencias
...
end
Dependencia < ActiveRecord::Base
belongs_to: TipoDocumento
belongs_to: TipoRequisito
...
end
The id's attributes for the join model Dependencia are TipoDocumento_id and TipoRequisito_id.
Now, when I try this in the rails console:
x = TipoDocumento.find(1)
x.TipoRequisitos
I get this error:
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: dependencia.tipo_documento_id: SELECT "tipo_requisitos".* FROM "tipo_requisitos" INNER JOIN "dependencia" ON "tipo_requisitos"."id" = "dependencia"."TipoRequisito_id" WHERE "dependencia"."tipo_documento_id" = 1
also if I try the opposite with TipoRequisito it's the same.
It seems that Rails is changing somehow the TipoDocumento_id column name for tipo_documento_id when it performs the query. So, I tried to change the id's column's names from their CamelCase to their snake_case, but I get the analog error (Cannot find TipoDocumento_id or TipoRequisito_id.)
I don't see what's wrong.
You need to follow the Rails convention and use down-cased names when you refer to models when defining relations:
TipoDocumento < ActiveRecord::Base
has_many :dependencias
has_many :tipo_requisitos, through: :dependencias
...
end
TipoRequisito < ActiveRecord::Base
has_many :dependencias
has_many :tipo_documentos, :through => :dependencias
...
end
Dependencia < ActiveRecord::Base
belongs_to :tipo_documento
belongs_to :tipo_requisito
...
end
you need to lower-case it, like this:
x = TipoDocumento.find(1)
x.tipo_requisitos
Please also check: http://guides.rubyonrails.org/association_basics.html
I am currently trying to create a custom method on a model, where the conditions used are those of a has_many association. The method I have so far is:
class Dealer < ActiveRecord::Base
has_many :purchases
def inventory
inventory = Vehicle.where(:purchases => self.purchases)
return inventory
end
end
This is not working, due to the fact that Vehicle has_many :purchases (thus there is no column "purchases" on the vehicle model). How can I use the vehicle.purchases array as a condition in this kind of query?
To complicate matters, the has_many is also polymorphic, so I can not simply use a .join(:purchases) element on the query, as there is no VehiclePurchase model.
EDIT: For clarity, the relevant parts of my purchase model and vehicle models are below:
class Purchase < ActiveRecord::Base
attr_accessible :dealer_id, :purchase_type_id
belongs_to :purchase_item_type, :polymorphic => true
end
class Vehicle < ActiveRecord::Base
has_many :purchases, :as => :purchase_item_type
end
class Dealer < ActiveRecord::Base
def inventory
Vehicle.where(:id => purchases.where(:purchase_item_type_type => "Vehicle").map(&:purchase_item_type_id))
end
end
Or:
def inventory
purchases.includes(:purchase_item_type).where(:purchase_item_type_type => "Vehicle").map(&:purchase_item_type)
end
I was able to do this using the :source and :source_type options on the Vehicle model, which allows polymorphic parents to be associated.
I have three classes; User, Feed, and Entries. Users have one or more Feeds and Feeds have one or more Entries:
class Feed < ActiveRecord::Base
has_many :entries
has_many :feeds_users
has_many :users, :through => :feeds_users, :class_name => "User"
end
Class User < ActiveRecord::Base
has_many :feeds_users
has_many :feeds, :through => :feeds_users, :class_name => "Feed"
end
class Entry < ActiveRecord:Base
belongs_to :feed
end
The User and Feed classes are related through a third table:
class FeedsUser < ActiveRecord::Base
belongs_to :user
belongs_to :feed
end
Now, my problem is that I need to get a certain user's feeds and each feed's latest n entries. I'm fiddling with something like this:
u = User.first
Feed.joins(:entries, :users).where(:entries => { :created_at => (Time.now-2.days)..Time.now }, :users => u)
My problem here is that this generates some SQL that tries to query "users" on the table "feeds" - which doesn't exist.
How do I go about creating a correct query that can give me all feeds and their latest 10 entries for a given user?
Take out has_many :feeds_users. :feeds_users shouldn't be a model.
You should keep the table, but some Rails magic goes on behind the scenes. If you have a table named feeds and a table named users, Rails derives the table name of the look-up unless you specify it differently.
For your example:
Class Feed < ActiveRecord::Base
has_many :users
end
Class User < ActiveRecord::Base
has_many :feeds
end
It will look for the relationship in the users_feeds table. If it looked like this:
Class Feed < ActiveRecord::Base
belongs_to :user
end
Class User < ActiveRecord::Base
has_many :feeds
end
It would look for the relationship in the user_feeds table.
It's linguistically correct, as many users will have many feeds in the first example, and one user will have many feeds in the second example. That's also a good indicator of whether your relation logic is correct.
It also sounds like you need the has_and_belongs_to_many relationship instead of just has_many.
For a complete run-down on all forms of model associations, check out the guide.
I have the following model:
class Advisor < ActiveRecord::Base
belongs_to :course
end
class Course < ActiveRecord::Base
has_many :advisors
has_many :sessions
has_many :materials, :through=>:sessions
end
class Session < ActiveRecord::Base
belongs_to :course
has_many :materials
end
class Material < ActiveRecord::Base
belongs_to :session
end
I.e., every advisor teaches one course, every course has sessions, and every session has materials.
I want to traverse from an advisor to all the associated materials, i.e. something like: Advisor.first.materials
I tried to do:
class Advisor < ActiveRecord::Base
belongs_to :course
has_many :sessions, :through=>:course
has_many :materials, :through=>:sessions
end
But it didn't work as it treated sessions as a many-to-many table: Unknown column 'sessions.advisor_id' in 'where clause': SELECT 'material'.* FROM 'materials' INNER JOIN 'sessions' ON 'materials'.session_id = 'sessions'.id WHERE (('sessions'.advisor_id = 1))
I then tried to do:
class Advisor < ActiveRecord::Base
belongs_to :course
has_many :materials, :through=>:course
end
In an attempt to have the association use the "materials" association in the "Course" model, but received:
ActiveRecord::HasManyThroughSourceAssociationMacroError: Invalid source reflection on macro :has_many :through for has_many :materials, :through=>:sessions. Use :source to specify the source reflection.
Tried to use "sessions" as the source which was a nice try but made me receive only the sessions rather than the materials.
Any ideas if this is at all possible?
I'm using Rails 2.3.8 (perhaps time to upgrade?)
Thanks!
Amit
I want to traverse from an advisor to
all the associated materials
Unless I'm missing something, using the associations specified in your first example, you can simply call materials on the associated course:
a = Advisor.first
materials = a.course.materials
Using a has_many,through association for another has_many,:through relation will not work in rails
instead of creating a association you can just create a method to access all the materials by a Advisor
def materials
sessions.collect(&:materials).flatten
end
This will work, but you wont be able to chain find queries to this method. If you want to be able to chain find methods something like #advisor.materials.find.. Then inside this method use Material.find() with appropriate conditions
Using Ruby on Rails, how can I achieve a polymorphic has_many relationship where the owner is always of a known but the items in the association will be of some polymorphic (but homogenous) type, specified by a column in the owner? For example, suppose the Producer class has_many products but producer instances might actually have many Bicycles, or Popsicles, or Shoelaces. I can easily have each product class (Bicycle, Popsicle, etc.) have a belongs_to relationship to a Producer but given a producer instance how can I get the collection of products if they are of varying types (per producer instance)?
Rails polymorphic associations allow producers to belong to many products, but I need the relationship to be the other way around. For example:
class Bicycle < ActiveRecord::Base
belongs_to :producer
end
class Popsicle < ActiveRecord::Base
belongs_to :producer
end
class Producer < ActiveRecord::Base
has_many :products, :polymorphic_column => :type # last part is made-up...
end
So my Producer table already has a "type" column which corresponds to some product class (e.g. Bicycle, Popsicle, etc.) but how can I get Rails to let me do something like:
>> bike_producer.products
#=> [Bicycle#123, Bicycle#456, ...]
>> popsicle_producer.products
#=> [Popsicle#321, Popsicle#654, ...]
Sorry if this is obvious or a common repeat; I'm having surprising difficulty achieving it easily.
You have to use STI on the producers, not on the products. This way you have different behavior for each type of producer, but in a single producers table.
(almost) No polymorphism at all!
class Product < ActiveRecord::Base
# does not have a 'type' column, so there is no STI here,
# it is like an abstract superclass.
belongs_to :producer
end
class Bicycle < Product
end
class Popsicle < Product
end
class Producer < ActiveRecord::Base
# it has a 'type' column so we have STI here!!
end
class BicycleProducer < Producer
has_many :products, :class_name => "Bicycle", :inverse_of => :producer
end
class PopsicleProducer < Producer
has_many :products, :class_name => "Popsicle", :inverse_of => :producer
end
please take it on format
class Bicycle < ActiveRecord::Base
belongs_to :bicycle_obj,:polymorphic => true
end
class Popsicle < ActiveRecord::Base
belongs_to :popsicle_obj , :polymorphic => true
end
class Producer < ActiveRecord::Base
has_many :bicycles , :as=>:bicycle_obj
has_many :popsicle , :as=>:popsicle_obj
end
Use this code. If you have any problem with it, please leave a comment.
Here is the workaround I'm currently using. It doesn't provide any of the convenience methods (collection operations) that you get from real ActiveRecord::Associations, but it does provide a way to get the list of products for a given producer:
class Bicycle < ActiveRecord::Base
belongs_to :producer
end
class Popsicle < ActiveRecord::Base
belongs_to :producer
end
class Producer < ActiveRecord::Base
PRODUCT_TYPE_MAPPING = {
'bicycle' => Bicycle,
'popsicle' => Popsicle
}.freeze
def products
klass = PRODUCT_TYPE_MAPPING[self.type]
klass ? klass.find_all_by_producer_id(self.id) : []
end
end
Another downside is that I must maintain the mapping of type strings to type classes but that could be automated. However, this solution will suffice for my purposes.
I find that polymorphic associations is under documented in Rails.
There is a single table inheritance schema, which is what gets the most documentation,
but if you are not using single table inheritance, then there is some missing information.
The belongs_to association can be enabled using the :polymorphic => true option. However, unless you are using single table inheritance, the has_many association does not work, because it would need to know the set of tables that could have a foreign key.
(From what I found), I think the clean solution is to have a table and model for the base class, and have the foreign key in the base table.
create_table "products", :force => true do |table|
table.integer "derived_product_id"
table.string "derived_product_type"
table.integer "producer_id"
end
class Product < ActiveRecord::Base
belongs_to :producer
end
class Producer < ActiveRecord::Base
has_many :products
end
Then, for a Production object, producer, you should get the products with producer.products.derived_products.
I have not yet played with has_many through to condense the association to producer.derived_products, so I cannot comment on getting that to work.
class Note < ActiveRecord::Base
belongs_to :note_obj, :polymorphic => true
belongs_to :user
end
class Contact < ActiveRecord::Base
belongs_to :contact_obj, :polymorphic => true
belongs_to :phone_type
end
class CarrierHq < ActiveRecord::Base
has_many :contacts, :as => :contact_obj
has_many :notes, :as => :note_obj
end