Rails - showing grouped records - ruby-on-rails

I have some records:
Type Name
2 USA
2 USA
3 Canada
4 Mexico
1 Canada
2 Mexico
3 USA
I just want to show the list of unique types: 1,2,3,4 (no double, triple records).
I was trying to do something like that in my view:
<% #Orders.group(:type).each do |order| %>
... showing in the table
<% end %>
But I got following error:
undefined method `group' for #
Do I need to add some method in the controller/model?
Why - if .each method works ok in the view, group doesn't?

It's a little unclear what exactly you are trying to return/display, but I think you might want to look at #group_by (http://api.rubyonrails.org/classes/Enumerable.html#method-i-group_by).
Alternatively, look at some of the aggregate methods on this page: http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-calculate

Usually an instance variable is lowercase, that is #orders not #Orders. The error is telling you that it cannot find a method group for whatever it is that's in #Orders, so that's what you would focus on.
Once that's sorted out, you may want to use either the ActiveRecord distinct operator, or, if your records are in ruby, use .uniq on a collection (i.e. array).

Related

ActiveRecord group with alias

First question ever on here, and pretty new to coding full apps/Rails.
I was creating a method to get the counts for titles by author, and noticed that if the author is cased differently, it would count as different authors. I wanted to place some sort of validation/check to disregard the casing and count it together. I don't care about the casing of the book titles in this particular case.
So I have table like this:
Author Book Title Year Condition
William Shakespeare Hamlet 1599 Poor
Stephen King The Shining 1977 New
Edgar Allen Poe The Raven 1845 Good
JK Rowling Harry Potter and the Sorcerer's Stone 2001 New
edgar allen poe The Tell-Tale Heart 1843 Good
JK Rowling Fantastic Beasts and Where to Find Them 2001 New
I want to output this:
Author Count
William Shakespeare 1
Stephen King 1
Edgar Allen Poe 2
JK Rowling 2
My method was originally something like this:
def self.book_counts
distinct_counts = []
Book.group(:author).count.each do |count|
distinct_counts << count
end
distinct_counts
end
To ignore casing, I referenced this page and came up with these, which didn't end up working out, unfortunately:
1) With this one I get "undefined method lower":
Book.group(lower('author')).count.each do |count|
distinct_counts << count
2) This runs, but with the select method in general, I get a bunch of ActiveRecord results/Record id: nil. I am using Rails 6 and it additionally notes "DEPRECATION WARNING: Dangerous query method (method whose arguments are used as raw SQL) called with non-attribute argument(s) ... Non-attribute arguments will be disallowed in Rails 6.1. This method should not be called with user-provided values, such as request parameters or model attributes. Known-safe values can be passed by wrapping them in Arel.sql(). (called from irb_binding at (irb):579)":
Book.select("lower(author) as dc_auth, count(*) as book_count").group("dc_auth").order("book_count desc")
3) I even tried to test a different, simplified function to see if it'd work, but I got "ActiveRecord::StatementInvalid (PG::GroupingError: ERROR: column "books.author" must appear in the GROUP BY clause or be used in an aggregate function)":
Book.pluck('lower(author) as dc_auth, count(*) as book_count')
4) I've tried various other ways, with additional different errors, e.g. "undefined local variable or method 'dc_auth'", "undefined method 'group' did you mean group_by?", and "wrong number of arguments (given 1, expected 0)" (with group_by), etc.
This query works exactly how I want it to in postgresql. The syntax actually populates in the terminal when I run #2, but as mentioned, unfortunately due to ActiveRecord doesn't output properly in Rails.
SELECT lower(author) as dc_auth, count(*) as book_count FROM books GROUP BY dc_auth;
Is there even a way to run what I want through Rails??
Maybe you can try
Book.group("LOWER(author)").count
You can execute your query using ActiveRecord. And I will suggest to go with SQL block
book_count_query = <<-SQL
SELECT lower(author) as dc_auth, count(*) as book_count
FROM books
GROUP BY dc_auth;
SQL
1- result = ActiveRecord::Base.connection.execute(book_count_query)
or
2- result = ActiveRecord::Base.connection.exec_query(book_count_query)
What difference between line 1 and line 2?
exec_query it returns an ActiveRecords::Result object which has handy methods like .columns and .rows to access headers and values.
The array of hashes from .execute can be troublesome to deal with and gave me redundant results when I ran with a SUM GROUP BY clause.
If you need read more about this topic
example of exec_query in api.rubyonrails
active_record_querying in Rails Documentation
This Resource have example for query and output .
Why you store authors in the same table with books. The better solution is to add a separate table for authors and add a foreign key to author_id to books table. With counter_cache you can easily count the number of books for each author.
Here is a guide with books and authors examples https://guides.rubyonrails.org/association_basics.html

active record query order where first

I am using c9 cloud ruby on rails and my Rails version is 5.0.0.1
I want to compose an ActiveRecord query to select the restaurant with the best rating which has not been visited yesterday. When I applied all where, order and first in the query, it throws an error. However, if I don't use where or order, it works fine.
This works:
restaurant1= Restaurant.order('rating').last!
#suggested_restaurants=restaurant1.name
This gives strange results:
restaurant1=Restaurant.order('rating').where{last_visit<Date.yesterday}
#suggested_restaurants=restaurant1
Results: #<ActiveRecord::QueryMethods::WhereChain:0x000000046af7f8>
This triggered error:
restaurant1=Restaurant.order('rating').where{last_visit<Date.yesterday}.last
#suggested_restaurants=restaurant1
Error: undefined method 'last' for #ActiveRecord::QueryMethods::WhereChain:0x00000004587a10>
I can get around this issue by using find_by_sql but I'd like to know why and how to use where in Rails 5. Thanks a lot!
try this:
restaurants = Restaurant.where('last_visit < ?', Date.yesterday).order({ rating: :asc })
this gives you a collection of restaurants matching your constraints passed in where method, the result of which is chained to order method that orders the collection in ascending order of the values in rating column of restaurants table. use :desc if you want to order in descending order.
Then, you can retrive last object with
restaurant1 = restaurants.last
This will give you the Relation for restaurants that have not been visited today, then you can call .last on that.
restaurant1 = Restaurant.order('rating').where('last_visit<?', Date.yesterday).last
#suggested_restaurants=restaurant1

How to sum columns and group by date column in Rails

I have a model with following columns
Charges Model
Date
fee
discount
Data
1/1/15, 1, 1
1/1/15, 2, 1
2/2/15, 3, 3
I have a few named scopes like this_year
I want to do something like Charges.this_year.summed_up
How do I make a named scope for this.
The returned response then should be:
1/1/15, 3, 2
2/2/15, 3, 3
Assuming you have a model with a date field(eg. published_at) and 2 integer fields(eg. fee, discount). You can use "group" method to run GROUP BY on published_at. Then just use sum method if you want only sum of one fields. If you want more than one field, you have to run a select with SQL SUMs inside, to get multiple column sums. Here is an example.
Charge..group(published_at)
.select("published_at, SUM(fee) AS sum_fee, SUM(discount) AS sum_discount")
.order("published_at")
Note: Summarized fields won't show up in rails console return value prompt. But they are there for you to use.
Depending upon what end result you want, you may want to look at .group(:attribute) rather than .group_by:
Charge.group(:date).each do |charge|
charge.where('date = ?', charge.date).sum(:fee)
charge.where('date = ?', charge.date).sum(:discount)
end
I found this approach easier, especially if setting multiple conditions on the data you want to extract from the table.
In any case, I had an accounting model that presented this kind of issue where I needed credit and debit plus type of payment info on a single table and spent a fruitful few hours learning all about group_by before realizing that .group() offered a simple solution.

How do i use .sort() to create a relation?

I am using the kaminari gem for pagination. I have a resources controller which paginates perfectly (due to the simple nature of the ordering). That can be seen here:
#resources = Resource.order("created_at desc").page(params[:page]).per(25)
That just sorts them by latest first. when i do .class it appears thats an activerecord::relation
On my tags though, I want to sort them by a relationship (the number of resources assigned to that tag)
#tags = Tag.all.sort{|a, b| b.number_of_resources <=> a.number_of_resources}.page(params[:page]).per(50)
It gives me the error however undefined methodpage' for #`
Tag.all returns an Array, hence your #page call failing, as it expects an ARel relation.
If #number_of_resources maps to a DB column, then all you need to do is:
Tag.order('number_of_resources').page(params[:page]).per(50)
If it's not, you either need to add it to the Tag database table, or just do your sort/paginate in Ruby rather than using kaminari. This will be feasible if the number of tags is under ~1000 or so.
If you do add the info to the db, check out this post: Counter Cache for a column with conditions?
you should do something like: 1) joins the two tables, 2) group rows by tag, 3) count how many rows belongs to each group, 4) order using that new column with the count
you should make a good sql statement and then you can call pagination

CONCAT_WS for Rails?

No matter what language I'm using I always need to display a list of strings separated by some delimiter.
Let's say, I have a collection of products and need to display its names separated by ', '.
So I have a collection of Products, where each one has a 'name' attribute. I'm looking for some Rails method/helper (if it doesn't exist, maybe you can give me ideas to build it in a rails way) that will receive a collection, an attribute/method that will be called on each collection item and a string for the separator.
But I want something that does not include the separator at the end, because I will end with "Notebook, Computer, Keyboard, Mouse, " that 2 last characters should not be there.
Ex:
concat_ws(#products, :title, ", ")
#displays: Notebook, Computer, Keyboard, Mouse
Supposing #products has 4 products with that names of course.
Thanks!
you should try the helper to_sentence.
If you have an array, you can do something like
array.to_sentence. If your array has the data banana, apple, chocolate it will become:
banana, apple and chocolate.
So now if you have your AR Model with a field named, you could do something like
MyModel.all.map { |r| r.name }.to_sentence
#products.map(&:title).join(', ')
As #VP mentioned, Array#to_sentence does this job well in rails. The code for it is here:
https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/array/conversions.rb
Saying that, its use of the Oxford Comma is questionable :-)

Resources