Dynamic bang finders in Rails 4 - ruby-on-rails

Rails 4 is getting rid of dynamic finders, so
User.find_by_hash(hash)
becomes
User.where(hash: hash) # .first
Okay, not a big deal. But what is the best way to deal do with dynamic bang finders like User.find_by_hash!(hash) since there is no where!() method? Rails 4 Release Notes conveniently avoid this.
Update: It plainly says: "All dynamic methods EXCEPT for find_by_... and find_by_...! are deprecated."
Either the pages has changed since or I was blind when reading it.

I think the new syntax is
User.find_by!(hash: hash)
At least that's how ryanb does it:
http://railscasts.com/episodes/400-what-s-new-in-rails-4
Hope that helps.

Well, if you need a method that finds all but raises exception if the relation is empty, you can create such new method for your models yourself (or mixin to ActiveRecord::QueryMethods). Something like:
def where!(*args)
rel = where(*args)
raise RecordNotFound if rel.empty?
rel
end

It plainly says: "All dynamic methods EXCEPT for find_by_... and find_by_...! are deprecated."

Related

Remove element from ActiveRecord_Relation in rails

How can i remove the last element from an ActiveRecord_Relation in rails?
e.g. if I set:
#drivers = Driver.all
I can add a another Driver object called #new_driver to #drivers by doing:
#drivers << #new_driver
But how can I remove an object from #drivers?
The delete method doesn't seem to work, i.e.
#drivers.delete(0)
You can use the reject! method, this will remove the object from the collection without affecting the db
for example:
driver_to_delete = #driver.first # you need the object that you want removed
#drivers.reject!{|driver| driver == driver_to_delete}
Very late too, but I arrived here looking for a fast answer and finished by thinking by myself ;)
Just to clarify about the different answers and the Rails 6.1 comment on accepted answer:
The OP wanted to remove one entry from a query, but NOT remove it from database, so any answer with delete or destroy is just wrong (this WILL delete data from your database !!).
In Ruby (and therefore Rails) convention, shebang methods (ending with !) tend to alter the given parameter. So reject! would imply modifying the source list ... but an ActiveRecord_Relation is basically just a query, NOT an array of entries !
So you'd have 2 options:
Write your query differently to specifically say you don't want some id:
#drivers.where.not(id: #driver_to_remove) # This still is an ActiveRecord_Relation
Use reject (NO shebang) on your query to transform it into an Array and "manually" remove the entry you don't want:
#drivers.reject{ |driver| driver == #driver_to_remove}
# The `reject` forces the execution of the query in DB and returns an Array)
On a performance point of view, I would personally recommend the first solution as it would be just a little more complex against the DB where the latter implies looping on the whole (eventually large) array.
Late to the question, but just had the same issue and hope this helps someone else.
reject!did not work for ActiveRecord_Relation in Rails 4.2
drop(1) was the solution
In this case #drivers.drop(0) would work to drop the first element of the relation
Since its an array of objects, have you tried to write something like #drivers.delete(#new_driver) or #drivers.delete(id: #new_driver.id) ?
This is the documentation you need:
#group.avatars << Avatar.new
#group.avatars.delete(#group.avatars.last)
--
.destroy
The problem you've got is you're trying to use collection methods on a non-collection object. You'll need to use the .destroy ActiveRecord method to get rid of the record from the database (and consequently the collection):
#drivers = Driver.all
#drivers.last.destroy
--
Scope
.delete will remove the record from the DB
If you want to pull specific elements from the db to populate the #drivers object, you'll need to use a scope:
#app/models/driver.rb
Class Driver < ActiveRecord::Base
scope :your_scope, -> { where column: "value" }
end
This will allow you to call:
#app/controllers/drivers_controller.rb
def index
#drivers = Driver.your_scope
end
I think you're getting the MVC programming pattern confused - data manipulation is meant to happen in the model, not the controller
As stated above, reject! doesn't work in Rails 4.2, but delete does, so #drivers.delete(#new_driver) works, and more generally:
#drivers.delete(Driver.where(your condition))

AcitveRecord where method that matches just one record

Program.where(name: "xxyyzz123") will return a collection, even if there's just one record that matches which forces me to do ugly things like:
puts Program.where(name: "xxyyzz123").first.age
or
puts Program.where(name: "xxyyzz123")[0].age
When I know for sure only one record will match, is there a shorter way to grab a property from that one record?
ActiveRecord's dynamic attribute-based finders (find_by_x) allow you to select the first record that matches in your database. For example:
Program.find_by_name('xxyyzz123')
will return the first record with name = 'xxyyzz123'
Note that these finders are 'mildly deprecated' in Rails 4. Using
Program.find_by(name: 'xxyyzz123")
achieves the same thing and may make it easier when needing to update to the next version of Rails if they ever remove the former's functionality.
See ActiveRecord::Base in the API for more.
Yes, you will have to access that with Program.where(name: "xxyyzz123").first.age, however, in Rails 3, it is usually recommended to do that type of query with: Program.find_by_name('xxyyzz123').age.
Rails 4 deprecates the above syntax and recommends you to use the following syntax for that:
Program.find_by(name: 'xxyyzz123')
If you have multiple conditions, then simply : Program.find_by(name: 'xxyyzz123', lang: 'ruby')
Behind the scene, it does the same tomfoolery - where clause and returns first object.

Is there any way to define a model's attribute as always html_safe?

I have a model called Feature with a variable called body_string, which contains HTML markup I'd like to render, rather than escape.
Every time I reference body_string in my views, I need to use <%=raw or .html_safe. This seems redundant and not-so-DRY.
Is there any way that I can establish once-and-for-all the body_string variable as html_safe?
I'm assuming this would happen in the app/models/feature.rb file, but I can't figure out what the right syntax would be, exactly. I've thought of this:
def body_string
return self.body_string.html_safe
end
But Rails doesn't like it; it raises a stack level too deep exception.
Naturally I could define a variable/method with a different name:
def safe_body_string
return self.body_string.html_safe
end
And then just change all references in the views from body_string to safe_body_string. But somehow this seems almost as un-DRY as simply using raw or .html_safe in the first place.
Any insights to how best to handle this? I feel like there must be something really elegant that I'm just not seeing.
Just use read_attribute to avoid the recursive call to body_string:
def body_string
read_attribute(:body_string).html_safe
end
read_attribute is complemented by write_attribute for setting attributes from within your model.
A note on style: Don't use explicit returns unless you actually need them. The result of the last statement in a method is implicitly the value returned from the method.
While #meager's answer will definitely work, I don't think this logic belongs in a model. Simply because it adds view-level concerns (HTML safeness) to the model layer, which should just include business logic. Instead, I would recommend using a Presenter for this (see http://nithinbekal.com/posts/rails-presenters/ or find a gem for this -- I personally love Display Case). Your presenter can easily override the body_string method and provide the .html_safe designation when displaying in the view. This way you separate your concerns and can continue to get body_string from other models without mixing in the view concern.
Maybe this gem is useful for you. I also wanted to stop repeating html_safe all the time when the content is completely trustable.
http://rubygems.org/gems/html_safe_attribute
Or you can also use this approach,
def body_string
super && super.html_safe
end

Rails 2: Model.find(1) gives ActiveRecord error when id 1 does not exist

I am using Rails 2.3.5 and in that if I give Model.find(1) and if 1 is not in the database, it returns ActiveRecord error. Should it just be returning nil as in the case of Model.find_by_column('..')?
This is the expected behavior. I think David explains this the best himself, so here is a quote from Ruby, S., Thomas, D. & Hansson, D.H., 2009. Agile Web Development with Rails, Third Edition Third Edition., Pragmatic Bookshelf (p.330).
When you use a finder driven by
primary keys, you’re looking for a
particular record. You expect it to
exist. A call to Person.find(5) is
based on our knowledge of the people
table. We want the row with an id of
5. If this call is unsuccessful—if the record with the id of 5 has been
destroyed—we’re in an exceptional
situation. This mandates the raising
of an exception, so Rails raises
RecordNotFound.
On the other hand,
finders that use criteria to search
are looking for a match. So,
Person.find(:first,
:conditions=>"name=’Dave’") is the
equivalent of telling the database (as
a black box) “Give me the first person
row that has the name Dave.” This
exhibits a distinctly different
approach to retrieval; we’re not certain up front that we’ll get a result.
It’s entirely possible the result set
may be empty. Thus, returning nil in
the case of finders that search for
one row and an empty array for finders
that search for many rows is the
natural, nonexceptional response.
If you really don't want the exception, you can use find_by_id:
# #user = User.find(params[:id]) # original code
#user = User.find_by_id(params[:id])
if #user
# found!
else
# not found
end
This should be faster than a separate exists? check.
EDIT: Note that as #Miguelgraz commented, in Rails 4 you should instead say User.find_by(id: params[:id]). Same functionality, but now the implementation won't need method_missing.
throwing the exception is the expected behavior.
in fact in the normal course of events if you let the exception go unhandled your rails server will return the proper 404 page not found error.
if you'd like for it to return nil you can catch it yourself:
begin
#model = Model.find(id_provided)
rescue ActiveRecord::RecordNotFound => e
#model = nil
end
If you want the exception to be thrown in find_by_attributes flavors of the finder methods, you can use the bang! version of the method.
For example,
Model.find_by_category!(a_category_value)
will throw RecordNotFound if no match is found.
I found this to be DRY in scenarios like RESTful controllers, where I have a common error handler for the exception and I want my actions to behave consistently when a resource matching the given parameters is not found.
You can check if the record exists before fetching it.
#model = Model.find(id) if Model.exists?(id)
Rails 4 Method
if user = User.find_by(id: params[:id])
#do something with user
else
#throw error or redirect
raise ActiveRecord::RecordNotFound
end
You can use find_by with the required attribute (in your case the id) this will return nil instead of giving an error if the given id is not found.
Model.find_by_id(id_value)
You could also use where but you have to know that where return an active record relation with zero or more records you need to use first to return only one record or nil in case zero records return.
Model.where(id: id_value).first
You can simply use:
user = User.find(10) rescue nil

model name to controller name

How can I get the controller name out of the object if I don't know what the object is?
I am trying to do:
object.class.tableize
but Rails says:
undefined method `tableize' for #<Class:0xb6f8ee20>
I tried adding demodulize with same result.
thanks
object.class.to_s.tableize
For semantic reasons, you might want to do:
object.class.name #=> 'FooBar'
You can also use tableize with this sequence, like so:
object.class.name.tableize #=> 'foo_bars'
I prefer it that way due to readability.
As well, note that tableize also does pluralization. If unwanted use underscore.
Hope it helps anyone, even if it's an old thread :)

Resources