Recursive :include in Rails - ruby-on-rails

I have a polymorphic model that can relate to itself:
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
has_many :comments, as: :commentable
end
These relationships work perfectly, except when I'm trying to call the full tree of child/parent comments through an include statement:
Post.find(1).include(:comments)
This only includes the comments directly tied to the post. I could perhaps retrieve a second level with:
Post.find(1).include(comments: :comments)
But what if I wanted to get all comments descending from the post, no matter how deeply nested? Is this possible?

It seems that you want to retrieve an adjacency list. Rails has no immediate support for it, but if you are using postgresql, you can use the "WITH RECURSIVE" operator.
That plugin takes care of it : https://github.com/chrisroberts/acts_as_sane_tree
Otherwise, you can pretty easily create your own postgresql function (declare it in a migration), and then use it in your queries. Have a look at : http://wiki.postgresql.org/wiki/Getting_list_of_all_children_from_adjacency_tree
WITH RECURSIVE is not currently implemented in mysql or sqlite3.

Related

How to access has_many through relationship possible in rails?

How can I access my related records?
class Post < ActiveRecord::Base
has_many :post_categories
has_many :categories, through: :post_categories
class Categories < ActiveRecord::Base
has_many :post_categories
has_many :post, through: :post_categories
class PostCategories < ActiveRecord::Base
belongs_to :post
belongs_to :category
PostCategories table has id, posts_id, and categories_id columns.
id | posts_id | categories_id
1. | 2 | 3
2. | 2 | 4
What I want is: to get posts related to a category. like: all Posts where in x category.
Yep, this is an easy one.
one_or_more_categories = # Category.find... or Category.where...
posts = Post.joins(:categories).where(category: one_or_more_categories)
Rails is clever enough to take either a model or a query that would find some data and turn that into an efficient appropriate query, that might be a subquery. Trying things out in the Rails console (bundle exec rails c) is a good way to see the generated SQL and better understand what's going on.
(EDIT: As another answer points out, if you've already retrieved a specific Category instance then you can just reference category.posts and work with that relationship directly, including chaining in .order, .limit and so-on).
Another way to write it 'lower level' would be:
Post.joins(:categories).where(category: {id: one_or_more_category_ids})
...which is in essence what Rails will be doing under the hood when given an ActiveRecord model instance or an ActiveRecord::Relation. If you already knew the e.g. category "name", or some other indexed text column that you could search on, then you'd adjust the above accordingly:
Post.joins(:categories).where(category: {name: name_of_category})
The pattern of joins and where taking a Hash where the join table name is used as a key with values nested under there can be taken as deep as you like (e.g. if categories had-many subcategories) and you can find more about that in Rails Guides or appropriate web searches. The only gotcha is the tortuous singular/plural stuff, which Rails uses to try and make things more "English-y" but sometimes - as in this case - just creates an additional cognitive burden of needing to remember which parts should be singular and which plural.
Not sure if this answers it but in ActiveRecord your Post will have direct access to your Category model and vice versa. So you could identify the category you want the posts from in a variable or an instance variable, and query #specific_category.posts. If you are doing this in your controller, you could even do it in before_action filter. If you are using it in serializers its not much different.
You could also create a scope in your Post model and use either active record or raw SQL to query specific parameters.
You also have an error in your Category model. Has many is always plural so it would be has_many :posts, through: :post_categories
Get the category object and you can directly fetch the related posts. Please see the following
category = Category.find(id)
posts = category.posts
Since you have already configured the has_many_through relation, rails will fetch post records related the category.

Rails 4: Active Record get entries based on belongs_to relation's properties

I've not been able to figure this one out -- perhaps I've missed something in the docs. I want to get all of the articles that are associated with published issues. (That is, article.issue.is_published = true.)
I have two models:
# Article model
class Article < ActiveRecord::Base
belongs_to :issue
...
end
# Issue model
class Issue < ActiveRecord::Base
has_many :articles
...
end
From what I understand, I can run something like this:
Article.includes(:issues)
.where('issue.is_published = true')
.references(:issues)
But I get nothing but funky business on the other end. All of the examples I've found have one-to-many relationships going the other way - I assumed that I've gotten something wrong with the naming convention, but no combination of "issue" and "issues" seems to work any better.
What am I missing here?
You'll want to do a joins instead of includes here. This translates to a SQL inner join. Article.joins(:issue).where(issues: {is_published: true})

Query that joins child model results item erroneously shown multiple times

I have the following models, each a related child of the previous one (I excluded other model methods and declarations for brevity):
class Course < ActiveRecord::Base
has_many :questions
scope :most_answered, joins(:questions).order('questions.answers_count DESC') #this is the query causing issues
end
class Question < ActiveRecord::Base
belongs_to :course, :counter_cache => true
has_many: :answers
end
class Answer < ActiveRecord::Base
belongs_to :question, :counter_cache => true
end
Right now I only have one Course populated (so when I run in console Course.all.count, I get 1). The first Course currently has three questions populated, but when I run Course.most_answered.count (most_answered is my scope method written in Course as seen above), I get 3 as the result in console, which is incorrect. I have tried various iterations of the query, as well as consulting the Rails guide on queries, but can't seem to figure out what Im doing wrong. Thanks in advance.
From what I can gather, your most_answered scope is attempting to order by the sum of questions.answer_count.
As it is there is no sum, and since there are three answers for the first course, your join on to that table will produce three results.
What you will need to do is something like the following:
scope :most_answered, joins(:questions).order('questions.answers_count DESC')
.select("courses.id, courses.name, ..., SUM(questions.answers_count) as answers_count")
.group("courses.id, courses.name, ...")
.order("answers_count DESC")
You'll need to explicitely specify the courses fields you want to select so that you can use them in the group by clause.
Edit:
Both places where I mention courses.id, courses.name, ... (in the select and the group), you'll need to replace this with the actual columns you want to select. Since this is a scope it would be best to select all fields in the courses table, but you will need to specify them individually.

How to I make my custom mySQL query to in rails 3?

Im trying to display recently added comments from tattoos a user has posted. So If I posted a tattoo, and then user_b posted "hey I like your tattoo" then Im trying to get just the comment.
First of all Im using the acts_as_commentable_with_threading gem which doesnt create a foreign key for the table im trying to join. So my controller cant look for tattoo_id, it has to look for commentable_id
In the controller I would have to call the Comment model and then pass some SQL stuff into it but apparently I have no clue how to pass custom SQL queries into ruby because even tho my query string works in terminal, I get all sorts of nonsense when trying to use it in rails.
Im basically trying to do this:
SELECT comments.id FROM comments,tattoos WHERE commentable_id = tattoos.id AND
tattoos.member_id = #{current_user}
where #{current_user} will be the current_user passed in.
You don't have to jump through so many hoops to accomplish this. acts_as_commentable assigns a polymorphic association, so you should set it up like this:
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :commentable, :polymorphic => true
end
class Tattoo < ActiveRecord::Base
has_many :comments, :as => :commentable
end
class User
has_many comments
end
Then you can access the association as usual:
Tattoo.where(:member_id => current_user).first.comments
See http://railscasts.com/episodes/154-polymorphic-association for a general tutorial on how polymorphic associations work. It just so happens that this railscast uses exactly :commentable as the polymorphic example, so you should be able to follow along directly if you want.
I think Ben's approach is best but for future reference if you do come across something more complicated you can always use sql for example:
Comment.find_by_sql("SELECT comments.* FROM comments,tattoos WHERE commentable_id = tattoos.id AND tattoos.member_id = ?", current_user)

Rails Advanced Sorting

I have three models, basically:
class Vendor
has_many :items
end
class Item
has_many :sale_items
belongs_to :vendor
end
class SaleItem
belongs_to :item
end
Essentially, each sale_item points to a specific item (but has an associated quantity and sale price which might be different from the item's base price, hence the separate model), and each item is made by a specific vendor.
I'd like to sort all sale_items by vendor name, but this means going through the associated item, because that's where the association is.
My first attempt was to change SaleItem to the following:
class SaleItem
belongs_to :item
has_one :vendor, :through => :item
end
Which allows me to look for SaleItem.first.vendor, but doesn't allow me to do something like:
SaleItem.joins(:vendor).all(:order => "vendors.name")
Is there an easy way to figure out these complex associations and sorting? It would be especially great if there were a plugin that could take care of these sort of things. I have a lot of different types of tables to add sorting to in this application, and I feel like this will be a big chunk of the figuring-out work.
This could definitely be done with a more complex SQL query (possibly using find_by_sql), but you could also do it pretty easily in Ruby. Try something like the following:
SaleItem.find(:all, :include => { :items => :vendors }).sort do |first,second|
first.vendor.name <=> second.vendor.name
end
I haven't tested it, so it might not work exactly like this, but it should give you a good idea of one possible solution.
Edit: Found an old blog post that seems to have solved this issue. Hopefully this still works in the lastest version of ActiveRecord.
source: http://matthewman.net/2007/01/04/eager-loading-objects-in-a-rails-has_many-through-association/
Second Edit: Straight from the Rails documentation
To include a deep hierarchy of associations, use a hash:
for post in Post.find(:all, :include => [ :author, { :comments => { :author => :gravatar } } ])
That’ll grab not only all the comments but all their authors and gravatar pictures. You can mix and match symbols, arrays and hashes in any combination to describe the associations you want to load.
There's your explanation.
Do you really need your sale_items sorted by the database, or could you wait until it is presented and do the sorting client side via javascript (there are some great sorting libraries out there) - that would save server CPU and (backend) code complexity.

Resources