Good notes posted to Ruby on Rails
RSS feedRE: FALSE: Creates record by given attributes only if table is empty
Dino’s comment was almost certainly due to a misunderstanding of the API, a mistake I made myself the first time I used it, and one I’ve witnessed multiple others make as well.
The fact that this method takes an attributes parameter leads one to think the passed params are the criteria for the selection. At first glance it would appear it would be used like this:
User.first_or_create(email: 'derek@somwhere.com')
Where in reality this would grab the “first” record (say w/ email “john@smith.com”), and change its email to “derek@somwhere.com”, a fairly surprising action that doesn’t obviously fail, leading to some pretty subtle bugs.
In reality it should be used like this:
User.where(email: 'derek@somwhere.com').first_or_create
And the attributes param. isn’t used in 99% of use cases. (If at all - the provision for the block that executes only on create fully duplicates what the attributes parameter can do in a much more obvious way.)
IMHO this is simply a bad API that one just needs to be aware of. But it’s unfair to knock dino for what’s likely a highly common misreading.
Creates record by given attributes only if table is empty
This method first searches the table for ANY FIRST RECORD, not the one matching given attributes. If no record is found at all, it creates one using the specified attributes. This might be misunderstood in many cases.
Text improvement
Where it says: without loading a bunch of records should say: without loading a bunch of columns/attributes Considering that record usually is a row.
Opposite of persisted?
So I can find it when I look next time.
Find the First Instance in the Table. If None Exists, Create One.
Specify the data you’re looking for. If it exists in the table, the first instance will be returned. If not, then create is called.
If a block is provided, that block will be executed only if a new instance is being created. The block is NOT executed on an existing record.
Code example
MyStat.where(name: statistic_name).first_or_create do |statistic| statistic.value = calculate_percentage statistic.statistic_type = "percentage" end
In Rails 4 it DOES return nil, even if the object you try from isn't nil
The try method does not raise a NoMethodError if you call a method that doesn’t exist on an object.
a = Article.new a.try(:author) #=> #<Author ...> nil.try(:doesnt_exist) #=> nil a.try(:doesnt_exist) #=> nil
Note:
a.try(&:doesnt_exist) #=> Raises NoMethodError
Use strings as parameters, not booleans
I just stumbled across this somewhere in our codebase. The first example is faulty, the second one is correct.
= f.check_box :public, {}, true, false # <input id="event_public" name="event[public]" type="checkbox" value="true" />
and:
= f.check_box :public, {}, "true", "false" # <input name="event[public]" type="hidden" value="false" /> # <input id="event_public" name="event[public]" type="checkbox" value="true" />
use validates :name, :presence => true instead
validates_presence_of is a holdover from the Rails 2 days.
This is the way it is done now http://guides.rubyonrails.org/active_record_validations_callbacks.html#presence
Beware - May cause performance issues
A serialized attribute will always be updated during save, even if it was not changed. (A rails 3 commit explains why: http://github.com/rails/rails/issues/8328#issuecomment-10756812)
Guard save calls with a changed? check to prevent issues.
class Product < ActiveRecord::Base serialize :product_data end
bad
product = Product.first product.save
good
product = Product.first product.save if product.changed?
rest of code is in NilClass#try
If you click “Show source” here, you may get confused. The logic for #try is shared between this method and NilClass#try . Both versions are currently implemented in the file activesupport/lib/active_support/core_ext/object/try.rb .
HTML5 data- attributes using RESTful approach
HTML5 specifies extensible attributes like data-foo=“bar” (or as in Twitter Bootstrap data-toggle=“modal”), which poses two problems for Rails.
First, if you’re using symbol notation in link_to to specify attributes, this fails (dash is not a valid symbol character), so
Invalid!
link_to "Edit", @user, :class => "btn", :data-toggle => "modal"
There are two solutions:
-
put the symbols in quotes,
-
use the special :data hash
Solution 1: Quote Symbols
link_to "Edit", @user, :class => "btn", "data-toggle" => "modal"
Solution 2: Use the :data hash
link_to "Edit", @user, :class => "btn", :data => {:toggle => "modal"}
Resulting HTML
<a href="/users/1" class="btn", data-toggle="modal">Edit</a>
The second is minimally documented, but as a hash, can accept multiple values and is perhaps a little cleaner
Makes it possible to use a scope through an association
This is a very useful method if you want to to use a scope through an association:
class Book < ActiveRecord::Base scope :available, where(:available => true) end class Author < ActiveRecord::Base has_many :books scope :with_available_books, joins(:books).merge(Book.available) end # Return all authors with at least one available book: Author.with_available_books
See http://asciicasts.com/episodes/215-advanced-queries-in-rails-3 for more info.
flash messages
In rails 3.1 the following does not work for me
redirect_to { :action=>'atom' }, :alert => "Something serious happened"
Instead, you need to use the following syntax (wrap with parens)
redirect_to({ :action=>'atom' }, :alert => "Something serious happened")
Links and basic explanation
This function, available on any Base derived class, allows eager loading. So when iterating over a result, all the data can be pre-cached, reducing the number of queries from 1+x to 2.
Several ways to use this include:
Post.includes(:author).where(..)
Post.includes([:author, :comments]).where(..)
Post.includes( :comments => :replies ).where(..)
The result is a Relation, so where and other function may be called.
.includes(:comments).to_a is the same as , .find(:all, :includes => :comments)
Some good documentation here: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
Apperently one can get similar results by using joins, but i’m not expert on joins, so please ammend and read: http://guides.rubyonrails.org/active_record_querying.html
Catching and throwing -- don't!
@wiseleyb and @glosakti, neither of your suggestions are necessary, and both are bad practices.
This test:
test "transactions" do assert_raises ZeroDivisionError do User.transaction do 1/0 end end end
passes just fine on its own, with the transaction rolled back as you’d expect. No need to hack something ugly together.
Removed in 3.1.x
This method (and #auto_link_urls) has been removed in Rails 3.1 - other options are out there, such as Rinku, however there is a gem you can use for migration purposes etc, which is rails_autolink: http://rubygems.org/gems/rails_autolink
Replaced by :on => :create
From rails 3,
before_validation_on_create
has been removed and replaced with:
before_validation :foo, :on => :create
Undocumented :location option
You can use undocumented :location option to override where respond_to sends if resource is valid, e.g. to redirect to products index page instead of a specific product’s page, use:
respond_with(@product, :location => products_url)
How to submit current url
For example to change some kind of param on select change…
<%= form_tag({}, {:method => :get}) do %> <%= select_tag :new_locale, options_for_select(I18n.available_locales, I18n.locale), :onchange => "this.form.submit();" %> <% end %>
finding without default scopes in rails 3
if you want to find without default scopes in rails 3 and with_exclusive_scope is giving you protected method errors in controllers, use unscoped for a similar purpose
Change to the way the block is handled
At least in 3.0.5, some of the previous examples no longer work: ActionView seems to quietly ignore Array content.
If you were using code of the form
content_tag(:li, nil, :class => 'someClass') { arr.collect { |x| content_tag(:ul, x) } }
it now needs to look like
content_tag(:li, nil, :class => 'someClass') { arr.reduce('') { |c, x| c << content_tag(:ul, x) }.html_safe }
How to specify :only_path when non-hash options
When passing in an object, as opposed to a hash, you can’t do this because url_for accepts one argument:
url_for(post, :only_path => true)
Instead, do this:
polymorphic_url(object, :routing_type => :path)
Timestamps
Note that ActiveRecord will not update the timestamp fields (updated_at/updated_on) when using update_all().
Dont use _delete
Most blog articles about accepts_nested_attributes_for, including the one from @mattsa and @annaswims, tell you to add a
'_delete' => 1
when you want a deletion checkbox, hidden attribute, etc.
But this stopped being true a while ago. This is just a “Watch Out!” Make sure you use
'_destroy' => 1
instead.
Using namespaces
If you are using a namespace in your routes.rb, for example:
namespace :admin do resources :products end
then you can:
url_for([:admin, @product])
and:
url_for([:edit, :admin, @product])