Check for nil result in ActiveRecord query - ruby-on-rails

I have a few places in a model that does stuff like
def ServerInfo.starttime(param)
find(:all, :conditions => "name ='#{param}_started'", :select => "date").first.date.to_datetime
end
Now, for reasons not relevant to the question, it can happen that this particular row is not in the database at all and the code above fails with NoMethodError (undefined method `date' for nil:NilClass):. My current fix is
res = find(:all, :conditions => "name ='#{param}_started'", :select => "date")
check_time = res.first.nil? ? 0 : res.first.date.to_datetime
This works find, but I feel it's not right to sprinkle that code all over the place. Is there some more ruby-ish / rail-ish way to prevent dereferencing nil?

In order to avoid the NoMethodError for nil, you should define a begin rescue block,
def ServerInfo.starttime(param)
begin
find(:all, :conditions => "foo").first.date.to_datetime
rescue
0
end
end
I also like the Rails try method:
find(:all, :conditions => "foo").first.try(:date).try(:to_datetime) || 0

maybe this is cleaner:
check_time = res.first.date.to_datetime if res.first
btw, don't use:
:conditions => "name ='#{param}_started'" # SQL injection vulnerability.
use this one instead:
:conditions => ["name = ?", "#{param}_started"] # This is safer. Pure clean Ruby
it's safer

You may also define a scope. For instance in a Rails3 app you should try:
In your ServerInfo.rb model:
scope :starttime, lambda{|param|
if self.has_attribute?(param+'_started')
where("name = ?", param+'_started' ).select('date')
else
false
end
}
// Remember to never put your params directly in your sql query, that is bad practice since you risk some sql injection //
Then in a controller:
res = ServerInfo.starttime('a_param')
check_time = res.first.date.to_datetime if res
I didn't try that code, then you may need to adapt it to your need (or to your Rails2 app)

Related

Datagrid Gem in Rails 4 returns PG:UndefinedColumn: ERROR:

No hair left on my head (and I have had lots :) ), I have been pulling out my hair and for the life of me I can't figure this out.
I have a one to many relations between 2 tables. I have installed the Datagrid Gem for reporting. I need to get the report from one model based on the other one.
Please have a look at my code.
reports_grid.rb
class ReportsGrid
include Datagrid
scope do
Land.includes(:estate)
end
filter(:estate, :enum, :select => proc { Estate.group("title").select("title").map {|c| [c.title] }})
column(:id, :header => "Land ID")
column(:current_stage, :header => "Stage")
column(:price)
column(:status)
end
reports_controller.rb
class ReportsController < ApplicationController
def index
#grid = ReportsGrid.new(params[:reports_grid]) do |scope|
if params[:reports_grid].present?
if params[:reports_grid][:estate].present?
scope.joins(:estate).where("estates.title = ? ",params[:reports_grid][:estate]).page(params[:page])
**# when I get the #grid.assets here all good and return correct number of rows**
else
scope.page(params[:page])
end
else
scope.page(params[:page])
end
end
end
end
Land.rb
belongs_to :estate
estate.rb
has_many :lands
Now when I go to /reports and try to run the filter I get the following error
PG::UndefinedColumn: ERROR: column lands.estate does not exist LINE 1: ..._id" WHERE (estates.title = 'Olive Gardens' ) AND "lands"."e... ^ : SELECT COUNT(*) FROM "lands" INNER JOIN "estates" ON "estates"."id" = "lands"."estate_id" WHERE (estates.title = 'Olive Gardens' ) AND "lands"."estate" = 'Olive Gardens'
Why is the Gem tries to add "lands"."estate" = 'Olive Gardens' to the query when I have defined it at the instance.
Please let me know if you need me to add anything. Thank you in advance.
Edit:
This is what I have done and worked in the Filter:
I have done this:
filter(:estate_id, :enum,
:select => lambda {Estate.all.map {|p| [p.title, p.id]}},
:multiple => false,
:include_blank => true
) do |value|
self.where(:lands => {:estate_id => value})
end
Do you it is a good approach?
I guess in the scope I could say Land.joins(:estate) then use the scope.all.map... in the query.
Datagrid filter designed to filter data but not to just be by default.
If you have some reason why estate should not filter data by itself then add :dummy => true option:
filter(:estate, :enum, :select => ..., :dummy => true)
But I'would recommend it. Do this instead and your hair will start growing instantly:
filter(:estate, :enum, :select => ...) do |scope, value|
scope.joins(:estate).where("estates.title = ? ", value)
end
It seems obvious from documentation here:
https://github.com/bogdan/datagrid/wiki/Filters#filter-block
Try using references
Land.includes(:estate).references(:estates)

update several records using update_all

So I'm trying to update few things in my user for it's role and its plan_id .. I've came up with something dirty in my console but everytime I'm trying to use update_all similar way I'm getting no where. I think I'm missing something.. here's my original console way;
expired_subscription_user_ids = Subscription.where("expiry_date < ?", Time.now.beginning_of_day).pluck(:user_id)
User.where(:id => cancelled_subscription).each do |user|
user.role = 'cancelled'
user.plan_id = 'cancelled'
user.save
end
Here's the same thing but using update all that's not working out for me.
User.where(:id => cancelled_subscription).each do |user|
user.update_all(:role => 'subscriber', :plan_id => 'subscriber')
end
So pretty much all users with cancelled_subscription will have their role and plan_id chanced.
You have to use the .update_all method on an ActiveRecord::Relation object, like this:
scope = User.where(:id => cancelled_subscription)
scope.update_all(:role => 'subscriber', :plan_id => 'subscriber')
Documentation: http://apidock.com/rails/ActiveRecord/Relation/update_all
An interesting comment in the documentation, from "openface":
Note that ActiveRecord will not update the timestamp fields (updated_at/updated_on) when using update_all().

How do I avoid nil with a dynamic finder when an attribute comes as nil?

link_to 'articles', articles_path, :attr1 => 'foo', :attr2 => 'bar'
And in the controller:
Article.find_all_by_attr1_and_attr2(params[:attr1], params[:attr2])
However if the controller receives only [:attr1] I get a nil.
Dynamic finders may not be the right way to go if some of finders aren't actually present. In this case, you're probably better off using Article.find(:all, :conditions => {}) on Rails 2 and Article.where() on Rails 3.
Here's a method I came up with for another question a while back:
conditions = [:attr1, :attr2].inject({}) do |hsh, field|
hsh[field] = params[field] if params[field] && params[field].present?
hsh
end
# Rails 2
#articles = Article.find(:all, :conditions => conditions)
# Rails 3
#articles = Article.where(conditions)
In the above case, you'd loop over all fields in the array, and add each one of them to the resulting hash if it's in and not empty in params. Then, you pass the hash to the finder, and everything's fine and dandy.

Checking if ActiveRecord find returns a result

I'm trying to check if a find method returns a result. My find method is the following:
post = Post.find(:all, :conditions => { :url => params['url'] }, :limit => 1)
What would be a good way to check that post contains a result?
find :all returns an empty array ([]) if no rows are returned, so you can just use it this way:
post = Post.find(:all, :conditions => { :url => params['url'] }, :limit => 1)
unless post.empty?
# do something...
end
By the way, if you do find :all you're going to get an array, not a single row. If you're trying to get just one Post, it would be cleaner to use the find_by helper or find :first or just first instead:
post = Post.find_by_url params['url']
# or
post = Post.first :conditions => { :url => params['url'] }
# then...
if post
# do something...
end
You can try ActiveRecord::Base.exists? before
Post.exists?(:conditions => { :url => params['url'] })
Use the BANG! version of the find_by_url method to get it to raise an exception of it could not be found and then rescue it later on in that same method/action.
def show
Post.find_by_url!(params[:url])
rescue ActiveRecord::RecordNotFound
flash[:notice] = "The URL you were looking for could not be found."
redirect_to root_path
end
end
If you didn't raise an exception here I believe that Rails would show the public/404.html page.
if post doesn't contain any result it will be an empty list and then:
post.empty?
will return true.
it may be as simple as changing your finder to:
post = Post.find(:first, :conditions => { :url => params['url'] })
With this finder, post will either return a single value or nil. Because nil behaves like false in a condition statement, you can say something like the following:
if post
# do something
else
# do something else
end
Post.find_by_id(id_column_value)
will return nil rathering than blowing up your program when it can't find a record.
Of course, there's
x = Post.where(:any_column_name => value)
which always returns an array of results. In which case you could just run an
x.each {|t| f(t) }
or
y = x.map {|t| f(t)}
or of course,
x[0], x[1], etc
Sorry I got a little carried away there
Another way to do it is checking with ActiveRecord#any?.

Dynamic find conditions in active record

I have an index action in rails that can handle quite a few params eg:
params[:first_name] # can be nil or first_name
params[:age] # can be nil or age
params[:country] # can be nil or country
When finding users I would like to AND all the conditions that are not nil. This gives me 8 permutations of the find conditions.
How can I can I keep my code DRY and flexible and not end up with a bunch of if statements just to build the conditions for the find. Keep in mind that if no conditions are specified I just want to return User.all
How about something like:
conditions = params.only(:first_name, :age, :country)
conditions = conditions.delete_if {|key, value| value.blank?}
if conditions.empty?
User.all
else
User.all(:conditions => conditions)
end
I would normally use named scopes for something like this:
class User < ActiveRecord::Base
named_scope :name_like, lambda {|name| {:conditions => ["first_name LIKE ?", "#{name}%"]}}
named_scope :age, lambda {|age| {:conditions => {:age => age}}}
named_scope :in_country, lambda {|country| {:conditions => {:country => country}}}
end
class UsersController < ActionController
def index
root = User
root = root.name_like(params[:first_name]) unless params[:first_name].blank?
root = root.age(params[:age]) unless params[:age].blank?
root = root.country(params[:country]) unless params[:age].blank?
#users = root.paginate(params[:page], :order => "first_name")
end
end
That's what I normally do.
This seems to work quite nicely:
conditions = params.slice(:first_name, :age, :country)
hash = conditions.empty? ? {} : {:conditions => conditions}
#users = User.all hash
Using James Healy answer, I modify the code to be used in Rails 3.2 (in case anyone out there need this).
conditions = params.slice(:first_name, :age, :country)
conditions = conditions.delete_if {|key, value| value.blank?}
#users = User.where(conditions)
You could try Ambition, or a number of other ActiveRecord extensions.
This works for me too
conditions = params[:search] ? params[:search].keep_if{|key, value| !value.blank?} : {}
User.all(:conditions => conditions)
If you happen to be on an ancient project (Rails 2.x) and very messy, you could do something like the following for adding new fields to the original query.
Original code:
User.find(:all,
:conditions => ['first_name LIKE ? AND age=? AND country=?',
"#{name}%", age, country]
Adding a new dynamic condition on zip_code field:
zip_code = params[:zip_code] # Can be blank
zip_query = "AND zip_code = ?" unless zip_code.blank?
User.find(:all,
:conditions => ['first_name LIKE ? AND age=? AND country=? #{zip_query}',
"#{name}%", age, country, zip_code].reject(&:blank?)
Adding a reject(&:blank?) to the conditions arrays will filter the nil value.
Note: The other answers are much better if you are coding from zero, or refactoring.

Resources