rails 3: database query - sql

I have an Artists model with name:string and other attributes. BUT I have multiple Artist entries under the SAME name.
Is there a way to pull an array of artist objects without any duplicates of name?
I've found ways to do with with only the name attribute but nothing where I can get the entire artist object.
These both do just the name attribute:
#artists = Artist.select('DISTINCT name').all
#artists = Artist.all.collect{ |a| a.name }.uniq

Activerecord group does what you're looking for: Artist.group(:name).all

My rails 3 is not so good but it still has rails 2 syntax.
#artists = Artist.find(:all, :select => 'DISTINCT name')
And then we can get some rails 3 love.

One way is to grab the ids of distinct rows and grab the rest of the data from there:
Artist.where('artists.id IN (SELECT MIN(a.id) FROM artists AS a GROUP BY a.name)').all

Related

How to connect ransacker query to ransack sort search parameter

Problem:
I am using the ransack gem to sort columns in a table. I have 2 models: Campaign and Course. A campaign has many courses, and a course belongs to one campaign. Each course has a number of total_attendees. My Campaigns table has a column for Total Attendees, and I want it to be sortable. So it would sum up the total_attendees field for each course that belongs to a single campaign, and sort based on that sum.
Ex. A campaign has 3 courses, each with 10 attendees. The Total Attendees column on the campaign table would show 30 and it would be sortable against total attendees for all the other campaigns.
I found ransackers:
https://github.com/activerecord-hackery/ransack/wiki/Using-Ransackers
and this SO question: Ransack sort by sum of relation
and from that put together a lot of what is below.
From Model - campaign.rb:
class Campaign < ApplicationRecord
has_many :courses
ransacker :sum_of_total_attendees do
query = "SELECT SUM(r.total_attendees)
FROM campaigns c
LEFT OUTER JOIN courses r
ON r.campaign_id = c.id
GROUP BY c.id"
Arel.sql(query)
end
end
From Model - course.rb:
class Course < ApplicationRecord
belongs_to :campaign, optional: true
end
View:
<th scope="col"><%= sort_link(#q, :sum_of_total_attendees, 'Total Attendees') %></th>
Controller - campaigns_controller.rb:
all_campaigns = Campaign.all
#q = all_campaigns.ransack(params[:q])
#campaigns = #q.result
Errors:
The ransacker query gives me the data I want, but I don't know what to do to get the right information .
Originally, when I clicked on the th link to sort the data, I got this error:
PG::CardinalityViolation: ERROR: more than one row returned by a
subquery used as an expression
I don't know what changed, but now I'm getting this error:
PG::SyntaxError: ERROR: syntax error at or near "SELECT"
LINE 1: SELECT "campaigns".* FROM "campaigns" ORDER BY SELECT SUM(r....
^
: SELECT "campaigns".* FROM "campaigns" ORDER BY SELECT
SUM(r.total_attendees)
FROM campaigns c
LEFT OUTER JOIN courses r
ON r.campaign_id = c.id
GROUP BY c.id ASC
This error seems to say that the ransack search parameter, #q and the ransacker query don't work together. There are two selects in this request, when there should definitely be only one, but the first one is coming from ransack, so I'm not sure how to address it.
How do I get my query to sort correctly with ransack?
Articles I've looked at but did not seem to apply to what I was looking to accomplish with this story:
Ransack Sort By Sum of Relation: This is the one I worked from a lot, but I'm not sure why it works for this user and not for me. They don't show what is changed, if anything, in the controller
Ransack Github Issue For Multiple Params: This doesn't cover the issue of summing table columns.
Rails Ransack Sorting Searching Based On A Definition In The Model: This didn't apply to my need to sort based on summed data.
Three Ways to Bend The Ransack Gem: This looks like what I was doing, but I'm not sure why theirs is working but mine isn't.

How to check if record does NOT exist in Rails 5 with active record querying?

I have an article model and comments model. How do i get a list of articles that does not have any comments using active record?
Model Columns:
Article: body:string (has many comments)
Comment: body:string, article_id:integer (belongs to article)
If you want to get the result using single query and want the result to be an activerecord relation, use:
Article.where('id NOT IN (SELECT DISTINCT(article_id) FROM comments)')
This is same but would be more rails way
Article.where.not('id IN (SELECT DISTINCT(article_id) FROM comments)')
try below code to fetch all articles with no comments:
Article.includes(:comments).where.not(comments: {article_id: nil})
OR
data = []
Article.all.each do |a|
data << a if a.comments.blank?
end
puts data
OR
ids = Comment.all.pluck(:article_id)
data = Article.where.not(id: ids)

Rails ActiveRecord Join Query With conditions

I have following SQL Query:
SELECT campaigns.* , campaign_countries.points, offers.image
FROM campaigns
JOIN campaign_countries ON campaigns.id = campaign_countries.campaign_id
JOIN countries ON campaign_countries.country_id = countries.id
JOIN offers ON campaigns.offer_id = offers.id
WHERE countries.code = 'US'
This works perfectly well. I want its rails active record version some thing like:
Campaign.includes(campaign_countries: :country).where(countries: {code: "US"})
Above code runs more or less correct query (did not try to include offers table), issue is returned result is collection of Campaign objects so obviously it does not include Points
My tables are:
campaigns --HAS_MANY--< campaign_countries --BELONGS_TO--< countries
campaigns --BELONGS_TO--> offers
Any suggestions to write AR version of this SQL? I don't want to use SQL statement in my code.
I some how got this working without SQL but surely its poor man's solution:
in my controller I have:
campaigns = Campaign.includes(campaign_countries: :country).where(countries: {code: country.to_s})
render :json => campaigns.to_json(:country => country)
in campaign model:
def points_for_country country
CampaignCountry.joins(:campaign, :country).where(countries: {code: country}, campaigns: {id: self.id}).first
end
def as_json options={}
json = {
id: id,
cid: cid,
name: name,
offer: offer,
points_details: options[:country] ? points_for_country(options[:country]) : ""
}
end
and in campaign_countries model:
def as_json options={}
json = {
face_value: face_value,
actual_value: actual_value,
points: points
}
end
Why this is not good solution? because it invokes too many queries:
1. It invokes query when first join is performed to get list of campaigns specific to country
2. For each campaign found in first query it will invoke one more query on campaign_countries table to get Points for that campaign and country.
This is bad, Bad and BAD solution. Any suggestions to improve this?
If You have campaign, You can use campaign.campaign_countries to get associated campaign_countries and just get points from them.
> campaign.campaign_countries.map(&:points)
=> [1,2,3,4,5]
Similarly You will be able to get image from offers relation.
EDIT:
Ok, I guess now I know what's going on. You can use joins with select to get object with attached fields from join tables.
cs = Campaign.joins(campaign_countries: :country).joins(:offers).select('campaigns.*, campaign_countries.points, offers.image').where(countries: {code: "US"})
You can than reference additional fields by their name on Campaign object
cs.first.points
cs.first.image
But be sure, that additional column names do not overlap with some primary table fields or object methods.
EDIT 2:
After some more research I came to conclusion that my first version was actually correct for this case. I will use my own console as example.
> u = User.includes(:orders => :cart).where(:carts => { :id => [5168, 5167] }).first
> u.orders.length # no query is performed
=> 2
> u.orders.count # count query is performed
=> 5
So when You use includes with condition on country, in campaign_countries are stored only campaign_countries that fulfill Your condition.
Try this:
Campaign.joins( [{ :campaign_countries => :countries}, :offers]).where('`countries`.`code` = ?', "US")

Rails .joins query over multiple associations

I have this query that works as expected:
#dog.listings.joins(:address_country).merge(Country.where(permalink: 'uk'))
This query gives me the Listings where the country matches 'uk' (Listing has_one :address_country, which is a country from the Country model)
But when I add another association to the chain in between cat and listing (litter), it doesn't work (a litter belongs to a listing, as well as to a cat):
#dog.litters.joins(:listing) & Listing.joins(:address_country) & Country.where(permalink: 'uk')
In this query I'd like it to fetch the Litters where the country (of the associated listing) matches. But it just returns an empty array. The first query works, and I guess I just need to bolt that on to #cat.litters?)
In Rails C, I'm getting this:
d.litters.joins(:listing) & Listing.joins(:address_country).merge(Country.where(permalink: 'uk'))
Litter Load (0.6ms) SELECT "litters".* FROM "litters" INNER JOIN "listings" ON "listings"."id" = "litters"."listing_id" WHERE "litters"."litterable_id" = 11 AND "litters"."litterable_type" = 'Dog'
Listing Load (0.4ms) SELECT "listings".* FROM "listings" INNER JOIN "countries" ON "countries"."id" = "listings"."address_country_id" WHERE "countries"."permalink" = 'uk'
=> []
Any ideas what I'm doing wrong?
One thing that's definitely wrong is to assume that & is the same as merge. It used to be but was removed in fbd917 - now it's just ruby's array intersection and that's not what you want.
I am not sure I follow the database schema from the brief description you gave but just rewriting it to merge is worth the shot:
#dog.litters.joins(:listing).merge(Listing.joins(:address_country)).merge(Country.where(permalink: 'uk'))
and again without actually running the code I would guess that this is equivalent:
#dog.litters.joins(listing: :address_country).where(countries: {permalink: "uk"})

Rails combined ('AND') searches on associated join tables

I cant get rails to return combined ('AND') searches on associated join tables of an Object.
E.g. I have Books that are in Categories. Lets say: Book 1: is in category 5 and 8
But I can't get 'AND' to filter results using the join table? E.g ::->
Class Books
has_and_belongs_to_many :categories, :join_table => "book_categories"
Book.find :all, :conditions => "book_categories.category_id = 5 AND book_categories.category_id = 8", :include => "categories"
... returns nil
(why does it not return all books that are in both 5 & 8 ??)
However: 'OR' does work:
Book.find :all, :conditions => "book_categories.category_id = 5 OR book_categories.category_id = 8"
... returns all books in category 5 and 8
I must be missing something?
The problem is at the SQL level. That condition runs on a link table row, and any individual link table row can never have a category_id of both 5 and 8. You really want separate link table rows to have these IDs.
Try looking into Rails' named_scope, specifically the part that allows filtering with a lambda (so you can take an argument). I've never tried it out myself, but if I had to implement what you're looking for, that's what I'd look in to.