Rails model references question - ruby-on-rails

class CreateMatches < ActiveRecord::Migration
def self.up
create_table :matches do |t|
t.integer :result_home
t.integer :result_away
t.references :clan, :as => :clan_home
t.references :clan, :as => :clan_away
t.references :league
t.timestamps
end
end
def self.down
drop_table :matches
end
end
I think code clears everything, I need to reference result_home to one clan and result_away to another.
What is the best way to do so? I could create has_and_belongs_to_many but i think it's not good way in this case.

This looks like a join association call it Match, and
class Clan < ActiveRecord::Base
has_many :home_matches, :class_name => 'Match', :foreign_key => :clan_home
has_many :away_matches, :class_name => 'Match', :foreign_key => :clan_away
has_many :opponents_at_home, :through => :home_matches, :source => :clan
has_many :opponents_away, :through => :away_matches, :source => :clan
end
class Match < ActiveRecord::Base
belongs_to :clan_home, :class_name => 'Clan'
belongs_to :clan_away, :class_name => 'Clan'
end
This is a little beyond my personal experience and I'm not 100% clear on the interpretation of the documentation for :source (check http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html). However, I think this will be along the right lines. YMMV.
Comments and improvements are welcome!

Related

counter_cache with has_many

I have read this isnt possible, and then someone else told me it is. They gave me this code but then had to go, now now my app is broken until I get this working :/
I have a Tag model, and each tag has_many resources :through => resource tags. Each resource also has_many tags.
I need to know the number of resource each tag has (I dont care about the other way around).
The problem is it says unkown key cache_counter
This is my model
Tag.rb
has_many :resource_tags, :dependent => :destroy, :counter_cache => :resource_count
has_many :resources, :through => :resource_tags
Resource.rb
has_many :resource_tags, :dependent => :destroy
has_many :tags, :through => :resource_tags
My migration:
class CreateTags < ActiveRecord::Migration
def change
create_table :tags do |t|
t.string :name
t.integer :resource_count, :default => 0
t.timestamps
end
end
end
:counter_cache option is for belongs_to method
in resorce_tag model
belongs_to :tag, :counter_cache => :resource_count
and i think it is better to name column resources_count (plural)

Rails intermediate table assosciations

I have a User model and a Tag model. The User has Skills and Interests.
A Skill is a Tag, and an Interest is a Tag.
I have a table for Users, Tags, UsersSkills, UsersInterests. The last two being the intermediate table. How do I associate all this. The following is what I have but is not working. Thanks ahead of time.
#User model
class User < ActiveRecord::Base
has_and_belongs_to_many :skills
has_and_belongs_to_many :interests
end
#Tag model
class Tag < ActiveRecord::Base
has_and_belongs_to_many :users
end
#Migrations
create_table :users_interests, :id => false do |t|
t.references :user
t.references :tag
end
create_table :users_skills, :id => false do |t|
t.references :user
t.references :tag
end
SO here is the answer for anyone else experiencing this problem. The intermediate table had to have its name be alphabetically in order, even if that means readability goes down the tube. A join_table was then used. If this is not the right answer (it works but might not be good coding), please let me know.
class User < ActiveRecord::Base
has_and_belongs_to_many :skills, :class_name => "Tag", :join_table => "skills_users"
has_and_belongs_to_many :interests, :class_name => "Tag", :join_table => "interests_users"
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :users
end
create_table :skills_users, :id => false do |t|
t.references :user
t.references :tag
end
create_table :interests_users, :id => false do |t|
t.references :user
t.references :tag
end
It's expecting your join tables to have skill_id and interest_id FK's rather than tag_id.
I believe you're looking for (don't have a terminal handy):
class User < ActiveRecord::Base
has_and_belongs_to_many :skills, :association_foreign_key => :tag_id
has_and_belongs_to_many :interests, :association_foreign_key => :tag_id
end

Ruby on Rails: polymorphic many-to-many design?

I'm having troubles getting a polymorphic many-to-many model working in ruby/rails. The model has three tables that need to be joined, Infection, Drug, and Symptom:
create_table "diseases" do |t|
t.string "name"
end
create_table "drugs" do |t|
t.string "name"
end
create_table "symptoms" do |t|
t.string "name"
end
create_table "to_symptoms" do |t|
t.integer "symptom_id"
t.integer "symptomatic_id"
t.string "symptomatic_type"
end
Where symptoms is linked to both infections and drugs. The tricky part is that the relationship of a symptom to a drug can be either as a side effect or as a contraindication. The way I tried to do this was:
class ToSymptom < ActiveRecord::Base
belongs_to :symptomatic, :polymorphic => true
belongs_to :symptom
end
class Drug < ActiveRecord::Base
has_many :to_symptom, :as => :symptomatic
has_many :contraindications, :class_name => "Symptom",
:through => :to_symptom, :source => :symptomatic,
:source_type => 'Contraindication'
has_many :side_effects, :class_name => "Symptom",
:through => :to_symptom, :source => :symptomatic,
:source_type => 'SideEffect'
end
class Symptom < ActiveRecord::Base
has_many :to_symptom
has_many :diseases, :through => :to_symptom, :source => :symptomatic,
:source_type => 'Disease'
has_many :contraindicated_drugs, :class_name => "Drug",
:through => :to_symptom, :source => :symptomatic,
:source_type => 'Contraindication'
has_many :caused_by, :class_name => "Drug", :through => :to_symptom,
:source => :symptomatic, :source_type => 'SideEffect'
end
class Disease < ActiveRecord::Base
has_many :to_symptom, :as => :symptomatic
has_many :symptoms, :through => :to_symptom
end
The Disease <-> Symptom relationship seems to be working the way I'd expect, but the relationships between Drug and Symptom aren't doing what I'd expect. The relationship in the direction of symptoms-> drugs seems to be working, but the reverse direction generates some weird SQL. If I try something like:
d = Drug.first
d.contraindications
I'll get the following SQL:
SELECT
`symptoms`.*
FROM `symptoms`
INNER JOIN `to_symptoms` ON `symptoms`.`id` = `to_symptoms`.`symptomatic_id`
WHERE `to_symptoms`.`symptomatic_id` = 2
AND `to_symptoms`.`symptomatic_type` = 'Drug'
AND `to_symptoms`.`symptomatic_type` = 'Contraindication'
The to.symptoms.symptomatic_type = drug shouldn't be in there, and the join in on the wrong field of to_symptoms (symptomatic_id vs. symptom_id. I've tried a ton of different combinations, but I can't seem to get this one to work. Is what I'm trying to do even possible in RoR?
It seems that this isn't very widely advertised, but it apparently doesn't work in Rails... (polymorphic mas_many :through) (at least not without insane hacks). I'll try to find some supporting links

Rails - Model Doubt

Given the fact that I have models like this:
class Person
has_many :owned_groups, :class_name => "Group", :foreign_key => :owner_id
has_many :owned_group_memberships, :through => :owned_groups,
:source => :group_memberships
has_many :group_memberships, :foreign_key => "member_id"
has_many :groups, :through => :group_memberships
end
class GroupMembership
belongs_to :member, :class_name => 'Person'
belongs_to :group
end
class Group
belongs_to :owner, :class_name => "Person"
has_many :group_memberships
has_many :members, :through => :group_memberships
end
How can I access the members a group has? Always I do #group.members or things like that, gives me an error saying that the relation can't be found in the model.
Thanks in advance.
##EDIT##
The error I'm getting is: Could not find the association "group_memberships" in model Group
I do a similar thing on a site I'm working on but the associations are a little different to how you're doing it but maybe it'll help. I think you need to use the has_and_belongs_to_many association to join up your many-to-many's.
In my database I have Users, Members and UsersMembers
You don't need to create a UsersMembers model (GroupMembership in your case) but you do need a database table to link the two.
#Migration
create_table :bands_users, :id => false, :force => true do |t|
t.integer :band_id, :null => false
t.integer :user_id, :null => false
end
#Models
class Band < ActiveRecord::Base
has_and_belongs_to_many :members, :class_name => 'User'
end
class User < ActiveRecord::Base
has_and_belongs_to_many :bands
end
From this I can now call #band.members or #user.bands
You may need to specify :class_name => 'Person' in your has_many :members statement.

Many-to-many association with multiple self-joins in ActiveRecord

I am trying to implement multiple relations between records of the same model via self-joins (based on #Shtééf's answer). I have the following models
create_table :relations, force: true do |t|
t.references :employee_a
t.string :rel_type
t.references :employee_b
end
class Relation < ActiveRecord::Base
belongs_to :employee_a, :class_name => 'Employee'
belongs_to :employee_b, :class_name => 'Employee'
end
class Employee < ActiveRecord::Base
has_many :relations, foreign_key: 'employee_a_id'
has_many :reverse_relations, class_name: 'Relation', foreign_key: 'employee_b_id'
has_many :subordinates, through: :relations, source: 'employee_b', conditions: {'relations.rel_type' => 'manager of'}
has_many :managers, through: :reverse_relations, source: 'employee_a', conditions: {'relations.rel_type' => 'manager of'}
end
With this setup I can successfully access the lists of subordinates and managers for each record. However, I have difficulties to create relations in the following way
e = Employee.create
e.subordinates.create
e.subordinates #=> []
e.managers.create
e.managers #=> []
The problem is that it does not set type of relations, so I have to write
e = Employee.create
s = Employee.create
e.relations.create employee_b: s, rel_type: 'manager of'
e.subordinates #=> [#<Employee id:...>]
Am I doing something wrong?
You can use before_add and before_remove callback on the has_many association :
class Employee < ActiveRecord::Base
has_many :relations, foreign_key: 'employee_a_id'
has_many :reverse_relations, class_name: 'Relation', foreign_key: 'employee_b_id'
has_many :subordinates,
through: :relations,
source: 'employee_b',
conditions: {'relations.rel_type' => 'manager of'}
:before_add => Proc.new { |employe,subordinate| employe.relations.create(employe_b: subordinate, rel_type: 'manager of') },
:before_remove => Proc.new { |employe,subordinate| employe.relations.where(employe_b: subordinate, rel_type: 'manager of').first.destroy }
has_many :managers,
through: :reverse_relations,
source: 'employee_a',
conditions: {'relations.rel_type' => 'manager of'}
:before_add => Proc.new { |employe,manager| employe.reverse_relations.create(employe_a: manager, rel_type: 'manager of') },
:before_remove => Proc.new { |employe,manager| employe.reverse_relations.where(employe_b: subordinate, rel_type: 'manager of').first.destroy }
This should works and make you able to use employe.managers.create
You may want to use build instread of create in the callback
Also you can read this question about this solution
In order to create a multiple many-to-many self-join association, I would recommend that it might make more sense to have multiple tables to manage the connection. That way it's very clear from a data standpoint as to exactly what is going on, and it's also clear from a logic standpoint. So something along these lines:
create_table :manage_relation do |t|
t.references :employee_id
t.references :manager_id
end
create_table :subordinate_relation do |t|
t.references :employee_id
t.references :subordinate_id
end
class Employee < ActiveRecord::Base
has_many :subordinates,
:through => :subordinate_relation,
:class_name => "Employee",
:foreign_key => "subordinate_id"
has_many :managers,
:through => :manage_relation,
:class_name => "Employee",
:foreign_key => "manager_id"
belongs_to :employee,
:class_name => "Employee"
end
This way it doesn't get any more convoluted than necessary from a coding standpoint, and you can access it using the standard collections and it will appropriately set up your connections for you without you having to manage them. So, both of these collections should work..
employee.managers
employee.subordinates
And you could not have to manage any other variables. Make sense? It adds a table, but improves clarity.
I would redo your models as follows:
class ManagerRelation < ActiveRecord::Base
belongs_to :manager, :class_name => 'Employee'
belongs_to :subordinate, :class_name => 'Employee'
end
class Employee < ActiveRecord::Base
has_many :manager_relations, :class_name => "ManagerRelation",
:foreign_key => :subordinate_id
has_many :subordinate_relations, :class_name => "ManagerRelation",
:foreign_key => :manager_id
has_many :managers, :source => :manager,
:through => :manager_relations
has_many :subordinates, :source => :subordinate,
:through => :subordinate_relations
end
Now you can do the following:
employee.managers
employee.subordinates
employee.managers << employee2
employee.subordinates << employee3
Note: It is usually a sign for one to leave the company when they are made to report to two managers :-)
Given the presented relation
create_table :relations, force: true do |t|
t.references :employee_a
t.string :rel_type
t.references :employee_b
end
class Employee < ActiveRecord::Base
has_many :subordinate_relations, :class_name => "Relation", :conditions => {:rel_type => 'manager of'}, :foreign_key => :employee_a
has_many :subordinates, :through => :subordinate_relations, :source => :subordinate, :foreign_key => :employee_b
has_many :manager_relations, :class_name => "Relation", :conditions => {:rel_type => 'manager of'}, :foreign_key => :employee_b
has_many :managers, :through => :manager_relations, :source => :manager, :foreign_key => :employee_a
end
class Relation < ActiveRecord::Base
belongs_to :manager, :class_name => "Employee", :foreign_key => :employee_a
belongs_to :subordinate, :class_name => "Employee", :foreign_key => :employee_b
end
e = Employee.create
e.subordinates.create #Employee ...
e.subordinates #[<Employee ...]
e2 = Employee.create
e2.managers.create #Employee
e2.managers #[<Employee ...]
Although the solution works - I'm a bit confused by tying the associations with "rel_type". In this case - I'd say the rel_type is redundant and the relation should be mapped as follows:
create_table :relations do |t|
t.reference :manager
t.reference :subordinate
end
In such case, the association mapping should be a tad simpler.

Resources