ActiveRecord :through to set default values on through table - ruby

I would like to set a default value in a has_many through association.
Lets say I have three models:
People
Friends
Dogs
A person can request that a dog becomes their friend.
So a person would create an association where friends has an active column = false.
User
has_many :friends
has_many :dogs, :through => :friends
Now when I assign a dog to a user
User.find(1).dogs << dog
The friends table has null in the active column.
My friends model is defined as
Friend
def initialize(args = {})
super(args)
active = false
end
yet this does not work because the friend object is never created. Do I have to manually create one?

To set default values of a model; In the model I do this
before_save :default_values
private
def default_values
self.status = :active unless self.status
end
Not sure if this is the correct approach though.

With the following code you'll create a new friend with active = false
class User < ActiveRecord::Base
has_many :friends, :conditions => "active = false"
has_many :dogs, :through => :friends
end
#user = User.new
#user.friends.create #or #user.friends.build

Related

merge ActiveRecord::Relation with ActiveRecord::Associations

I have 2 models
class User < ActiveRecord::Base
has_many :games
def created_games
Game.where(created_by: self.id)
end
end
class Game < ActiveRecord::Base
belongs_to :user
end
u = User.take
joined_games = u.games
created_games = u.created_games
the variable joined_games is a instance of ActiveRecord::Associations, and created_games is an instance of ActiveRecord::Relation.
Is there any way I can join joined_games and created_games together?
Try adding a scope in User model
scope :joined_games, where(user_id: self.id, created_by: self.id)

ActiveRecord association seems to read differently depending on reading forward or backward

I've read a ton of ActiveRecord SO Questions and haven't come across this yet. I know that the following code snippet is a little long winded but I'm not using Rails so I wanted to be clear about how my database was created/structured.
A household has the head (or heads) of household (like mom and dad), and it has children. Mom, Dad and the kids are all members of the household. I tried to implement that as follows. This is the full code snippet so you can just copy and paste and run it if you have active_record and sqlite3.
I wrote the question in-line in the code, but here it is in case you don't want to skim the code: when I do household.heads I get the members which I assigned as heads of household. But when I run member.household.heads (on the same household!) I don't get the heads, I get the kids! My only thought is that I shouldn't be using two 'has_many's with the same foreign_key, but everything else I've tried doesn't work.
require 'active_record'
ActiveRecord::Base.logger = Logger.new(File.open('database.log', 'w'))
ActiveRecord::Base.establish_connection(
:adapter => 'sqlite3',
:database => 'test.db'
)
ActiveRecord::Schema.define do
unless ActiveRecord::Base.connection.tables.include? 'members'
create_table :members do |table|
table.integer :household_id
table.integer :head_id
table.integer :child_id
table.string :name
end
end
unless ActiveRecord::Base.connection.tables.include? 'households'
create_table :households do |table|
table.string :address
end
end
end
class Member < ActiveRecord::Base
belongs_to :household
end
class Household < ActiveRecord::Base
has_many :heads, class_name: "Member", foreign_key: :household_id
has_many :children, class_name: "Member", foreign_key: :household_id
end
#Create some members
m1 = Member.create(name: "Foo")
m2 = Member.create(name: "Bar") #Foo's wife
m3 = Member.create(name: "foo-foo") #Foo and Bar's little girl
#Create a household
h1 = Household.create(address: "123 Fake St.")
#Assign members to households
h1.heads = [m1, m2]
h1.children = [m3]
#first let's check, h1 is m1's household. The two are the same.
p h1.id == m1.household.id
#So why doesn't this work?
h1.heads.each{|head| p head.name} #returns Foo and Bar
m1.household.heads.each{|head| p head.name} #<= Doesn't return Foo and Bar ?!?
h1.children.each{|child| p child.name} #returns foo-foo
m1.household.children.each{|child| p child.name} #Also returns foo-foo as expected
As it turns out, ActiveRecord is not OK with two Models of the same class being referred to by the same foreign key (household_id). You're just overwriting the assignment of h1's members when you assign children (or something like that, this still isn't clear to me).
You either need different classes: Head and Child or you need to be ok with the child calling the household differently.
I ended up changing the foreign keys to: household_id and house_id since I need heads.household more than I need child.household
(I still think it is annoying to use children.house but oh well!)
To be clear:
class Household < ActiveRecord::Base
has_many :heads, class_name: "Member", foreign_key: :household_id
has_many :children, class_name: "Member", foreign_key: :house_id
end

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 callback after_save not setting attribute

I'm dealing with a problem on a after_save callback. I'm sure there is a easy solution, but I can't figure it out.
I have 3 models: User, Product, Bid. The Product table contains a boolean field "available", which is set default to true. If a User places a bid, the available field should be set to false.
I thought this should work with a callback on the bid model.
I can access and set the available field in the console by typing:
b = Bid.last
b.product.available = false
=> false
However I can't change it via the controller, so I think it doesn't execute the callback. What am I doing wrong?
Thank you all for your help!
product.rb
class Product < ActiveRecord::Base
has_one :bid
belongs_to :user
end
bid.rb
class Bid < ActiveRecord::Base
attr_accessible :product_id, :user_id, :product
belongs_to :product
belongs_to :user
after_save :set_product_status
def set_product_status
self.product.available = false
end
end
bids_controller.rb
...
def create
#user = current_user
product = Product.find(params[:product_id])
#bid = #user.bids.build(product: product)
respond_to do |format|
if #bid.save
...
Since bid belongs_to product, you should save the product too.
def set_product_status
self.product.available = false
self.product.save
end

Rails get related items through two different relationships

I have a "two middleman" model setup as shown below:
User
has_many :comments
has_many :ratings
Comment
belongs_to :user
belongs_to :movie
Rating
belongs_to :user
belongs_to :movie
Movie
has_many :comments
has_many :ratings
Whats the best way to get all Movies that a User is associated with (either commented on or rated)?
I'd like to be able to call User.get_movies(user_id) and get back an ActiveRecord::Relation object so that it's chainable (i.e. User.get_movies(user_id).limit(3).order(...)). This returns a regular old array, and I suspect I'm hitting the database way more than I need to be.
def self.get_movies(user_id)
user = self.where(:id => user_id).includes({:comments => :movie}, {:ratings => :movie})
movies = []
user.comments.each do |comment|
movies.push(comment.movie)
end
user.ratings.each do |rating|
movies.push(rating.movie)
end
movies.uniq!
end
def movies
Movie.includes(:ratings, :comments).where("`ratings`.user_id = ? OR `comments`.user_id = ?", self.id, self.id)
end
Untested, but I'm pretty sure using a joins instead of includes also works.

Resources