rails4 pluck with order and limit - ruby-on-rails

In my sidebar I display the freshly created user profiles. Profile belongs_to user and user has_one_profile. I realized that I only use 3 columns from the profile table so it would be better to use pluck. I also have a link_to user_path(profile.user) in the partial, so somehow I have to tell who the user is. At the moment I'm using includes, but I don't need the whole user table. So I use to many columns both from the user and the profile tables.
How can I optimize this with pluck? I tried a few versions, but always got some error (most of the time profile.user is not defined).
My current code:
def set_sidebar_users
#profiles_sidebar = Profile.order(created_at: :desc).includes(:user).limit(3) if user_signed_in?
end
create_table "profiles", force: :cascade do |t|
t.integer "user_id", null: false
t.string "first_name", null: false
t.string "last_name", null: false
t.string "company", null: false
t.string "job_title", null: false
t.string "phone_number"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
t.string "avatar"
t.string "location"
end

Okay let's explain three different way to accomplish what you are looking for.
First of all there is a difference in includes and joins
Includes just eager load the association with all of the specified columns for associations. It does not allow you to query or select multiple columns from both table. It what joins do . It allow you to query both tables and select columns of your choice.
def set_sidebar_users
#profiles_sidebar = Profile.select("profiles.first_name,profiles.last_name,profiles.id,users.email as user_email,user_id").joins(:user).order("profile.created_at desc").limit(3) if user_signed_in?
end
It will return you the Profiles relation which has all of the columns you provided in select clause. You can get them just like you do for profile object e-g
#profiles_sidebar.first.user_email will give you user email for this profile.
This approach is best if you want to query on multiple tables or wanna select multiple columns from both table.
2.Pluck
def set_sidebar_users
#profiles_sidebar = Profile.order(created_at: :desc).includes(:user).limit(3).pluck("users.email,profiles.first_name") if user_signed_in?
end
Pluck is just used to get columns from multiple associations but it does not allow you to use the power of ActiveRecord. It simply returns you the array of selected columns in same order.
like in the first example you can get the user for profile object with #profiles_sidebar.first.user But with pluck you cannot because it's just a plain array. So that's why your most of the solutions raise error profile.user is not defined
Association with selected columns.
Now this is option three. In first solution you can get multiple columns on both tables and use the power of ActiveRecord but it does not eager load the associations. So it will still cost you N+1 queries if you loop through the association on returned result like #profiles_sidebar.map(&:user)
So if you wanna use includes but want to use selected columns then you should have new association with selected columns and call that association.
e-g
In profile.rb
belongs_to :user_with_selected_column,select: "users.email,users.id"
Now you can include it in above code
def set_sidebar_users
#profiles_sidebar = Profile.order(created_at: :desc).includes(:user_with_selected_column).limit(3) if user_signed_in?
end
Now this will eager load users but will select only email and id of user.
More information can be found on
ActiveRecord includes. Specify included columns
UPDATE
As you asked about the pros for pluck so let's explain it.
As you know pluck returns you the plain array. So it does not instantiate ActiveRecord object it simply returns you the data returned from database.
So pluck is best to use where you don't need ActiveRecord Objects but just to show the returned data in tabular form.
Select returns you the relations so you can further query on it or call the model methods on it's instances.
So if we summaries it we can say
pluck for model values, select for model objects
More informations can be found at http://gavinmiller.io/2013/getting-to-know-pluck-and-select/

Related

How to query on a method value?

Consider this table:
create_table "liquor_lots", force: :cascade do |t|
t.integer "recipe_id"
t.datetime "created_at", precision: 6, null: false
t.integer "counter"
end
And the resulting model
class LiquorLot < ApplicationRecord
def lotcode
"#{recipe_id}#{created_at.strftime("%y")}#{created_at.strftime("%W")}#{created_at.strftime("%u")}"
end
def pallet_lotcode
"#{lotcode}-#{counter}"
end
end
I'd like to do the equivalent of this in SQL:
Select distinct(lotcode) from liquor_lots
I've tried this and it understandably fails because lotcode is not a column on the liquor_lots table. But I've always been advised against adding columns to store data that is derived from data in other columns.
So how do I search for those values?
For context, my lotcode actually consists of many more values concatenated together, I just limited to three in the example for readability.
As far as I know, with basic ActiveRecord you cannot do that.
ActiveRecord would have to know too much about your ruby code.
You could implement a SQL query that concatenates the relevant values by hand (see comment to your question).
Or you can query all objects (or just the relevant values using pluck()) and then work on that with standard Ruby Array/Enumerable methods (in memory). If the application is not performance-critical, happens rarely, and you do not have thousands of the liquor_lots, that would be an okay productivity-tradeoff in my eyes.
Besides storing it in an own column, you could also extract the codes in separate table and make PalletLotcode an entity of its own. LiquorLots would than belong_to a single PalletLotcode which would have_many LiquorLots. But compared to the separate column this is a rather complex operation, but makes sense if other information is to be stored on the Lotcodes.
You can try something like:
LiquorLot.where("recipe_id = :rcp_id AND created_at >= :begin_of_day AND created_at <= :end_of_day", {begin_of_day: calculate_begin_of_day, end_of_day: calculate_end_of_date, rcp_id: id})
calculate_begin_of_day and calculate_end_of_date can be implemented using Date.comercial method and Date.beginning_of_day and Date.end_of_day

Creating a filter for active record in rails

Just started coding in Ruby on Rails and have managed to create the basic CRUD functionality for my app.
I can also list them all.
Now I would like to create a filter for the user to interact with.
Database Schema
create_table "nades", force: :cascade do |t|
t.string "title"
t.integer "grenade"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "map_id"
end
The "grenade" can have a value from 1-4, corresponding to a specific grenade:
[[1,"smoke"], [2,"flash"], [3,"molotov"], [4,"he-grande"]
Now I'm trying to create a filter with 4 buttons in the view. Where you can toggle each nade on/off to show or hide them in the results.
[x]Smoke [ ]Flash [ ]Moltov [x]HE
This should only return the nades where grenade = [1,4]
After some reading it looks like scoped would be nice to use to manage this.
However I'm not sure how to make it work as I want.
Was thinking of doing something like:
scope :by_nade, -> grenade { where(grenade: grenade) if status.grenade? }
However this only allows me to get 1 specific nade type from the database.
Is it posible to send multiple parameters like:
http://localhost:3000/nades/?by_nade=1,2 ??
Or is it a better solution to my problem?
Look into creating a form with an array of options (e.g., here). That will allow you to get multiple values for the specific type of grenade.
Your scope will work with either a single id or an array of ids and using the array of options approach should yield an array that will work. In your example above it would effectively be Nade.by_grenade([1,4]). You might want to guard against the array being empty (assuming that an empty list would be a bad thing).

Rails 4/postgresql - Send data to another randomly chosen rows of another table

I have a Ruby on Rails 4 app and postgresql9.4 as database.
I have a Deal, DealPrize and Prize models.
Prizes have a quantity and a name. For example prize1 has name dvd and quantity 67.
DealPrizes have a column deal_id and prize_id
class Deal
has_many :prizes, dependent: :destroy
has_many :deal_prizes, dependent: :delete_all
end
class Prize
belongs_to :deal, :foreign_key => 'deal_id'
has_many :deal_prizes, dependent: :destroy
end
class DealPrize
belongs_to :deal, :foreign_key => 'deal_id'
belongs_to :prize, :foreign_key => 'prize_id'
end
Database structure:
create_table "deal_prizes", id: :bigserial, force: :cascade do |t|
t.string "name",
t.integer "deal_id"
t.integer "prize_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "prizes", force: :cascade do |t|
t.string "prize_name", limit: 255
t.integer "quantity"
t.integer "deal_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "prizes", ["deal_id"], name: "index_prizes_on_deal_id", using: :btree
create_table "deals", force: :cascade do |t|
t.string "name", limit: 255
t.string "description", limit: 255
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
I'm gonna take an example to explain what I want and so far fail to achieve:
I need to select all the prizes associated with Deal id= 5.
For example I will get as result
prize id=5 => dvd (whose prize.quantity =67)
prize id= 9 => Cd (whose prize.quantity = 32)
then send these prizes (that is to say the 67 DVDs and the 7 CDs) in randomly chosen rows inside the DealPrizes rows where Deal_id = 5 AND prize_id=empty (needed so that if it has already been attributed a prize, it should not be again put another prize inside the same row)
Note: I don't want to find them and write on rows that are consecutive: i need random rows.
then WRITE into these Dealprizes randomly chosen rows. so I'll write on 67 randomly chosen rows prize_id = 5 and on 9 randomly chosen rows prize_id = 9
Note: the number of rows in DealPrize is superior to 2 millions so need for performance is required.
I would rather use raw sql than active record and will try to not use Rand and random functions directly on the whole table like this guy advice not to http://www.adamwaselnuk.com/projects/2015-03-28-here-be-taverns.html
"The problem is these can lead to performance issues on larger tables
because when you say "Hey database order the table randomly and then
give me the first row" the table says "Okay, I am going to go through
every single row and assign it a random number and then limit my
selection to one". Asking for one thing results in a computation on
every single row in the table!"
I'm totally new to Rails so I could only explain/map the different steps needed on Models/deal.rb
# 1. find the first prize where deal_id= seld.id
# and take each of the prizes quantity and id ofr the next steps
def find_prizes_for_this_deal
Prize.find_by deal_id: self.id # here I could only find one, don't know how to find many prizes at once and keep both id and quantity
end
# 2. choose Deal prize's table randomly chosen rows (created for the same
# deal attached to prizes) where deal_id=self.id and prize_id = empty
def DealPrize.random
# trick found here http://www.adamwaselnuk.com/projects/2015-03-28-here-be-taverns.html, in order not to use the poor-performance rand() methodon the whole table
offset(rand(count)).first.where(["deal_id = ?", self_id] && prize_id= nil)
end
#3. send the prizes selected in step 1. into the randomly chosen rows of
# DealPrizes in step 2
def NO IDEA here how to do this at
end
How to make this work ?
There is a lot that needs to be done for this problem. I can help you out with most and point you in the right direction.
The first part has been answered
<pre><code>deal = Deal.find(5)
prizes = deal.prizes
</code></pre>
You can loop through prizes
prizes.each do |prz|
d_id = prz.deal_id
p_id = prz.id
quantity_count = prz.quantity
end
To get the quantity of the first prize
quantity_count = prizes[0].quantity
Or get one prize by id
prz = prizes.where(id: 5)
I suggest creating two new arrays that will store random numbers to use in your queries. Get the last record id from deal_prizes to be your max number.
max = DealPrize.order("id").last.id
Based on the quantity count, loop to populate array with random numbers
first_prize = []
(1..quantity_count).each do |n|
first_prize << rand(max)
end
You can use that array the find or update records
DealPrize.where(id: first_prize).update_all(deal_id: d_id, prize_id: p_id)
Or if your going to hardcode it
"UPDATE deal_prizes SET deal_id = #{d_id}, prize_id = #{p_id} WHERE id IN ( #{first_prize.join(',')} )"
Be mindful of updating records with prize_id not null or zero (however you indicate it). You can run queries till you find exactly 67 records that have prize_id is null.
good_rows = DealPrize.where(id: first_prize).where("prize_id IS NULL")
bad_rows = first_prize - good_rows
and then replace ids in bad_rows by randomly generating new ids and query again. Continue doing that till you find all good rows then update by adding your new set of good rows with the existing one perfect_first_prize = good_rows + new_good_rows
Your question is very complex and confuses me a lot. Let's go step by step.
I need to select all the prizes associated with Deal id= 5.
That's easy.
deal = Deal.find(5)
prizes = deal.prizes

How can I delete duplicates based on two attributes?

My model is really simple:
create_table "stack_items", force: true do |t|
t.integer "stack_id"
t.integer "service_id"
t.text "description"
end
I need to remove duplicate StackItem records that have the same stack_id and service_id. However if one of the dupes has anything in the description field, I have to keep that one, and delete the other duplicate.
StackItem.group(:stack_id, :service_id).order("count_id desc").where("COUNT(*) > 1")
So far I've tried to just grab the duplicates but it's saying I cannot count within a where statement.
ActiveRecord::StatementInvalid: PG::GroupingError: ERROR: aggregate functions are not allowed in WHERE
How can I achieve this using Rails 4 and ActiveRecord? My database is Postgresql.

Sort by date span

Let's say we have the following model.
create_table :meetings do |t|
t.datetime :started_at
t.datetime: ended_at
end
class Meeting < ActiveRecord::base
end
How would I order a meetings_result, so that the longest meeting is the first meeting in the collection and the shortest meeting the last.
Something like
Meeting.order(longest(started_at..ended_at))
Obviously that doesn't work.
How would I achieve this, preferably without using raw SQL?
I don't think you can do it without using raw SQL.
Using Raw SQL:
Meeting.order('(ended_at - start_at) DESC')
(works with PostGreSQL)
No SQL? Two options come to mind. Create an array of hashes and sort it there, or add another column in the db and sort on that.
# How many records in the meetings table? This array of hashes could get huge.
meetings_array = []
Meeting.all.each do |meeting|
meetings_array << {id: meeting.id, started_at: meeting.started_at, ended_at: meeting.ended_at , duration: meeting.ended_at - meeting.started_at }
end
meetings_array.sort_by { |hsh| hsh[:duration] }
Or, create another column:
# Is it worth adding another column?
create_table :meetings do |t|
t.datetime :started_at
t.datetime :ended_at
t.datetime :duration
end
Update this column whenever you have both started_at and ended_at. Then you can:
Meeting.order("duration")

Resources