Rails difference between find_by_column vs where [duplicate] - ruby-on-rails

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Rails “find_all_by” vs “.where”
In this piece of code
I was wondering what the difference between line 1 and 2.
The COLUMN_NAME is either nil, or unique value.
def get_row
id = "someidhere"
1 r = Model.find_by_COLUMN_NAME(id)
2 r = Model.where('COLUMN_NAME = ? ', id).first
if !r.nil?
r
else
nil
end
end
Is 2 more explicit than 1? What are some side effects that I should watch out for? (If id was nil, or searching for non existing id)
I was using find_by_COLUMN_NAME before and I was getting unexpected results.
When the function returns, I am calling r.id.to_s where r should be an instance of Model, however, sometimes I am getting the value 2 from nowhere.

.where methods return an ActiveRecord relation, which means they can be chained with other such methods and scopes, as in Model.where(:user_id => id).published. The dynamic finders (.find_by_name, etc.), return model instances or arrays, which cannot be chained with additional scopes.
Dynamic finders can return an ActiveRecord::RecordNotFound error when the expected record is not found (rather than nil, [], or an empty relation) if used with an exclamation point, e.g. User.find_by_email!('example#email.com)`.
They each have their own uses; it's up to you to decide based on the cases in which your method would be called.

Related

Why `where` method doesn't work, unlike `find`? [duplicate]

This question already has answers here:
Rails .where vs .find
(3 answers)
Closed 2 years ago.
Faced a problem completely incomprehensible to me.
I am getting an error when using the method where.
#winner = Team.where(id: params[:winner_id])
#winner.update(rating: #winner.rating += 20)
undefined method `rating' for #<Team::ActiveRecord_Relation:0x00007faed9018490>
However, if I change #winner = Team.where(id: params[:winner_id]) to #winner = Team.find(params[:winner_id])it will work.
Why where method doesn't work?
Because where always gives you an ActiveRecord_Relation, not a single object. In your case you're filtering on the id, so if that's the primary key, as I suppose, you only get one record, but that's into a relation anyway, because that's the semantic of where.
#winner = Team.where(id: params[:winner_id]).first
or
#winner = Team.find_by(id: params[:winner_id])
One of these should do the trick
where works this way because you could even filter for some not so restrictive columns and get many rows in returns, so you always get a collection of rows. For example, think about this
#people = Person.where(age: 20)
this query retrieves all the people 20 years old. That could get zero, one or many rows but you always get a relation, for sure.

How to get weighted average grouped by a column

I have a model Company that have columns pbr, market_cap and category.
To get averages of pbr grouped by category, I can use group method.
Company.group(:category).average(:pbr)
But there is no method for weighted average.
To get weighted averages I need to run this SQL code.
select case when sum(market_cap) = 0 then 0 else sum(pbr * market_cap) / sum(market_cap) end as weighted_average_pbr, category AS category FROM "companies" GROUP BY "companies"."category";
In psql this query works fine. But I don't know how to use from Rails.
sql = %q(select case when sum(market_cap) = 0 then 0 else sum(pbr * market_cap) / sum(market_cap) end as weighted_average_pbr, category AS category FROM "companies" GROUP BY "companies"."category";)
ActiveRecord::Base.connection.select_all(sql)
returns a error:
output error: #<NoMethodError: undefined method `keys' for #<Array:0x007ff441efa618>>
It would be best if I can extend Rails method so that I can use
Company.group(:category).weighted_average(:pbr)
But I heard that extending rails query is a bit tweaky, now I just want to know how to run the result of sql from Rails.
Does anyone knows how to do it?
Version
rails: 4.2.1
What version of Rails are you using? I don't get that error with Rails 4.2. In Rails 3.2 select_all used to return an Array, and in 4.2 it returns an ActiveRecord::Result. But in either case, it is correct that there is no keys method. Instead you need to call keys on each element of the Array or Result. It sounds like the problem isn't from running the query, but from what you're doing afterward.
In any case, to get the more fluent approach you've described, you could do this:
class Company
scope :weighted_average, lambda{|col|
select("companies.category").
select(<<-EOQ)
(CASE WHEN SUM(market_cap) = 0 THEN 0
ELSE SUM(#{col} * market_cap) / SUM(market_cap)
END) AS weighted_average_#{col}
EOQ
}
This will let you say Company.group(:category).weighted_average(:pbr), and you will get a collection of Company instances. Each one will have an extra weighted_average_pbr attribute, so you can do this:
Company.group(:category).weighted_average(:pbr).each do |c|
puts c.weighted_average_pbr
end
These instances will not have their normal attributes, but they will have category. That is because they do not represent individual Companies, but groups of companies with the same category. If you want to group by something else, you could parameterize the lambda to take the grouping column. In that case you might as well move the group call into the lambda too.
Now be warned that the parameter to weighted_average goes straight into your SQL query without escaping, since it is a column name. So make sure you don't pass user input to that method, or you'll have a SQL injection vulnerability. In fact I would probably put a guard inside the lambda, something like raise "NOPE" unless col =~ %r{\A[a-zA-Z0-9_]+\Z}.
The more general lesson is that you can use select to include extra SQL expressions, and have Rails magically treat those as attributes on the instances returned from the query.
Also note that unlike with select_all where you get a bunch of hashes, with this approach you get a bunch of Company instances. So again there is no keys method! :-)

How to get column from active record query

1 and #2 both do not work. Active Record drives me nuts because I cam never remember when it returns an object or an array. Neither is working this time.
question = Question.select('id, question, promo_title, promo_code, group_id').where(:group_id => group_id).limit(1)
1
cookies[:question_id] = question['id']
2
cookies[:question_id] = question.id
You need to do
cookies[:question_id] = question[0].id
Your query will give you Question::ActiveRecord_Relation object. In order to get the data, you can use #each to iterate through all the records, and #[] to get any specific from the resultant collection. In your case it is holding only one record, so you can use #[] method with the argument to it as 0.
Now question[0] will give you a Question instance, now you can call the #id method on it as per the regular Rails way.

In Ruby on Rails ActiveRecord, how to make an empty ActiveRecord::Relation object and copy a record from another ActiveRecord::Relation object (full)? [duplicate]

This question already has answers here:
What is the easiest way to duplicate an activerecord record?
(12 answers)
Closed 8 years ago.
I have an ActiveRecord object (x) with some records in it.
First, I want to create an empty recordset (y). Something like :
y = x.class.new EDIT: -or- y = ActiveRecord::Relation.new
Second, I want to copy (duplicate) a record from x to y. Something like :
y << x.first.dup
How can I do that ? (Is it possible ?)
I have an error on the first line, saying that an argument is missing. I'm able to make new String with this method, but not with ActiveRecord objects.
# x.class : ActiveRecord::Relation::ActiveRecord_Relation_Analysis
x = Analysis.where(category: 6)
# In a helper ...
# The x object is not always an Analysis. So I must use its class
y = x.class.new
Error on the last line :
ArgumentError: wrong number of arguments (0 for 1+)
from .. /activerecord-4.0.1/lib/active_record/relation/delegation.rb:76:in `new'
Suppose you have an ActiveRecord model Model, and a collection of Records x that was obtained from a previous query and is of type ActivRecord::Relation. Then you can duplicate the entire list this way:
y = x.map(&:dup)
note that this will set the id attribute to nil, you will still have to create the records to persist them to the database:
y.map(&:save)
or in one go:
x.each do |record|
record.dup.save
end
If you want to clone just a single record, you don't need the collection wrapped around and you can just do:
clone = x.first.dup
clone.save

Rails and Arel's where function: Can I call where on objects instead of making a call to the database?

Consider the following:
budget has many objects in its another_collection.
obj has many objects of the same type as object in its another_collection.
budget and some_collection are already declared before the following loop
they've been previous saved in the database and have primary keys set.
some_collection is a collections of objs.
-
some_collection.each do |obj|
another_obj = obj.another_collection.build
budget.another_collection << another_obj
end
budget.another_collection.collect {|another_obj| another_obj.another_obj_id}
=> [1, 2, 3, 4]
some_obj_with_pk_1 = some_collection.where(obj_id: obj.id)
some_obj_with_pl_1.id
=> 1
budget.another_collection.where(another_obj_id: some_obj_with_pk_1.id)
=> []
This shouldn't happen. What is happening is that rails queries the database for any items in another_collection with another_obj_id = 1. Since this collection hasn't been saved to the database yet, none of these items are showing up in the results.
Is there a function or something I can pass to Arel's where method that says to use local results only? I know I can just iterate over the items and find it but it would be great if I didn't have to do that and just use a method that does this already.
You could always use Enumeable#select which takes a block and returns only the elements that the block returns true for. You'd want to make sure that you had ActiveRecord retrieve the result set first (by calling to_a on your query).
records = Model.where(some_attribute: value).to_a
filtered_records = records.select { |record| record.something? }
Depending on your result set and your needs, it is possible that a second database query would be faster, as your SQL store is better suited to do these comparisons than Ruby. But if your records have yet to be saved, you would need to do something like the above, since the records aren't persisted yet

Resources