Rails 4 Sum by Model Method - ruby-on-rails

In my app, I have a User model, with a goal_ytd method, which performs some calculations.
In a controller, I have a variable #users that might be User or an ActiveRecord::Relation of users, and I would like to sum all of the #users's goal_ytds.
My first inclination was:
#users.sum(&:goal_ytd)
Which threw a deprecation warning in both cases because using sum on an ActiveRecord::Relation is going away in Rails 4.1.
So, I changed the code to:
#users.to_a.sum(&:goal_ytd)
Which then threw a NoMethodError because, in a certain circumstance, #users is assigned by #users = User and User has no to_a method.
Assigning #users using #users = User.all throws a deprecation warning because Relation#all is also deprecated.
Is there a way to get all Users as an array? Is there a better way?

On Rails 4.1
If goal_ydt is a column in the users table:
#users.sum(:goal_ydt)
If goal_ydt is a method in User class:
#users.to_a.sum(&:goal_ydt)

I like to use a combination of map and sum
#users.map(&:goal_ydt).sum

You should not use enumerable methods here. Use sum which is defined on ActiveRecord::Relation and takes symbol as parameter. The main difference is that it will perform SUM query in your database, so it is much faster than pulling all the records form db. Also if any of your record has blank value for given field, enumerable sum will throw an error, while ActiveRecord's one will not. In short:
#users.sum(:goal_ydt)
EDIT:
However since goal_ydt is not a field but a method, you have no choice but to loop over the models. The way I usually do this is by using scoped method:
#users.scoped.sum(&:goal_ydt)

The issue here is rooted in a fundamental misunderstanding of the Relation#all deprecation. While Relation#all is deprecated, Model#all is not. Therefore:
#users = User.all
is still perfectly valid, while:
#users = User.where(first_name: "Mike").all
is deprecated.
So the end solution looks like:
#users = User.all
unless current_user.admin?
#users = #users.where(company_id: current_user.company_id)
end
#users.to_a.sum(&:goal_ytd)
A new question would be: How do I sum all the users goals, preferably in one line, without loading them all into memory? I suppose that's for another day.

Related

Controller syntax Ruby on Rails 6

Having an issue with displaying only three posts on a page. Here is the closest I've gotten to getting the controller working with a param.
def index
#Post = Post.all
#Posts = Post.find.limit('3').order('date_posted')
end
This is rendering a syntax error of sorts because it wants an ID, but I don't want to give it an ID, I want it to find the three most recent posts. How should I go about that?
Try this way
def index
#recent_posts = Post.limit(3).order(date_posted: :desc)
end
What's wrong in your code
all gives you all the objects for a model
find and find_by give you just one object which meet the id passed to find or the first one which meets the conditions passed to find_by

How does Rails perform SQL queries for multiple requests to one model?

Not sure if I'm reading Puma logs right. So I'd like to clarify three things:
First
If in controller we do something like:
def find_user
user = User.where(name: "Alex")
#user = user.where(age: 25)
end
How many sql queries are made?
Second
def find_user
user = User.where(name: "Alex")
#user = user
end
The same question.
Third
def find_user
user = User.find(1).id
#user = User.find(1).first_name
end
The same question.
While the third piece of code generate 2 DB queries, the first and second, technically speaking, generate 0. Any query may be generated if you use #user variable or the return value of find_user method (which is the same thing, obviously).
The reason behind this behavior is that ActiveRecord uses lazy evaluation of the queries, so they are performed by the time you need their results.
You can often be misguided by the console output, where all of these where calls, if you execute them one by one, generate SQL query, but it's only because under the hood the console calls inspect on every evaluated value, to generate output, and that's where ActiveRecord gets the message, 'oh, I need to actually perform DB operation to get the results needed'. Consider this example, you can check it in your console:
lambda do
user = User.where(name: "Alex")
#user = user.where(age: 25)
return 'any other value'
end.call
You'll get the 'any other value' string returned from this lambda expression, but guess what - the #user variable will be set to hold ActiveRecord::Relation instance and there would be 0 DB queries generated.
In Ruby on Rails ActiveRecord no SQL query will be made until you use ActiveRecord's object to fetch some record or execute some operation which will effect your DB.
So in first case there will be only 1 query to database same as second case. But in third case if you are using both user and #user to fetch record then two queries will be fired as both are different object. If you use only #user then only one query will be fired

Rails 4, Active Record query returns ActiveRecord

I want to retrieve data from my database and show it in a view.
My controller is:
bancas_controller.erb
def show
#user = current_user
#a=Banca.find_by(user_id: #user.id)
end
The view is:
banca: <%=#a%>
My model has some attributes: num, scadenza and user_id. I don't understand why my view looks like this:
banca: #<Banca:0xb715330>
Just doing
#a=Banca.find_by(user_id: #user.id)
This resturns a record with some desired id. To get the desired attribute use
#a.attribute_name
#a.name
#a.email
#a.gender
In case you want to see all poperties use #a.inspect.
<%=#a%> will run the code in the erb tag, and then call to_s on the result. If you call to_s on an ActiveRecord object then you get the result you saw. You need to decide how you want to display the data associated with the object, in the erb tag. eg
banca: <%= #a.scadenza %>
or whatever.
Find by is a dynamic finder, it's like using a where clause, even if there is only one it will return an Active Record relation. Just pop .first on the end to get the first one if that is what you want.
#a=Banca.find_by(user_id: #user.id).first
#a is an object . In the view do this #a.inspect to see it's properties. To iterate over it use .each or .map.

Rails: helper returns correct SQL result but ignores columns

I've got a helper in my ApplicationHelper file that works just fine:
def latest_issue
#issue = Issue.find(:all, :order => "id DESC", :limit => 1)
return #issue
end
After that, using #issue works just fine in any view, but considering that #issue has a column named message using #issue.message returns a No Method Error.
Any help would be fantastic! Cheers.
The issue instance variable is returning an array of objects not an instance. If you would like to select an attribute of an Issue object you need to return an instance of the object.
#issue = Issue.find(:last)
#issue.message
You may be trying to output all the message attributes of the Issue object, if that is the case you need to pass the #issue to an block
#issue.each do |issue|
issue.message
end
As Kyle C says, you're attempting to access the member message of an array of Issues, when you should be returning a single issue from your helper.
Assuming you're using Rails 3, A vastly improved version of your helper would be written this way:
def latest_issue
Issue.order(:id).last
end
A few notes on writing idomatic Ruby:
Avoid explicit return statements, and let the last statement be the return value of a method
Use the chainable methods like order(), where(), and limit() instead of passing arguments to find

Constructing a Rails ActiveRecord where clause

What's the best way to construct a where clause using Rails ActiveRecord? For instance, let's say I have a controller action that returns a list of blog posts:
def index
#posts = Post.all
end
Now, let's say I want to be able to pass in a url parameter so that this controller action only returns posts by a specific author:
def index
author_id = params[:author_id]
if author_id.nil?
#posts = Post.all
else
#posts = Post.where("author = ?", author_id)
end
end
This doesn't feel very DRY to me. If I were to add ordering or pagination or worse yet, more optional URL query string params to filter by, this controller action would get very complicated.
How about:
def index
author_id = params[:author_id]
#posts = Post.scoped
#post = #post.where(:author_id => author_id) if author_id.present?
#post = #post.where(:some_other_condition => some_other_value) if some_other_value.present?
end
Post.scoped is essentially a lazy loaded equivalent to Post.all (since Post.all returns an array
immediately, while Post.scoped just returns a relation object). This query won't be executed until
you actually try to iterate over it in the view (by calling .each).
Mmmh, the best approach you want to use can be to spread this in 2 actions
def index
#post = Post.all
end
def get
#post = Post.where("author=?", params[:author_id])
end
IMHO it has more sense if you think about a RESTful API, index means to list all and get (or show) to fetch the requested one and show it!
This question is pretty old but it still comes up high in google in 2019, and also some earlier answers have been deprecated, so I thought I would share a possible solution.
In the model introduce some scopes with a test for the existence of the parameter passed:
class Post
scope :where_author_ids, ->(ids){ where(author_id: ids.split(‘,’)) if ids }
scope :where_topic_ids, ->(ids){ where(topic_id: ids.split(‘,’)) if ids }
Then in the controller you can just put as many filters in as you wish e.g:
def list
#posts = Post.where_author_ids(params[:author_ids])
.where_topic_ids(params[:topic_ids])
.where_other_condition_ids(params[:other_condition_ids])
.order(:created_at)
The parameter can then be a single value or a comma separated list of values, both work fine.
If a param doesn’t exist it simply skips that where clause and doesn’t filter for that particular criteria. If the param exists but its value is an empty string then it will ‘filter out’ everything.
This solution won’t suit every circumstance of course. If you have a view page with several filters on, but upon first opening you want to show all your data instead of no data until you press a ‘submit’ button or similar (as this controller would) then you will have to tweak it slightly.
I’ve had a go at SQL injecting this and rails seems to do a good job of keeping everything secure as far as I can see.
You should model url using nested resources. The expected url would be /authors/1/posts. Think of authors as resources. Read about nested resources in this guide: http://guides.rubyonrails.org/routing.html (scroll to 2.7 - Nested Resources).
Would something like this work?
def get
raise "Bad parameters...why are you doing this?" unless params[:filter].is_a?(Hash)
#post = Post.where(params[:filter])
end
Then you can do something like:
?filter[author_id]=1&filter[post_date]=... etc.

Resources