I'm getting 5 samples (randoms) from a List, using:
list = List.all.sample(5)
list.each do |list|
list.statuses.create(:user_id => #user.id)
end
in my Status model I have:
validates :list_id, uniqueness: { scope: :user_id }
that validates that the list_id is unique for the user.
However what happens is that it it gives 5 samples, and if one of those are present in statuses it doesn't save.
What I'd like to accomplish though is that it goes and finds a new list, so I always end up with 5 samples, instead of say 3-4 because it got 2 random duplicates.
What I am trying to do is record which list id that gets sent for the specific user, so that when they do it again the same list id doesn't get sent. But a new random 5.
This should do it:
specific_user = User.find(params[:id])
lists = List.order('random()') # This random() is a PostgreSQL function!
lists = lists.where('id NOT IN (?)', specific_user.lists.pluck(:id).presence || -1) # exclude the list of the specific_user
lists = lists.limit(5) # limit to only 5 records
lists.each do |list|
list.statuses.create(:user_id => specific_user.id)
end
First filter out the duplicates, then sample from the remainder. So if User have a has_many :lists you could do something like:
(List.all - #user.lists).sample(5)
The has_many could be through :statuses.
Related
I'm using an ActiveRecord::Base.transaction to make a whole bunch of calls pertaining to a grouping of objects that must update/create simultaneously or not at all. One method in this transaction is supposed to use where to find and delete all Trades that match certain parameters.
class Trade < ActiveRecord::Base
include Discard::Model
belongs_to :trade_requester, class_name: "User"
belongs_to :wanted_share, class_name: "Share"
validates :trade_requester, :wanted_share, presence: true
validates :amount, presence: true
def new_wanted_total
wanted_share.amount - amount
end
def update_wanted_share_amount(new_wanted_total)
wanted_share.update_attribute(:amount, new_wanted_total)
end
def delete_extraneous_wanted_trades(wanted_share)
self.class.where("wanted_share_id = ? AND amount > ? AND approved_at = ? AND discarded_at = ?", wanted_share.id, new_wanted_total, nil, nil).delete_all
end
def accept
ActiveRecord::Base.transaction do
delete_extraneous_wanted_trades(wanted_share)
update_wanted_share_amount(new_wanted_total) if new_wanted_total >= 0
Share.create(user_id: self.trade_requester.id, item_id: self.wanted_share.item.id, amount: self.amount, active: false)
self.touch(:approved_at)
end
end
end
When I accept and check the output in my terminal, one line I get says this:
SQL (0.3ms) DELETE FROM "trades" WHERE (wanted_share_id = 8 AND amount > 25 AND approved_at = NULL AND discarded_at = NULL).
I am passing the correct information to the method, and the rest of the terminal output shows that the related records have been updated with the appropriate attributes (one Share set to amount:25 and another Share created with amount:50). But then I check my database, and it says that there is still one Trade for amount: 60. This record exceeds the available total, which is now 50 (it was previously 75), and should have been deleted. But according to the terminal output, it was ignored. Why did this record go untouched?
approved_at = ? AND discarded_at = ? in the where clause should be approved_at IS NULL AND discarded_at IS NULL
User.rb
has_many :votes
Event.rb
has_many :votes
Vote.rb
belongs_to :user
belongs_to :event
I am trying to render all the Events that the current_user has not voted on.
Consider this situation:
There are 100 events Event.includes(:votes).count ==> 100
The User has voted on 5 events current_user.votes.count ==> 5
There are 75 events with at least one vote from other users
20 of the events have not received votes from any users
The result I'm looking for should render the 95 events that have not been voted on by the current_user, including events that have not been voted on by any user.
This query gets all the events that NO users have voted on:
Event.includes(:votes).where(:votes => {:id => nil}).references(:votes).count ==> 20
What query can I use to get all the events that have been voted on by users excluding those that have been voted on by current user (should return 75 in the example above)? I tried the below query but it returned 80 (it included events voted on by the current_user):
Event.includes(:votes).where.not(:votes => {:id => current_user}).references(:votes).count ==> 80
What query can I use to get events with votes, excluding ones that current user voted for
Can I combine the two queries into one?
Hacky but fun answer
class Event
has_many :votes
has_many :user_votes,
->() { where({user_id: Thread.current[:user]&.id}.compact) },
class_name: 'Vote'
def self.using_user(user)
old_user, Thread.current[:user] = Thread.current[:user], user
yeild
ensure
Thread.current[:user] = old_user
end
end
Event.using_user(current_user) do
Event.includes(:user_votes).where("votes.id IS NULL")
end
Original answer:
Event.
joins(
"LEFT OUTER JOIN votes ON votes.event_id = events.id AND " +
"votes.user_id = #{user.id}"
).
where("votes.id IS NULL")
Can I combine the two queries into one?
Use merge if you want to combine using AND:
results = frecords.merge(lrecords)
If you want to combine using OR, use or (only available in ActiveRecord 5+):
results = frecords.or(lrecords)
What query can I use to get events with votes, excluding ones that current user voted for.
events_current_user_voted_on = current_user.votes.pluck(:event_id)
events_all_users_voted_on = Vote.pluck(:event_id).uniq
events_only_others_voted_on = events_all_users_voted_on - events_current_user_voted_on
# 75 events with at least one vote from other users
Event.where(id: events_only_others_voted_on)
Answer for your first question:
I have modified your tried query a little bit so try this
Event.includes(:votes).where.not(:votes => {:user_id => current_user}).references(:votes).count
notice the where condition. You need to check user_id column of votes
or you can try string syntax like:
Event.includes(:votes).where.not("votes.user_id" => current_user).references(:votes).count
about your second question, I am not sure I understand your requirement. If you need to filter events with at least 1 vote and from other user only then just add another to where statement .
Event.includes(:votes).where.not("votes.user_id" => current_user, "votes.user_id" => nil).references(:votes).count
And BTW, I think you have a error in vote.rb you have specified a association to itself instead of association to Event i.e belongs_to :event
Let's say I have a User model. User has 2 has_many associations, that is User has many pencils and has many cars. Cars and Pencils table has same attribute, :date, and separate such as :speed(car) and :length(pencil). I want to join a user's pencils and cars on their common attribute, :date, so that I have an array/relation [:date, :speed, :length]. How do I achieve that, I tried joins and merge but they were no use.
I'd definitely recommend getting this into a query rather than a loop, for efficiency's sake. I think this will work:
Car.joins(:user => :pencils).where("pencils.date = cars.date")
And if you want to reduce it to the array immediately:
Car.joins(:user => :pencils).where("pencils.date = cars.date").pluck("cars.date", "cars.speed", "pencils.length")
If you need to include matches where date is nil, you might need to add:
Car.joins(:user => :pencils).where("(pencils.date = cars.date) OR (pencils.date IS NULL AND cars.date IS NULL)")
Many more efficient options exist, but here is one possible approach:
class User < ActiveRecord::Base
def get_merged_array
dates = (cars.map(&:date) & pencils.map(&:date))
results = []
dates.each do |date|
cars.where(date: date).each do |car|
pencils.where(date: date).each do |pencil|
results << [date, car.speed, pencil.length]
end
end
end
results
end
end
In my Ruby on Rails 3 app controller, I am trying to make an instance variable array to use in my edit view.
The User table has a user_id and reseller_id.
The Certificate table has a user_id.
I need to get the reseller_id(s) from the User table that have the user_id(s) in both User table and Certificate table.
Here is my User model:
class User < ActiveRecord::Base
attr_accessible :email, :name, :password, :password_confirmation, :remember_token, :reseller_id, :validate_code, :validate_url, :validated, :admin, :avatar
belongs_to :reseller
has_one :certificate
end
Here is my Certificate model:
class Certificate < ActiveRecord::Base
attr_accessible :attend, :pass, :user_id
validates :user_id, presence: true
end
Here is my controller, this seems to only store the last user_id in the Certificate table.
##train should be reseller.id(s) of all users in Certification table.
#certs = Certificate.all
#certs.each do |user|
#id = []
#id << user.user_id
#id.each do |id|
if User.find(id)
#train = []
#train << User.find(id).reseller_id
end
end
end
Thank you
1) Correct version of your code
#certs = Certificate.all
#reseller_id = [] # 1
#certs.each do |user|
id = user.user_id # 2
if u = User.find(id)
#reseller_id << u.reseller_id # 3
end
end
2) Rails way
Something like this
#reseller_id = User.joins(:certificates).select('users.reseller_id').map { |u| u['reseller_id']}
PS
Don't keep this code in controller please :-)
Well, first of all, you should not nest your ids' each block inside of the each block for certificates. You should build your ids array, then loop over it later. The reason you are only getting the last user_id, is because "#id" will only ever have a single element in it as your code is currently written. You will also run into the same problem with your "#train" array. Because you are declaring the array inside the iterator, it is getting re-created (with nothing in it) on every iteration. Using your existing code, this should work:
#certs = Certificate.all
#ids = []
#certs.each do |user|
#ids << user.user_id
end
#train = []
#ids.each do |id|
if User.find(id)
#train << User.find(id).reseller_id
end
end
A more Rubyish and concise way would be the following:
cert_user_ids = Certificate.all.map(&:user_id)
reseller_ids = cert_user_ids.map { |id| User.find(id).reseller_id rescue nil }.compact
Map is an enumerable method that returns an array of equal size to the first array. On each iteration, whatever the code inside the block returns "replaces" that element in the new array that is returned. In other words, it maps the values of one array to a new array of equal size. The first map function gets the user_ids of all certificates (using &:user_id is a shortcut for Certificate.all.map { |cert| cert.user_id } ) The second map function returns the "reseller_id" of the user, if a user is found. It returns nil if no user is found with that id. Finally, compact removes all nil values from the newly mapped array of reseller_ids, leaving just the reseller ids.
If you want to do this in the most efficient and railsy way possible, minimizing database calls and allowing the database to do most of the heavy lifting, you will likely want to use a join:
reseller_ids = User.joins(:certificates).all.map(&:reseller_id)
This grabs all users for which a certificate with that user's id exists. Then it utilizes map again to map the returned users to a new array that just contains user.reseller_id.
Ruby tends to be slower at this type of filtering than RDBM systems (like mysql), so it is best to delegate as much work as possible to the database.
(Note that this join will compare user.id to certificate.user_id by default. So, if your 'primary key' in your users table is named 'user_id', then this will not work. In order to get it to work, you should either use the standard "id" as the primary key, or you will need to specify that 'user_id' is your primary key in the User model)
I fetched all users from the database based on city name.
Here is my code:
#othertask = User.find(:all, :conditions => { :city => params[:city]})
#othertask.each do |o|
#other_tasks = Micropost.where(:user_id => o.id).all
end
My problem is when loop gets completed, #other_task holds only last record value.
Is it possible to append all ids record in one variable?
You should be using a join for something like this, rather than looping and making N additional queries, one for each user. As you now have it, your code is first getting all users with a given city attribute value, then for each user you are again querying the DB to get a micropost (Micropost.where(:user_id => o.id)). That is extremely inefficient.
You are searching for all microposts which have a user whose city is params[:city], correct? Then there is no need to first find all users, instead query the microposts table directly:
#posts = Micropost.joins(:user).where('users.city' => params[:city])
This will find you all posts whose user has a city attribute which equals params[:city].
p.s. I would strongly recommend reading the Ruby on Rails guide on ActiveRecord associations for more details on how to use associations effectively.
you can do it by following way
#othertask = User.find(:all, :conditions => { :city => params[:city]})
#other_tasks = Array.new
#othertask.each do |o|
#other_tasks << Micropost.where(:user_id => o.id).all
end
Here is the updated code:
#othertask = User.find_all_by_city(params[:city])
#other_tasks = Array.new
#othertask.each do |o|
#other_tasks << Micropost.find_all_by_user_id(o.id)
end
You are only getting the last record because of using '=' operator, instead you need to use '<<' operator in ruby which will append the incoming records in to the array specified.
:)
Try:
User model:
has_many :microposts
Micropost model:
belongs_to :user
Query
#Microposts = Micropost.joins(:user).where('users.city' => params[:city])