Notes posted to Ruby on Rails
RSS feedFormat not coming out properly?
Date, Time and DateTime may have different formats defined.
If you do:
@user.created_at.to_formatted_s(:long_ordinal)
You will get (or something):
April 16th, 2009 22:03
But if you do:
@user.created_at.to_date.to_formatted_s(:long_ordinal)
You will get:
April 16th, 2009
So, be sure you know which one you are working with.
Do not forget to add indexes
Don’t forget to add indexes to HATM table:
add_index :developers_projects, [:developer_id, :project_id]
Merges with inherited values from super class
http://www.spacevatican.org/2008/8/19/fun-with-class-variables
“When you set a class_inheritable_array or a class_inheritable_hash you are actually concatenating (or merging) with the value inherited from the super class.”
Code example
class Base class_inheritable_hash :attrs self.attrs = {:name => 'Fred'} end class Derived < Base self.attrs = {:export => 'Pain'} end Derived.attrs #=> {:name => 'Fred', :export => 'Pain'}
strip_tags method not functioning in controllers, models, or libs
It comes up with an error about white_list_sanitizer undefined in the class you’re using it in. To get around this, use:
ActionController::Base.helpers.strip_tags('string')
To shorten this, add something like this in an initializer:
class String def strip_tags ActionController::Base.helpers.strip_tags(self) end end
then call it with:
'string'.strip_tags
strip_links method not functioning in controllers, models, or libs
It comes up with an error about white_list_sanitizer undefined in the class you’re using it in. To get around this, use:
ActionController::Base.helpers.strip_links('string')
To shorten this, add something like this in an initializer:
class String def strip_links ActionController::Base.helpers.strip_links(self) end end
then call it with:
'string'.strip_links
sanitize method not functioning in controllers, models, or libs
It comes up with an error about white_list_sanitizer undefined in the class you’re using it in. To get around this, use:
ActionController::Base.helpers.sanitize('string')
To shorten this, add something like this in an initializer:
class String def sanitize ActionController::Base.helpers.sanitize(self) end end
then call it with:
'string'.sanitize
Corrected link to ActiveRecord's "new_record?" method
rafaelrosafu, the correct tink to ActiveRecord’s “new_record?” method is:
"http://apidock.com/rails/ActiveRecord/Base/new_record%3F"
Cycle with first and last
I needed a cycle that was also aware of the first and last items in the collection. This is adapted from a snippet I found while Googling:
def cycle_with_first_last(object, collection, options = { }) addition = "" addition += " #{options[:first]}" if object == collection.first addition += " #{options[:last]}"if object == collection.last cycle(options[:odd], options[:even]) + addition end
Just put that in your helpers…
Example
This function can be used to pass the ID of selected item, for example:
# with select or collection_select helpers: { :onchange => remote_function(:url => { :action => 'do_smth' }, :with => "'id=' + $('the_id').value") } # and grab ID in controller action as usually: YourModel.find(params[:id])
Testing HTTP Digest authentication
Testing HTTP Digest authentication is a bit tricky. I wrote a post describing how to accomplish it.
http://lightyearsoftware.com/blog/2009/04/testing-http-digest-authentication-in-rails/
Note also that Digest auth is broken for REST actions using PUT or DELETE. There is an open Lighthouse ticket for this, #2490:
rails.lighthouseapp.com/projects/8994/tickets/2490-http-digest-auth-uses-wrong-request-method-for-put-delete
Using global $! to address exception
@noxyu3m: Your code is actually syntactically wrong. The global is called $!
Your code should have been:
def create @model = Model.new(params[:model) @model.save! rescue logger.error($!.to_s) end
Although I would prefer
def create @model = Model.new(params[:model) @model.save! rescue ActiveRecord::RecordInvalid logger.error($!.to_s) end
to only catch expected exceptions, just like the documentation proposed.
Re: Force initial value
An alternative to @eric_programmer’s would be to extract it entirely from the controller logic…
class Person def sender self[:sender] || 'contact@host.com' end end
There are a ton of ways to do it, but making this a business logic decision will let you get the same logic from any possible implementation angle. Scripts, web service, etc.
To find a tag with an id or class
assert_select ‘td.highlight’, { :count => 2 }
finds 2 td tags with the highlight class.
assert_select ‘div#special’
finds div with id=special
Takes attribute as a symbol
Attribute must be passed as a symbol:
User.toggle(:funny)
not
User.toggle(funny)
Deprecation warning for old-style options
You will get a warning if you don’t define your separators as a hash:
DEPRECATION WARNING: number_with_delimiter takes an option hash instead of separate delimiter and precision arguments
So while you can still use that style, it’s not without a scolding.
Outside of app code
How do I call this from script/console?
Define handlers in order of most generic to most specific
The later the definition of the rescue handler, the higher the priority:
rescue_from Exception, :with => :error_generic rescue_from Exception::ComputerOnFire, :with => :panic
Declaring the Exception catch-all handler last would have the side-effect of precluding any other handlers from running.
This is what is meant by being “searched…from bottom to top”.
Method has moved to ActionController::Rescue::ClassMethods module
This method has simply moved, still works the same way in 2.3+
New location: ActiveSupport::Rescuable::ClassMethods#rescue_from
Like select_values for multiple values
The names are somewhat confused:
Model.connection.select_values("SELECT id,name FROM users") => ["1","2","3"] Model.connection.select_rows("SELECT id,name FROM users") => [["1","amy"],["2","bob"],["3","cam"]]
select_values returns Strings for MySQL
This method will return all values as strings from MySQL. It is easy to convert if required, for example, to integers:
select_values("SELECT id FROM companies LIMIT 3") => ['1','2','3'] select_values("SELECT id FROM companies LIMIT 3").collect(&:to_i) => [1,2,3]
use create table :force => true
if you want to drop a table before creating one in a migration, use the :force => true option of the create_table method
Setting child_index while using nested attributes mass assignment
When using nested attributes mass assignment sometimes you will want to add new records with javascript. You can do it with pure javascript, but if HTML is long your javascript will be long and messy and it will not be DRY as probably you already have a partial for it.
So to add a partial dynamically you can do something like that (notice string “index_to_replace_with_js”):
link_to_function
def add_object_link(name, form, object, partial, where) options = {:parent => true}.merge(options) html = render(:partial => partial, :locals => { :form => form}, :object => object) link_to_function name, %{ var new_object_id = new Date().getTime() ; var html = jQuery(#{js html}.replace(/index_to_replace_with_js/g, new_object_id)).hide(); html.appendTo(jQuery("#{where}")).slideDown('slow'); } end
js method in one of helpers (from minus mor plugin)
def js(data) if data.respond_to? :to_json data.to_json else data.inspect.to_json end end
This method will generate link adding generated partial to html.
The thing that is not mentioned in docs is how to set child_index. You must add it as an argument in hash.
Example of partial
<% form.fields_for :tasks, task, :child_index => (task.new_record? ? "index_to_replace_with_js" : nil) do |tasks_form| %> <% tasks_form.text_field :name %> <% end %>
Using add_object_link
<% form_for :project do |form| %> <div id="tasks"> <%# displaying existing tasks %> </div> <%= add_object_link("New task, form, Task.new, "task", "#tasks") %> <% end %>
Thanks to child_index after insertion it will change indexes to current time in miliseconds so added tasks will have different names and ids.
Translations of label method
The label method won’t use your translated attribute names - which seems like big disadvantage of this method.
For a quick workaround, try using this in a helper:
def label(object_name, method, text = nil, options = {}) text ||= object_name.classify.constantize.human_attribute_name(method.to_s) ActionView::Helpers::InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options) end
I didn’t properly test this, but it seems to work.
Superclass of OrderedHash
Note that in Rails 2.3, OrderedHash changed from being a subclass of Array to a subclass of Hash. This is contrary to what the documentation says above.
HTML entities in options
Unfortunately everything is escaped with ERB::Util#html_escape. Your only option is either manually construct options or compeletely overwrite this method.
Array clustering
Sometimes you don’t want to mangle sequence of an array and just want to group adjacent values. Here’s a nice method to do so (drop it in your initializers directory or something):
module Enumerable # clumps adjacent elements together # >> [2,2,2,3,3,4,2,2,1].cluster{|x| x} # => [[2, 2, 2], [3, 3], [4], [2, 2], [1]] def cluster cluster = [] each do |element| if cluster.last && yield(cluster.last.last) == yield(element) cluster.last << element else cluster << [element] end end cluster end end
Similarly you can do the clustering on more complex items. For instance you want to cluster Documents on creation date and their type:
Document.all.cluster{|document| [document.created_on, document.type]}
Take care when writing regex
When you want to validate a field for a continuous string you’d probably write something like this (if it’s really early in the morning and you didn’t have your coffee yet):
validates_format_of :something => /\w/
At the first sight it looks like it’s working because something = “blahblahblah” is valid. However, so is this: something = “blah meh 55”. It’s just that your regex matched a substring of the value and not the whole thing. The proper regex you’re looking for is actually:
validates_format_of :something => /^\w$/