Rails group by year problem - ruby-on-rails

I have a Post model which includes a date_published. I want to display links on the index page to view all the posts of a certain year (a bit like where blogs have months you can view by)
The only way I can think of returning the years is by iterating over all of the posts and returning a unique array of the years that posts fall in. But this seems like a long way round. Does anyone have a better solution to this? Maybe it would be easier just creating new table so a Post belongs_to :year
Any suggestions?

You don't have to iterate over all of them in your Ruby code. You can use the :group option with count:
>> Post.count(:all, :group => "Year(date_published)")
=> [["2005", 5], ["2006", 231], ["2007", 810], ["2009", 3]]
And then build the links from that.

I'd use one of these:
a find_by_sql to get the distinct years, using whatever year-revealing functionality is available in your database. Would probably resolve against an index, which I'd expect you'd have;
or
Post.minimum(:date_published).year..Post.maximum(:date_published).year
I like the second one a lot, even though it doesn't account for the possibility that a year may have no posts!

Use an SQL search of the date time field (in my example it is updated_at)
years = ActiveRecord::Base.connection.select_all("SELECT DISTINCT DATE_FORMAT(updated_at, '%Y') AS updated_at FROM posts ORDER BY updated_at DESC")
This will return all the year values in the form
[{"updated_at"=>"2009"}, {"updated_at"=>"2008"}, {"updated_at"=>"1979"}, {"updated_at"=>"1922"}]
years.each { |y| puts y['updated_at'] }
2009
2008
1979
1924

Related

Group by decade with counts

I have a table of Album's that has a date column named release_date.
I want to get a list of all the decades along with the number of albums released in that decade.
So, the output might be something like:
2010 - 11
2000 - 4
1990 - 19
1940 - 2
Ruby 2.3.1 w/ Rails 5 on Postgres 9.6, FWIW.
This is essentially a followup question to a previous one I had: Group by month+year with counts
Which may help with the solution...I'm just not sure how to do the grouping by decade.
Using Ruby for processing db data is inefficient in all senses.
I would suggest doing it on the database level:
Album.group("(DATE_PART('year', release_date)::int / 10) * 10").count
What happens here, is basically you take a year part of the release_date, cast it to integer, take it's decade and count albums for this group.
Say, we have a release_date of "2016-11-13T08:30:03+02:00":
2016 / 10 * 10
#=> 2010
Yes, this is pretty similar to your earlier question. In this case, instead of creating month/year combinations and using the combinations as your grouping criteria, you need a method that returns the decade base year from the album year.
Since you have a pattern developing, think about writing the code so it can be reused.
def album_decades
Album.all.map { |album| album.release_date.year / 10 * 10 }
end
def count_each(array)
array.each_with_object(Hash.new(0)) { |element, counts| counts[element] += 1 }
end
Now you can call count_each(album_decades) for the result you want. See if you can write a method album_months_and_years that will produce the result you want from your earlier question by calling count_each(album_months_and_years).
There are more than one possible solution to your problem, but I would try:
Add a new column to the Album table, called decade. You can use a migration for this porpoise.
Create a callback (its like a trigger, but in the programmer side) that set the decade value before saving the Album in the DB.
Finally you can use this useful query to group the Albums by decade. In your case would be Album.group(:decade).count wich would give you a hash with the numbers of Albums by decade.
...
Profit ?
Jokes aside, the callback should be something like:
class Album < ActiveRecord::Base
# some code ...
before_save :set_decade # this is the 'callback'
# ...
private
def set_decade
self.decade = self.release_date.year / 10
end
Then, if you use the step 3, it would return something like:
# => { '195' => 7, '200' => 12 }
I did not test the answer, so try it out and tell me how it went.

Get first entry from an associated table Ruby on Rails

I have a one to many relationship: User has many Payments. I am trying to find a query that gets the first payment of each user(using created_at from the payments table).
I have found a similar question with an SQL response, but I have no idea how to write it with Active Record.
how do I query sql for a latest record date for each user
Quoting the answer:
select t.username, t.date, t.value
from MyTable t
inner join (
select username, max(date) as MaxDate
from MyTable
group by username
) tm on t.username = tm.username and t.date = tm.MaxDate
For me, it would be min instead of max.
Thank you :)
Try this one for POSTGRES
Payment.select("DISTINCT ON(user_id) *").order("user_id, created_at ASC")
And For SQL
Payment.group(:user_id).having('created_at = MAX(created_at)')
If I'm going to answer the question above with: (I don't based on given raw SQL)
User has many Payments. I am trying to find a query that gets the first payment of each user(using created_at from the payments table).
Let say:
# Assumed to have a Single User, as reference
user = User.first
# Now, get first payment (from Payment model)
user.payments.last
# .last since it will always get the first created row by created_at.
If I fully understand what you're trying to do. I'm don't know why you need max or min date?
What about this?
If you want first payment of each user
dates = Payment.group(:user_id).minimum(:created_at).values
payments = Payment.where(created_at: dates)
From payment you can find user too.
I think you have username as foreign key, you can change accordingly. :)
Let me know if you face any issue, as I tested it works.
I know this answer is not the best, but it will work even or transactions with milliseconds difference, as rails saves date(created_at and updated_at) with ms level.
I am sorry for not replying to everything, but after multiple tests, this is the quickest answer (in run time) I came with:
Payment.where(:id => Payment.group(:user_id).pluck(:id))
I am saying it might not be the quickest way because I am using a sub query. I am getting the unique values and getting the ID's:
Payment.group(:user_id).pluck(:id)
Then I am matching those ID's.
The downside of this is that it won't work reversed, for getting the last payment.
There was also a possibility to use group_by and map but, since map is coming from ruby, it is taking much more time.
I'm not sure but try this :
In your controller :
def Page
#payments = Payment.first
end
in your html.erb :
<% #payments.each do |payment| %>
<p> <%= payment.amount %> </p>
Hope this help !
Record.association.order(:created_at).first

How to update thousands of records

I have to update an age column based on the value in a date of birth column. There are thousands of records to update.
How do I do this using rails?
Is this the right way to do it?
User.update_all(:age => some_method);
def some_method
age = Date.today.year - dob.year
end
Yes, update_all is the right method but no, you can't do it like this. Your some_method will only get called once to set up a database call (I assume you're persisting to a database). You'll then get an error because dob won't be recognised in the scope of the User class.
You'll need to translate your date logic to SQL functions.
Something like (for mysql):
User.update_all("age = year(now()) -
year(dob) -
(DATE_FORMAT(now(), '%m%d') < DATE_FORMAT(dob, '%m%d'))")
(NB. the date_format stuff is so that you get the right age for people who's birthdays are later in the year than the current date - see this question for more details)
The other option is to use one of the batches functionality in rails.
User.where(some_condition).find_in_batches do |group_of_users|
# some logic
# e.g. group_of_users.update_all(:age => some_logic)
end
This would lock your db for less time. Note that you should pretty much always update with a condition in mind. I can't think of many cases you would want to update an entire table every time something happens.
There are a few options checkout the rails docs or the api.
your query is right.
There are many way to update record in a batch/lot.
But, I think that your query is best. Because it is rails query that will support every condition for all database.
for updating more than one attributes
Model.update_all(:column1 => value1, :column2 => value2, ........)
or
you can use :
Model.update_all("column1 = value1, column2 = value2, ........")

How can I query all records that don't have a specific association without loading them all into memory?

If I want a list of all the shops that are open on Sunday, I do
Shop.includes(:opening_times).where("opening_times.day =?", 'Sunday')
Is there any way to get a list of all the shops that are closed on Sundays? That is, all the shops that are not associated with a record where the day column is 'Sunday'?
I asked this question here and accepted an answer. However, now my database is getting too big to solve this by loading all the open shops into memory first. Is there a way to do this without first getting an array of all the open shops and passing that array back to the database?
There could be a more Railsy way, but with a single query (and subquery):
Shop.where("shop_id NOT IN (select opening_times.shop_id from opening_times where opening_times.day = 'Sunday')")
Or, based on the linked question, you could improve by using pluck:
shop_ids = Shop.includes(:opening_times).where("opening_times.day = ?", 'Sunday').pluck(:id)
shops = Shop.where("id NOT IN(?)", shop_ids)
The current method you're using (map(&:id)) is instantiating objects for every row, whereas pluck(:id) will perform a select id query.
A faster way without map would be to pluck the id's instead.
open = Shop.includes(:opening_times).where(opening_times: { day: 'Sunday' }).pluck(:id)
closed = Shop.where.not(id: open)
I'm not sure I understand what you're asking, but I'll have a go.
When I need to query for objects that don't have an association, I normally created a counter cache for the # of associated objects and query that. So, in the end, your query would look like this:
Shop.include(:opening_times).where("opening_times.sunday_count", 0)

ActiveRecord find and only return selected columns

edit 2
If you stumble across this, check both answers as I'd now use pluck for this
I have a fairly large custom dataset that I'd like to return to be echoe'd out as json. One part is:
l=Location.find(row.id)
tmp[row.id]=l
but I'd like to do something like:
l=Location.find(row.id).select("name, website, city")
tmp[row.id]=l
but this doesn't seem to be working. How would I get this to work?
thx
edit 1
alternatively, is there a way that I can pass an array of only the attributes I want included?
pluck(column_name)
This method is designed to perform select by a single column as direct SQL query Returns Array with values of the specified column name The values has same data type as column.
Examples:
Person.pluck(:id) # SELECT people.id FROM people
Person.uniq.pluck(:role) # SELECT DISTINCT role FROM people
Person.where(:confirmed => true).limit(5).pluck(:id)
see http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-pluck
Its introduced rails 3.2 onwards and accepts only single column. In rails 4, it accepts multiple columns
In Rails 2
l = Location.find(:id => id, :select => "name, website, city", :limit => 1)
...or...
l = Location.find_by_sql(:conditions => ["SELECT name, website, city FROM locations WHERE id = ? LIMIT 1", id])
This reference doc gives you the entire list of options you can use with .find, including how to limit by number, id, or any other arbitrary column/constraint.
In Rails 3 w/ActiveRecord Query Interface
l = Location.where(["id = ?", id]).select("name, website, city").first
Ref: Active Record Query Interface
You can also swap the order of these chained calls, doing .select(...).where(...).first - all these calls do is construct the SQL query and then send it off.
My answer comes quite late because I'm a pretty new developer. This is what you can do:
Location.select(:name, :website, :city).find(row.id)
Btw, this is Rails 4

Resources