Rails

From WhyNotWiki
Revision as of 06:28, 16 February 2007 by Tyler (Talk | contribs)
Jump to: navigation, search

See also:

Contents

Getting started / tutorials / books

ONLamp.com tutorial

Ruby syntax highlighting in vim

Testing

Crossing borders: Testing in integrated frameworks, Part 1

Crossing borders: Testing in integrated frameworks, Part 2

Reference/reminders

Advice: "Work on the models and domain logic, trying stuff out in the console. Leave the controller/view stuff ’til later…" (http://woss.name/2006/05/07/notes-from-a-rails-course/)

"config/database.yml goes through the erb processor before YAML gets it." (http://woss.name/2006/05/07/notes-from-a-rails-course/) So you can put Ruby code in there.

Checklist for creating a new Rails application

  • rails app_name
  • add to Subversion (see "balloon" plugin)
  • change session ID
  • set up ExceptionNotifier

How to change the session ID

ActionController::Base.session_options[:session_key] = 'app_name_session_id'

Why? "It’s a good idea to change the session cookie’s key to prevent conflicts with other Ruby apps from the same server. Otherwise, they all try to use a cookie called ”_session_id” and thus only one application will work properly at a time from any particular Web browser." [1]

Configuration

http://glu.ttono.us/articles/2006/05/22/configuring-rails-environments-the-cheat-sheet

Commands/command-line tools

rake

Rake in the context of Rails

How does Rails find/load its tasks and my tasks?

your_app/Rakefile:

require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'

require 'tasks/rails'

/usr/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/tasks/rails.rb:

  1 $VERBOSE = nil
  2
  3 # Load Rails rakefile extensions
  4 Dir["#{File.dirname(__FILE__)}/*.rake"].each { |ext| load ext }
  5
  6 # Load any custom rakefile extensions
  7 Dir["./lib/tasks/**/*.rake"].sort.each { |ext| load ext }
  8 Dir["./vendor/plugins/*/tasks/**/*.rake"].sort.each { |ext| load ext }

test-related tasks

rake test                           # Test all units and functionals / Test app + shared Glass.net models
rake test:functionals               # Run tests for functionalsdb:test:prepare
rake test:integration               # Run tests for integrationdb:test:prepare
rake test:plugins                   # Run tests for pluginsenvironment
rake test:recent                    # Run tests for recentdb:test:prepare
rake test:uncommitted               # Run tests for uncommitteddb:test:prepare
rake test:units                     # Run tests for unitsdb:test:prepare

database tasks

...are in /usr/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/tasks/databases.rake

db:migrate, for example, is defined as:

  desc "Migrate the database through scripts in db/migrate. Target specific version with VERSION=x"
  task :migrate => :environment do
    ActiveRecord::Migrator.migrate("db/migrate/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
    Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
  end

...which then uses the Migrator class defined here:

/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/migration.rb

  class Migrator#:nodoc:
    class << self
      def migrate(migrations_path, target_version = nil)
        Base.connection.initialize_schema_information

        case
          when target_version.nil?, current_version < target_version
            up(migrations_path, target_version)
          when current_version > target_version
            down(migrations_path, target_version)
          when current_version == target_version
            return # You're on the right version
        end
      end

      def up(migrations_path, target_version = nil)
        self.new(:up, migrations_path, target_version).migrate
      end

[Rails dissections (category)]

script/console

You can specify an environment: script/console test

Understanding the Script files (./script/*)

http://wiki.rubyonrails.org/rails/pages/UnderstandingTheScriptFiles

./script/about

About your application's environment
Ruby version                 1.8.4 (i486-linux)
RubyGems version             0.8.11
Rails version                1.1.6
Active Record version        1.14.4
Action Pack version          1.12.5
Action Web Service version   1.1.6
Action Mailer version        1.2.5
Active Support version       1.3.1
Application root             /wherever/
Environment                  development
Database adapter             mysql
Database schema version      79

script/plugin

  -x, --externals                  Use svn:externals to grab the plugin.
                                   Enables plugin updates and plugin versioning.
  -o, --checkout                   Use svn checkout to grab the plugin.
                                   Enables updating but does not add a svn:externals entry.

Requiring files

The require statement is executed at runtime (there is no compile-time!) and uses the global variable $: to decide where to look for the file. You can modify or inspect $: if you discover that it can't find the file you're trying to include.

Downloadable cheat sheets

I wouldn't actually recommend these, as I'd rather make my own cheat sheet (in this wiki) as I go along... (A cheat sheet's usefulness is kind of a personal matter.)

http://www.blainekendall.com/index.php/rubyonrailscheatsheet/

http://slash7.com/cheats/rails_files_cheatsheet.pdf

http://slash7.com/cheats/form_helpers.pdf

http://slash7.com/cheats/activerecord_cheatsheet.pdf

Comparison of diffrent session containers/interfaces/storage mechanisms

PStore, ActiveRecordStore, etc.

http://scott.elitists.net/sessions.html

http://errtheblog.com/post/24

Migrations

Migrations: valid column types

http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html :

 :primary_key, :string, :text, :integer, :float, :datetime, :timestamp, :time, :date, :binary, :boolean.

Migrations: valid methods

http://api.rubyonrails.org/classes/ActiveRecord/Migration.html

  • create_table(name, options)
  • drop_table(name)
  • rename_table(old_name, new_name)
  • add_column(table_name, column_name, type, options) A default value can be specified by passing an options hash like { :default => 11 }.
  • rename_column(table_name, column_name, new_column_name)
  • change_column(table_name, column_name, type, options)
  • remove_column(table_name, column_name)
  • add_index(table_name, column_names, index_type, index_name)
  • remove_index(table_name, index_name)

Migrations: how to wrap it in a commit

  def self.up
    execute "begin;"
      add_column 'table', 'column', :string
      ...
    execute "commit"
  end

Migrations/Postgresql: how to see the result of a query

Before I switched to using Rails migrations, I did migrations using plain Postgresql SQL commands. This had the advantage that it would tell you how many rows you updated when you did an update/delete.

How to get this information with Rails migrations?

The execute command returns a PGresult object (http://ruby.scripting.ca/postgres/rdoc/classes/PGresult.html), which you can then query to get Postgresql's output.

  def self.up
    puts execute("update orders set ...").cmdstatus
  end

UPDATE 244

Migrations: Don't forget to add indexes!

http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#M000612

add_index(:suppliers, :name)
add_index(:accounts, [:branch_id, :party_id], :unique => true)


ActiveRecord

To find associations from the console, do Model.reflections.keys. (http://woss.name/2006/05/07/notes-from-a-rails-course/)

Caveat: "For has_many :books, :conditions => 'published = 1', we’ve discovered that if you do model_instance.books.create ..., it will not automatically set published = 1 on the new instance. So while it seems like you’re adding a books to the has_many collection of published books, you’re not. It should be possible to emulate that in some way with with_scope." (http://woss.name/2006/05/07/notes-from-a-rails-course/)

with_scope

with_scope doesn’t work with find_by_sql. (http://woss.name/2006/05/07/notes-from-a-rails-course/)

with_scope can be combined with a bit of metaprogramming to automatically limit the scope of actions very easily. See "Nested with_scope" [2]. (http://woss.name/2006/05/07/notes-from-a-rails-course/)

Caveat: When I tried it, it seemed that with_scope didn't limit the scope of associations. So this would example would not do what it's author was trying to do:

def User
  def do_something_with_cool_posts_only
    Post.with_scope({:find => {:conditions => ['cool = true']} }) do
      my_cool_posts = self.posts   # nope, this actually gets *all* of my posts, not just the cool ones
      do_something_with my_cool_posts
    end
  end
end

Eager loading

"Appointment.find(:first, :include => [:contacts => [:addresses => :cities]]) is going to eagerly load the first appointment along with its contacts, their addresses and the addresses’ cities!" (http://woss.name/2006/05/07/notes-from-a-rails-course/)

ActiveRecord booleans

From Agile Web Development with Rails (2nd ed).20060613.pdf page 291:

# DON'T DO THIS
user = Users.find_by_name("Dave")
if user.superuser
  grant_privileges
end

# INSTEAD, DO THIS
user = Users.find_by_name("Dave")
if user.superuser?
  grant_privileges
end

ActiveRecord: relationships: belongs_to or has_* ?

belongs_to :other_table if the table has a foreign key to other_table

ActiveRecord: might be useful: ActiveRecord::Base.blank?

Returns true if its receiver is nil or an empty string.

ActiveRecord: callbacks

http://rubyonrails.org/api/classes/ActiveRecord/Callbacks.html

/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/callbacks.rb

    # Is called when the object was instantiated by one of the finders, like Base.find.
    #def after_find() end

    # Is called after the object has been instantiated by a call to Base.new.
    #def after_initialize() end

after_initialize() seems to be called even when I call reload(). Is that supposed to happen? Possibly. I guess the "the object [is] instantiated by a call to Base.new" part happens even when the programmer calls model.find or model.reload rather than ModelClass.new ! Just something to be aware of.

The main reason I wanted it was so that I could have code that was only called when a record is first created, not when an existing record is re-loaded into the model. One can detect that condition like so:

  def after_initialize
    if self.new_record?
      # Do stuff that should only happen when an object is first instantiated, *before* it is saved...
    else
      # The object was instantiated from an existing record.
    end
    # The object was instantiated either from a new record or an existing record.
  end

To put it another way:

  • The after_find callback is triggered only when a find occurs
  • The after_initialize callback is triggered both when a find occurs and when a new/initialize is called

ActiveRecord: different ways to update attributes

  • Model.new(initial_attribute_values): Updates/initializes but doesn't save. Calls validations??
  • Model.create(initial_attribute_values): Updates/initializes and saves. Calls validations.
  • model.attributes = hash_attributes: Updates in memory but doesn't saves. [Skips validations.??]
  • model.update_attribute(name, value): Updates and saves. Skips validations.
  • model.update_attributes(attributes_to_change): Updates and saves. Calls validations. If the object is invalid, the saving will fail and false will be returned.
  • model.write_attribute(name, value): private. Call it from your custom setter methods to prevent infinite loops.
  • model[:name] = value. Updates in memory, setting the attribute directly; doesn't save until you call model.save. (self[:attribute]=(value) and self[:attribute] are just a shorter form of write_attribute(:attribute, vaule) and read_attribute(:attribute).)
  • model.name = value. Updates in memory, using the setter method; doesn't save until you call model.save.


ActiveRecord: It's using the wrong foreign key in the join clause for your join table? Try :association_foreign_key !

          #  profiles LEFT OUTER JOIN profiles_hobbies ON profiles_hobbies.profile_id = profiles.id
          #           LEFT OUTER JOIN option_lists ON option_lists.id = profiles_hobbies.option_list_id
          #Mysql::Error: Unknown column 'profiles_hobbies.option_list_id' in 'on clause'
          #  Should be doing:
          #  Profile.find(:all, :limit => 10, :joins =>
          #         'join profiles_hobbies on profiles.id = profiles_hobbies.profile_id join option_lists on profiles_hobbies.hobby_id = option_lists.id')
          # Solved with:
          # has_and_belongs_to_many :hobbies, :join_table => :profiles_hobbies, :association_foreign_key => 'hobby_id'


ActiveRecord: Multiple joins on same table breaks find :include (ambiguous column)

          #   Profile.find(:all, :include=>[:hobbies, :majors])
          #LEFT OUTER JOIN profiles_hobbies ON profiles_hobbies.profile_id = profiles.id
          #LEFT OUTER JOIN option_lists ON option_lists.id = profiles_hobbies.hobby_id AND option_lists.`type` = 'Hobby' AND approved = 1
          #LEFT OUTER JOIN profiles_majors ON profiles_majors.profile_id = profiles.id
          #LEFT OUTER JOIN option_lists majors_profiles ON majors_profiles.id = profiles_majors.major_id AND majors_profiles.`type` = 'Major' AND approved =
          # Mysql::Error: Column 'approved' in on clause is ambiguous:

http://dev.rubyonrails.org/ticket/3574

Supposedly fixed it, but didn't seem to:

http://dev.rubyonrails.org/changeset/3776

Develop test case?

How do I create a user interface for editing a HABTM association with checkbooxes?

http://wiki.rubyonrails.org/rails/pages/CheckboxHABTM

Caveat! update_attribute (singular) apparently saves the record even if it was previously unsaved, skipping validations

I spent a couple hours trying to debug some strange behavior where one of my columns was getting nil values even though I had this line:

validates_presence_of :survey_response

I couldn't figure out how it was possible for those to get in there since the validation was supposed to make that impossible. I even added a validate function:

  def validate
    if survey_response.nil?
      errors.add :survey_response, 'is missing'
    end
    #$out += "in validate: {{{ #{survey_response.inspect} }}}<br>"
  end

For some reason, though, it didn't seem to get called in the cases where it was nil. (It got called just fine when it wasn't nil.)

The reason for all this is the behavior of update_attribute... which, according to the docs:

Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records. Note: This method is overwritten by the Validation module that’ll make sure that updates made with this method doesn’t get subjected to validation checks. Hence, attributes can be updated even if the full object isn’t valid.

I even read that before adding the update_attribute call in my code. "Fine," I thought, "it will skip validations when updating an existing record." But it didn't even occur to me that it actually cause the record to be saved if it was still an unsaved record, without paying any regard to my validations.

My code looked something like this, not that it matters much:

      for question_id, value in params[:survey_question_responses]
        question = SurveyQuestion.find(question_id)
        question_response = @survey_response.question_responses.select {|r|
          r.question == question
        }
        if question_response.size > 0
          question_response = question_response.first
        else
          question_response = SurveyQuestionResponse.new(:question => question)
        end
        question_response.update_attributes({:selected_option_ids, value[:selected_option_ids]})
      end
      @survey_response.question_responses << question_response

Solution: Use update_attributes (plural) instead, which doesn't have this strange behavior. Any call to be update_attribute can easily be converted into a call to update_attributes.

If you clone a model and save it, will it create a new one or keep the same id and save over the existing one?

Cloning a model is mostly (as far as I can see) a technique useful for testing...

Would something like this work, for example?


    # Set up the model once
    shop1 = Shop.new
    shop1.is_active = true
    shop1.save
    
    shop2 = shop1.clone
    shop2.save
    
    shop3 = shop1.clone
    shop3.save

    assert_equal 3, Shop.find_active_shops

Why or why not?

ActiveRecord / Database

What if I already have a database schema?? Can I still use migrations??

That's okay. Yep, you can. Just not to create your initial tables/columns...

http://greenprogrammer.blogspot.com/2006/04/further-adventures-along-our-trail.html

Use rake db:schema:dump one time when you want to start using migrations from a pre-existing SQL DDL generated schema. At this point you will be at version 0. Generate your next model or generate a new migration which will be version 1. If you back out via rake migrate VERSION=0 you will be at the last point before you started using migrations.

Here's how I do it:

  • rake db:schema:dump
  • script/generate all your models (using --skip-migration). (If you forget to use --skip-migrations, just delete any migrations that it generates (if you don't it will throw an error when you migrate, saying the table already exists).)
  • rake migrate. It may look like nothing happened, but this will actually create a new table in your database called schema_info:
# select * from schema_info;
 version
---------
       0
(1 row)
  • You can use script/console and interact with your models now.

You can also take the schema that db:schema:dump generates and throw it into your migration_000.rb.

Can I use a primary key other than 'id'?

Yep. Let's say you want the primary key of your users table to be user_id...

  • In your model, set_primary_key :user_id
  • In your fixture, make sure you specify user_id: 1 rather than id: 1 (which is what Rails will generate for you)

How do I add support for DB-specific data types in ActiveRecord?

http://ghouston.blogspot.com/2006/07/let-activerecord-support-enterprise.html Greg Houston: Let ActiveRecord support Enterprise Databases.


Caveat (mostly ActiveRecord) [actualy Ruby-general]: Always include 'self.' when calling my_attribute= from within a model!

Otherwise, it will think you are setting a local variable named my_attribute.

Example:

# Don't do this:
  def before_save
    date_time = Time.now() if date_time.nil?
  end

# Do this:
  def before_save
    self.date_time = Time.now() if date_time.nil?
  end

Caveat: ActiveRecord doesn't respect PostgreSQL's default values

# I had a table that had a not-null constraint on its date_time field. So when I did a plain a = MyModel.new; a.save it said 
ActiveRecord::StatementInvalid: PGError: ERROR:  null value in column "date_time" violates not-null constraint

# To solve this, I created a before_save method in my model that would initialize the column to Time.now() if it was nil. (The column was set up in PostgreSQL to have a default value of Now(), but ActiveRecord doesn't look at / respect that default. It tries to set it to nil, which translates to Null in SQL, which overrides PostgreSQL's default value.)

This works:

  def before_save
    self.date_time = Time.now() if date_time.nil?
  end

So does this:

  def before_save
    write_attribute(:date_time, Time.now()) if date_time.nil?
  end


ActiveRecord: What's the difference between write_attribute and update_attribute?

write_attribute update_attribute
protected public
doesn't save after writing the attribute saves after writing the attribute
skips the setter method and directly modifies the internal attributes hash?? invokes the setter method (my_attribute=)

http://api.rubyonrails.org/classes/ActiveRecord/Base.html:

You can alternatively use self[:attribute]=(value) and self[:attribute] instead of write_attribute(:attribute, vaule) and read_attribute(:attribute) as a shorter form.

ActiveRecord: When to use self.my_attr, my_attr on its own, @my_attr, or read_attribute(:my_attr)??

I think the answer is, for all DB-backed attributes that are created automatically for you by the ActiveRecord framework:

  • use the plain old my_attr whenever possible (this is an accessor method that is automatically created)
  • use read_attribute(:my_attr) if just saying my_attr would conflict with something (a Ruby keyword, for example, or when you're inside of a method with that name and don't want to get stuck in an endless loop!)
  • I'm not sure when you'd want to use self.my_attr ; as far as I know, that's identical to just calling my_attr : the self. part is implied as long as you're calling it from another class/instance method [?]!

How do I safely include user input in my queries?

sanitize_sql

sanitize_sql(["name='%s', "foo'bar"]  =>  "name='foo''bar'"

How do I do migrations outside of Rails?

http://blogs.pragprog.com/cgi-bin/pragdave.cgi/Tech/Ruby/MigrationsOutsideRails.rdoc

    ActiveRecord::Schema.define do
      create_table children, :force => true do |t|
        t.column :parent_id, :integer
        t.column :name,      :string
        t.column :position,  :integer
      end
    end

How do I do straight-SQL select queries?

res = Order.connection.select_all("select id, quantity*unit_price as total from line_items")
p res

This produces something like

[{"total"=>"29.95", "id"=>"91"},
{"total"=>"59.90", "id"=>"92"},
{"total"=>"44.95", "id"=>"93"}]

You don't even have to use a class that makes sense; you're perfectly free to call Books.connection.select_all("select * from cars") or something.

When would you want to use raw-SQL queries like this? Well, you should only rarely need to, if ever...but at least you can! Perhaps you need to get the result of some database-specific function or select from a (database-specific) view...

How do I access my environment/ActiveRecord/my models from a (nearly) stand-alone command-line script?

For example, say you want to do some non-Web-based report that you run from the command line. Or a database maintenance script that you want to run regularly (so it wouldn't make sense to put it in a migration, since those are only expected to be run once). Or anything else you'd call from cron.

#!/bin/env ruby
$LOAD_PATH << "../"

require "config/environment"
require "app/models/book.rb"

result = Book.connection.select_all("select * from books")

Or: require File.expand_path(File.dirname(__FILE__) + "/../config/environment")

One could probably do something like this too (haven't tried it yet), which would automatically load the environment before executing the code that you pass to runner:

./script/runner "require 'send_mailing'"

$ ./script/runner -e production 'puts ENV["RAILS_ENV"]'
production

How do I access a controller from a stand-alone (runner) script?

Notice that this doesn't work:

$ ./script/runner "puts MainController.new"
/usr/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/commands/runner.rb:27: /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:123:in `const_missing': uninitialized constant ApplicationController (NameError)
        from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:131:in `const_missing'
        from script/../config/../app/controllers/main_controller.rb:3
        from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:140:in `load'
        from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:140:in `load'
        from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:56:in `require_or_load'
        from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:30:in `depend_on'
        from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:85:in `require_dependency'
        from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:98:in `const_missing'
        from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:131:in `const_missing'
        from (eval):1
        from /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:21:in `eval'
        from /usr/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/commands/runner.rb:27
        from /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:21:in `require__'
        from /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:21:in `require'
        from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:147:in `require'
        from ./script/runner:3

But this does work:

]$ ./script/console
Loading development environment.
Welcome!
irb -> MainController.new
    => #<MainController:0xb7aefaa8>

So after examining the source of script/console and commands/console, I think I figured out what the difference was that allowed console to have access to it but not runner:

 12 libs =  " -r irb/completion"
 13 libs << " -r #{RAILS_ROOT}/config/environment"
 14 libs << " -r console_app"
 15 libs << " -r console_sandbox" if options[:sandbox]
 16 libs << " -r console_with_helpers"

This seems to work:

$ ./script/runner "require 'console_with_helpers'; puts MainController.new"
#<MainController:0xb78ee6dc>

Ugly, but it works.

How do I access url_for from a stand-alone (runner) script / from a mailer?

I don't know. I've tried to get it to work and failed.

http://lists.rubyonrails.org/pipermail/rails/2006-March/028892.html [Rails] Re: runner: url_for with mailers impossible

http://wrath.rubyonrails.org/pipermail/rails-core/2006-June/001652.html [Rails-core] The old deal with url_for in Mailer

This

puts MainController.new.url_for(:action => 'a')

causes this:

/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_controller/base.rb:490:in `url_for': You have a nil object when you didn't expect it! (NoMethodError)
The error occured while evaluating nil.rewrite  from ./script/whatever.rb:5

This:

http://wiki.rubyonrails.org/rails/pages/HowtoUseUrlHelpersWithActionMailer/ HowtoUseUrlHelpersWithActionMailer in Ruby on Rails

seems to suggest that maybe this would work:

include ActionView::Helpers::UrlHelper
puts url_for(:action => 'a')

but again we get:

/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_controller/base.rb:490:in `url_for': You have a nil object when you didn't expect it! (NoMethodError)
The error occured while evaluating nil.rewrite  from /usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_view/helpers/url_helper.rb:27:in `send'
        from /usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_view/helpers/url_helper.rb:27:in `url_for'
        from ./script/whatever.rb:7

Big caveat!!: has_and_belongs_to_many relationship caveat: make sure your join table has no id column!

Otherwise you will get DB errors saying you're duplicating a unique key (namely, 'id').

To cause it to omit the id column when you create the join table:

create_table :users_groups, :id => false do |t|
...
end

(What do I mean by "join" table? This is the table used in a has_and_belongs_to_many association. :users has_and_belongs_to_many :groups would expect (by default) a join table to exist named "groups_users".)

add_column with :null => false didn't work

add_column :my_table, :first_opened_time, :datetime, { :default => 'now', :null => false }

== AddFirstOpenedTime: migrating ========================================
-- execute("begin")
   -> 0.0003s
-- add_column(:my_table, :first_opened_time, :datetime, {:default=>"now", :null=>false})
rake aborted!
PGError: ERROR:  column "first_opened_time" contains null values
: ALTER TABLE my_table ALTER first_opened_time SET NOT NULL

Complaint: You have to use :allow_nil => true when you do validates_length_of :maximum

Otherwise you'll get "value is too long" validation errors when you try to set it to nil.

I can see why it might be intuitive for :minimum validations, but not for :maximum validations!

Is there any way to have two applications share the same database?

The immediate problem I see is that each application wants to have its own schema_info table.

I guess maybe if you only did your migrations in one application... (maybe call it "shared_migrations" and have it only contain the migrations...)

Can I use a composite (multi-column) primary key?

Sure. Just use http://rubyforge.org/projects/compositekeys .

http://www.pjhyett.com/posts/208-composite-primary-keys-are-good Composite Primary Keys are Good

class AddViewsTable < ActiveRecord::Migration
  def self.up
    create_table :views, :primary_key => [:topic_id, :user_id] do |t|
      t.column :topic_id, :integer
      t.column :user_id,  :integer
      t.column :post_id,  :integer
    end
  end
end

Ajax/RJS/Prototype/Javascript

You can't use Ajax.Updater to update a table in IE!

Let me clarify... The following code works just fine in Firefox, but does not work in IE:

app/views/test/ajax.rhtml:
<table id="table_without_tbody">
  <tr id="tr1">
    <td>
      Static content
    </td>
  </tr>
</table>
<%= link_to_remote "add to :bottom of 'table'", :update => 'table_without_tbody', :url => {:action => 'an_ajax_action'}, :position => :bottom %>

app/controllers/test_controller.rb:                                                                                                     
class TestController < ApplicationController
  def an_ajax_action
    render :layout => false
  end
end

app/views/test/an_ajax_action.rhtml:
Content to be added via Ajax

Symptoms: Nothing happened when I clicked the link. The request showed up in the logs and there were no errors server side, but no changes in the DOM were visible. I didn't even get an alert saying "RJS Error" like some people have reported.

The solution is to update the tbody instead of the table directly:

app/views/test/ajax.rhtml:
<table id="table_with_tbody">
<tbody id="tbody">
  <tr id="tr">
    <td>
      Static content
    </td>
  </tr>
</tbody>
</table>
<%= link_to_remote "add to :bottom of 'tbody'", :update => 'tbody', :url => {:action => 'an_ajax_action'}, :position => :bottom %>
<%= link_to_remote "add :after 'tr'", :update => 'tr', :url => {:action => 'an_ajax_action'}, :position => :after %>

References:

Views/Helpers/Layouts

Layouts

content_for :what/yield :what

http://errtheblog.com/post/28 err.the_blog.find_by_title('Content for Whom?')

How do I access the controller?

@controller gives you the actual controller instance

params[:controller] gives you its URL component/name

How do I link to a popup window?

<%= link_to( "Give us some feedback", {:controller => 'popup', :action => 'feedback'}, { :popup => ['new_window', 'height=650,width=450,scrollbars=yes'], :target => '_blank' } ) %>

As you can see, you use the :popup option of link_to. That generates a standard Javascript popup call. You can pass all the options that you'd normally use for such a thing, like hight and width.

I usually like to specify a target, too, so that just in case they have Javascript turned off, it will at least open in a new window.

Make sure to create a new, very simple layout for use by your popup windows -- because you probably don't want everything that your normal layouts provide.

[Open question] How do I make a helper function pretty-looking with lots of HTML like ERb templates?

Usually when I am motivated to make a helper it is by duplication in my view: I do the same thing twice and I'd rather call a method twice than have all that code duplicated twice. So the natural thing to want to do is to move that code -- that ERb code! -- from the view into the helper. Don't make me reformat it a lot; I want it to be ERb just like the view!

Can we use erb to create the helper function?? render_to_string()??

Could we use partials instead?

I hate building a string with "blah" + "blah" and return_value += "something"!

Ruby on Rails / Ugly helper function example

Maybe use render :inline?

# Renders "hello david"
render :inline => "<%= 'hello ' + name %>", :locals => { :name => "david" }

Alternatives to ERb in templates

Also can do it this way:

%Q{
  Name is #{name}
}

or with a here-document string.

or nest calls to other helpers within you helper:

      content_tag('div',
        content_tag('h2', "#{pluralize(error_count, 'error')} prevented your account information from being saved") +
        content_tag('ul',
          error_objects.inject([]) {
            |array, object|
            array + object.full_messages.collect { |message| content_tag('li', message) }
          }
        ),
      :id => 'errorExplanation', :class => 'errorExplanation')
    end

But the problem with any of these solutions remains that sometimes you need to use control structures/logic (if statements primarily) in the helper and thus need to split up your strings, and thus need to concatenate strings together (yuck). ERb remains the best for this.

Can you make layouts/templates include other layouts/templates?

Useful, for example, when you want an "outer layout" that's shared by all per-controller layouts. This "outer layout" would include site-wide stuff, like the <html> tags, including some stylesheets, etc.

Example

views/layouts/master_layout.rhtml:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>My site: <%= @title %></title>
  <%= stylesheet_link_tag "common", :media => "all" %>
  <%= @content_for_head %>
</head>
<body>

</body>
</html>

views/layouts/controller1_layout.rhtml:

<h1><%= @title %></h1>

<%= @content_for_layout %>

<div id="Sidebar">
   ...
</div>

views/controller1/view1.rhtml


Potential solution: CaptureHelper

capture and content_for ! http://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html

Potential problem: "Beware that content_for is ignored in caches. So you shouldn’t use it for elements that are going to be fragment cached."

[Comments in ERb]

Using =begin/=end seems to work for commenting out sections of ERB files. (http://woss.name/2006/05/07/notes-from-a-rails-course/0)

[Open complaint] [Comments in ERb] Using # for comments inside of a one-line <%=  %> ERb block causes an error!

Example:

Throws an error:

<%= #pagination_links @order_pages %>

Doesn't throw an error:

<%= #pagination_links @order_pages 
%>

It seems like it's trying to comment out the %> delimiter.

But that doesn't make sense to me -- the %> isn't Ruby code, so it shouldn't be trying to comment it out! The <% and %> only mark where the actualy Ruby begins and ends.

Update: I think the standard way to comment out stuff in ERb is <%# rather than <%=# :

<%# #pagination_links @order_pages %>

How do I create a multiple select box for use in editing a has_and_belongs_to_many relationship?

http://lists.rubyonrails.org/pipermail/rails/2006-April/036962.html :

class User < ActiveRecord::Base
  # id, name fields
  has_and_belongs_to_many :groups
end

class Group < ActiveRecord::Base
  # id, name fields
  has_and_belongs_to_many :users
end

class UserController < AbstractApplicationController
  model :user
  def edit
    case @request.method
    when :get
      @user = User.find(params['id'])
      @available_groups = Groups.find_all - @user.groups
    when :post
      @user = User.find(params['user']['id'])
      @user.groups << Group.find(params['add_groups'])
      redirect_to :action => 'show', :id => @user.id
    end
  end
end

views/user/edit.rhtml
<%= form_tag :action => 'edit' %>
  <%= hidden_field 'user', 'id' %>
  <select name="add_groups[]" multiple="multiple">
    <%= options_from_collection_for_select @available_groups, 'id',
'name' %>
  </select>
</form>

How do I make a link to an image?

Answer: Sorta like this...

<%= link_to image_tag('next.gif'), :action => :next %>

<%= link_to image_tag('next.gif', :border => 0), :action => :next %>

<%= link_to( "<img src=" + image_path('logo.png') + ' />', { :controller => 'main' } ) %>

<%= link_to( "<img src=" + image_path('logo.png') + ' style="float: right;" />', { :controller => 'main' } ) %>

How do I make an image submit button?

image_submit_tag('next.gif')

Not with submit_tag (image_path(...)).


text_field or text_field_tag??

text_field_tag field_name, options

text_field object_name, field_name, options

How do I make a UI for editing multiple obects of the same class, and having an 'Add another _' button?

http://rails.techno-weenie.net/question/2006/8/24/multiple-records-from-one-form


[Used by View or Controller] Workaround for Rails bug: url_for flattens multi-dimensional params hashes

http://dev.rubyonrails.org/ticket/4947 "URL rewriter shoud support deeply nested parameters"

Solution: http://marklunds.com/articles/one/314

  def flatten_hash(hash = params, ancestor_names = [])
    flat_hash = {}
    hash.each do |k, v|
      names = Array.new(ancestor_names)
      names << k
      if v.is_a?(Hash)
        flat_hash.merge!(flatten_hash(v, names))
      else
        key = flat_hash_key(names)
        key += "[]" if v.is_a?(Array)
        flat_hash[key] = v
      end
    end
    
    flat_hash
  end
  
  def flat_hash_key(names)
    names = Array.new(names)
    name = names.shift.to_s.dup 
    names.each do |n|
      name << "[#{n}]"
    end
    name
  end
  
  def hash_as_hidden_fields(hash = params)
    hidden_fields = []
    flatten_hash(hash).each do |name, value|
      value = [value] if !value.is_a?(Array)
      value.each do |v|
        hidden_fields << hidden_field_tag(name, v.to_s, :id => nil)          
      end
    end
    
    hidden_fields.join("\n")
  end

Controllers

How do I do something like puts from a controller?

Do I have to set it to a member variable, and then add <%= my_debug_output %> to the template??

Do I have to use a logger instead?

Can't I just say puts my_debug_output? Or render :text => my_debug_output? And have it added to the HTML that will be rendered?

That's one thing I miss about PHP. In PHP, you can put a echo/print statements everywhere to help you debug your code.

My solution: Usually I just use

raise whatever_object.inspect

Sometimes I'll use a global, something like this:

$output = ''
$output += 'some debug output'

And outputting <%= $output %> in my view. But really I should just be using the logger at that point.

What's the difference between redirect_to and render?

both render the view (.rhtml file).

Only redirect_to causes the action in the controller to be called. So if you are doing render and any set up needs to be done, you need to do it in the action method that is calling render or you need to explicitly call the action before you render the template for that action (confusing, I know!).

render keeps the local variables (such as error messages and the current, possibly invalid state of the objects being edited); redirect loses them

both of them keep and show the contents of flash

[need real example]

ActionMailer: Can use the helpers that are used by my normal (controller) views)?

Yes! Just put something like the following in your ActionMailer::Base subclass:

helper :application, :other

filters (before_filter, etc.)

meantime_filter

It's sort of like an around_filter, only cooler! (available as a plugin, I think)

after_filter

after_filter happens after the view, not after the controller (http://woss.name/2006/05/07/notes-from-a-rails-course/0)

Flash

Hey, how come my flash message is sticking around too long?

Normally the flash message will be available to the next action, whatever that is...

Maybe you're setting flash and then rendering and wanting your message to only appear for the current action.

Solution: Try setting flash.now[:error] = 'whatever' instead of flash[:error].

Reference: http://api.rubyonrails.org/classes/ActionController/Flash/FlashHash.html#M000142

Information about the request: remote IP address

David Reynolds said:

Not sure if anyone else knows this, but request.env['REMOTE_ADDR'] doesn't return the correct visitor IP address when the user is behind a proxy or if the web server forwards requests to an internal server (one server on port 80 that forwards to a different port). In theses cases, request.remote_ip works way better. In Rails, you can apparently find the originating host even if they are proxy chaining.
Here's some more info: http://railsmanual.com/class/ActionController::AbstractRequest/remote_ip

ActionMailer

ActionMailer: How do I get started?

Read the section on the Agile Rails book, or read the Rdoc.

./script/generate mailer OrderMailer confirm sent

OrderMailer.deliver_confirm(order)

ActionMailer: How should I send a mass mailing?

If it's spam you're trying to send, you should always make sure to send it to /dev/null.

Otherwise, for legitimate mass mailings...

MyOwnDB Blog » Blog Archive » Mass mailing with active mailer

ActionMailer: Can use the helpers that are used by my normal (controller) views)?

Yes! Just put something like the following in your ActionMailer::Base subclass:

helper :application, :other






View/Controller/params--Model interaction (form processing)

Things to consider when building form-processing code

  • Do you want the models to save even if there are validation errors?
    • If no, then be sure to check model.valid? to find out if there are errors and not check model.save
  • Do you have any fields that are required for the form, but not necessarily be the model and everywhere the model is used?
    • If yes, consider using a FormValidator object
  • Which model objects are being edited in your forms? You need to handle each of them.
  • Associations...


Updating associations from form input

      @company.update_services(params[:services])

  def update_services (services_hash)
    if services_hash
      services.clear
      services_hash.each { |service_id, value|
        services << Service.find(service_id.to_i)
      }
    end

forms for associations: my_model = MyModel.new(params[:my_model]) complains that the type passed to the association setter is of type String

For example, you have:

Profile Model:
  habtm :hobbies
View:
  <%= select("profile", "hobbies", @hobbies.options_for_select, { :include_blank => true }) %>
Controller:
  @profile = Profile.new(params[:profile])

When you submit the form, it says "Hobby expected, got String".

/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations/association_proxy.rb:134:in `raise_on_type_mismatch'
/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations/association_collection.rb:118:in `replace'
/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations/association_collection.rb:118:in `replace'
/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations.rb:895:in `hobbies='

Solution:

In this case, I made it extra difficult, because I'm using a single-select field (typically used for has_many associations, not habtm) to a habtm (usually a multi-select would be used).

If I were using a multi-select, this would be all I need to change:

View:
  <%= multi_select("profile", "hobby_ids", @hobbies.options_for_select, { :include_blank => true }) %>

In my case, however, I need to convert my single string value submitted in params[:profile][:hobby_ids] (for example, "3") into an array of strings (["3"]). So I did this (which could easily be thrown into the model logic):

    for key, value in params[:profile]
      if key.match /_ids/ and !value.is_a?(Array)
        params[:profile][key] = [value]
      end
    end


http://wiki.rubyonrails.com/rails/pages/HowtoUseFormOptionHelpers/:

Assigning to has_many or has_and_belongs_to_many collections

If you want to have your multiple choice select assign to a has_many or has_and_belongs_to_many (habtm) collection on your object, you should specify the collection using the <collection name>_ids name. For example, if we have an AddressGroup object with a collection of Address objects called addresses, we would not use this:

<select name="addressgroup[addresses][]" multiple="multiple">

but rather this:

<select name="addressgroup[address_ids][]" multiple="multiple">

With either select statement the returned value is an array of Address object ids, not the actual array objects, which will fail if we tried to assign to addressgroup.addresses. If you try to do that you will get an error message like “Expected Address and got String”. Instead, we use the implicit address_ids= property (automatically added to an object when it has a has_many or habtm collection), which assigns to the collection based on a collection of IDs.

Questions / Complaints

Alias: Questions and Answers / Problems and Solutions

This category has been relegated to the lowly status of "secondary category". Please use the topic (ActiveRecord, Views, etc.) as the primary category.

How to change the working directory (cd)

 > require 'rubygems'
=> true
 > require 'rake'
=> true
 > Rake::FileList
=> Rake::FileList
 > FileUtils.pwd
=> "/var/www/whatever_app/db"
 > FileUtils.cd '..'; FileUtils.pwd
=> "/var/www/whatever_app"
 > FileUtils.cd 'config'; FileUtils.pwd
=> "/var/www/whatever_app/config"
 > FileList['*']
=> ["environments", "environment.rb", "routes.rb", "boot.rb", "database.yml", "lighttpd.conf", "deploy.rb", "database.mydb.yml"]

Ruby: If you override attribute my_attr, will it also override my_attr= for you?

alias_method :date_placed, :creation_date

I think the answer is no, you'd need to override that separately:

alias_method :date_placed=, :creation_date=

Is there/should there be another method that does both of these for you?

Why does it not seem to get my changes until I restart the server?

If it's not derived from a standard Rails class (like ActiveRecord::Base), you might need to add this line to your class:

include Reloadable

This is usually the case for code you put in the lib/ directory! Expect to have to restart every time you make a little change there. Or make it Reloadable.

Modules, unfortunately, cannot include the Reloadable module.

Caveat!!: Why am I getting a "wrong number of arguments (2 for 0)" error?

One possible reason: you have a column named 'quote'.

http://rails.techno-weenie.net/question/2006/8/10/wrong-number-of-arguments-issue:

Make sure you don't have a column called "quote" in any of your tables. This will override the current ActiveRecord "quote" method. http://dev.rubyonrails.org/ticket/3628

Dissection: What does Rails do when an (otherwise unhandled) exception occurs?

Just look at the source for rescue_action(exception):

    # File vendor/rails/actionpack/lib/action_controller/rescue.rb, line 26
       def rescue_action(exception)
         log_error(exception) if logger
         erase_results if performed?
 
         if consider_all_requests_local || local_request?
           rescue_action_locally(exception)
         else
           rescue_action_in_public(exception)
         end
       end

It

  • logs it
  • displays it to the screen (maybe)

Rails makes the following assumptions:

  • During development, you want to see a page with detailed debug info. This is called a "local rescue" or a "rescue for the developer view" ([3]).
  • For everyone else in the world, you want to show the visitor a user-friendly but uninformitive message saying that there was an error (but not giving the details of this error).

To decide whether you are a developer or an "everyone else", it doesn't look at your environment ('development' or 'production'), like I might have expected, but at your IP address (by default, 127.0.0.1 is the only IP considered local by default -- which basically only includes the case when you are doing development with webrick). That could be useful, I suppose, if you were debugging an error remotely and wanted it to automatically show the developer view any time you connected from your home computer. But I'm still not sure I like it.... My opinion:

  • On the production site, the developers are going to want to always be notified about exceptions (via e-mail or whatever).
  • Deciding whether or not you should see extra, developer-only info based on your IP is (1) slightly unsafe, (2) not all that helpful (what if you have a dynamic IP at home or you travel a lot and could be connecting from any number of different IPs?).

See http://api.rubyonrails.org/classes/ActionController/Rescue.html .

I might find it useful to override local_request?() sort of like this:

def local_request?()
  if environment == "production" then false
  if environment == "development" then true
end

I just noticed that something like this is already possible via the environments/* files:

config/environments/development.rb:

config.action_controller.consider_all_requests_local = true

That will ensure that all requests done during development will have a local rescue (verbose errors).

(Question: Won't that happen anyway? When would the request be not from localhost?)

Errors: What's the difference between "Application error" and "Application error (Rails)"?

"Application error" is found in public/500.html and (I believe) is rendered by ... (??)

"Application error (Rails)", on the other hand, is defined here:

/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_controller/rescue.rb:

      # Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>).
      def rescue_action_in_public(exception) #:doc:
        case exception
          when RoutingError, UnknownAction then
            render_text(IO.read(File.join(RAILS_ROOT, 'public', '404.html')), "404 Not Found")
          else render_text "<html><body><h1>Application error (Rails)</h1></body></html>"
        end
      end

It is also defined here, but this probably doesn't happen as often:

/usr/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/dispatcher.rb

      # If the block raises, send status code as a last-ditch response.
      def failsafe_response(output, status, exception = nil)
        yield
      rescue Object
        begin
          output.write "Status: #{status}\r\n"

          if exception
            message    = exception.to_s + "\r\n" + exception.backtrace.join("\r\n")
            error_path = File.join(RAILS_ROOT, 'public', '500.html')

            if defined?(RAILS_DEFAULT_LOGGER) && !RAILS_DEFAULT_LOGGER.nil?
              RAILS_DEFAULT_LOGGER.fatal(message)

              output.write "Content-Type: text/html\r\n\r\n"

              if File.exists?(error_path)
                output.write(IO.read(error_path))
              else
                output.write("<html><body><h1>Application error (Rails)</h1></body></html>")
              end
            else
              output.write "Content-Type: text/plain\r\n\r\n"
              output.write(message)
            end
          end
        rescue Object
        end
      end

When would that get called instead of the normal 500 error? I don't know yet...

Normally, it looks like the ugly "Application error (Rails)" message is rendered for all "normal" exceptions (rescue_action_in_public in /usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_controller/rescue.rb). This is surprising; I would have expected it to use 500.rhtml. (Why doesn't it??)

However, if you have the exception_notifiable plugin installed (which I recommend everyone do!), then rescue_action_in_public will be overridden to have the desired behavior. Yay!

./vendor/plugins/exception_notification/lib/exception_notifiable.rb:

  def render_500
    respond_to do |type|
      type.html { render :file => "#{RAILS_ROOT}/public/500.html", :status => "500 Error" }
      type.all  { render :nothing => true, :status => "500 Error" }
    end
  end

  def rescue_action_in_public(exception)
    case exception
      when ActiveRecord::RecordNotFound, ActionController::UnknownController, ActionController::UnknownAction
        render_404

      else
        render_500

        deliverer = self.class.exception_data
        data = case deliverer
          when nil then {}
          when Symbol then send(deliverer)
          when Proc then deliverer.call(self)
        end

        ExceptionNotifier.deliver_exception_notification(exception, self,
          request, data)
    end
  end

How do I set it up to e-mail me when an error message occurs?

What, you mean you aren't at the terminal tailing production.log at all hours of the day and night in case an important error should occur?? Shame on you.

Read Global Error Handling with Exception Notification

... which will tell you to

./script/plugin install exception_notification

From Readme:

= Exception Notifier Plugin for Rails

The Exception Notifier plugin provides a mailer object and a default set of
templates for sending email notifications when errors occur in a Rails
application. The plugin is configurable, allowing programmers to specify:

* the sender address of the email
* the recipient addresses
* the text used to prefix the subject line

The email includes information about the current request, session, and
environment, and also gives a backtrace of the exception.

This plug-in makes the following (default) assumption: you only want to send the exception details via e-mail if the exception details are not printed to the screen.

More verbosely:

  • For local requests, you don't want to send an e-mail
  • For non-local requests, you want to send an exception notification e-mail with the details of the exception

I have a hard time deciding whether I would also like to be e-mailed of every error that happens during development. That would probably get annoying, so I think I agree with their assumptions. You already see the error on the screen (you've already been 'notified' of the error that way), so why do you need it e-mailed to you as well?

If you do decide you want to get e-mails for errors during development, that is fortunately pretty easy to do too:

Just override rescue_action_locally (in addition to rescue_action_in_public)! (http://rails.techno-weenie.net/tip/2006/8/31/exception-notification-plugin-fix)

Another way to get it to e-mail you also during development is to override log_error(). Then whenever an error is logged, you also get an e-mail. (Idea from Ruby Cookbook, Chapter 15, Section 20.)


How to I change the error pages for my application?

http://wiki.rubyonrails.com/rails/pages/HowtoConfigureTheErrorPageForYourRailsApp

How do I deal with dependencies?

There are several kinds of dependencies you may have:

  • gems (of which Rails is one)
  • svn:externals
  • plugins (often included via svn:externals)

http://wiki.rubyonrails.org/rails/pages/HowtoLockToSpecificRailsVersions

Some solutions for depending on a specific version of Rails:

Chad Woolley on http://weblog.rubyonrails.org/2006/3/31/freeze-is-cool-so-freeze-for-goodness-sake :

Please don’t force untold numbers of duplicate copies of Rails to be checked into SCM repositories the world over. This would be a Very Bad Idea.
Take a lesson from Maven, using versioned external dependencies from a central repository is a proven and reliable practice. It can also be extended to encompass your own custom dependencies from a local repository.

How to freeze gems

http://blog.nanorails.com/articles/2006/03/28/freeze-all-your-ruby-gems-on-a-shared-host Freeze all your ruby gems on a shared host

 cd vendor/rails
 gem unpack activerecord
 gem unpack activesupport
 gem unpack actionpack
 gem unpack actionmailer
 gem unpack actionwebservice
 mv activerecord-1.13.2/ rails/
 mv actionmailer-1.1.5/ actionmailer
 mv actionpack-1.11.2/ actionpack
 mv actionwebservice-1.0.0/ actionwebservice
 mv activerecord-1.13.2/ activerecord
 mv activesupport-1.2.5/ activesupport

How does an object get converted to a field get converted to a param get converted to an object?

# "Model" object
class Address
  def line_1
    "123 Fake St."
  end
  def line_2
    "Apt. 42"
  end
end
@address = Address.new

# Helpers
text_field("address", "line_1")
text_field("address", "line_2")

# Produce essentially this HTML output:
<input type="text" name="address[line_1]"  />
<input type="text" name="address[line_2]"  />

# Produces these params:
"address"=>{"line_1"=>"123 Fake St.", "line_2"=>"Apt. 42"}}

params[:address][:line_1] = "123 Fake St."
params[:address][:line_2] = "Apt. 42"
<input type="text" name="address[][line_1]"  />
<input type="text" name="address[][line_2]"  />
# Not very useful
"address"=>{""=>{"line_1"=>"234", "line_2"=>"234"}

<input type="text" name="address[lines][]"  />
<input type="text" name="address[lines][]"  />
"address"=>{"lines"=>["123 Fake St.", "Apt. 42"]
params[:address][:lines[0] = "123 Fake St."
params[:address][:lines[1] = "Apt. 42"

How do I handle/prevent ActiveRecord::MultiparameterAssignmentErrors resulting from bad input from date_select?

http://i.nfectio.us/articles/2006/02/22/handling-invalid-dates-with-activerecord-date-helpers :

Rescue the exception in your controller. Iterate over the error details, removing from params all of the values that caused the error. Then send your modified params to the ActiveRecord model again.

http://wiki.rubyonrails.org/rails/pages/Validates+Multiparameter+Assignments :

Add a validates_multiparameter_assignments call in your model. Then it will add the errors to the model's errors collection rather than throwing an exception. Genius!


How do I call an action and render its template from a stand-alone command-line/cron (runner) script?

Just instantiate the controller?

This seems to work, at least from console (should work from runner too?):

x = ReportController.new x.run_report

How to call render/render_to_string from outside of a controller?

This problem was possed on http://lists.rubyonrails.org/pipermail/rails/2006-May/043642.html but I didn't see a solution posted there.

This does not work:

      renderer = ActionController::Base.new
      @my_string = render_class.render_to_string(:template => 'controller/template')
      @my_string = renderer.instance_eval { render_to_string('controller/template') }
      @my_string = renderer.send(:render_template,'controller/template')

Those give errors such as:

The error occured while evaluating nil.[]=
/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_controller/base.rb:994:in `add_class_variables_to_assigns'"

http://rails.techno-weenie.net/forums/1/topics/281 had two possible solutions: curl the URL of the template (don't like that idea, because then your templates have to be public), or call ERB directly.

Since you apparently can't call render/render_to_string from outside of a controller, how do I render my templates?

You can use ERb directly, much like Rails does behind the scenes when it renders a template:

      template = ERB.new(File.readlines('app/views/dir/my_view.rhtml').join(''), nil, '-')
      html = template.result(binding)

how do I use helpers, like image_tag?

Story: (hide)

lib/some_class.rb:
class SomeClass
  include ApplicationHelper
  include ActionView::Helpers::AssetTagHelper
end

But then you get this error:

NoMethodError: undefined method `relative_url_root'
    /usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_view/helpers/asset_tag_helper.rb:156:in `compute_public_path'
    /usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_view/helpers/asset_tag_helper.rb:125:in `image_path'
    /usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_view/helpers/asset_tag_helper.rb:140:in `image_tag'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_view/helpers/asset_tag_helper.rb:
152         def compute_public_path(source, dir, ext)
153           source  = "/#{dir}/#{source}" unless source.first == "/" || source.include?(":")
154           source << ".#{ext}" unless source.split("/").last.include?(".")
155           source << '?' + rails_asset_id(source) if defined?(RAILS_ROOT) && %r{^[-a-z]+://} !~ source
156           source  = "#{@controller.request.relative_url_root}#{source}" unless %r{^[-a-z]+://} =~ source
157           source = ActionController::Base.asset_host + source unless source.include?(":")
158           source
159         end
lib/some_class.rb:

class FakeRequest
  def relative_url_root; "http://example.com"; end
end
class FakeController
  def request; FakeRequest.new; end
end
class SomeClass
  include ApplicationHelper
  include ActionView::Helpers::AssetTagHelper
end

But then you get this error:

NoMethodError: undefined method `tag'
    /usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_view/helpers/asset_tag_helper.rb:148:in `image_tag'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_view/helpers/asset_tag_helper.rb:
148         tag("img", options)

Final version:

lib/some_class.rb:

class FakeRequest
  def relative_url_root; "http://example.com"; end
end
class FakeController
  def request; FakeRequest.new; end
end
class SomeClass
  include ApplicationHelper
  include ActionView::Helpers::AssetTagHelper
  include ActionView::Helpers::TagHelper
end

How do I do things from a stand-alone command-line/cron (runner) script?

http://forums.site5.com/showthread.php?t=8063 how to run rails action via cron job? - Site5 Web Hosting Forums

There are some maintenance actions in my admin controller that I'd like to run via cron job daily, rather than manually via the browser. Any suggestions on how to do this?
script/runner is your best option
running by wgetting an action might results in "action timeout" for long jobs. the preferred way is to use script/runner through a cron job caller.


http://wiki.rubyonrails.com/rails/pages/HowToRunBackgroundJobsInRails HowToRunBackgroundJobsInRails in Ruby on Rails

.script/runner -e production "Model.do_something"

is by far the most stable way of running background processes in Rails.

Cron, when used with RoR, has the following shortcomings:

  • Significant startup resources required for each job
  • Lots of RAM to run simultaneous processes
  • Hard to start/stop/affect the background processes from within Rails.

BackgrounDRb

BackgrounDRb is a distributed ruby server daemon that runs outside of rails but allows for you to kick off and manage long running tasks from your rails app. It has hooks for creating ajax progress bars and status updates in the browser while your long tasks runs in the background and thereby does not hold up the rails request/response cycle.

http://rubyforge.org/pipermail/backgroundrb-devel/2006-September/000350.html [Backgroundrb-devel] Could a BackgrounDrb worker do this?

http://kylemaxwell.typepad.com/everystudent/2006/09/railscron_depre.html RailsCron Deprecation / DaemonGenerator

If you want bare-bones, stable, and functional, use daemon_generator. If you want messaging and features, use backgroundRb. I don't see value in RailsCron anymore.

http://kylemaxwell.typepad.com/everystudent/2006/08/after_writing_r.html Every Student: Announcing daemon_generator

http://svn.kylemaxwell.com/rails_plugins/daemon_generator/

http://svn.kylemaxwell.com/rails_plugins/daemon_generator/tags/rel-0.5.0/

http://wiki.rubyonrails.org/rails/pages/RunnerScript RunnerScript in Ruby on Rails

Note: These methods must be created within models. The runner script does Not load any controllers. Controllers are associated with an http request. An http request does not exist in the command line environment.


not really helpful:

http://blog.yanime.org/articles/2006/08/05/rails-calling-render-outside-your-controllers

How do I access a helper from a controller or other non-view class (like -- gasp! -- a model)?

http://www.bigbold.com/snippets/posts/show/1799

Add this to your ApplicationController:

  def help
    Helper.instance
  end

  class Helper
    include Singleton
    include ActionView::Helpers::TextHelper
  end








How do I make modules reloadable too?

In other words, I'm tired of restarting my application just to test out the changes I just made to my module, every time I change a line of code!

http://www.mathewabonyi.com/articles/2006/07/28/class-module-reloadable-everything

(Haven't tried it yet)

http://dev.rubyonrails.org/ticket/5329

Can I call methods of the controller from my view?

You're not supposed to.

But it seems that you can by using @controller.

For example: <%= @controller.send(:some_protected_method?) %>

I think it's okay to do for debugging (only).

User interface: how to tie a yes/no select box to a boolean field in your model

View: 
To default to whatever is in the model by default:
<%= select_tag "person[is_bald]", options_for_select(["Yes", "No"], selected = (@person.is_bald? ? "Yes" : "No")) %>
To default to "Yes" if the column is nil in the model:
<%= select_tag "person[is_bald]", options_for_select(["Yes", "No"], selected = ((@person.is_bald.nil? || @person.is_bald?) ? "Yes" : "No")) %>

Model:
  def is_bald=(new)
    if new.is_a? String
      write_attribute :is_bald, (new == "Yes")
    else
      write_attribute :is_bald, new
    end
  end

How to make sessions work if cookies are blocked?

http://lists.rubyonrails.org/pipermail/rails/2005-January/001361.html

http://wrath.rubyonrails.org/pipermail/rails/2005-March/003876.html

http://www.troubleshooters.com/codecorn/ruby/rails/rapidlearning_rails.htm :

By far the toughest part of web programming is that the HTTP protocol, which is the protocol used by the web, is stateless. That means that every web page starts knowing nothing of web pages that came before it. Programs cannot be written under such circumstances.

For that reason, programmers from the dawn of time have found various ways to pass information between web pages:

   * Passing info in the URL
   * Passing info in form variables
   * Passing info in cookies

All persistence kludges, and that's what they are, kludges to compensate for HTTP statelessness, are ugly, and every single page on the website must pass state info, even if such pages have no dynamic content, or else state is lost. URL passing is a horrible security faux pax. Passing in form variables means every single page on the website must pass state info, even if such pages have no dynamic content, or else state is lost. Passing in cookies has privacy implications, and some people turn off cookies on their browser.

The best a framework can do for you is to shield you, the application programmer, from the drudgery of maintaining state. Rails does this with a hash like structure called session. This hash like structure is your abstraction for cookies-based state maintenance. That's right -- Rails won't work if the browser has cookies turned off -- you need to warn the user to turn on cookies before proceeding to dynamic pages that need state.

http://comments.gmane.org/gmane.comp.lang.ruby.rails/26374 Jens-Christian Fischer | 22 Oct 13:15

I worked on a game for mobile phones last year in rails and had to use url based session management. It's certainly feasible [...]

In your views:

Add the session_id to every link like this:

link_to "somewhere", { ..., :params => { "_session_id" =>  
 <at> session.session_id }}

and in your forms, add a hidden field to carry the session id like this:

<input name="_session_id" type="hidden" value="<%=  
 <at> session.session_id %>"/>

that's basically all there is to it. It would be nice, if this could be auto-generated by rails, but I haven't gotten around to investigate this further.

http://comments.gmane.org/gmane.comp.lang.ruby.rails/26374 Robert | 22 Oct 21:50

class ActionController::Base
   def default_url_options(options)
     { '_session_id' => session.session_id }
   end
end

But here again bug #210 strikes back. Rails doesn't seem to "absorb" the _session_id param when POSTing. So you can't do:

<form action="/some_controller/some_action?_session_id=XXXXX">

Just doesn't work (at least for me, when I tested it).

(also mentioned here: http://one.textdrive.com/pipermail/rails/2005-January/001415.html / http://lists.rubyonrails.org/pipermail/rails/2005-January/001361.html)

(bug mentioned at http://dev.rubyonrails.com/ticket/210)

So the default_url_options thing works for all url_for-based URLs, but doesn't help with redirect_to or forms (which use POST).

http://www.freeonrails.com/node/12798 Bob Norfolk on September 29, 2006 - 7:03pm.

You can override url_for and form_tag in application_helper.rb.

One potential danger with allowing session IDs to be passed in the URL is that it opens us up to impersonation attacks. (See, for example, the section on Impersonation at http://shiflett.org/articles/the-truth-about-sessions .)

Arguments that it's okay to require users to use (session) cookies

http://lists.rubyonrails.org/pipermail/rails/2005-January/001415.html

I agree that for online shopping, sessions encoded in the URL is a security issue waiting to happen. But for some apps using cookies also isn't an option because of browser limitations (e.g. most mobile browsers). So it would be nice to have this option available

http://www.freeonrails.com/node/12798 Bob Norfolk on September 30, 2006 - 3:56am:

As to whether Rails "is simply not usable for professional development" - there's a lot of evidence to refute that. 37Signals is making a boatload of money- you think they're not "professional"? If so, I have to say, that's the kind of non-professionalism I aspire to ;-)
Having said that, I think support for session-id-in-url would be a great addition to Rails- as DHH might say, "please do write a plugin" ;-)
Lastly, much of this talk about people having cookies disabled misses a fine distinction. The cookie that Rails uses for session id expires when you close your browser. While some people may have persistent cookies disabled, I would venture to guess that the number that have non-persistent (session) cookies disabled is considerably less. As Eric Knapp points out, above, much of the modern web would be practically unusable without session cookies. Ever used a shopping cart?
Over at Lingr (http://www.lingr.com), we haven't heard any feedback from users complaining about our requirement of session cookies. I doubt we ever will. I'm certainly not loosing any sleep over whatever users, if any, we turn away due to that requirement.

http://www.freeonrails.com/node/12798 snacktime on September 29, 2006 - 7:17pm:

Without a way to make sessions work with the id passed in the query string, it will limit rails in some areas, although how many I don't know. I do know that many ecommerce sites need to work without cookies, especially those that have mobile phone friendly pages. We have a fairly large ecommerce app written in perl being used by a lot of merchants, and the number of people who block cookies is small, but significant enough that we don't require cookies.

http://one.textdrive.com/pipermail/rails/2005-January/001412.html On 03/01/2005, at 2:31 PM, Tobias Luetke

Well, from a business point of view, your client might say "it's too dangerous to only rely on cookies, because I'll lose sales", which is fair enough.
PHP's trans-sid stuff is pretty good at deciding if a link or form needs to have the session ID appended, and the url_form link_to etc functions could surely be extended to take care of this automatically.

How to test for and require cookies

http://jameshalberg.wordpress.com/2006/05/12/requiring-and-testing-cookies/

My log files are getting huge! How do I set up log rotation?

Put something like this in your environment.rb:

RAILS_DEFAULT_LOGGER = Logger.new("#{RAILS_ROOT}/log/#{RAILS_ENV}.log", 50, 1.megabyte)

(http://schwuk.com/articles/2006/03/21/log-rotation-in-rails-apps)

Cool features

foo.blank? tests to see if it’s empty or nil in Rails. 0 is not blank, though! (http://woss.name/2006/05/07/notes-from-a-rails-course/)

Country select helper

http://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#M000401


Blogs/Other

http://weblog.jamisbuck.org/ !

http://errtheblog.com/ !

Loud Thinking by David Heinemeier Hansson !

http://glu.ttono.us/articles/category/rails

http://weblog.rubyonrails.com/

http://www.planetrubyonrails.org/tags/view/ruby

http://blog.innerewut.de/

http://blog.zenspider.com/

http://jayfields.blogspot.com/

http://blog.segment7.net/

http://www.softiesonrails.com/

http://codefluency.com/

http://nubyonrails.com/

http://www.robbyonrails.com/

http://woss.name/category/geekery/ruby-and-rails/

http://blog.codahale.com/

http://www.lukeredpath.co.uk/

Ruby on Rails Blog

Articles (how-to-ish)

Helpers that take Blocks

http://www.railsdiary.com/diary/validation_securing_form_input The Rails Diary | Validation and Securing Input

http://rails.techno-weenie.net/tip/2005/11/29/textilize_with_syntax_highlighting

Articles: Opinions/philosophy/applicability/etc.

http://glu.ttono.us/articles/2006/08/30/guide-things-you-shouldnt-be-doing-in-rails

Code reuse

http://www.loudthinking.com/arc/000407.html The case against high-level components

One of the clear goals for Rails from the start was to stay at the infrastructure layer. I didn't want Rails to succumb to the lure of high-level components like login systems, forums, content management, and the likes.
...
On the surface, the dream of components sounds great and cursory overviews of new projects also appear to be "a perfect fit". But they never are. Reuse is hard. Parameterized reuse is even harder. And in the end, you're left with all the complexity of a swiss army knife that does everything for no one at great cost and pain.

http://article.gmane.org/gmane.comp.lang.ruby.rails/29166

Engines have not recieved the blessing of the RoR core team, and I

wouldn't expect any different, because it would be madness to include them in the core Rails. It's a mechanism far too easily [ab]used for things it's not actually suitable for. I did speak to Jamis (cc'd to DHH) about them quite some time ago, before plugins existed in a released form. Their advice was to reimplement our mechanism as a plugin, and if anything we were doing could not be achieved in this way, they would re-examine their plugin mechanism to see what could be adapted.

As it turns out, we can do everything we need within their plugins

architecture, so we have a happy coexistance - Rails stays clean, but is flexibly enough to support our (hopefully not too outlandish) needs. It's not like we're forking the project or taking digging our heels in and taking a stand against anything.

http://rails-engines.org/faq/oh-god-what-have-we-done/#extended

Engines aren’t any more evil than generators are or ever were. The LoginEngine doesn’t claim or try to be a one-solution-fits-all high-level component. If anything, it’s easier to hack than the old generators. And easier to replace when you want to drop the “login scaffolding” it provides for something more comprehensively tailored to your needs.
Engines are not evil. It’s possible to write inappropriate Engines in as much as it’s possible to write inappropriate generators, or inappropriate controllers, or inappropriate Rails applications. Engines are not high level components any more than generators are. Engines are plugins which include default views. Every part of an engine is simple to override.

DHH on "Why engines and components are not evil but distracting"

http://weblog.rubyonrails.com/2005/11/11/why-engines-and-components-are-not-evil-but-distracting

I’ve been following the enthusiasm for engines, components, and bigger plugins from the sidelines for a while now. It’s a subject of very mixed emotions. On the one hand, I’m really glad to see that people get so excited and start dreaming of bigger and better things. That’s passion in the works and its great.

On the other hand, I think these developments are basically another name for high-level components. And you all know how I feel about those. The short summary is that high-level components are a mirage: By the time they become interesting, their fitting will require more work than creating something from scratch.

But I start getting really high eyebrows when I hear of “engines that depend on other engines that can be swapped out with yet another engine”. Even plugin dependencies are dangerously close to something I would consider unfit for Rails. Simply because it encourages a style of development that I find unhealthy.

So this is not a slam against the technical merits or implementation of either engines or anything else in the same boat. It’s a concern that they will distract people, that they will appear as needed, and in turn, that they will take the debacle that was Salted Hash Login to a new standardized level.

Rails is all about making the simple things so easy that you need not abstract them. It’s about making the creation of logins, of access control, of content management, of all these business logic components so very easy that you will treasure the application-specific solutions of your own rather than long for The One True Login System.

So what am I saying? That engines should be stopped? Of course not. But I am saying that Rails will continue to send a signal with what’s included in the core that this is a sideshow project. It satisfies some needs for some people and that’s great. But the goal of Rails is to create a world where they are neither needed or strongly desired. Obviously, we are not quite there yet.

One way of getting there is to do a better job of educating new comers in common patterns. Answer the question “if engines and components are not the way, then show me how!”. So this is a call to all those experts out there. Help us spread the good patterns. Make videos, write tutorials, help newbies on #rubyonrails, answer requests on the mailing list.

And if you have a great idea for an engine, or a high-level component in general, think about this: Is there a way I could abstract a smaller slice of functionality as an independent plugin and then release that alongside a pattern that described how to use it like the component would have done all in software? More often than not, I think you could find this to be true.

I think it's worth re-stating this, because it is a very well-said statement about Rails' goals:

Rails is all about making the simple things so easy that you need not abstract them. It’s about making the creation of logins, of access control, of content management, of all these business logic components so very easy that you will treasure the application-specific solutions of your own rather than long for The One True Login System.

http://weblog.rubyonrails.com/2005/11/11/why-engines-and-components-are-not-evil-but-distracting Matt, 2005-11-11 13:10:

I just spent the last two days trying to integrate ModelSecurity (and the SaltedHash login system that comes bolted to it by default) into my application. I took me a good 6 hours just to figure out how the whole thing fit together. The rest of the time was spent trying to figure out how to divest it of the features I didn’t need or wanted to do differently (non-HTTP-AUTH logins, for one.)

http://weblog.rubyonrails.com/2005/11/11/why-engines-and-components-are-not-evil-but-distracting Gabe da Silveira, 2005-11-11 13:46:

This is a serious philosophical debate. I’m glad to hear David’s stance, because it encourages self-sufficiency / roll-your-own behaviour which is rarely encouraged amongst programmers. Why reinvent the wheel? Because you can get one that does exactly what you want and no more.
The pitfall is that most programmers will not be great programmers, especially as RoR moves into the mainstream. Of course standardized engines and frameworks will be necessary, but I’m glad to see that Rails will continue to be focused around core development processes and not providing a library of components.

http://weblog.rubyonrails.com/2005/11/11/why-engines-and-components-are-not-evil-but-distracting jankowski , 2005-11-11 15:22:

I get the same uneasy feeling when people start to talk about building generic CMS tools and photo galleries and groupware and whatnot on the mailing list. Sure, maybe those things are needed in places, but trying to build them in some abstract general-case ways only winds up providing a solution to some very non-specific problems that no one in particular has.
Much better to build a solution to YOUR problems, and then as you optimize and generalize w/in your application you can extract any bits that might be usable elsewhere and get them into either the rails distribution itself, or a plugin/engine/whathaveyou.

http://weblog.rubyonrails.com/2005/11/11/why-engines-and-components-are-not-evil-but-distracting Forrest, 2005-11-11 15:40:

I too needed a login system, saw the reference in the book to the Salted Hash stuff, and started figuring out how to incorporate it. . . about 3 hours later I flushed everything in frustration with an “svn revert”. After another 4 hours I had written the whole thing myself, complete with email verification, salts, forgotten password email messages, and the works-and it was exactly the way I wanted it. In other words: as David said, the mechanisms are already in Rails to make login systems easy (really easy) to do--so easy that it ended up taking less time to write it from scratch than it was going to take me just to learn to use someone else’s, and then coerce my application to work with their way of doing things—-and even then it wouldn’t have worked the way I really wanted it to.
The philosophy of providing the tools to make it easy to build high-level functions, rather than generic functional modules themselves, really does work.

http://weblog.rubyonrails.com/2005/11/11/why-engines-and-components-are-not-evil-but-distracting James Adam, 2005-11-11 23:28:

I think there’s a huge confusion here between ‘generic’ and ‘simple’. The login engine has never tried to be generic. It’s ridiculously simple, that’s all. It’s very opinionated, too. Almost none of its behaviour is dictated by configuration. Engines are only “plug `n’ play” as much as Generators are “script/generate `n’ play”.
...
“supposedly plug ‘n’ play solutions for everybody” – why does everyone think this? It’s no more true than saying the same thing about the SHLG, and who believes that? I’d encourage you to look at what ‘flexibility’ actually means in terms of engines. It’s not configuration, and it never has been.

http://weblog.rubyonrails.com/2005/11/11/why-engines-and-components-are-not-evil-but-distracting David Heinemeier Hansson, 2005-11-12 00:05:

What I’m arguing against is thinking that because you could make logins work generically for your own applications, that it’ll make a great fit for everyone else.
I’ve found that abstraction at an API level is successful for infrastructure-level pieces. Higher abstractions than that are called applications.

Philosophy

http://notrocketsurgery.com/articles/2006/06/24/getting-rails-backwards

What makes Rails special isn’t that it’s a thing for making websites more easily; that is just an ancillary side-effect of what makes Rails a truly stunning achievement. What so many people seem to be missing here at RailsConf is that the achievements of Rails aren’t primarily technical; they are aesthetic.
DHH rightly calls Rails opinionated software. DHH understands that the key to productivity as a programmer is happiness. Rails imposes well-thought-out conventions in order to create a framework that is simple and that results in beautiful code and elegant solutions.
Using Rails is joyful precisely because the core team has had the restraint to say no by default. The core team have maintained DHH’s singular vision for convention over configuration and simplicity and in so doing has bought a tremendous amount of productivity, beauty, and happiness in the process. As Thucydides reminds us, “of all manifestations of power, restraint impresses men most.”

http://www.37signals.com/svn/archives/000887.php

Steve Jobs says: "It comes from saying no to 1,000 things to make sure we don’t get on the wrong track or try to do too much. [...] It’s only by saying no that you can concentrate on the things that are really important.
Jason Fried says: "Always say no default. Make features and ideas beg to be made, beg to be included, beg to be pursued. You’ll know when they need to be."

Erik Kastner said at http://notrocketsurgery.com/articles/2006/06/24/getting-rails-backwards :

"... the overarching goal of making the framework disappear until you truly only work on the business rules that are interesting"

It's not about the scaffolding!

http://notrocketsurgery.com/articles/2006/06/24/getting-rails-backwards

But scaffolding is not and never was intended to be central the the Rails development process. It’s literally a way to save you some typing when getting your application up and going, so you can start poking around with data early on. It’s not supposed to build the application for you. Only you know your business logic, and scaffolding isn’t going to help you with it.

Applications built on Ruby on Rails

Radiant CMS

http://mephistoblog.com/ Mephisto—The best blogging system ever

http://wiki.rubyonrails.com/rails/pages/OpenSourceProjects

http://www.hieraki.org/

http://www.typosphere.org/

Extensions to Ruby

blank?

Defined in /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/core_ext/blank.rb

"", " ", nil, [], and {} are blank

Debugging

Logger

Normally (and by that I mean in your controller) you can access the logger with the easy [helper method] named "logger":

logger.info "whatever you want to log"

However, there are places where that method isn't available: for example, if you have some class that is called by a runner. Or in your tests.

For those places, you can use this alternative...

http://www.robbyonrails.com/articles/2006/01/25/rails-logger-and-those-pesky-tests :

RAILS_DEFAULT_LOGGER

How stuff works / Dissections

Layouts

/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.3/lib/action_controller/layout.rb:

    def render_with_a_layout(options = nil, deprecated_status = nil, deprecated_layout = nil, &block) #:nodoc:
      template_with_options = options.is_a?(Hash)

      if apply_layout?(template_with_options, options) && (layout = pick_layout(template_with_options, options, deprecated_layout))
        options = options.merge :layout => false if template_with_options
        logger.info("Rendering #{options} within #{layout}") if logger

        if template_with_options
          content_for_layout = render_with_no_layout(options, &block)
          deprecated_status = options[:status] || deprecated_status
        else
          content_for_layout = render_with_no_layout(options, deprecated_status, &block)
        end

        erase_render_results
        add_variables_to_assigns
        @template.instance_variable_set("@content_for_layout", content_for_layout)
        render_text(@template.render_file(layout, true), deprecated_status)
      else
        render_with_no_layout(options, deprecated_status, &block)
      end
    end

You notice that @content_for_layout is just the result of calling render_with_no_layout(...).

CaptureHelper

/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.3/lib/action_view/helpers/capture_helper.rb

      # Content_for will store the given block
      # in an instance variable for later use in another template
      # or in the layout.
      #
      # The name of the instance variable is content_for_<name>
      # to stay consistent with @content_for_layout which is used
      # by ActionView's layouts
      #
      # Example:
      #
      #   <% content_for("header") do %>
      #     alert('hello world')
      #   <% end %>
      #
      # You can use @content_for_header anywhere in your templates.
      #
      # NOTE: Beware that content_for is ignored in caches. So you shouldn't use it
      # for elements that are going to be fragment cached.
      def content_for(name, &block)
        eval "@content_for_#{name} = (@content_for_#{name} || '') + capture(&block)"
      end

How do association accessor methods work, how do attribute accessor methods work, and what’s the difference (in ActiveRecord)?

Accessor Missing (http://errtheblog.com/post/20) (2006-08-22). Retrieved on 2007-02-15 22:35.

The question arose from a failed attempt to override an association accessor method. Something like this did not work:

class Story < ActiveRecord::Base
  belongs_to :author

  def author
    auth = super
    auth.name
  end
end

Obviously I want stories.author to return the name of a particular story’s author rather than the associated Author object. Remember that the super method calls the current method on the class parent. I’m trying to call the author method, then, on ActiveRecord::Base (hoping it will return the associated author object like normal). Rails, unfortunately, is having none of it:

>> story = Story.find(:first)
>> story.author
NoMethodError: super: no superclass method `author'

The truth’s that authors is not really a method_missing trick. It’s a real method defined on my Story class. Where does it get defined? When belongs_to is called. Like much of Rails’ sugar, belongs_to is a class method of ActiveRecord::Base. When you call it in a subclass, stuff happens.

The adventurous can peek into active_record/associations.rb around line 646 to see how belongs_to is defined. Here’s the cliffs: when belongs_to is called, ActiveRecord defines reader and writer methods on the calling class. Now it’s starting to make sense: author is added to my Story class when belongs_to is called. It’s not a method_missing trick and doesn’t exist on ActiveRecord::Base.

Accessor Missing (http://errtheblog.com/post/20) (2006-08-22). Retrieved on 2007-02-15 22:35.

That feels great. But I still have a question: what about, say, story.title? Does ActiveRecord define all of my attribute accessor methods in the same way it defines my association accessor methods? All told, attribute accessor methods are indeed method_missing magic. Get pumped: super will work when overriding them. class Story < ActiveRecord::Base belongs_to :author def title "This story's title is: " + super end end So close, so close…

>> story = Story.find(:first)
=> #<Story:0x279f9e0 ...>

>> story.title
=> "This story's title is: Accessor Missing"

["Accessor Missing" is the actual title of his article; this is not an error message!]

Capistrano

http://dev.rbsinteractive.com/wiki/Capistrano

Dissection: Where the tasks are actually implemented

/usr/lib/ruby/gems/1.8/gems/capistrano-1.2.0/lib/capistrano/recipes/standard.rb

task :migrate, :roles => :db, :only => { :primary => true } do
  directory = case migrate_target.to_sym
    when :current then current_path
    when :latest  then current_release
    else
      raise ArgumentError,
        "you must specify one of current or latest for migrate_target"
  end

  run "cd #{directory} && " +
      "#{rake} RAILS_ENV=#{rails_env} #{migrate_env} migrate"
end

task :deploy_with_migrations do
  update_code

  begin
    old_migrate_target = migrate_target
    set :migrate_target, :latest
    migrate
  ensure
    set :migrate_target, old_migrate_target
  end

  symlink

  restart
end

What if your development server doesn't have access to the db?

Then you need to run "rake migrate" from the production server.

I'd like to figure out how to do that with capistrano.

rake remote:migrate VERSION=4

??

http://docs.rubyrake.org/

How stuff works / Dissections

Layouts

/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.3/lib/action_controller/layout.rb:

    def render_with_a_layout(options = nil, deprecated_status = nil, deprecated_layout = nil, &block) #:nodoc:
      template_with_options = options.is_a?(Hash)

      if apply_layout?(template_with_options, options) && (layout = pick_layout(template_with_options, options, deprecated_layout))
        options = options.merge :layout => false if template_with_options
        logger.info("Rendering #{options} within #{layout}") if logger

        if template_with_options
          content_for_layout = render_with_no_layout(options, &block)
          deprecated_status = options[:status] || deprecated_status
        else
          content_for_layout = render_with_no_layout(options, deprecated_status, &block)
        end

        erase_render_results
        add_variables_to_assigns
        @template.instance_variable_set("@content_for_layout", content_for_layout)
        render_text(@template.render_file(layout, true), deprecated_status)
      else
        render_with_no_layout(options, deprecated_status, &block)
      end
    end

You notice that @content_for_layout is just the result of calling render_with_no_layout(...).

CaptureHelper

/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.3/lib/action_view/helpers/capture_helper.rb

      # Content_for will store the given block
      # in an instance variable for later use in another template
      # or in the layout.
      #
      # The name of the instance variable is content_for_<name>
      # to stay consistent with @content_for_layout which is used
      # by ActionView's layouts
      #
      # Example:
      #
      #   <% content_for("header") do %>
      #     alert('hello world')
      #   <% end %>
      #
      # You can use @content_for_header anywhere in your templates.
      #
      # NOTE: Beware that content_for is ignored in caches. So you shouldn't use it
      # for elements that are going to be fragment cached.
      def content_for(name, &block)
        eval "@content_for_#{name} = (@content_for_#{name} || '') + capture(&block)"
      end

Uploading files

http://manuals.rubyonrails.com/read/chapter/57 Rails Cookbook: file upload forms

http://www.kanthak.net/opensource/file_column/ Sebastian Kanthak - FileColumn - easy handling of file uploads in Rails

On DreamHost

http://www.railshosting.org/#dreamhost Ruby on Rails Hosting § Rails Hosting Reviews, Tutorials, Demos, and More...

http://gizmoojo.wordpress.com/2006/02/28/6-tips-for-deploying-ruby-on-rails-with-dreamhost/ 6 tips for deploying Ruby on Rails with Dreamhost « Gizmoojo!

http://casperfabricius.com/blog/2006/05/21/rails-on-dreamhost/ Fingerprints of Casper Fabricius » Deploying Rails with Edge and Engines to Dreamhost using Capistrano

http://woss.name/2006/01/22/using-switchtower-with-ruby-on-rails-and-dreamhost/ Howto: Using Switchtower with Ruby on Rails and DreamHost at Notes from a messy desk

Testing

Fixtures

What if the name of my model is different than the name of my table?

http://wiki.rubyonrails.org/rails/pages/FlexibleFixtures

Rather than assume your fixture files, database tables, model class names and fixture group names all match in every case, this fixture module will let you define multiple fixtures for the same table, or give you the flexibility to use alternate fixture files in your tests.

http://dev.rubyonrails.org/ticket/1911 flexible fixtures

fixture :additional_topics, :table_name => "topics"

Don't like fixtures?

Sometimes I don't.

http://blog.floehopper.org/articles/2006/08/10/fed-up-with-rails-fixtures-part-one Fed up with Rails fixtures? (part one)

I want to use the data in my database! I'm sick of manually copying the data into a fixture.

Me too.

http://www.bigbold.com/snippets/posts/show/2525 (Download)

Also:

http://www.jamesbritt.com/index.rb/Development@Rails_Fixtures_from_Database_Contents.txt

How are functional tests and integration tests different?

From Rdoc: "An IntegrationTest is one that spans multiple controllers and actions, tying them all together to ensure they work together as expected. It tests more completely than either unit or functional tests do, exercising the entire stack, from the dispatcher to the database."

"Note that each of the tests shown above test multiple requests. Most functional tests only test one." Also, integration tests run through the entire stack, from the dispatcher, through the routes, into the controller and back. Functional tests skip straight to the controller." http://weblog.jamisbuck.org/2006/3/9/integration-testing-in-rails-1-1

Common to Functional/Integration tests

After a request, you can inspect/test against "session" like you are used to calling in a controller. (module TestProcess #session just returns @response.session)

Confusing: functional tests and integration tests are incorrectly named

"Integration" tests are really acceptance tests

Luke Redpath on http://www.sitepoint.com/forums/showthread.php?t=354597 recommends that Rails rename their tests accordingly. I agree.

"Functional" tests are really unit tests for controllers

As Jon Tirsen commented on [4]: "Jamis, these "integration" tests are really nice and I think they hit a sweet spot in between exercising a large portion of your application without requiring too much testing infrastructure. These tests are certainly more functional tests than the so called functional tests are! (They are actually unit tests for a controller.)" That's right, the so-called "functional tests" can almost be considered unit tests for controllers.

Luke Redpath on http://www.sitepoint.com/forums/showthread.php?t=354597 recommends that Rails rename their tests accordingly. I agree.

Functional tests

Things to keep in mind

  • You're essentially dealing with a lot of mock objects here -- TestSession instead of CGI::Session, for example. Although the mocks behave as the real ones do in most cases (hurray for duck typing), there are exceptions. Watch out for them.

Setting sessions

@request.session[:whatever] = 'expected'
get 'action_name'
assert_equal 'expected', @response.session[:whatever]

Problem: If you use a custom ActiveRecord session store and try to refer to session.some_column in your functional tests, you'll be disappointed when you get an error

Why? Because in the functional tests, @response.session is of class ActionController::TestSession, not of class [whatever your custom class is], so it doesn't have your model's columns, associations, etc.

Researching a workaround... Currently the best workaround I've heard of is to just use Integration tests, which do use your real sessions -- because they initialize the whole Rails stack.

A closely related problem for those who don't have a custom ActiveRecord session store per se but are trying to use ActiveRecordStore's model attribute: http://dev.rubyonrails.org/ticket/4424

Integration tests

How to create an integration test

$ ./script/generate integration_test a
      exists  test/integration/
      create  test/integration/a_test.rb

Good example of an integration test: http://weblog.jamisbuck.org/2006/3/9/integration-testing-in-rails-1-1

Setting sessions

Setting sessions in integration tests is different than setting them in functional tests:

  • @request.session (and plain old session, which is an alias for that) will be nil until your first request, after which time it will be a CGI::Session. Actually, I think @request is nil, so @request.session is nil.session!
    #puts session.inspect  # this will not work! You need to do a page request first to initialize your session!
    get '/test/page1'
    puts session.inspect

What if you really need to set up the session before your first page request? Well, you can always make a dummy controller/action and call it. That way, @request.session will be initialized but whatever side-effect of calling the real-action-you-want-to-call will be avoided.

  get '/dummy/'
  @request.session[:whatever] = true
  @request.session.update
  get '/real/request'

  • Question: Will setting @request.session cause the next request you make to have those changes?

No, not unless you save the session (I'm assuming you're using ActiveRecordStore or similar, but it's probably the same regardless).

  get '/dummy/'
  @request.session[:whatever] = true
  @request.session.update
  get '/real/request'

/usr/lib/ruby/1.8/cgi/session.rb:
315     # Store session data on the server.  For some session storage types,
316     # this is a no-op.
317     def update
318       @dbman.update
319     end

Print a helpful message when an assertion fails

You can pass a helpful message that gets printed only upon failure. This is very useful to help you quickly figure out why the assertion failed. (I find it very frustrating when a tests fail and the message given gives me no clue as to what went wrong.)

assert_redirected_to( {:action => "some action"}, @response.body[%r{errorExplanation(.*?)</ul>}m, 1] )

Testing plugins

http://www.pluginaweek.org/2006/11/24/plugin-tip-of-the-week-testing-your-plugins-the-right-way/

Very nice article.

They suggest that you make a tiny Rails application in a subdirectory of your plugin and tell your tests to use that.

It takes a bit of "hacking" your test_helper and stuff to load the Rails environment from your subdirectory and have it find your plugin even though your plugin isn't in vendor/plugins, but ... it works, I guess.

Only other way I can think to do this (be able to test your plugin without installing it in a specific application) is to distribute your plugin inside of a dummy/demo app...

Deployment

http://brainspl.at/files/AgileRailsDeployment.pdf : Quite informative

http://gizmoojo.wordpress.com/2006/02/28/6-tips-for-deploying-ruby-on-rails-with-dreamhost/ 6 tips for deploying Ruby on Rails with Dreamhost « Gizmoojo!

http://manuals.rubyonrails.org/read/chapter/97#page257 Capistrano: Automating Application Deployment |

http://blog.nanorails.com/articles/2006/03/05/a-better-alternative-to-killall


http://weblog.textdrive.com/article/175/rails-optimizing-resource-usage : killall -9 ruby

http://forums.asmallorange.com/lofiversion/index.php/t7259.html : pkill -9 yournamehere -f dispatch.fcgi 1:16

http://weblog.jamisbuck.org/2005/7/14/application-deployment-with-rails : "You type a (single!) command on your local development box which deploys your application to both of your application servers and restarts the fcgi processes for them."

http://forums.hostmysite.com/about1772.html : Well the mod_fastcgi servers should restart themselves automatically from time to time. If you want to give your server a kick, try the following at the command line: ps uxww|grep 'dispatch\.\(fcgi\)\?'|awk '{print $2}'|xargs kill -9 1:27

I noticed that too... Eventually it will reload it ... but not always as soon as you'd like


http://rails.techno-weenie.net/question/2006/5/23/capistrano_wont_spawn_fcgi_processes Capistrano won't spawn fcgi processes


http://manuals.rubyonrails.org/read/chapter/97#page257 Capistrano: Automating Application Deployment |


http://blog.nanorails.com/articles/2006/03/05/a-better-alternative-to-killall A better alternative to killall

Mongrel

http://blog.codahale.com/2006/06/19/time-for-a-grown-up-server-rails-mongrel-apache-capistrano-and-you/

http://blog.dgibbons.net/articles/2006/08/08/why-you-need-multiple-mongrel-instances-with-rails

http://bliki.rimuhosting.com/space/knowledgebase/linux/miscapplications/ruby+on+rails RimuHosting Bliki :: knowledgebase/linux/miscapplications/ruby on rails

http://mongrel.rubyforge.org/docs/apache.html Apache Best Practice Deployment

http://blog.kovyrin.net/2006/08/22/high-performance-rails-nginx-lighttpd-mongrel/

Hosting providers

http://www.planetargon.com/hosting.html: $11.25/month for high-quality shared hosting.

http://rimuhosting.com/order/startorder.jsp: RimuHosting: $20/month for VPS.

http://rimuhosting.com/vps/aboutvps.jsp

http://engineyard.com/pricing

http://dreamhost.com

FastCGI configuration

fastcgi docs: http://fastcgi.coremail.cn/doc.htm

http://wiki.rubyonrails.org/rails/pages/Rails+on+CentOS+4.3+with+Apache+and+FastCGI+Simply/versions/71 :

  AddHandler fcgid-script .fcgi
  SocketPath /var/lib/apache2/fcgid/sock
  IdleTimeout 3600
  ProcessLifeTime 7200
  MaxProcessCount 8
  DefaultMaxClassProcessCount 2
  IPCConnectTimeout 8
  IPCCommTimeout 60
  DefaultInitEnv RAILS_ENV production

http://wiki.rubyonrails.org/rails/pages/HowtoUploadFiles :

In /etc/apache2/mods-enabled/fcgid.conf:

  AddHandler fcgid-script .fcgi
  SocketPath /var/lib/apache2/fcgid/sock
  IdleTimeout 3600
  ProcessLifeTime 7200
  MaxProcessCount 8
  DefaultMaxClassProcessCount 2
  IPCConnectTimeout 8
  IPCCommTimeout 60
  DefaultInitEnv RAILS_ENV production

Performance

http://www.rubyinside.com/railstips/user/peter/tag/performance

Troubleshooting

Application error (failed to start)

Check the log file.

Reasons for this error:

  • file script/../config/../tmp/sessions//ruby_sess.0f36e74cd2a06698 not readable (solution: delete cookie to force new session)
  • permissions
  • failed to include some required file
  • (production:) pretty much any exception


Troubleshooting

ActiveRecord::ConnectionNotEstablished : may simply be the permissions on production.log!

At least that's how I fixed it in this case (2007-01-16):

$ ./script/console production
Loading production environment.
/usr/lib/ruby/1.8/logger.rb:527:in `initialize':Errno::EACCES: Permission denied - script/../config/../config/../log/production.log
/usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:123:in `const_missing':NameError: uninitialized constant ExceptionNotifiable
>> User.find(:first)
ActiveRecord::ConnectionNotEstablished: ActiveRecord::ConnectionNotEstablished
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/connection_adapters/abstract/connection_specification.rb:225:in `retrieve_connection'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/connection_adapters/abstract/connection_specification.rb:78:in `connection'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/base.rb:1046:in `add_limit!'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/base.rb:1017:in `construct_finder_sql'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/base.rb:924:in `find_every'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/base.rb:918:in `find_initial'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/base.rb:380:in `find'
        from (irb):1

Tried this, but I didn't have permissions to modify the file:

$ chmod a+w log/production.log
chmod: changing permissions of `log/production.log': Operation not permitted

But I do have drwxrwxrwx on the log directory, so that must be sufficient permissions to move the file:

$ mv log/production.log log/production.log.20070116; touch log/production.log

(The already-running application continued to append to the old file (log/production.log.20070116). However, when I ran script/console production, the console logged to the new file (log/production.log).)

When I tried it this time, it worked!

$ ./script/console production
Loading production environment.
>> User.find(:first)
=> #<User:0xb7460604 ...>

ActiveRecord

http://weblog.rubyonrails.org/2006/03/28/rails-1-1-rjs-active-record-respond_to-integration-tests-and-500-other-things/ : has stuff on eager loading

Migrations

http://www.perham.net/mike/log/?p=462 Mutterings » Blog Archive » Rails Migrations

http://wiki.rubyonrails.org/rails/pages/Foreign+Key+Schema+Dumper+Plugin Foreign Key Schema Dumper Plugin in Ruby on Rails

Jobs

http://www.workingwithrails.com/ Working With Rails

Troubleshooting

ActiveRecord::ConnectionNotEstablished : may simply be the permissions on production.log!

At least that's how I fixed it in this case (2007-01-16):

$ ./script/console production
Loading production environment.
/usr/lib/ruby/1.8/logger.rb:527:in `initialize':Errno::EACCES: Permission denied - script/../config/../config/../log/production.log
/usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/dependencies.rb:123:in `const_missing':NameError: uninitialized constant ExceptionNotifiable
>> User.find(:first)
ActiveRecord::ConnectionNotEstablished: ActiveRecord::ConnectionNotEstablished
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/connection_adapters/abstract/connection_specification.rb:225:in `retrieve_connection'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/connection_adapters/abstract/connection_specification.rb:78:in `connection'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/base.rb:1046:in `add_limit!'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/base.rb:1017:in `construct_finder_sql'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/base.rb:924:in `find_every'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/base.rb:918:in `find_initial'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/base.rb:380:in `find'
        from (irb):1

Tried this, but I didn't have permissions to modify the file:

$ chmod a+w log/production.log
chmod: changing permissions of `log/production.log': Operation not permitted

But I do have drwxrwxrwx on the log directory, so that must be sufficient permissions to move the file:

$ mv log/production.log log/production.log.20070116; touch log/production.log

(The already-running application continued to append to the old file (log/production.log.20070116). However, when I ran script/console production, the console logged to the new file (log/production.log).)

When I tried it this time, it worked!

$ ./script/console production
Loading production environment.
>> User.find(:first)
=> #<User:0xb7460604 ...>
Retrieved from "http://whynotwiki.com/Rails"
Ads
Personal tools