Joins + group query works but throws error in view - ruby-on-rails

I'm working on an app with applicants and answers. Association:
applicant has_many :answers
I want to return only the applicants whose answers are not archived (in the answers table, archived: false). Currently, this is my query:
def index
#super_admin = current_admin.super_admin
#applicants = Applicant.joins(:answers)
.where(answers: {archived: false})
.group("applicant_id")
end
The query runs fine, but I run into trouble in the view. I want to list the ids of all of the applicants returned by the query and link to that applicant's answers (as found by their answers.applicant_id). I added the .group in the index action because without it, I was getting every answer that wasn't archived (as opposed to every applicant whose answers weren't archived). So for applicant 4, I want to see one link '4' to lead to all of their answers.
Here's the link in the view:
<% #applicants.each do |a| %>
<%= link_to "#{a.applicant_id}", "/admins/view/#{a.applicant_id}" %><br>
<% end %>
It throws this error:
PG::GroupingError: ERROR: column "applicants.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: SELECT "applicants".* FROM "applicants" INNER JOIN "answers"...
^
: SELECT "applicants".* FROM "applicants" INNER JOIN "answers" ON "answers"."applicant_id" = "applicants"."id" WHERE "answers"."archived" = $1 GROUP BY applicant_id
Any thoughts? Thank you!!!

I found a workaround (it's not as sleek as it could be, but it'll get me through a presentation tomorrow):
def index
#super_admin = current_admin.super_admin
#ids = Applicant.all
#applicants = Applicant.joins(:answers)
.where(answers: {archived: false})
.group("applicant_id")
end
view:
<% #ids.each do |a| %>
<%= link_to "#{a.id}", "/admins/view/#{a.id}" %><br>
<% end %>

Related

Rails HABTM query only returning matching associations

i'm trying create a record filter on their associations. So I have creatives and they have multiple talents. I want to have av view that filters the creatives with a specific talent. But still display each creatives multiple talents in the view.
class Creative
has_and_belongs_to_many :talents
end
Creative -> HABTM -> Talents
#creatives = Creative.includes(:talents, user: [:profile_image_attachment])
#creatives = #creatives.where(talents: { id: searched_talent_id })
The problem is that when displaying each creative it only returns the matching talent.
So rendering:
<% #creatives.each do |creative| %>
<% creative.talents.each do |talent| %>
<%= talent.name %>
<% end %>
<% end %>
Only shows the talent matched by the query, not all of them. I.e. the creative has multiple talents.
If I change the code to include a call to .all.
<% #creatives.each do |creative| %>
<% creative.talents.all.each do |talent| %>
<%= talent.name %>
<% end %>
<% end %>
Then I do get all talents BUT the database is hit with query for each creative.
Can I avoid this? I.e. eager loading all talents in creative and not getting limited by the one i search on!?
You can solve this by using a subquery:
#creatives = Creative.includes(:talents, user: [:profile_image_attachment])
.where(
id: Creative.joins(:talents)
.where(talents: { id: searched_talent_id })
)
This creates the following SQL:
SELECT
"creatives".* FROM "creatives"
WHERE
"creatives"."id" IN (
SELECT "creatives"."id"
FROM "creatives"
INNER JOIN "creatives_talents" ON "creatives_talents"."creative_id" = "creatives"."id"
INNER JOIN "talents" ON "talents"."id" = "creatives_talents"."talent_id"
WHERE "talents"."id" = $1
)
LIMIT $2
This only applies the WHERE clause to the subquery instead of the rows fetched by .includes.

ruby - Order a group_by collection desc?

I have a collection of products users have purchased, grouped by their name, so I can count the number of unique products and how many of each has been purchased:
Controller:
#line_items = Spree::LineItem.joins(:order).where(spree_orders: {state: "complete"})
#products = #line_items.group_by(&:name)
View:
<% #products.each do |name, line_items| %>
<%= name %> - <%= line_items.count %><br>
<% end %>
Is there a way to order the .each loop so that it descends by line_items.count?
Thanks
It will perform better getting the correct data directly from the db:
#products = #line_items.group(:name).order("count_all DESC").count
That will give you the names and counts directly, e.g.
# => { "line_1" => 3, "line_2" => 2, "line_3" => 8 }
There's a bit of Rails magic at work here: the SQL generated using group, order and count will look like:
SELECT COUNT(*) AS count_all, name AS name FROM spree_line_items GROUP BY name ORDER BY count_all DESC
That's where count_all comes from: Rails attaches it to the count column automatically.
Then you can plug this directly into your view:
<% #products.each do |name, line_item_count| %>
<%= name %> - <%= line_item_count %><br>
<% end %>
Alternatively, if you're using the instance variable elsewhere, here's a simple Ruby solution:
#products = #line_items.group_by(&:name).sort_by { |_k, line_items| line_items.count }.reverse
This simply uses sort_by to get records ordered by the relevant count, then reverses to get decending order. There's a good benchmark on doing this here.
Hope that helps - let me know how you get on / if you've any questions.

Rails error message 'The asset "" is not present in the asset pipeline'

I have 2 models, Product and Cart
Here are their columns
Product (name, image, intro_image_1, intro_image_2, ...etc)
Cart(user_id, product_id, product_amounts, ...etc)
In CartController, I join these table with below code
#cart_product = Cart.left_outer_joins(:product).select('carts.*, products.name, products.description, products.image, products.price, products.remain_amount').where(cart_type: 'UNCONFIRMED')
#carts = #cart_product.all
And in View (cart/index.html.erb), when I call #carts image by
<% #carts.each_with_index do |item, index| %>
<%= image_tag(item.image, :class => 'img-responsive')%>
<% end %>
It shows the error message like this
But if I call Product directly, it can show image without error.
Can someone give me advices to solve this problem?
Thanks

Rails Single Results from Multiple Tables

Using Rails 4.2
I have two models, suppliers and clients. Both models contain a name (string) and email (string). They do not have any relationship between them.
I would like to generate a list of all the names and emails from both suppliers and clients. In this list I would also like to know if the partner is a supplier or client.
Controller
#suppliers = Supplier.all
#clients = Client.all
#all_partners = (#suppliers + #clients).sort { |x, y| x.name <=> y.name }
View
<% #all_partners.each do |partner| %>
<%= partner.name %>, <%= partner.email %>, <%= partner.type %>
<!-- I need some way to know if the partner type is a supplier or client -->
<% end %>
How can I put in which type of partner it is? Is there a way to do this with one single AR call or query? This is basically how to use an SQL Union statement in Rails.
You could get the class name of the object I believe <%= partner.class.model_name.human %>
Thanks for the help all.
I ended up using the same controller as in the question, with some additional information in the view.
View
<% #all_partners.each do |partner| %>
<%= partner.name %>, <%= partner.email %>, <%= partner.try(:client_type) %>, <%= partner.class.model_name.human %>
<% end %>
Union in ActiveRecord works only within a single model. You could use union for two different tables using raw SQL, something like this:
Supplier.connection.execute("(SELECT id, ..., 'suppliers' as table FROM suppliers WHERE...) UNION (SELECT id,... 'clients' as table FROM clientsWHERE...)")
but the result would be of type PG::Result.
So the best way, unfortunately, is to use two ActiveRecord queries.
OR if clients and suppliers have similar fields, you could put them in one table
class Partner < ActiveRecord::Base
default_scope where(is_supplier: true)
scope :clients, -> { where(is_supplier: false) }
end
so Partner.all will output only suppliers, Partner.unscoped - all partners

Select multiple values from associated model rails

Assuming I have this association
User have_many posts
Post belongs_to user
User Post
----------------
id id
name title
user_id
How to list only post title and username with includes/joins ?
(list of posts [title - username])
#posts = Post.includes(:user).select('........')
don't offer this
#posts = Post.all.each {|p| p.user.username}
__________________UP_____________________
It worked for joining 2 tables.
What if I want to use it for more complex example?
check out my prev question optimize sql query rails
#Humza's answer partly worked.
it might be something like this
#posts = Post.joins(:user, :category).paginate(:page => params[:page]).order("created_at DESC")
but It doesn't display posts that don't have category
I also need to display gravatar but I think I can just use user.email as usr_email and use gravatar_for (post.usr_email) but I'll have to customize gravatar helper for this.
posts_controller.rb
def index
#posts = Post.includes(:user).includes(:comments).paginate(:page => params[:page]).order("created_at DESC")
end
index.html.erb
<%= render #posts %>
_post.html.erb
<%= gravatar_for post.user, size:20 %>
<%= link_to "#{post.title}", post_path(post) %>
<%= time_ago_in_words(post.created_at) %>
<%= post.comments.count %>
<%= post.category.name if post.category %>
Take a look at pluck.
Post.joins(:user).pluck(:title, :name)
Note that it works in this case because there's no ambiguity regarding the name column, you might want to specify the table explicitly (pluck(:title, "users.name")).
includes is used in case of eager-loading. You need joins in this case.
posts = Post.joins(:user).select("posts.title AS title, users.name AS username")
You can access the values then in the following way:
post = posts.first
post.title # will give the title of the post
post.username # will give the name of the user that this post belongs to
If you can pluck multiple columns, then the following might be more useful to you:
posts = Post.joins(:user).pluck("posts.title", "users.name")
The result will be a 2D array, with each element being an array of the form [post_title, post_username]
Post.joins(:user, :category)
but It doesn't display posts that don't have category
That's because joins uses INNER JOIN to join the tables together. If you want to everything from Post even though the particular record doesn't have its counterpart in the other table, you need to use LEFT JOIN. Unfortunately ActiveRecord doesn't have a nice way of generating it and you will need to do that manually:
Post.joins("LEFT OUTER JOIN categories ON categories.post_id = posts.id")...
See A Visual Explanation of SQL Joins for more information.
You can call array methods on a scope so:
Post.includes(:user).map { |p| [p.title, p.user.name] }
will get the posts with included user and map each post to a tuple of the post title and the user name.
That may not entirely answer your question as I think you might want to restrict the results of the query to just the required fields in which case, I think you can add a .select('title', 'users.name') to the query. (Not in a position to test at the moment)

Resources