find_or_create_by on intermediate table in rails 2.3 - ruby

I have 2 tables , related to a third table as follows.
class A < ActiveRecord::Base
has_many :B, :through => :AB
has_many :AB
end
class B < ActiveRecord::Base
has_many :A, :through => :AB
has_many :AB
end
class AB < ActiveRecord::Base
belongs_to :B,
belongs_to :A
end
I want to be able to insert into the intermediate table(AB), using find_or_create.But find_or_create doesnot work on AB. How can I do it, other than finding and creating seperately ??
PS: I dont want to use has_and_belongs_to_many because that doesnot create the intermediate model and i want to insert into the intermediate table without creating any extra rows in the end tables, A and B.
Some suggestions please??

find_or_create_by has been added in rails 4. You can write it by yourself (should go to initializers):
class ActiveRecord::Base
def self.find_by(params)
scoped.where(params).limit(1).first
end
def self.find_or_create_by(params)
find_by(params) || create(params)
end
end

Related

Rail ActiveRecord get all many to many relations

I have looked through similar questions on here but cant seem to make sense of them.
I have 3 tables Items, Sizes, and Item_Sizes where Items has many Sizes through Item_Sizes.
item.rb:
class Item < ApplicationRecord
has_many :images
has_many :category_items
has_many :categories, through: :category_items
has_many :item_sizes
has_many :sizes, through: :item_sizes
has_many :colour_items
has_many :colours, through: :colour_items
end
size.rb
class Size < ApplicationRecord
has_many :item_sizes
has_many :items, :through => :item_sizes
end
item_size.rb:
class ItemSize < ApplicationRecord
belongs_to :item
belongs_to :size
end
That all seems to be working. But I'm wondering how to get all Sizes that are associated with a subset of items.
I have written a loop to do this but I doubt it's very efficient.
def get_all_sizes(items)
results = []
items.each do |item|
item.sizes.each do |size|
results << size unless results.include?(size)
end
end
results
end
Is there a proper query I could use in place of this for loop?
I think you could join the Item_Sizes table and get the sizes by the items' ids:
Size.joins(:item_sizes).where(item_sizes: { item_id: items.ids })
You can do it in one line.
result = items.map{|item| item.sizes }.flatten.uniq

ActiveRecord - Finding all objects with shared attributes in a join model

I have three models
class Boat < ActiveRecord::Base
belongs_to :captain
has_many :boat_classifications
has_many :classifications, through: :boat_classifications
end
class Classification < ActiveRecord::Base
has_many :boat_classifications
has_many :boats, through: :boat_classifications
end
class BoatClassification < ActiveRecord::Base
belongs_to :boat
belongs_to :classification
end
I'm trying to write a simple ActiveRecord query to find all the boats of type sailboat. Something like Boat.where(classifications: "Sailboat")
I think this could work:
Boat.joins(:classifications).where(classifications: { name: 'Sailboat' }) # name or whatever field contains Sailboat
Generates this query:
SELECT `boats`.* FROM `boats` INNER JOIN `boat_classifications` ON `boat_classifications`.`boat_id` = `boats`.`id` INNER JOIN `classifications` ON `classifications`.`id` = `boat_classifications`.`classification_id` WHERE `classification`.`name` = 'Sailboat'
I think you want something like this:
Boat.includes(:classifications).where(classifications: {id: Classification.sailboats})
For this to work, you also need a scope on Classification like this:
def self.sailboats
where(name: "Sailboat")
end

activerecord records not in the :through condition

I need to get the records not in the :through condition.
I have 3 tables:
class A < ActiveRecord::Base
has_many :B_A
has_many :B, through: :B_A
end
class B < ActiveRecord::Base
has_many :B_A
has_many :A, through: :B_A
end
class BA < ActiveRecord::Base
belongs_to :A
belongs_to :B
end
I get all A linked to a specific B by listOfA = B.A but I need also to get all A not in any B. How can I do ?
Ok, I had two conditions to put in my join query.
My MySql was :
select a.`id`, a.`label`
from `A` a
left join `BA` ba
on a.`id` = ba.`a`
where a.`uid`='userId'
and ba.`a` is null
My activeRecord now is :
A.select(['A.id', :label])
.joins('left outer join B_A on B_A.a_id = A.id')
.where({ user_id: current_user.id, "B_A.a_id" => nil })
And I have all my A not in BA

How to have an ordered model association allowing duplicates

I have two models, Song and Show. A Show is an ordered list of Songs, in which the same Song can be listed multiple times.
That is, there should be an ordered array (or hash or anything) somewhere in Show that can contain Song1, Song2, Song1, Song3 and allow re-ordering, inserting, or deleting from that array.
I cannot figure out how to model this with ActiveRecord associations. I'm guessing I need some sort of special join table with a column for the index, but apart from starting to code my SQL directly, is there a way to do this with Rails associations?
Some code as I have it now (but doesn't work properly):
class Song < ActiveRecord::Base
attr_accessible :title
has_and_belongs_to_many :shows
end
class Show < ActiveRecord::Base
attr_accessible :date
has_and_belongs_to_many :songs
end
song1 = Song.create(title: 'Foo')
song2 = Song.create(title: 'Bar')
show1 = Show.create(date: 'Tomorrow')
show1.songs << song1 << song2 << song1
puts "show1 size = #{show1.songs.size}" # 3
show1.delete_at(0) # Should delete the first instance of song1, but leave the second instance
puts "show1 size = #{show1.songs.size}" # 2
show1.reload
puts "show1 size = #{show1.songs.size}" # 3 again, annoyingly
Inserting might look like:
show1.songs # Foo, Bar, Foo
song3 = Song.create(title: 'Baz')
show1.insert(1, song3)
show1.songs # Foo, Baz, Bar, Foo
And reordering might (with a little magic) look something like:
show1.songs # Foo, Bar, Foo
show1.move_song_from(0, to: 1)
show1.songs # Bar, Foo, Foo
You're on the right track with the join table idea:
class Song < ActiveRecord::Base
attr_accessible :title
has_many :playlist_items
has_many :shows, :through => :playlist_items
end
class PlaylistItem < ActiveRecord::Base
belongs_to :shows #foreign_key show_id
belongs_to :songs #foreign_key song_id
end
class Show < ActiveRecord::Base
attr_accessible :date
has_many :playlist_items
has_many :songs, :through => :playlist_items
end
Then you can do stuff like user.playlist_items.create :song => Song.last
My current solution to this is a combination of has_many :through and acts_as_list. It was not the easiest thing to find information on combining the two correctly. One of the hurdles, for example, was that acts_as_list uses an index starting at 1, while the array-like methods created by the ActiveRecord association start at 0.
Here's how my code ended up. Note that I had to specify explicit methods to modify the join table (for most of them anyway); I'm not sure if there's a cleaner way to make those work.
class Song < ActiveRecord::Base
attr_accessible :title
has_many :playlist_items, :order => :position
has_many :shows, :through => :playlist_items
end
class PlaylistItem < ActiveRecord::Base
attr_accessible :position, :show_id, :song_id
belongs_to :shows
belongs_to :songs
acts_as_list :scope => :show
end
class Show < ActiveRecord::Base
attr_accessible :date
has_many :playlist_items, :order => :position
has_many :songs, :through => :playlist_items, :order => :position
def song_at(index)
self.songs.find_by_id(self.playlist_items[index].song_id)
end
def move_song(index, options={})
raise "A :to option is required." unless options.has_key? :to
self.playlist_items[index].insert_at(options[:to] + 1) # Compensate for acts_as_list starting at 1
end
def add_song(location)
self.songs << location
end
def remove_song_at(index)
self.playlist_items.delete(self.playlist_items[index])
end
end
I added a 'position' column to my 'playlist_items' table, as per the instructions that came with acts_as_list. It's worth noting that I had to dig into the API for acts_as_list to find the insert_at method.

rails belongs_to through via association

I'm on rails 3.0 and trying to figure out what would be the proper way to setup a belong_to :through relationship (which) I know is not possible. Here's an example:
class ParentCompany < ActiveRecord::Base
has_many :subsidiaries
has_many :employees, :through => :subsidiaries
end
class Subsidiary < ActiveRecord::Base
belongs_to :parent_company
has_many :employees
end
class Employee < ActiveRecord::Base
belongs_to :subsidiary
belongs_to :parent_company, :through :subsidiary # <-- I know this is invalid
end
I know I can solve it by doing:
class Employee < ActiveRecord::Base
def parent_company
subsidiary.parent_company
end
end
However, I'd like to know if I can do the above via associations.
You can use delegate to accomplish this without using an association
class Employee < ActiveRecord::Base
belongs_to :subsidiary
delegate :parent_company, to: :subsidiary
end

Resources