Quick Puppet Tip

2012-01-24

While testing my Puppet recipes, I usually boot a new instance then apply configuration manually. I have a base configuration, which is extended with other things. I just noticed I could apply a manifest using STDIN:


1 # ( echo "include sysbase" ; echo "include rabbitmq" ) | puppet apply -v

Viva the Unix tradition!

If you do any kind of configuration management, I highly recommend Puppet.

If you’re like me, you installed PostgreSQL (or MySQL or whatever) locally for development purposes. Then you copied data into your instance, and along you went.

Eventually, you wrote a migration that touched nearly all rows in your database, and you waited, and waited, and waited some more (grab some coffee!) for the migration to finish.

In my case, I had a migration using pure Ruby which took 30 minutes. The migration ran along the lines of:


1 def self.up
2 add_column :segments, :pathname, :string
3
4 # There are 200,000 segments in the DB
5 Segment.find_each do |segment|
6 segment.pathname = segment.full_pathname
7 end
8
9 change_column :segments, :pathname, :string, :null => false
10 end
11
12 class Segment < ActiveRecord::Base
13 belongs_to :parent, :class_name => "Segment"
14
15 def full_pathname
16 full_name = []
17 m = self
18 while m
19 full_name << m.name
20 m = m.parent
21 end
22
23 full_name.reverse.join(" / ")
24 end
25 end

The intent being that we’d cache the segment’s pathname in each record (think a directory tree). Since we had 200,000 segments, and each of those would walk up the chain, it took around 30 minutes to run that migration. I rewrote the migration to use CTE, which PostgreSQL supports out-of-the-box. My migration now ran in 17 minutes or so. I warned my teammates about this migration, and let it be. Eventually, a colleague took my branch and ran the migration. When he reported the migration took 30 seconds for him, at first I thought his SSD was the major difference between our machines. It turns out it wasn’t…


1 # PostgreSQL installed through Homebrew
2 # /usr/local/var/postgres/postgresql.conf
3 # Your file location will vary
4 # …
5 shared_buffers = 2400kB
6 # …

Changing that single line from 2400kB to 96MB let me run the migration in 30 seconds as well… I was very impressed at my stupidity. Let it be a lesson learned!

Back in October 2005, I wrote an article entitled Building the SQL WHERE Clause Dynamically. This article is obsolete and I’m replacing it with this one instead.

What do you do with a FORM that has multiple conditions? Something like this:

The way I would do it today is using named scopes:

app/models/person.rb

1 class Person < ActiveRecord::Base
2 is_taggable
3
4 named_scope :in_region,
5 lambda {|region| {:conditions => {:region => region}}}
6 named_scope :with_salary,
7 lambda {|lower, upper| {:conditions =>
8 {:salary => (lower .. upper)}}}
9
10 # Elided, but something similar to the above
11 named_scope :tagged_with,
12 lambda {|tags| {:conditions =>
13 …}}
14 end

Then you need your controller to translate between what the view provides and what the model expects:

app/controllers/searches_controller.rb

1 class SearchesController < ApplicationController
2 def show
3 root = Person
4 root = root.in_region(params[:region]) unless params[:region] =~ /all/i
5 unless params[:salary] =~ /all/i
6 params[:salary].gsub!("K", "000")
7 lower, upper = params[:salary].split("-")
8 root = root.with_salary(lower, upper)
9 end
10 root = root.tagged_with(Tag.parse(params[:tags])) unless params[:tags].blank?
11 @people = root.paginate(:page => params[:page])
12 end
13 end

For simpler forms, the approach above would be sufficient. For more complex forms, I suggest you look at plugins that aim to provide more structure around your SQL clauses, such as:

I also suggest looking at Searching and Queries Plugins – Agile Web Development to find other plugins.

Back in October 2005, I wrote an article entitled Actions with Dashes. This article is perfectly obsolete and uses a trick instead of the router as it was intended.

Let’s say you want an action where the URL is http://myproject.com/contact-us. The best way to do that currently is to use Rails’ router:

config/routes.rb

1 map.contact_us "/contact-us", :controller => "pages", :action => "show", :page => "contact_us"

app/controllers/pages_controller.rb

1 class PagesController < ActionController
2 def show
3 render :action => params[:page]
4 end
5 end

The PagesController above is also good for any other static pages you want to serve on your site.

If you want to use the Classifier gem within a Rails project, you’re in for a surprise:


1 /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:102:in `sum’: wrong number of arguments (1 for 0) (ArgumentError)
2 from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:102:in
`
instance_method_already_implemented?
3 from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:72:in `define_attribute_methods

4 from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:71:in `each’
5 from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:71:in
`
define_attribute_methods
6 from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:351:in `respond_to?

7 from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/associations/association_proxy.rb:219:in `method_missing’
8 from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/associations/association_proxy.rb:219:in
`
each
9 from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/associations/association_proxy.rb:219:in `send

10 from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/associations/association_proxy.rb:219:in `method_missing’
11 from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/associations/association_collection.rb:364:in
`
method_missing_without_paginate
12 from /Users/francois/Projects/family_budget/vendor/gems/mislav-will_paginate-2.3.5/lib/will_paginate/finder.rb:167:in `method_missing

13 from script/classifier:8

This is because the Classifier gem implements Array#sum. Unfortunately, Classifier is loaded after Rails, which means ActiveSupport’s own #sum is replaced. The solution? Use either Yury’s fork, or my own. Both use ActiveSupport’s #sum instead of a custom implementation.

The only difference between my version and Yury’s is I use Jeweler to maintain and package the gem.

While discussing in Campfire with James Golick today, James said he would prefer to have his output directly in Vim, our editor of choice. I initially said that since Vim isn’t Emacs and can’t support a live console, it wouldn’t work. James told me to think outside the box.


1 $ autotest &>tmp/autotest.out &
2 $ vim
3 # :e tmp/autotest.out

Yes, thinking outside the box, and knowing your tools makes it much easier. So far, I’m liking autotest output in Vim very much. It’s easy to navigate the output using Vim movement commands: /Person finds the errors in person, gf opens the named file, etc. All in all, this is a pretty good experience.

This evening, I was fooling around with mod_passenger for XLsuite. I ran into a little problem where Passenger was telling me “No such file or directory – /nonexistent”. A quick search on Google revealed Phusion Passenger error “No such file or directory – /nonexistent”.

That post really made my day.

I wanted to paste some HTML into Vim, and I was having a hard time getting something that preserved existing indentation.

After doing :help autoindent, I found out about smartindent. So, I turned both smartindent and autoindent off before doing my paste. That worked wonders!


1 :set nosmartindent
2 :set noautoindent
3 i
4 <CMD>-V
5 <ESC>
6 :set smartindent
7 :set autoindent

Voilà, a correctly pasted, non-indented version of whatever was in the clipboard!

UPDATE: 2 minutes after I posted the link to this article on Twitter, @jpalardy replied:

I put everything I code into ~/Documents/work. Could be called projects, same thing. Anyway, everytime I opened Terminal, I would have to type cd Documents/work. Of course, with tab completion, it’s not so bad. I’m a bit stupid though, and it takes me eons before I added cd ~/Documents/work to the end of my ~/.bash_profile.

Whenever I open a Terminal, I’m really going to start doing something in this folder. Now, why didn’t I think of that earlier? Next: retrain myself to not cd anywhere when I open a new Terminal…

Here’s my full ~/.bash_profile:

~/.bash_profile

1 #!/usr/bin/sh
2
3 # Installed by Git OSX Installer from
4 # http://code.google.com/p/git-osx-installer/
5 export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Home
6 export ANT_HOME=/usr/share/ant
7 export JRUBY_HOME=~/Library/Java/JRuby/current
8 export MYSQL_PATH=/usr/local/mysql
9 export PATH=~/bin:/opt/local/sphinx/bin:/usr/local/git/bin:$MYSQL_PATH/bin:$PATH:$JRUBY_HOME/bin
10 export MANPATH=/usr/local/git/man:$MANPATH
11 export EDITOR=vim
12
13 # And this is from:
14 # http://code.google.com/p/git-osx-installer/
15 export PS1=’\w$(git branch &>/dev/null; if [ $? -eq 0 ]; then echo " (\[\033[00m\]$(git branch | grep ^|sed s/\\ //))"; fi) \$\[\033[00m\] ’
16
17 export VISUALWORKS=~/Applications/vw7.6nc
18 export AWS_ACCESS_KEY_ID= # your AWS access key, for cliaws gem
19 export AWS_SECRET_ACCESS_KEY= # your AWS secret access key, for cliaws gem again
20
21 # http://www.macosxhints.com/article.php?story=20060502160527780&query=terminal%2Btitle
22 function settitle() { echo -ne "\e]2;$\a\e]1;$\a"; }
23
24 if [ -f ~/.bash_aliases ]; then
25 source ~/.bash_aliases
26 fi
27
28 cd ~/Documents/work

I just found about Media Convert, a free online tool that converts files from one format to another. I converted an FLV to SWF, and it worked flawlessly. Their converters also convert from many other formats. Hope this helps someone.

Today, I was replacing RailsCron and BackgrounDRb with generated Daemons, and I happened to investigate how Rails sets up it’s logger. For the longest time, I knew it was possible to change the options, but I just never investigated how to do it. I thought I’d share my findings so others won’t be in the dark as I was.

Actually doing the replacement is very easy:

config/environment.rb

1 Rails::Initializer.run do |config|
2 config.logger = Logger.new(File.dirname(FILE) + "/../log/#{RAILS_ENV}.log")
3 end

That’s it. Have fun. Stop reading… Unless you want more details.

This is essentially what Rails does, except now you have complete control over how the Logger is instantiated.

Some options you might want to investigate:


1 # Keep at most 2 2 megabytes log files
2 config.logger = Logger.new(File.dirname(FILE) + "/../log/#{RAILS_ENV}.log", 2, 210241024)
3
4 # Create a new log file each day
5 config.logger = Logger.new(File.dirname(FILE) + "/../log/#{RAILS_ENV}.log", "daily")

If you are running a script which loads the Rails environment manually, you can also do this:

lib/daemons/futures_worker.rb

1 #!/usr/bin/env ruby
2
3 # You might want to change this
4 raise "No RAILS_ENV defined" if ENV["RAILS_ENV"].to_s.empty?
5
6 require "logger"
7
8 RAILS_DEFAULT_LOGGER = Logger.new(File.dirname(FILE) + "/../../log/futures_runner.rb.log", 3, 210241024)
9 require File.dirname(FILE) + "/../../config/environment"
10
11 # Other code as appropriate

The Rails::Initializer is smart enough to use either the RAILS_DEFAULT_LOGGER or the one defined in the configuration block. For the gory details, please read Rails::Initializer#initialize_logger

I just received a mail from Walter McGinnis asking:

In other words start a new client project based on an existing open source project that is an entire rails application like Typo or Mephisto and be able to update, modify, and merge accordingly like you would for plugins with piston.

Walter proposed to use the following technique:


1 svn checkout repos_for_new_client_project new_client_project
2 cd new_client_project
3 piston import repos_for_third_party_open_source_rails_app_like_typo/trunk/app app
4 piston import repos_for_third_party_open_source_rails_app_like_typo/trunk/db db

This would work, but as Walter says:

… it doesn’t seem very DRY.

Since this isn’t the first time I am being asked the question, I decided to investigate a solution. I ended up with something that works just fine. The technique boils down to simply not creating trunk/ and using Piston to create that folder. You have to start from a fresh project to use this technique.


1 $ piston version
2 Piston 1.3.3
3
4 $ svn checkout svn://my-server/project/ ~/project/
5 Checked out revision 0.
6
7 $ cd ~/project/
8
9 $ piston import \
10 http://svn.techno-weenie.net/projects/mephisto/trunk/ \
11 trunk/
12 Exported r2856 from ‘http://svn.techno-weenie.net/projects/mephisto/trunk/’ to ‘trunk/’
13
14 $ svn commit —message "Imported Mephito trunk@2756"
15
16 # Some time later…
17 $ cd ~/project/
18 $ piston update .
19 Processing ‘.’…
20 Fetching remote repository’s latest revision and UUID
21 Restoring remote repository to known state at r2756
22 Updating remote repository to r2857
23 Processing adds/deletes
24 Removing temporary files / folders
25 Updating Piston properties
26 Updated to r2857 (56 changes)
27 $ svn commit —message "Updated Mephisto to r2857 (56 changes)"

If you use Piston to manage sub-directories (such as vendor/rails), everything will still work:


1 $ svn update
2 At revision 2.
3
4 $ piston import http://dev.rubyonrails.org/svn/rails/trunk vendor/rails/
5 Exported r6957 from ‘http://dev.rubyonrails.org/svn/rails/trunk’ to ‘vendor/rails’
6
7 $ svn commit —message "Imported Rails into vendor/rails at r6957"
8
9 $ svn update
10
11 $ piston update
12 Processing ‘.’…
13 Fetching remote repository’s latest revision and UUID
14 unchanged from revision 2857
15 Processing ‘vendor/rails’…
16 unchanged from revision 6957

I just hit upon a technique which I haven’t seen before. It is akin to using modules to add behaviour to an object. Let me just present the technique and discuss it afterwards:

app/models/future.rb

1 class Future < ActiveRecord::Base
2 def execute
3 # execute the Future
4 end
5
6 def execute_with_interval
7 execute_without_interval
8
9 if completed? && interval then
10 # schedule a copy of this Future to execute at
11 # a later date/time
12 end
13 end
14
15 alias_method_chain :execute, :interval
16 end

A Future is an object that promises to return a result sometime in the future. I am using it here in the context of a Rails application.

Some of my futures are repeatable tasks, while others are simply one-shot, fire-and-forget. The method I want to introduce is how I added behaviour to an existing method within the same class. I did not include an extra module, nor did I introduce subclasses.

To tell you the truth, I initially implemented RecurringFuture to add the behaviour there, but I had already created a RetsFuture that includes additional behaviour specific to talking with a RETS server.

In my RecurringFuture implementation, I had already implemented #execute_with_interval and the correct #alias_method_chain. What I did is I simply pulled the code up, and deleted the now extraneous subclass.

I don’t know if this is a common technique, but I just found it very useful to have a clear separation of concerns: one method executes, the other recurs.

Even though Capistrano “tags” each release by creating a new folder on the production server(s), it might be interesting to have a historical perspective in your repository anyway.

This makes it easier to know exactly what went up for a release.

I would like to share the following Capistrano recipe for your pleasure:

config/deploy.rb

1 require uri
2 task :after_deploy do
3 source = repository
4 dest = URI.parse(repository).merge("../releases/#{File.basename(release_path)}")
5 cmd = "svn copy —revision=#{revision} —quiet —message \"Auto tagging release #{release_path}\" #{source} #{dest}"
6 puts cmd
7 `#{cmd}`
8 end

First, we start by requiring uri, because Subversion does not like relative URLs.

Next, we find the location into which to tag the release, and finally, we just do it.

Simple, effective.

Enjoy !

CriteriaQuery is a Rails plugin which eases arbitrary query building.

From the README:

If you frequently face the problem of constructing complex dynamic queries, you will get some benefit out of this. Consider the following search form (taken from a real application):

Name (first or last): [               ]
Customer Category: [ Please Select ][^]
Last call between: [ ] and [ ]
Active Contacts only: [x]
Address
State: [ ]
City: [ ]
Street Addres: [ ]




CriteriaQuery allows you to write the above as:



1 pq = Person.query
2
3 pq.disjunction.first_name_eq(params[:name]).last_name_eq(params[:name]) if params[:name]
4 pq.category_id_eq(params[:category]) if params[:category]
5
6 address = pq.join("address")
7 address.state_eq(params[:address[:state]]) if params[:address[:state]]
8
9 end




1 pq = Person.query
2
3 pq.disjunction.first_name_eq(params[:name]).last_name_eq(params[:name]) if params[:name]
4 pq.category_id_eq(params[:category]) if params[:category]
5
6 address = pq.join("address")
7 address.state_eq(params[:address[:state]]) if params[:address[:state]]
8
9 end

This is all fine and dandy, except using the current version (r41 from the repository), you cannot add arbitrary conditions, such as LENGTH(name) &gt; 0.

ActiveRecord scopes to the rescue. I recently hit that in an application where we allow the admin to find only non anonymous estimates. Non anonymous is defined as having either a phone or an E-Mail address.


1 conds = LENGTH > 0 OR LENGTH > 0 if filters</span>[<span class="sy">:non_anonymous</span>] <span class="no">2</span> <span class="co">Estimate</span>.with_scope(<span class="sy">:find</span> =&gt; {<span class="sy">:conditions</span> =&gt; conds}) <span class="r">do</span> <span class="no">3</span> <span class="iv">estimates = query.find(:order => updated_at DESC)
4 end

Of course, you will ask “why in the world aren’t your fields ”sql">NULL to begin with if they don’t contain a value ?" Now, that is a very interesting question which I will now explore with gusto !

However you cut it, sometimes there are differences between the development and production environments.

For example, if you are integrating your website with PayPal, you probably want your local tests in the development environment to hit https://developer.paypal.com/, and not the main site at https://www.paypal.com/.

In my applications, I always store the URL to the PayPal service in a Setting or Configuration object. This allows me the flexibility of changing the value whenever I need to.

Of course, I use Migrations to generate my tables, or pre-populate them with data. Here is a typical migration:

db/migrate/023_setup_paypal_config.rb

1 class SetupPaypalConfig < ActiveRecord::Migration
2 def self.up
3 config = Configuration.find_or_create_by_name(paypal.url)
4 config.value = https://www.paypal.com/
5 config.save!
6 end
7
8 def self.down
9 Configuration.find_or_create_by_name(paypal.url).destroy
10 end
11
12 class Configuration < ActiveRecord::Base; end
13 end

Nothing prevents me from keying off RAILS_ENV like this:


1 config.value = case RAILS_ENV
2 when development
3 https://developer.paypal.com/
4 else
5 https://www.paypal.com/
6 end
7 config.save!

An alternative would be to store the PayPal URL in a constant, and initialize the constant in config/environments/*.rb. This means much less code. But in my case, I usually have a need for a configuration-like object where admins of the system can change some values.

Since 1.1.0, Rails includes grouping and calculation functions. maximum is what we want to use here.

The system I am building has many users, and each user has it’s own invoice number sequence. Here is the code in it’s entirity:

app/models/invoice.rb

1 class Invoice < ActiveRecord::Base
2 belongs_to :user
3
4 def self.find_next_available_number_for(user, default=999)
5 (user.invoices.maximum(:no) || default).succ
6 end
7 end

This code is pretty simple to read. In fact, in English, it translates to:

Find the maximum value of no in invoices for user, or use the default value, and then take the successor of this value.

You see, Ruby code is so much more succint !

Anyway, the features used in this article were:

In Ruby, methods automatically define begin/end blocks. That makes it easy to write exception handlers and ensure blocks:


1 def access!(options={})
2 raise AuthorizationFailure if self.email != options[:email]
3 self.download_count += 1
4
5 rescue AuthorizationFailure
6 self.unauthorized_access_count += 1
7 raise
8
9 ensure
10 self.save!
11 end

It is important to re-raise the exception in the exception handler, or else the exception will be silently thrown away.

Over on rails-core, I posted Edge Rails fails saving parent when has_many child ?.

The models I am using are:

app/models/invoice.rb

1 class Invoice < ActiveRecord::Base
2 belongs_to :customer
3 has_many :lines, :class_name => InvoiceItem
4 validates_presence_of :no, :customer
5 end

app/models/invoice_item.rb

1 class InvoiceItem < ActiveRecord::Base
2 belongs_to :invoice
3 validates_presence_of :invoice
4 end

As is, it was impossible to use the normal build and save idiom:


1 $ ruby script\console
2 Loading development environment.
3 >> invoice = Invoice.new
4 => #<Invoice:0×3a76060 …>
5 >> line = invoice.lines.build
6 => #<InvoiceItem:0×3a5d610 …>
7 >> invoice.save!
8 ActiveRecord::RecordInvalid: Validation failed: Lines is invalid
9 from
10 ./script/../config/../config/../vendor/rails/activerecord/lib/active_record/validations.rb:736:in
11 `save!’
12 from (irb):3
13 >> puts line.errors.full_messages
14 Invoice can’t be blank
15

Well, of course. I know invoice can’t be blank. If I remove :invoice from the validates_presence_of, things work out fine:


1
2 >> invoice.save!
3 => nil
4 >> invoice.new_record?
5 => false
6 >> line.new_record?
7 => false
8 >> line.invoice
9 => nil
10 >> # Huh?

Digging into the code, ActiveRecord::Associations::AssociationProxy#set_belongs_to_association_for it turns out that only the foreign key is assigned to the child instance, not the full parent model. The behavior as seen above is therefore “normal”.

Turns out that if I validate the foreign key instead, things work perfectly:

app/models/invoice_item.rb

1 class InvoiceItem < ActiveRecord::Base
2 belongs_to :invoice
3 validates_presence_of :invoice_id
4 end


1
2 >> invoice.save!
3 => nil
4 >> line.new_record?
5 => false
6 >> line.invoice
7 => #<Invoice:0×39fbea8 …>

Lesson learned: don’t validate the presence of the associated model, only it’s foreign key.

In one of my projects, I wanted to prevent instances of particular classes to be deleted if they were in any way associated to another object. In database terms, I wanted an ON DELETE RESTRICT constraint.

Since I cannot rely on the database to enforce it for me (MySQL 4, MyISAM engine), I coded the following:

test/unit/city_test.rb

1 require File.dirname(FILE) + /../test_helper
2
3 class CityTest < Test::Unit::TestCase
4 fixtures :cities, :contacts
5
6 def setup
7 city</span> = <span class="co">City</span>.find(<span class="sy">:first</span>) <span class="no"> 8</span> <span class="r">end</span> <span class="no"> 9</span> <span class="no"><strong>10</strong></span> <span class="r">def</span> <span class="fu">test_prevent_destruction_if_associated_to_any_contact</span> <span class="no">11</span> <span class="iv">city.contacts << contacts(:jill)
12 city</span>.destroy <span class="no">13</span> assert_not_nil <span class="co">City</span>.find(<span class="iv">city.id), should not have been destroyed
14 assert_match /cannot destroy.*contacts/i, city</span>.errors.on_base, <span class="no"><strong>15</strong></span> <span class="s"><span class="dl">'</span><span class="k">reports error condition to user</span><span class="dl">'</span></span> <span class="no">16</span> <span class="r">end</span> <span class="no">17</span> <span class="no">18</span> <span class="r">def</span> <span class="fu">test_allow_destruction_if_not_associated_to_any_contact</span> <span class="no">19</span> <span class="iv">city.destroy
20 assert_raises ActiveRecord::RecordNotFound do
21 City.find(@city.id)
22 end
23 end
24 end

app/models/city.rb

1 class City < ActiveRecord::Base
2 has_and_belongs_to_many :contacts, :join_table => contacts_cities
3
4 def destroy
5 unless self.contacts.empty?
6 self.errors.add_to_base \
7 We cannot destroy this instance since one or more contacts refer to it)
8 return
9 end
10
11 super
12 end
13 end

I had to override destroy because in has_and_belongs_to_many relationships, Rails deletes join table records before deleting the record. This means that in before_destroy filters, self.contacts.empty? will always report true. Ticket #1183: dependents are destroyed before client before_destroy hooks are called is already opened on this issue.

UPDATE (2006-03-08) Fixed since 2006-02-13

Most of the Rails application I build these days have no use whatsoever for the autocomplete feature modern browsers.

FireFox and Internet Explorer have extended the input element to provide an autocomplete attribute.

This attribute may be set to on or off, indicating whether autocomplete should be enabled, on a field-per-field basis.

In Rails, that translates to:


1 <%= text_field :payment, :credit_card,
2 :autocomplete => ‘off’ %>


Obviously, if we have ten fields on the form, that would not be very DRY

Instead, you can do the following:

app/helpers/application_helper.rb

1 module ApplicationHelper
2 def text_field(args)
3 unless args.last.kind_of?(Hash) then
4 args << {}
5 end
6
7 args.last[:autocomplete] = off \
8 unless args.last[:autocomplete]
9 super(
args)
10 end
11 end

This is great when most of your fields need to have autocomplete disabled. But, while researching this topic, I stumbled on How to Turn Off Form Autocompletion
for Mozilla based browsers.

You do it this way:


1 <%= start_form_tag({}, {:autocomplete => ’off’}) >
2
3 <= end_form_tag %>


The same trick as above for overriding text_field_tag can be used to override form_tag.

Actions with dashes

2005-10-25

This article is obsolete. You may find it’s replacement at Actions With Dashes – updated

Want to get an action with a dash in it’s name and still do something useful ? Try this:


1 class WelcomeController < ApplicationController
2 define_method("sign-up".to_sym) do
3 if request.post? then
4 # Do something
5 end
6 end
7 end

Of course, if you don’t have anything to do, just name the view file appropriately: app/views/welcome/sign-up.rhtml.

Search

Your Host

A picture of me

I am François Beausoleil, a Ruby on Rails and Scala developer. During the day, I work on Seevibes, a platform to measure social interactions related to TV shows. At night, I am interested many things. Read my biography.

Top Tags

Books I read and recommend

Links

Projects I work on

Projects I worked on