Retrieve database records in Rails - ruby-on-rails

My model named Person contains 3 columns namely name,age,gender.
Now how to get all the rows if the gender = "male". I try to
fetch the data as shown below.
p = Person.find_by_gender("male")
The above statement properly worked. But it returns only 1 record. Because, the statement is converted to like following query.
SELECT "persons".* FROM "persons" WHERE "persons"."gender" = $1 LIMIT 1 [["gender", "male"]]
Due to limit is set to 1 it returns only 1 record. So, how to unset the limit? My requirement to get all the records in table if gender
matches "male".

use where
Person.where(gender: "male")
find method always returns only one record

In rails find methods always return single record that's why it returning single record.
Person.find_by_gender("male")
Use Where which give you array of matching records(which is ActiveRecord::Relation)
Person.where(:gender => "male")

Related

Populate an active record collection with different SQLs on the same model in rails

I'm trying to populate an active record collection from several SQLs on the same model. The only thing that differs between the SQLs is the where clause. My models have a type_id. As an example I have
models = Model.where("type_id = ?", 1)
logger.debug 'models.count ' + models.count.to_s
m = Model.where("type_id = ?", 2)
models << m
logger.debug 'models.count ' + models.count.to_s
From that, my logfile shows me
SELECT COUNT(*) FROM "models" WHERE (type_id = 1)
models.count 1
SELECT COUNT(*) FROM "models" WHERE (type_id = 1)
models.count 1
The second SQL is not correct for my situation, I wanted
SELECT COUNT(*) FROM "models" WHERE (type_id = 2)
The only way I've found to get around this is to do Model.all, iterate over each and add the ones I want. This would be very time consuming for a large model. Is there a better way?
From the sounds of it, you're looking for any Model with a type_id of either 1 or 2. In SQL, you would express this as an IN subclause:
SELECT * FROM models WHERE type_id IN (1, 2);
In Rails, you can pass an array of acceptable values to a where call to generate the SQL IN statement:
Model.where(:type_id => [1, 2])
As stated by #ArtOfCode what you want is to do the query on one pass. That being said, what you are trying to do there won't work because when you are adding with << the object of your second query to the first one you are just appending the instance to the first collection. The object type of the resulting query is an ActiveRecord_Relation which happens to hold two instances of your custom models (in this case Model) but when you send / call count thats actually executing an ActiveRecord query.
How can you tell the difference? Well, if you do run that code you used and do:
models.count
You'll see that there's SQL executed for whatever the conditions of the query on models you did, however, if you do this:
models.length
You'll notice the result is 2, and the reason is because the length of the collection of your own objects which happens to be inside the ActiveRecord_Relation is indeed two, and that is what happens if you use <<; it'll add object instances to the relation but that does not mean that they are part of the query.
You could even do this:
models << Model.new
And calling models.length would effectively return 3 because that is the amount of instances of your model that are contained within the relation, again, not a part of the query. So as you can see you can even add new object instances which have not even been saved to the database.
TL;DR if you want to query objects that are stored in the database do it on the query itself, or chain conditions at once, but don't try to mix activerecord relation collections.

Rails: Omit order by clause when using ActiveRecord .first query?

I'm having a problem with a .first query in Rails 4 ActiveRecord. New behavior in Rails 4 is to add an order by the id field so that all db systems will output the same order.
So this...
Foo.where(bar: baz).first
Will give the query...
select foos.* from foos order by foos.id asc limit 1
The problem I am having is my select contains two sum fields. With the order by id thrown in the query automatically, I'm getting an error that the id field must appear in the group by clause. The error is right, no need for the id field if I want the output to be the sum of these two fields.
Here is an example that is not working...
baz = Foo.find(77).fooviews.select("sum(number_of_foos) as total_number_of_foos, sum(number_of_bars) as total_number_of_bars").reorder('').first
Here is the error...
ActiveRecord::StatementInvalid: PG::GroupingError: ERROR: column "foos.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: ...bars FROM "fooviews" ORDER BY "...
Since the select is an aggregate expression, there is no need for the order by id, but AR is throwing it in automatically.
I found that I can add a reorder('') on to the end before the .first and that removes the order by id, but is that the right way to fix this?
Thank you
[UPDATE] What I neglected to mention is that I'm converting a large Rails 3 project to Rails 4. So the output from the Rails 3 is an AR object. If possible, the I would like the solution to keep in that format so that there is less code to change in the conversion.
You will want to use take:
The take method retrieves a record without any implicit ordering.
For example:
baz = Foo.find(77).fooviews.select("sum(number_of_foos) as total_number_of_foos, sum(number_of_bars) as total_number_of_bars").take
The commit message here indicates that this was a replacement for the old first behavior.

Rails 4 how to calculate sum for each method

I'm trying to find solution how to calculate sum for each method in rails.
I feel like tried hundreds different approaches, but couldn't quiet figure it out yet.
For example:
Helper return 2 ids for products: (using alert just to make it visible)
view_context.time_plus.each
returns 1,2
But when I combine it with call, and selecting multiple option it is only returns last one instead of sum for both value
view_context.time_plus.each do |i|
flash[:alert] = [Service.find_by_price_id(i).time].sum
end
I see in logs call made for both value:
Service Load (0.2ms) SELECT `services`.* FROM `services` WHERE `services`.`price_id` = 0 LIMIT 1
Service Load (0.2ms) SELECT `services`.* FROM `services` WHERE `services`.`price_id` = 1 LIMIT 1
find_by_column always returns only one record.
You can use where condition for multiple ids like this
Model.where(column_name: [array_of_ids])
if view_context.time_plus returns an array of ids
Service.where(price_id: view_context.time_plus]).sum(:time)
You can try this
flash[:alert] = view_context.time_plus.map{|i| Service.find_by_price_id(i).time}.sum
This should work
You can use inject method:
sum = view_context.time_plus.inject(0) { |sum,i| sum+=Service.find_by_price_id(i).time }
But if you are using ruby on rails, better way is use active_record and sql:
sum2 = Service.where(price_id: [1,2]).sum(:time)

Model.first does not retrieve first record from table

Model.first doesnot retrive first record from table. Instead it retrives any random record from table.
eg:
Merchant.first
Query
SELECT "merchants".* FROM "merchants" LIMIT 1
=> <Merchant id: 6, merchant_name: "Bestylish", description: "", description_html: "" >
Instead the query should be
SELECT "merchants".* FROM "merchants" ORDER BY "merchants"."id" ASC LIMIT 1;
Why it doesnot retrive the first record
Model.first will use the default sorting of your database.
For example. In Postgresql default sorting is not necessarily an id.
This seems to be default behaviour with Postgres, as some active-record versions do not add a default ordering to the query for first, while adding one for last.
https://github.com/rails/rails/issues/9885
PostgreSQL does not by default apply a sort, which is generally a good thing for performance.
So in this context "first" means "the first row returned", not "the first row when ordered by some meaningless key value".
Curiously "last" does seem to order by id.
It is defined here, in Rails 4, to order by primary key if no other order conditions are specified.
In Rails 3.2.11, it is as such:
def find_first
if loaded?
#records.first
else
#first ||= limit(1).to_a[0]
end
end
Without the order method, which will just apply the limit and then leave the ordering up to your database.
You need to apply the ordering yourself. Try calling Merchant.order('id ASC').first
It may be possible to automate this using default scopes in your model but I'm not sure about that.

Get a Rails record count without quering a 2nd time

I've got a Rails ActiveRecord query that find all the records where the name is some token.
records = Market.where("lower(name) = ?", name.downcase );
rec = records.first;
count = records.count;
The server shows that the calls for .first and .count were BOTH hitting the database.
←[1m←[35mCACHE (0.0ms)←[0m SELECT "markets".* FROM "markets" WHERE (lower(nam
e) = 'my market') LIMIT 1
←[1m←[36mCACHE (0.0ms)←[0m ←[1mSELECT COUNT(*) FROM "markets" WHERE (lower(na
me) = 'my market')←[0m
Why is it going to the database to get the count when it can use the results already queried?
I'm concerned about future performance. Today there are 1000 records. When that table holds 8 million rows, doing two queries one for data, and one for count, it will be expensive.
How do I get the count from the collection, not the database?
RactiveRecord use lazy query to fetch data from database. If you want to simple count the records, you can only call size of the retrun array.
records = Market.where("lower(name) = ?", name.downcase ).all
records.size
So, records is an ActiveRelation. You would think it's an array of all your Market records that match your where criteria, but it's not. Each time you reference something like first or count on that relation, it performs the query retrieve what you're asking for.
To get the actual records into an array, just add .all to the relation to actually retrieve them. Like:
records = Market.where("lower(name) = ?", name.downcase).all
count = records.count
For Rails 6.0.1 and Ruby 2.6.5
You will need to store the results into an array by using the to_a.
records = Market.where("lower(name) = ?", name.downcase).to_a
This will create the SQL query and store the results in the array records.
Then, when you call either records.first or records.count it will only return the data or do the calculation, not rerun a query. This is the same for records.size and records.length.
Another Example
I was needing to do this for a blog I am developing. I was trying to run a query to find all of the tags associated with a post, and I wanted to count how many tags there were. This was causing multiple queries until I came across the to_a suffix.
So, my SQL query looks like this:
#tags = TagMap.where(post_id: #post).joins(:tag).select(:id, '"tags"."name"').to_a
This looks through my TagMap table for all records that have post_id equal to the id of the post that I am viewing. It then joins to the Tags table and pulls only the id of the TagMap record and the name of the tag from the Tags table. Then it puts them all into an array. I can then run #tags.count and it will return the number of TagMap records for that post without doing another query.
I hope that this helps anyone using Rails 6+

Resources