has_many and No method error issue - ruby-on-rails

I have two tables: Stores and products. The store model has a has_many :products and the products has a belongs_to :store
I'm trying to do this though in the rails console:
Store.where(open: true).products.where("created_at <= ?", 1.month.ago)
and get the error (paraphrased a bit): NoMethodError: undefined method products for #<Store

Not a very easy thing to do - products is a method defined on an instance of Store and you are calling it on the relation. I would probably go with:
Product.where(store_id: Store.where(open:true).pluck(:id)).where("created_at <= ?", 1.month.ago)
which would generate two db calls, but also returns a clean and easy to query scope. Another approach would be to use join:
Product.joins(:store).where(store: { open: true }).where("created_at <= ?", 1.month.ago)
This will do the work with one query, but due to the join it won't be that easy to manipulate the resulting scope that easily.

You have it backwards. Since you can have many stores, Rails will not return all the products where open: true.
You need to join and lookup the products where the store is open.
Product.joins(:store).where(store: {open: true}).where("created_at <= ?", 1.month.ago)

Related

Rails finding all items in a joins that comply with multiple conditions

I have Venue, Review and Voucher models. A Venue has_many Reviews, and a Review has_one Voucher.
A voucher has a boolean claimed field.
I'm trying to build a query to select all claimed: true Vouchers that are before a certain date, and to return the length of that query.
So far I've tried a few varieties of the following query:
# #venue is an instance of a Venue
#venue.reviews.joins(:voucher)
.where(vouchers: { claimed: true })
.where("created_at < ?", Date.today.beginning_of_week - 7)
.length
However I just get a vague ActiveRecord::StatementInvalid error, with my .length being highlighted. When I try the above query without the .length in the console, I get a <Review::ActiveRecord_AssociationRelation>
If you want the count of vouchers with claimed true and the right timestamp you can just do:
Voucher.where(vouchers: { claimed: true })
.where("created_at < ?", Date.today.beginning_of_week - 7)
.length
Now if you want that for a specific venue, you still want to start the query with voucher and then join the venue table onto it:
Voucher.joins(review: :venue)
.where(venue_id: #venue.id)
.where(vouchers: { claimed: true })
.where("created_at < ?", Date.today.beginning_of_week - 7)
.length
You might need to play around a bit with .joins(review: :venue) you can find more information here: https://guides.rubyonrails.org/active_record_querying.html#joining-tables

Ruby, query model with aggregate with model

In my rails application, at some point, I query my model simply. I want to query customers order information like how many orders were given by this customer within three months.
Just now, I query the model in that way:
#customer = Customer.all
customer.rb
class Customer < ApplicationRecord
audited
has_many :orders
end
And customer may have orders.
order.rb
class Order < ApplicationRecord
audited
belongs_to :customer
end
What I would like to do is to query customers model and to inject aggregate function result to every customer records.
EDİT
I tried to simulate every solution but couln't achieve.
I have the following query in mysql.
How do I need to code in ruby with activerecord to create that query ?
SELECT
(SELECT
COUNT(*)
FROM
orders o
WHERE
o.customer_id = c.id
AND startTime BETWEEN '2017.12.04' AND '2018.01.04') AS count_last_month,
(SELECT
COUNT(*)
FROM
orders o
WHERE
o.customer_id = c.id
AND startTime BETWEEN '2017.10.04' AND '2018.01.04') AS count_last_three_month,
c.*
FROM
customers c;
How can I achieve that?
Thanks.
Customer.
joins(:orders).
group('customers.id').
where('orders.created_at > DATE_SUB(NOW(), INTERVAL 3 MONTH)')
select('sum(orders.id), customers.*')
As my understanding of you question. I have this solution for you question. Please have a look and try it once. In below query, 'includes' used to solve N+1 problem.
Customer.includes(:orders).where('created_at BETWEEN ? AND ?', Time.now.beginning_of_day, Time.now.beginning_of_day-3.months).group_by{|c|c.orders.count}
If you are looking for particular customer's order count then you can try this one.
#customer.orders.where('created_at BETWEEN ? AND ?', Time.now.beginning_of_day, Time.now.beginning_of_day-3.months).group_by{|c|c.orders.count}

Sort data in ruby on rails

Association is like
class User < ActiveRecord::Base
has_many :products
has_many :ratings
I want to sort products according to user ratings.
Let suppose I want to sort all those product whose ratings is greater than 4.
I cant find any way to do that.
I do something like
User.joins(:ratings).where("ratings.rate > ?", 4).includes(:ratings)
From that I get all user whose ratings is greater than 4 but how join with product and sort them?
User.joins(:ratings).where("ratings.rate > ?", 4).order('ratings DESC')
I am not sure what includes(:ratings) doing at the last.
Should just use something like this and it should probably work:
User.includes(:ratings).where("ratings.rate > ?", 4).order('ratings DESC')
Reference: issue in order clause and limit in active record
User.joins(:ratings).where("ratings.rate > ?", 4).order('ratings.rate')
And if you want to find associated products then this should work:
Product.joins(user: :ratings).where("ratings.rate > ?", 4).order('ratings.rate')

Ruby on Rails 4 count distinct with inner join

I have created a validation rule to limit the number of records a member can create.
class Engine < ActiveRecord::Base
validates :engine_code, presence: true
belongs_to :group
delegate :member, to: :group
validate :engines_within_limit, on: :create
def engines_within_limit
if self.member.engines(:reload).distinct.count(:engine_code) >= self.member.engine_limit
errors.add(:engine, "Exceeded engine limit")
end
end
end
The above doesn't work, specifically this part,
self.member.engines(:reload).distinct.count(:engine_code)
The query it produces is
SELECT "engines".*
FROM "engines"
INNER JOIN "groups"
ON "engines"."group_id" = "groups"."id"
WHERE "groups"."member_id" = $1 [["member_id", 22]]
and returns the count 0 which is wrong
Whereas the following
Engine.distinct.count(:engine_code)
produces the query
SELECT DISTINCT COUNT(DISTINCT "engines"."engine_code")
FROM "engines"
and returns 3 which is correct
What am I doing wrong? It is the same query just with a join?
After doing long chat, we found the below query to work :
self.member
.engines(:reload)
.count("DISTINCT engine_code")
AR:: means ActiveRecord:: below.
The reason for the "wrong" result in the question is that the collection association isn't used correct. A collection association (e.g. has_many) for a record is not a AR::Relation it's a AR::Associations::CollectionProxy. It's a sub class of AR::Relation, and e.g. distinct is overridden.
self.member.engines(:reload).distinct.count(:engine_code) will cause this to happen:
self.member.engines(:reload) is a
AR::Associations::CollectionProxy
.distinct on that will first
fire the db read, then do a .to_a on the result and then doing
"it's own" distinct which is doing a uniq on the array of records
regarding the id of the records.
The result is an array.
.count(:engine_code) this is doing Array#count on the array which is returning
0 since no record in the array equals to the symbol :engine_code.
To get the correct result you should use the relation of the association proxy, .scope:
self.member.engines(:reload).scope.distinct.count(:engine_code)
I think it's a little bit confusing in Rails how collection associations is handled. Many of the "normal" methods for relations works as usual, e.g. this will work without using .scope:
self.member.engines(:reload).where('true').distinct.count(:engine_code)
that is because where isn't overridden by AR::Associations::CollectionProxy.
Perhaps it would be better to always have to use .scope when using the collection as a relation.

Rails HABTM query where condition is based on association attribute

I have User and Album models with HABTM relationship
class Album < ActiveRecord::Base
has_and_belongs_to_many :users
class User < ActiveRecord::Base
has_and_belongs_to_many(:albums)
I'd like to find all the albums that are stored in the database but not associated with a particular user.
So far my code is like this:
Album.all(:order => "albums.created_at DESC", :include => "users", :limit => limit, :conditions => ["users.id != ? AND users.id IS NOT NULL", current_user.id])
but for some reason this is not working. It's returning albums that are associated with current_user.
here take a look at this ouptput from the console.
Check the users id i first fetch.
Then i fetch albums which should not have the users id
I then find one of the listed albums and ask it to return the associated users
one of those associated users is the one from above and shouldnt be there.
Can anyone help with the above?
I usually try to stay away sub-selects but I can't seem to get it to work any other way:
class Album < ActiveRecord::Base
scope :without_user, lambda{|u| where("#{quoted_table_name}.id NOT IN (SELECT `albums_users`.album_id FROM `albums_users` WHERE `albums_users`.user_id = ?)", u.id) }
end
user = User.find(30)
Album.without_user(user)
Assuming you created table albums_users to hold relationship data:
Album.includes(:users).
where(["albums_users.user_id IS NULL OR albums_users.user_id != ?", user_id])
I think it will generate SQL along the lines of
SELECT *
FROM albums LEFT OUTER JOIN albums_users ON albums.id = albums_users.album_id
WHERE albums_users.album_id IS NULL OR albums_users.album_id != #{user_id}
Try:
:conditions => ["users.id <> ? AND users.id IS NOT NULL", current_user.id]
A non-sql solution would be:
Album.all.reject{|album| user.albums.include?(album)}
Obviously, if you have tens of thousands of rows in your database, you might not want to do this.
Might do something like this, too:
Album.where(["id NOT IN (?)", user.albums_ids])
But if your users have a lot (say hundreds) of albums, you shouldn't do this either.
Just throwing in easy solutions if you're out for one of those.

Resources