How to write rspec for a model method in rails? - ruby-on-rails

Hi i have the following method in a my EnrolledAccount models for which i want to write rpec. My question is how can i create the association between Item and EnrolledAccount in rspec.
def delete_account
items = self.items
item_array = items.blank? ? [] : items.collect {|i| i.id }
ItemShippingDetail.destroy_all(["item_id in (?)", item_array]) unless item_array.blank?
ItemPaymentDetail.destroy_all(["item_id in (?)", item_array]) unless item_array.blank?
Item.delete_all(["enrolled_account_id = ?", self.id])
self.delete
end

Generally you would use factory_girl to create a set of related objects in the database, against which you can test.
But, from your code I get the impression that your relations are not set up correctly. If you set up your relations, you can instruct rails what to do when deleting an item automatically.
E.g.
class EnrolledAccount
has_many :items, :dependent => :destroy
has_many :item_shipping_details, :through => :items
has_many :item_payment_details, :through => :items
end
class Item
has_many :item_shipping_details, :dependent => :destroy
has_many :item_payment_details, :dependent => :destroy
end
If your models are defined like that, the deletion will be automatically taken care of.
So instead of your delete_account you can just write something like:
account = EnrolledAccount.find(params[:id])
account.destroy
[EDIT] Using a gem like shoulda or remarkable, writing the spec is then also very easy:
describe EnrolledAccount do
it { should have_many :items }
it { should have_many :item_shipping_details }
end
Hope this helps.

Related

How to query a collection through multiple associations?

I need to collect objects that are all connected through multiple layers of associations, and I don't know who to do so.
I need to get a collection of CustomText based on a string query param.
Basically I need to do a query that will pull a collection of CustomText by name:
#searched_content = params[:search].downcase
#query = CustomText.where("lower(name) like ?", "%#{#searched_content}%")
but then also filters the #query to only search LineItems that have been a part of an approved order. I am using Spree, where Spree::Order has_many Spree::LineItem. Basically, doing something like this (doesn't work at all, but hopefully you'll be able to see what I'm trying to do):
#query = Spree::LineItem.joins(:order).where(spree_orders: {state: "complete"}).joins(:custom_texts).where("lower(name) like ?", "%#{#searched_content}%"))
Models:
class CustomText < ActiveRecord::Base
belongs_to :custom_set, :inverse_of => :custom_texts
end
class CustomSet < ActiveRecord::Base
belongs_to :spree_line_item, :class_name => Spree::LineItem, :foreign_key => :spree_line_item_id
has_may :custom_texts, :dependent => :destroy, :inverse_of => :custom_set
end
class LineItem < ActiveRecord::Base
has_many :custom_texts, :through => :custom_sets
has_many :custom_sets, :dependent => :destroy, :foreign_key => :spree_line_item_id
end
Any help would be very appreciated.
Well, this was just a simple syntax error with one too many end parenthesis at the end...stupid spelling error.
#query = Spree::LineItem.joins(:order).where(spree_orders: {state: "complete"}).joins(:custom_texts).where("lower(name) like ?", "%#{#searched_content}%")

Multiple entries in a :has_many through association

I need some help with a rails development that I'm working on, using rails 3.
This app was given to me a few months ago just after it's inception and I have since become rather fond of Ruby.
I have a set of Projects that can have resources assigned through a teams table.
A team record has a start date and a end date(i.e. when a resource was assigned and de-assigned from the project).
If a user has been assigned and deassigned from a project and at a later date they are to be assigned back onto the project,
instead of over writting the end date, I want to create a new entry in the Teams table, to be able to keep a track of the dates that a resource was assigned to a certain project.
So my question is, is it possible to have multiple entries in a :has_many through association?
Here's my associations:
class Resource < ActiveRecord::Base
has_many :teams
has_many :projects, :through => :teams
end
class Project < ActiveRecord::Base
has_many :teams
has_many :resources, :through => :teams
end
class Team < ActiveRecord::Base
belongs_to :project
belongs_to :resource
end
I also have the following function in Project.rb:
after_save :update_team_and_job
private
def update_team_and_job
# self.member_ids is the selected resource ids for a project
if self.member_ids.blank?
self.teams.each do |team|
unless team.deassociated
team.deassociated = Week.current.id + 1
team.save
end
end
else
self.teams.each do |team|
#assigning/re-assigning a resource
if self.member_ids.include?(team.resource_id.to_s)
if team.deassociated != nil
team.deassociated = nil
team.save
end
else
#de-assigning a resource
if team.deassociated == nil
team.deassociated = Week.current.id + 1
team.save
end
end
end
y = self.member_ids - self.resource_ids
self.resource_ids = self.resource_ids.concat(y)
self.member_ids = nil
end
end
end
Sure, you can have multiple associations. has_many takes a :uniq option, which you can set to false, and as the documentation notes, it is particularly useful for :through rel'ns.
Your code is finding an existing team and setting deassociated though, rather than adding a new Team (which would be better named TeamMembership I think)
I think you want to just do something like this:
add an assoc for active memberships (but in this one use uniq: => true:
has_many :teams
has_many :resources, :through => :teams, :uniq => false
has_many :active_resources,
:through => :teams,
:class_name => 'Resource',
:conditions => {:deassociated => nil},
:uniq => true
when adding, add to the active_resources if it doesn't exist, and "deassociate" any teams that have been removed:
member_ids.each do |id|
resource = Resource.find(id) #you'll probably want to optimize with an include or pre-fetch
active_resources << resource # let :uniq => true handle uniquing for us
end
teams.each do |team|
team.deassociate! unless member_ids.include?(team.resource.id) # encapsulate whatever the deassociate logic is into a method
end
much less code, and much more idiomatic. Also the code now more explicitly reflects the business modelling
caveat: i did not write a test app for this, code may be missing a detail or two

Rails: Overriding ActiveRecord association method

Is there a way to override one of the methods provided by an ActiveRecord association?
Say for example I have the following typical polymorphic has_many :through association:
class Story < ActiveRecord::Base
has_many :taggings, :as => :taggable
has_many :tags, :through => :taggings, :order => :name
end
class Tag < ActiveRecord::Base
has_many :taggings, :dependent => :destroy
has_many :stories, :through => :taggings, :source => :taggable, :source_type => "Story"
end
As you probably know this adds a whole slew of associated methods to the Story model like tags, tags<<, tags=, tags.empty?, etc.
How do I go about overriding one of these methods? Specifically the tags<< method. It's pretty easy to override a normal class methods but I can't seem to find any information on how to override association methods. Doing something like
def tags<< *new_tags
#do stuff
end
produces a syntax error when it's called so it's obviously not that simple.
You can use block with has_many to extend your association with methods. See comment "Use a block to extend your associations" here.
Overriding existing methods also works, don't know whether it is a good idea however.
has_many :tags, :through => :taggings, :order => :name do
def << (value)
"overriden" #your code here
super value
end
end
If you want to access the model itself in Rails 3.2 you should use proxy_association.owner
Example:
class Author < ActiveRecord::Base
has_many :books do
def << (book)
proxy_association.owner.add_book(book)
end
end
def add_book (book)
# do your thing here.
end
end
See documentation
I think you wanted def tags.<<(*new_tags) for the signature, which should work, or the following which is equivalent and a bit cleaner if you need to override multiple methods.
class << tags
def <<(*new_tags)
# rawr!
end
end
You would have to define the tags method to return an object which has a << method.
You could do it like this, but I really wouldn't recommend it. You'd be much better off just adding a method to your model that does what you want than trying to replace something ActiveRecord uses.
This essentially runs the default tags method adds a << method to the resulting object and returns that object. This may be a bit resource intensive because it creates a new method every time you run it
def tags_with_append
collection = tags_without_append
def collection.<< (*arguments)
...
end
collection
end
# defines the method 'tags' by aliasing 'tags_with_append'
alias_method_chain :tags, :append
The method I use is to extend the association. You can see the way I handle 'quantity' attributes here: https://gist.github.com/1399762
It basically allows you to just do
has_many : tags, :through => : taggings, extend => QuantityAssociation
Without knowing exactly what your hoping to achieve by overriding the methods its difficult to know if you could do the same.
This may not be helpful in your case but could be useful for others looking into this.
Association Callbacks:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
Example from the docs:
class Project
has_and_belongs_to_many :developers, :after_add => :evaluate_velocity
def evaluate_velocity(developer)
...
end
end
Also see Association Extensions:
class Account < ActiveRecord::Base
has_many :people do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by_first_name_and_last_name(first_name, last_name)
end
end
end
person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
person.first_name # => "David"
person.last_name # => "Heinemeier Hansson"
Rails guides documents about overriding the added methods directly.
OP's issue with overriding << probably is the only exception to this, for which follow the top answer. But it wouldn't work for has_one's = assignment method or getter methods.

Thinking Sphinx, Rails, :has_many :through => ... has

Model: Product
has-many product-categories, :through => ...
Question 1) How do I index a many to many association with thinking sphinx
Must I use has?
Questions 2) How is this searched in the controller
ex. Product.search params[:search-params], :conditions => {some_conditions}
I've not tried this on a has_many :through so shoot me down in flames if you have, but I don't see why this wouldn't work for you too, (I'm using it on a has_many association) you basically use your association in the index definition. Then searches against that model will also search the child records.
class Product < ActiveRecord::Base
has_many :product_categories
define_index do
indexes a_product_field_to_index
indexes product_categories.name, :as => :categories
end
end
In the controller:
#products = Product.search(params[:query] || '')
#params[:query] is simply the search string, I can't remember if you need to sanitize this, I would always assume you do unless you find out otherwise
In the view:
#products.each do |p|
p.categories.each do |cat|
end
end
If you don't already have it I would highly recommend the thinking-sphinx book available on peepcode: https://peepcode.com/products/thinking-sphinx-pdf
Hope that helps.

how to access rails join model attributes when using has_many :through

I have a data model something like this:
# columns include collection_item_id, collection_id, item_id, position, etc
class CollectionItem < ActiveRecord::Base
self.primary_key = 'collection_item_id'
belongs_to :collection
belongs_to :item
end
class Item < ActiveRecord::Base
has_many :collection_items
has_many :collections, :through => :collection_items, :source => :collection
end
class Collection < ActiveRecord::Base
has_many :collection_items, :order => :position
has_many :items, :through => :collection_items, :source => :item, :order => :position
end
An Item can appear in multiple collections and also more than once in the same collection at different positions.
I'm trying to create a helper method that creates a menu containing every item in every collection. I want to use the collection_item_id to keep track of the currently selected item between requests, but I can't access any attributes of the join model via the Item class.
def helper_method( collection_id )
colls = Collection.find :all
colls.each do |coll|
coll.items.each do |item|
# !!! FAILS HERE ( undefined method `collection_item_id' )
do_something_with( item.collection_item_id )
end
end
end
I tried this as well but it also fails with ( undefined method `collection_item' )
do_something_with( item.collection_item.collection_item_id )
Edit: thanks to serioys sam for pointing out that the above is obviously wrong
I have also tried to access other attributes in the join model, like this:
do_something_with( item.position )
and:
do_something_with( item.collection_item.position )
Edit: thanks to serioys sam for pointing out that the above is obviously wrong
but they also fail.
Can anyone advise me how to proceed with this?
Edit: -------------------->
I found from online documentation that using has_and_belongs_to_many will attach the join table attributes to the retreived items, but apparently it is deprecated. I haven't tried it yet.
Currently I am working on amending my Collection model like this:
class Collection < ActiveRecord::Base
has_many :collection_items, :order => :position, :include => :item
...
end
and changing the helper to use coll.collection_items instead of coll.items
Edit: -------------------->
I've changed my helper to work as above and it works fine - (thankyou sam)
It's made a mess of my code - because of other factors not detailed here - but nothing that an hour or two of re-factoring wont sort out.
In your example you have defined in Item model relationship as has_many for collection_items and collections the generated association method is collection_items and collections respectively both of them returns an array so the way you are trying to access here is wrong. this is primarily case of mant to many relationship. just check this Asscociation Documentation for further reference.
do_something_with( item.collection_item_id )
This fails because item does not have a collection_item_id member.
do_something_with( item.collection_item.collection_item_id )
This fails because item does not have a collection_item member.
Remember that the relation between item and collection_items is a has_many. So item has collection_items, not just a single item. Also, each collection has a list of collection items. What you want to do is probably this:
colls = Collection.find :all
colls.each do |coll|
coll.collection_items.each do |collection_item|
do_something_with( collection_item.id )
end
end
A couple of other pieces of advice:
Have you read the documentation for has_many :through in the Rails Guides? It is pretty good.
You shouldn't need the :source parameters in the has_many declarations, since you have named your models and associations in a sensible way.
I found from online documentation that using has_and_belongs_to_many will attach the join table attributes to the retreived items, but apparently it is deprecated. I haven't tried it yet.
I recommend you stick with has_many :through, because has_and_belongs_to_many is more confusing and doesn't offer any real benefits.
I was able to get this working for one of my models:
class Group < ActiveRecord::Base
has_many :users, :through => :memberships, :source => :user do
def with_join
proxy_target.map do |user|
proxy_owner = proxy_owner()
user.metaclass.send(:define_method, :membership) do
memberships.detect {|_| _.group == proxy_owner}
end
user
end
end
end
end
In your case, something like this should work (haven't tested):
class Collection < ActiveRecord::Base
has_many :collection_items, :order => :position
has_many :items, :through => :collection_items, :source => :item, :order => :position do
def with_join
proxy_target.map do |items|
proxy_owner = proxy_owner()
item.metaclass.send(:define_method, :join) do
collection_items.detect {|_| _.collection == proxy_owner}
end
item
end
end
end
end
Now you should be able to access the CollectionItem from an Item as long as you access your items like this (items.with_join):
def helper_method( collection_id )
colls = Collection.find :all
colls.each do |coll|
coll.items.with_join.each do |item|
do_something_with( item.join.collection_item_id )
end
end
end
Here is a more general solution that you can use to add this behavior to any has_many :through association:
http://github.com/TylerRick/has_many_through_with_join_model
class Collection < ActiveRecord::Base
has_many :collection_items, :order => :position
has_many :items, :through => :collection_items, :source => :item, :order => :position, :extend => WithJoinModel
end

Resources