Rails create object has_many through - ruby-on-rails

My models
class Collection < ActiveRecord::Base
has_many :outfits
has_many :products, through: :outfits
end
class Outfit < ActiveRecord::Base
belongs_to :product
belongs_to :collection
end
class Product < ActiveRecord::Base
has_many :outfits
has_many :collections, through: :outfits
end
I want to save product in collection model
so one collection can have few product in it
How can i do it? i'm a bit struggle with it
it have tried something like this
p = Product.find_by_code('0339').id
p.collections.create(product_id:p1)
but i guess i'm wrong

When you're chaining through a through collection you don't need to reference the parent's id since that is known.
Instead of:
p = Product.find_by_code('0339').id # NOTE that you have an 'id' not an object here
p.collections.create(product_id:p1) # you can't call an association on the id
Build the association between two existing models (I'm assuming you have other fields in your models; I'm using name as an example).
p = Product.find_by(code: '0339')
c = Collection.find_by(name: 'Spring 2016 Clothing')
o = Outfit.new(name: 'Spring 2016 Outfit', product: p, collection: c)
o.save!
Assuming p and c exist and assuming o passes validation then you now have an assocaition between one product and one collection using a new outfit as the join table.
p.reload
p.collections.count # => 1
c.reload
c.products.count # => 1

Related

How to find records, whose has_many through objects include all objects of some list?

I got a typical tag and whatever-object relation: say
class Tag < ActiveRecord::Base
attr_accessible :name
has_many :tagazations
has_many :projects, :through => :tagazations
end
class Tagazation < ActiveRecord::Base
belongs_to :project
belongs_to :tag
validates :tag_id, :uniqueness => { :scope => :project_id }
end
class Project < ActiveRecord::Base
has_many :tagazations
has_many :tags, :through => :tagazations
end
nothing special here: each project is tagged by one or multiple tags.
The app has a feature of search: you can select the certain tags and my app should show you all projects which tagged with ALL mentioned tags. So I got an array of the necessary tag_ids and then got stuck with such easy problem
To do this in one query you'd want to take advantage of the common double not exists SQL query, which essentially does find X for all Y.
In your instance, you might do:
class Project < ActiveRecord::Base
def with_tags(tag_ids)
where("NOT EXISTS (SELECT * FROM tags
WHERE NOT EXISTS (SELECT * FROM tagazations
WHERE tagazations.tag_id = tags.id
AND tagazations.project_id = projects.id)
AND tags.id IN (?))", tag_ids)
end
end
Alternatively, you can use count, group and having, although I suspect the first version is quicker but feel free to benchmark:
def with_tags(tag_ids)
joins(:tags).select('projects.*, count(tags.id) as tag_count')
.where(tags: { id: tag_ids }).group('projects.id')
.having('tag_count = ?', tag_ids.size)
end
This would be one way of doing it, although by no means the most efficient:
class Project < ActiveRecord::Base
has_many :tagazations
has_many :tags, :through => :tagazations
def find_with_all_tags(tag_names)
# First find the tags and join with their respective projects
matching_tags = Tag.includes(:projects).where(:name => tag_names)
# Find the intersection of these lists, using the ruby array intersection operator &
matching_tags.reduce([]) {|result, tag| result & tag.projects}
end
end
There may be a couple of typos in there, but you get the idea

how to find out active record property through many to many

I'm currently adjusting fedena to have a many:many relationship between students and guardians (as opposed to one:many student:guardians).
So this is what I did:
class Guardian < ActiveRecord::Base
has_many :parentings, :dependent=>:destroy
has_many :students, :through=>:parentings
end
class Student < ActiveRecord::Base
has_many :parentings, :dependent=>:destroy
has_many :guardians, :through=>:parentings
end
class Parenting < ActiveRecord::Base
attr_accessible :student_id, :guardian_id
belongs_to :student
belongs_to :guardian
end
inside guardian.rb there was this class method:
def self.shift_user(student)
# find all the guardians having a ward_id = student.d (comment my own)
self.find_all_by_ward_id(student.id).each do |g|
..
end
I want to change it using the newly defined relationshop ie
self.find_all_by_student_id(student.id).each do |g|
..
It doesn't work! I thought it would work since I've already defined that a Guardian has many students through the Parenting class.. I've tried several permutations of the command above and I keep on getting the error:
undefined method `find_all_by_student_id' for #<Class:0x1091c6b28>
ideas? I'm using ruby 1.8.7 and RoR 2.3.5
Guardian has no propety student_id so there is no method find_all_by_student_id. So I don't understand why you are confused. Why don't you just use student.guardians?
You can do this using a named scope and a more complex query
class Guardian < ActiveRecord::Base
has_many :parentings, :dependent=>:destroy
has_many :students, :through=>:parentings
named_scope :find_all_by_student_id, lambda {|student_id|
{ :all,
:select => "guardians.*",
:joins => "JOIN parentings ON parentings.guardian_id = guardians.id
JOIN students ON students.id = parentings.student_id",
:conditions = ["students.id = ?", student_id] } }
end

Model relationship many to many with attribute on relational entity

I am trying something very simple. At this point I have three models:
Player >> PlayerMatch >> Match
class Match < ActiveRecord::Base
attr_accessible :date, :goals_team_a, :goals_team_b
has_many :PlayerMatches
has_many :Players, :through => :PlayerMatches
end
class Player < ActiveRecord::Base
attr_accessible :name, :password_confirmation, :password, :user
has_many :PlayerMatches
has_many :matches, :through => :PlayerMatches
end
class PlayerMatch < ActiveRecord::Base
attr_accessible :match_id, :player_id, :team
belongs_to :player
belongs_to :match
end
The model PlayerMatch is the join entity. In each Match a player plays, he can be on team A or team B, that is why I made that attribute team on PlayerMatch.
How can I set that value team for each match? I want to do something like:
p = Player.new
//set players attributes
m = Match.new
//set match attributes
p.matches << m
Now I just want to set his team on that specific match.
Thanks in advance!
Using the models you have set up, you can do something like this:
p = Player.create
m = Match.create
pm = PlayerMatch.create(:player => p, :match => m, :team => 'Team')
If you want the PlayerMatch automatically created as in your example, you can retrieve it afterwards and set the team at that point:
p = Player.create
m = Match.create
p.matches << m
pm = p.matches.where(:match_id => m.id).first
pm.update_attributes(:team => 'Team')
Unless you are saying an individual player can play different matches for different teams though, it seems like you may want a Player to belong_to a Team instead.
This post also has some info related to this question.

Serial joins in a Rails 3 model; how to associate through a many-to-many relationship?

I have Model X and Model Y which are associated through a has_and_belongs_to_many macro. There's a table in the database that retains data on the many-to-many relationships between Model X and Model Y.
I also have Model A, which is associated through a has_many macro with Model X. I want to be able to run a very simple command to reach all Model Y objects associated with Model A. So, in other words, let's say I have the following objects:
objectA.kind_of? ModelA = true
objectX.kind_of? ModelX = true
objectY.kind_of? ModelY = true
I want to be able to run objectA.objectYs and have returned to me [objectY].
What must I put in my model definitions to be able to do this?
(I have tried placing in Model A: (1) has_many :modelY; and (2) has_many :modelY, :through => :modelX. Neither is right.)
Caveat: normally one wouldn't use such perverse model names.
Assuming the models look like this:
class ModelA < ActiveRecord::Base
has_many :model_xes
end
class ModelX < ActiveRecord::Base
belongs_to :model_a
has_and_belongs_to_many :model_ies, :class_name => 'ModelY',
:join_table => 'model_x_model_ies'
end
class ModelY < ActiveRecord::Base
has_and_belongs_to_many :model_xes, :class_name => 'ModelX',
:join_table => 'model_x_model_ies'
end
We can create a scope to get the ModelY's for all the ModelX's belonging to a Model A:
class ModelY < ActiveRecord::Base
scope :find_by_a, lambda { |a| joins(:model_xes).\
where(:model_xes=>{:model_a_id=>a.id})}
end
then a simple method to call the scope on an instance of ModelA:
class ModelA < ActiveRecord::Base
def ys
ModelY.find_by_a(self)
end
end
Test like so:
require 'spec_helper'
describe ModelA do
before(:each) do
#a = ModelA.create(:name=>"a")
2.times { #a.model_xes.create(:name=>"x") }
end
it "relates model_x" do
#a.model_xes.count.should == 2
end
it "relates model y" do
x = #a.model_xes.first
x.model_ies.create
x.model_ies.count.should == 1
end
it "relates model y through model x" do
#a.model_xes.each do |x|
2.times { x.model_ies.create(:name=>"y") }
end
ys = #a.ys
ys.count.should == 4
ys.all? { |y| y.name == "y" }.should be_true
end
end
Note that HABTM has fallen out of favor, so you're encouraged to use has_many :through instead.

How To Get Additional Attributes From Has Many Through

I am using Rails 3 beta 4.
I have the following models:
class Player < ActiveRecord::Base
has_many :players_items, :dependent => :destroy
has_many :items, :through => :players_items
end
class PlayersItem < ActiveRecord::Base
belongs_to :player
belongs_to :item
end
class Item < ActiveRecord::Base
has_many :players_items, :dependent => :destroy
has_many :players, :through => :players_items
end
In the players_controller
def items
#player = Player.find(params[:id])
#player_items = #player.items
end
I have the following attributes
--Items Model--
Item_id:Integer
Name:String
Cost:Integer
Description:Text
--PlayersItem Model--
Item_id:Integer
Player_id:Integer
Total:Integer
Traded:Integer
I am trying to print out all the items associated with a player and for each item print out the "Name", "Cost", "Description", "Total", and "Traded" values.
When I call #player_items in the items.html.erb, I can only access the attributes associated with the Item Model and not any of the attributes associated with PlayersItem model.
I am trying to access the attributes from both the items model and players_items model in the same "call" similar to SQL Join Statement like this
SELECT * FROM players_items INNER JOIN items ON players_items.item_id=items.id
WHERE players_items.player_id = "#player"
Is this possible?
#player = Player.order("created_at").last
#player.players_items.each do |item|
puts "#{item.player}: #{item.description} cost:#{item.cost}"
end
Has many through is a little weird. Think of it as a model whose name should (ideally) be descriptive of the relationship between the two other models. So maybe if equipment is being distributed to the players you could call the join model distributions or loans or something. The players_items is the naming convention for join tables which aren't going to be addressed directly.
I hope that helps!
Used in the controller
#player = Player.find(params[:id], :include => [:items,:players_items])
And in the view
#player.players_items.each do |player|
puts "#{player.name}: #{player.player.item.description} cost:#{player.item.cost}"
end

Resources