Rails 4.2: has_many using condition from belongs_to - ruby-on-rails

I have three ActiveRecords.
Order, which has_many OrderItems. Also has a field named status.
OrderItem, which belongs_to an Order and a Script
Script, which has_many OrderItems
I want to set up the has_many in the Script model so that only OrderItems that belong to an Order which has a status of 'paid' are given.
I'm new to rails, and this isn't my codebase so I'm struggling a bit. I've seen other answers use the :include and :conditions keywords, but neither are valid in rails 4.2
Any help would be great.

I would try keeping things a bit pure by ensuring that the models do not get too involved in defining how other models behave.
Start by defining a scope for Order to define what you mean by "paid".
def self.has_been_paid
where(status: "Paid")
end
Then you can associate OrderItems with a paid Order.
belongs_to :paid_order, -> {merge(Order.has_been_paid)}, :class_name => "Order", :foreign_key => :order_id
... and a scope ...
def self.for_paid_orders
joins(:paid_order)
end
Then for the Script:
has_many :order_items, -> {merge(OrderItem.for_paid_orders)}
So now each model takes care of its own business.

Here's how to setup a has_many :order_items that only include items that belong to a paid order:
class Script < ActiveRecord::Base
has_many :order_items, -> {
includes(:order).where(orders: {status: 'paid'})
}
end

Related

Rails - find records without specific type of associated records

I have three models:
class Person < ActiveRecord::Base
has_many :assignments
has_many :projects, through: :assignments
end
class Project < ActiveRecord::Base
has_many :assignments
has_many :people, through: :assignments
end
class Assignment < ActiveRecord::Base
belongs_to :person
belongs_to :project
end
In my assignment model I am using a 'kind' attribute to determine the types of relationships. At the moment assignments come in the following kinds: "Supervisor", "Worker", "Inspector". In the future there will likely be more kinds of assignments.
I am having trouble writing Active Record scopes to find projects which are lacking assignments of a specific type. For example at the moment I am using the following to find projects which have workers assigned.
class Project < ActiveRecord::Base
def self.assigned_workers
joins(:assignments).where(assignments: { :kind => 'worker' })
end
end
How would I write the opposite of the above example, and find projects which have no assigned workers?
I think you want a left join for this. Unfortunately Rails doesn't make this easy. But you can still use SQL fragments:
joins("LEFT JOIN assignments ON assignments.project_id = projects.id AND assignments.kind = 'worker'").where(assignments: {id: nil})
So SQL does a LEFT JOIN on assignments where the assignment kind is 'worker', but only selects rows for which there was no matching assignment.
If you're using Rails 4.0+, You could negate the one above:
joins(:assignments).where.not(assignments: { :kind => 'worker' })
This would give you all the projects that do not have workers assigned.

How to get sum of invoices across join table in Ruby on Rails?

I have these models in my Rails 4 app:
class Invoice < ActiveRecord::Base
has_many :allocations
has_many :payments, :through => :allocations
end
class Allocation < ActiveRecord::Base
belongs_to :invoice
belongs_to :payment
end
class Payment < ActiveRecord::Base
has_many :allocations
has_many :invoices, :through => :allocations
end
Obviously, it is possible that one payment belongs to many invoices.
In the Payment model I have this function that sums the total of all the invoices that one specific payment relates to:
def invoice_total_of_siblings
invoice_ids = Allocation.where(:payment_id => id).map(&:invoice_id)
invoices = Invoice.where(:id => invoice_ids)
invoices.to_a.sum(&:total)
end
However, this function feels cumbersome and I wonder how it could be made more concise.
Thanks for any help.
Using your set of associations (payment has many invoices through allocations), you could simply do this:
def invoice_total_of_siblings
invoices.sum(:total)
end
EDIT:
This solution works as-is for database fields, provided that the given set is an ActiveRecord association.
In this speicific case, however, as it is produced from the comments, total is a calculated field. Hence, the given set will not be an ActiveRecord association, but an Array. Then, you will need to map the field in order to sum it. The correct syntax in this case will be:
def invoice_total_of_siblings
invoices.sum(&:total)
end

Rails: Using custom column to look up for has_many association

I have 3 models: User, List, Following
I'm trying to implement a system, where
A user can create many lists (list contains photos, but it's not relevant in this question)
A user can follow lists created by other users
Here's how I'm trying to build this system:
First we have a database table of lists:
lists: id, user_id
And specifying the models like the following:
class User < ActiveRecord::Base
has_many :lists
end
class List < ActiveRecord::Base
belongs_to :user
end
We can do User.first.lists without problem.
Now my challenge comes when trying to create the followership. I'd like a user to be able to find
All the lists he's following
All the lists being followed that are created by him
All the users following his lists (or, equivalently, all "followers")
Here's the database table I'm trying to use to fulfill the above function:
followings: user_id, list_id, list_user_id
In this table definition, user_id specifies who's following the list, list_id specifies the list being followed, and list_user_id specifies the owner of the list being followed. list_user_id is used here to speed up the database lookups, so that we don't have to join lists table with users table.
And now I'm stuck. I tried to change the user model to the following:
class User < ActiveRecord::Base
has_many :lists
has_many :followings
# Works
has_many :following_lists, :through => :followings, :class_name => "List", :source => :list
# Doesn't work
has_many :followed_lists, :through => :followings, :class_name => "List", :source => :list, :conditions => {:list_user_id => self.id}
# Doesn't work
has_many :followers, :through => :followings, :class_name => "User", :source => :user
end
The first goal, "Find all the lists he's following", is done, through has_many :following_lists, without problem. However, it seems it's difficult to get "all lists being followed" and "all followers" of a user.
The problem is that there seems to be no way to specify the key to use for lookup in the followings table. For example, when looking for user A's followers, I'll need to find all rows in followings table where the list_user_id equals A.id, but has_many method doesn't provide an option to do this, nor does the condition :conditions => {:list_user_id => self.id} work (it'll complain undefined method 'id').
So..how would you deal with this situation? Is there a better way to design the tables, or can we actually work out something based on the current table definitions?
Btw, here's how Following model is defined:
class Following < ActiveRecord::Base
attr_accessible :list_id, :list_user_id, :user_id
belongs_to :user
belongs_to :list
belongs_to :list_user, :class_name => "User"
end
You are trying to get these 2 things:
1.) All the lists being followed that are created by him
2.) All the users following his lists (or, equivalently, all "followers")
Both of these are filtering on top of lists owned by the user. Therefore the associations with :through => followings are incorrect. Since that scopes the lists to ones you follow, not ones you own.
One way to do want you want would be like this:
def followed_lists
# This essentially limits your owned lists to ones with an entry in the followings table
lists.join(:followings).all
end
def users_following_owned_lists
lists.followers.all
end
You will need to add the followers association to the List AR.
class List < ActiveRecord::Base
has_many :followings
has_many :followers, :through => :followings, :class_name => "User", :source => :user
end
Note also that list_user_id on followings table is not really needed.

generic categories for my rails models

I am working in a rails app which main points are articles and products.
Someone has implemented categories for articles.
class ArticleCategory < MainSchemaBase
belongs_to :user
has_many :articles, :through => :article_category_articles, :conditions => 'articles.deleted_at IS NULL'
has_many :article_category_articles, :conditions => 'article_category_articles.deleted_at IS NULL'
And I have been asked to do basically the same thing for products.
Of course I want to DRY but products belongs to brand instead of user, and I have many products instead of many articles
The model is almost empty (some named scopes), controller and views also very dependent of the context (article)
Can this be DRY? Or should I just copy the implemntation ?
Make it polymorphic: The best way, i think, is to set up a polymorphic many-to-many relationship using has_many_polymorphs (https://github.com/Nielsomat/has_many_polymorphs) so that a single category could be applied to a product and an article.
class Category
#doesn't have any association fields
has_many_polymorphs :categorizables, :from => [:products, :articles], :through => :categorizations, :dependent => :destroy
end
class Categorization < ActiveRecord::Base
#has fields categorizable_id, categorizable_type, :category_id
belongs_to :categorizable, :polymorphic => true
belongs_to :category
end
class Product < ActiveRecord::Base
#doesn't need anything to set up the association
end
class Article < ActiveRecord::Base
#doesn't need anything to set up the association
end
"Categorizable" is a bit of a mouthful but you won't actually be using it. You'll be saying #product.categories or #category.articles etc.

Setting a type for one side of a many-to-many association with a join model

I set up a public github app (see: https://github.com/greenplastik/testapp to download) to work through a problem I'm having with specifying a type on one side of a many-to-many association between two models, via a join model.
Given Person and Book models and a Book_Person join model, I want to be able to do the following:
#book = Book.first
#book.people # lists people for book
#book.authors # lists author-type people for book
#book.editors # lists editor-type people for book
and
#person = Person.first
#person.books # lists books for people
This app was set up in part using the instructions found through Google. There's a link to those instructions in the README of my testapp.
I tried, as best I could, to remove the inconsistencies and typos. I can't get it to work.
Any help would be appreciated. I've included the sqlite database for easier testing on your end.
I'm sorry i haven't got time to post neither a full solution or a tested one, but this should at least put you on the right track..
The best way to achieve what you're looking for is with Single Table Inheritance used with a polymorphic relationship.
I'd suggest redefining Author and Editor to be subclasses of Person
As in
class Person < ActiveRecord::Base
has_many :book_people
has_many :books, :through => :book_people
end
class Author < Person
end
class Editor < Person
end
class BookPerson < ActiveRecord::Base
belongs_to :person, :polymorphic => true
belongs_to :book
end
class Book < ActiveRecord::Base
has_many :book_people
has_many :people, :through => :book_people
has_many :authors, :through => :book_people, :source => :person, :source_type => "Author"
has_many :editors, :through => :book_people, :source => :person, :source_type => "Editor"
end
However this would be suboptimal if a single person could fill all three roles. There's probably a way around that by using the same STI name for the three of them. But then you'd make it harder to query for all authors.

Resources