Why undefined method "has_many" in Rspec example? - activerecord

I am playing around with an Example on testing a has_many through association in RSpec.
I am getting a
1) Foo specifies items
Failure/Error: subject.should have_many(:items)
NoMethodError:
undefined method `has_many?' for #
# ./spec/models/foo_spec.rb:10
My question: Why would has_many be undefined?
The spec is:
describe Foo do
it "specifies items" do
subject.should have_many(:items)
end
end
My models are:
foo.rb:
class Foo < ActiveRecord::Base
has_many :bars
has_many :items, :through => :bars
end
bar.rb:
class Bar < ActiveRecord::Base
belongs_to :foo
belongs_to :item
end
and item.rb:
class Item < ActiveRecord::Base
has_many :foos, :through => :bars
has_many :bars
end

Well, there is no has_many? method on model objects. And rspec-rails does not provide such matcher by default. However, shoulda-matchers gem does:
describe Post do
it { should belong_to(:user) }
it { should have_many(:tags).through(:taggings) }
end
describe User do
it { should have_many(:posts) }
end
(example from shoulda-matchers documentation)
Just add gem 'shoulda-matchers' to your Gemfile and you will be able to use that syntax.

Related

Versioning and polymorphic associations

I'm creating a second version of my app using using the following pattern:
class List < ActiveRecord::Base
has_many :listable_items
has_many :lists, through: :listable_items, source: :listable, source_type: 'List'
end
class ListableItem < ActiveRecord::Base
belongs_to :list
belongs_to :listable, polymorphic: true
end
module V2
class List < List
has_many :listable_items
has_many :lists, through: :listable_items, source: :listable, source_type: 'V2::List'
self.inheritance_column = :_non_existing_column
end
end
Module V2
class ListableItem < ListableItem
belongs_to :list, class_name: "V2::List"
belongs_to :listable, polymorphic: true
end
end
list = V2::List.find_by(slug: "people")
=> #<V2::List:0x007fd6e9007f18 id: 97, title: "People"....>
list.listable_items
=> [#<V2::ListableItem:0x007fd6e6497ec8 id: 2633, list_id: 97, listable_id: 100, listable_type: "List",....]
list.listable_items.first.listable
=> #<List:0x007fd6e4185868 id: 100,...>
I assume this is happening because of the class definition in the listable_type column of ListableItems. Is there a way to have it refer to the V2 version of the model instead of the one defined in the db column when I call the associated record?
adding
def listable_type
"V2::" + super
end
to the ListableItem class didn't change the class of the called listable.
The solution was to over-write the listable method, not the listable_type method. There may very well be a better way to do this, but this way worked for me.
Module V2
class ListableItem < ListableItem
belongs_to :list, class_name: "V2::List"
belongs_to :listable, polymorphic: true
def listable
("V2::" + listable_type).constantize.find(listable_id)
end
end
end

ActiveRecord get all + associated details

I am trying to retrieve a list of all tasks, where each task has a developer and reviewer. I am able to retrieve the list but it contains developer_id and reviewer_id. How do I retrieve a list containing developer name and retriever name?
class Person < ActiveRecord::Base
end
class Unread_Object < ActiveRecord::Base
belongs_to :person
end
class Developer < Person
has_many :tasks
end
class Reviewer < Person
has_many :tasks
has_many :unread_objects
end
class Task < ActiveRecord::Base
belongs_to :developer
belongs_to :reviewer
has_many :documents
after_save :add_task_to_unread_objects
protected
def add_task_to_unread_objects
Person.find_each do |person|
Unread_Object.create(
:person_id => person.id,
:internal_object_id => self.internal_object_id,
:unread_cause => 'Create')
end
end
end
Things I have tried.
get '/taskslist' do
#Task.includes([:developer, :reviewer]).all.to_json
#Task.joins(:developer,:reviewer).select("tasks.*, people.*").to_json #works somewhat but only shows one name
#Task.includes(:reviewer.name,:developer.name).all.to_json #"undefined method `name' for :reviewer:Symbol"
#Task.find(:all, :include => {:people => :name}).to_json #Couldn't find all Tasks with 'id': (all, {:include=>{:people=>:name}})
end
I hope to get Tasks json with nested json for developer, reviewer and other objects.
This question is follow up of this.
After some searching found as_json(include: <association>)
So this works
Task.includes(:developer,:reviewer).all.as_json(include: [:developer,:reviewer]).to_json
But other alternatives need to be seen.

How to get the association name in rails?

I have the following association code in my user.rb model file
class User < ActiveRecord::Base
has_many :sent_messages, class_name: 'ChatMessage', foreign_key: 'sender_id'
has_many :received_messages, class_name: 'ChatMessage', foreign_key: 'receiver_id'
end
I want a method in the ChatMessage model which should be triggered by the following
current_user.sent_messages
current_user.received_messages
The method should return the name of the association that was called.
Eg:
class ChatMessage < ActiveRecord::Base
after_find :get_association_name
def get_association_name
self.association_name // this should return sent_message or received_message depending on whether current_user.sent_messages or current_user.received_messages was called
end
end
Is there a way to get this association name in rails?
Any help is much appreciated. Thanks
I am not sure, exactly what you are looking for, but
CurrentUser.reflect_on_all_associations(:has_many)
will give an array of all has_many associations.
I haven't used AR associations extensions for cases like this, but you should be able to do:
has_many :sent_messages, class_name: 'ChatMessage', foreign_key: 'sender_id' do
def get_association_name; 'sent_messages'; end
# or, to make this more generic,
# def get_association_name; proxy_association.reflection.name.to_s; end
end
And the method should be accessible from your relation. If you were using Rails 4, you could extract the generic version out into a separate module to extend your associations more succinctly. See http://guides.rubyonrails.org/association_basics.html#association-extensions.
EDIT
Try:
has_many :sent_messages, class_name: 'ChatMessage', foreign_key: 'sender_id' do
def and_set_type
proxy_association.target.each do |msg|
msg.update_attribute(:type, 'sent')
end
scoped
end
end
And then access your sent_messages with current_user.sent_messages.and_set_type.

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