Joining with named scopes in Ruby - sql

I originally asked this question about acts_as_tree but it seems to be more general so I'm rephrasing it a bit here.
I have a model "category" using acts_as_tree. I often find myself needing to join it with other models for various queries, but I don't know how to use the provided scopes such as children, ancestors, siblings, descendants to create the conditions that I need.
Concrete example:
class Category < ActiveRecord::Base
acts_as_tree
has_many :photo
end
class Photo
belongs_to :category
end
cat = Category.first
How can I query for all the photos of children of cat or all the photos of its siblings?
Seems like I need something like:
Photo.joins(cat.children)
Photo.joins(cat.siblings)
which I know is not a valid code.

Since act_as_tree doesn't really use scopes but methods that return results, you can do it like:
class Photo
belongs_to :category
scope :from_category, -> (category) {
joins(:category).where(category: category)
}
end
cat = Category.first
Photo.from_category(cat.children)
Photo.from_category(cat.siblings)
And that should work.

Related

Returning associations for specific model when there is a polymorphic association in Rails 3.2

I have a polymorphic association in a Rails 3 app where a User may favorite objects of various classes.
class Favorite < ActiveRecord::Base
belongs_to :user
belongs_to :favoriteable, :polymorphic => true
end
class User < ActiveRecord::Base
has_many :favorites
end
class Image < ActiveRecord::Base
has_many :favorites, :as => :favoriteable
end
class Video < ActiveRecord::Base
has_many :favorites, :as => :favoriteable
end
I would like to be able return a list of just a User's favorite_images for example.
user.favorite_images #returns a list of the images associated with the user via :favoritable
I'm guessing there is a straightforward way of doing this but I haven't been able to figure it out. Let me know if you need anymore info.
Thanks!
===edit====
I know that I could retrieve what I am looking for via
favorite_images = user.favorites.collect{|f| if f.favoriteable_type=='Image'; f.favoriteable; end}
I could define an instance method for the User class and put that inside. I was wondering if there is a way to do it as some sort of has_many association. Really just because going forward it would be easier to have all that in one place.
When you created the table for Favorite you created a couple of columns favoriteable_id and favoriteable_type and you can use this information to restrict your query.
If you do user.favorites you will get all of the favorites and to restrict them to say just the images then you can do user.favorites.where(favoriteable_type: 'image') but that just gives you the favorite records and it sounds like you want the actual images. To get those you can do it by then mapping and pulling the favoriteable out. You'll likely want to include it in the query though so you don't hit the database so much. I would also make this a method on User.
def favorite_images
favorites.includes(:favoriteable).where(favoriteable_type: 'Image').map(&:favoriteable)
end

Associated records via custom query in Rails

I have two models that are connected via a has_many/belongs_to association:
Class Project < ActiveRecord::Base
has_many :tasks
end
Class Tasks < ActiveRecord::Base
belongs_to :project
end
Each of the tasks are tagged with a HABTM relationship:
Class Tasks < ActiveRecord::Base
belongs_to :project
has_and_belongs_to_many :tags
end
I am trying to get a list of projects based on a tag id. I can get a list of projects that have tasks with a specific tag by using a class method on my Project model:
def by_tag(tag_id)
Project.joins(:tasks => :tags).where(:tags => {:id = tag_id})
end
Ideally, I'm looking to be able to list all the projects and their associated tasks for a given tag in my view. I could normally get a list of tasks belonging to a given project by using project.tasks if I used a typical find with project like Project.find(1).
However, when I try project.tasks on results found using my new class method Project.by_tag(1), I get a "NoMethodError: Undefined Method 'tasks'" error.
I looked into Named Scopes to get the Project by Tag results but it seems like people are moving away from that approach in favor of class methods. Is that true?
On your project model you need to add it to the class not the instance. Also note that this raises the self object to the class so you can eliminate "Project." unless you want to be explicit.
class << self
def by_tag(tag_id)
joins(:tasks => :tags).where(:tags => {:id = tag_id})
end
end
There is always debate over what is the best method. I myself prefer whatever gets the job done quicker. I like scopes personally but to each his own.

Rails Form with has_many through - Which model to choose?

I have the following models:
Student has_many :subjects, :through => :classes
Subject has_many :students, :through => :classes
Class belongs_to :subject
belongs_to :student
The model class has an extra attribute (among the foreign keys to subject and students table) called level.
Basically I want to be able to have a form that will let the student to choose a subject and relate that subject to its record. So, I have this:
ClassesController < ApplicationController
def new
#list_of_subjects = Subject.all
# What should I do here?
end
My question is: How should I create the object for the form? From which model it should be, subject, student or class? I want to be able to create a record in the class table that would relate the student and the subject that the student has chosen, but I don't know if I am doing it wrong.
Thanks
I didn't think you could create a model called Class since it's a keyword, but that's neither here nor there...
First I think your controller and view should be using Student since it's the student that's selecting these things. Next, I think what you want to do is to add accepts_nested_attributes_for :class in your Student model which allows you to create an instance of the Class connector model from Student.
What you're trying to do sounds a little like something I tried to do. I have my full code there.
Using nested attributes to easily select associations in a form
I later refined it a bit in this question too to make the code less hideous:
Rails: How do I prepend or insert an association with build?
I know it's late, but I hope that helps.

ActiveRecord - find parents

I want to find things that have some children. So given:
class Foo < ActiveRecord::Base
has_many :bars
has_many :bazes
scope :is_a_parent ...what goes here?...
I want to get Foos that have any bars or any bazes. Certainly all things are possible with raw SQL, exists (select 1 from bars ...) or exists (select 1 from bazes ...), but yuk.
Surely there's some way to use any? in conjunction with arel or method? Some other way to do it without resorting to SQL?
Does this help you? That prior answer is looking for the inverse of yours (Foos that have no bars nor bazes) but you should be able to invert the logic.
class Foo < ActiveRecord::Base
has_many :bars
has_many :bazes
scope :is_a_parent, (joins(:bars) or joins(:bazes)).uniq
would give you all foos having a bar or a baze
Btw, usually functions starting with "is_", should end with "?", and should return only true/false.

rails and namespaced models issue

Using rails 3/3.1 I want to store invoices with their items (and later more associations like payments, etc…).
So in a first approach I set up the models like this:
class Invoice < ActiveRecord::Base
has_many :invoice_items
end
class InvoiceItem < ActiveRecord::Base
belongs_to :invoice
end
And the routes likes this:
resources :invoices do
resources :invoice_items
end
I chose InvoiceItem instead of Item because I already have a model named Item and I somehow want to namespace the model to invoices. But this name has the huge disadvantage that one has to use invoice.invoice_items instead of a intuitive invoice.items. Also the generated url helpers look real ugly, for example "new_invoice_invoice_item_path(invoice)" (notice the double invoice_invoice).
So I changed to namespaced models like this:
class Invoice < ActiveRecord::Base
has_many :items, :class_name => "Invoice::Item"
end
class Invoice::Item < ActiveRecord::Base
belongs_to :invoice
end
And the routes likes this:
resources :invoices do
resources :items, :module => "invoice"
end
Now the assocation is named nicely and also the url helpers look pretty. But I can't use dynamic urls (ex. [:new, invoice, :item]) anymore, because the controller is set to "invoice_item" instead of "invoice/item".
I wonder how other people solve this problem and what I'm doing wrong. Or is this simply a bug in rails 3.0.7/ 3.1.rc?
EDIT:
Sorry, I seems I didn't correctly express my concern. My model Item is not related to Invoice::Item. Order::Item is also not related to Item nor Invoice::Item. An Invoice::Item can only belong to one invoice. An Order::Item can only belong to an Order. I need to namespace - but why doesn't rails properly support namespacing out of the box? Or what am I doing wrong with namespacing?
Corin
If an order item and an invoice item are not the same object in the real world then I would name them differently rather than trying to namespace, for example OrderItem and InvoiceItem - this will keep things clearer as your codebase grows and avoid the need to make sure you use the right namespace everywhere you reference an Item.