Rails, as an open source framework, benefits greatly from contributions from the community. Rails incorporates code from hundreds of developers, not just the dozen or so on the core team. Writing code to expand, extend, or fix Rails is often the best way to learn about its internals.
Of course, not all functionality belongs in Rails itself. Rails is an opinionated frame-work, so there are some defaults that may not be useful to everyone. The plugin system was designed so that Rails would not have to incorporate every feature that is useful to someone. Refer to Chapter 3 for information on writing plugins to extend Rails; it is only minimally more work than patching the Rails codebase.
There are several reasons that useful features are rejected from Rails in favor of being plugins. The primary reason for rejection is that the feature is too specific; it would not be useful to most Rails developers. Alternatively, it may be contrary to the "opinion" of the framework. However, features may be rejected simply because there are many valid ways of accomplishing one goal, and it does not make sense to default to one. Some common areas of functionality that have been repeatedly discussed and rejected from Rails core are the following:
David Heinemeier Hansson's rejection of high-level components in Rails is a topic that has generated much more heat than light. Rails engines (http://rails-engines.org) are full-stack (model, view, and controller) components that can be incorporated into larger applications; in effect, they augment the plugin system to structure the sharing of model, view, and controller code.
The trouble with engines comes when they are treated as high-level components, as if dropping a content-management-system engine into an application will accomplish 90% of the CMS functionality a particular project needs. In many cases, the work required to integrate such a high-level component into an existing application outweighs the benefits of not writing the component from scratch.
In short, engines are best seen as a way to structure plugins that need controller and view code, rather than a drop-in replacement for high-level features. In this respect, engines are amazingly powerful; they allow plugins to augment an application's models, views, controllers, helpers, and even routes and migrations.
There are many valid approaches to the problems of internationalization and localization. We discuss several solutions in Chapter 8. ActiveSupport's Multi-Byte standardizes the low-level operations on Unicode text, but there are still many valid ways to localize an application at the high level.
Again, there are many application-specific ways to authorize users that authorization does not belong in Rails. Authorization can range from a simple "admin" Boolean flag on a user's record to a complete role-based access control system, and there are plenty of plugins available for authorization in Rails.
Similarly, there are many valid ways to authenticate users. The only authentication method in Rails proper is HTTP Basic authentication, because it is very simple. However, the most popular authentication solutions descend from the acts_as_authenticated plugin by Rick Olson (which has its roots in Tobias Lütke's original Login Generator). The newer version of acts_as_authenticated is restful_authentication, which is the same logic molded into Rails 2.0's REST-ful paradigm.
Scaffolding is another misunderstood issue in Rails. Like high-level components, scaffolding can be overapplied. Developers who see scaffolding as a substitute for writing complex application code will be disappointed.
Still, there are many valid uses for simple CRUD features, especially on an application's administrative interface. Streamlined (http://streamlinedframework.org)isa way to quickly build CRUD interfaces on top of Rails. Additionally, AjaxScaffold (http://www.ajaxscaffold.com/) is a scaffolding system with a richer interface than the built-in Rails scaffolds.
There is another reason for starting a feature as a plugin,
though. Plugins are often a testing ground for experimental or risky
features before they are rolled into the main distribution. As an
example, the RESTful features of Rails 2.0 were a plugin, simply_ restful
, before they were pulled into
Rails trunk.
There is a new process for contributing Rails bug reports and patches, to help deal with the large volume of tickets and contributed patches. The process is as follows:
Bug reports are always welcome, but patches get more attention. Fixing problems that you experience, even if they are problems with Rails itself, has some advantages. It gives you greater familiarity with the Rails source, which is very well written. It also helps you verify and prove that the problems you are experiencing are actually problems with Rails.
Most patches should include tests. Especially for bugfixes, test-driven development is a good philosophy to use. A test-driven methodology would incorporate the following basic steps:
Before writing any application code, write a test that verifies the correct functionality. Run the test; it should fail.
Write code to fix the bug until the test passes.
Verify that all other tests pass, not just the test in question.
Most areas of Rails are well-tested; however, there are some components (such as the generators and CGI processing code) that are not very easy to test. When writing nontrivial patches for those areas, the burden of proof is higher to ensurethat there are no regressions of other functionality.
The basic procedure to create a patch against Rails is simple:
svn up
to grab the
latest version of Rails.
Make the appropriate changes to the source.
Run rake
to ensure
all of the tests pass.
Check the output of svn
st
as a sanity check for missing files, merge
conflicts, or other junk.
svndiff>my_patch.diff
to create a unified diff of the
changes.
Manually inspect the generated diff to verify that there are no extraneous changes and that all necessary changes are included.
Rails uses the Trac issue-tracking system, which is set up at http://dev. rubyonrails.org/. You need an account to use the system, due to excessive spam in the past. Once you have an account, click "New Ticket" to create a ticket.
If you have a patch for the issue you are reporting, use
[PATCH]
at the beginning of
the ticket summary, and remember to check the "I have files to
attach to this ticket" button.
There are a few points of basic ticket-creation etiquette. Leave the "Keywords" field blank unless you know better. The "Assign to" and "Cc" fields should in most cases be left blank as well; if other developers want to be added to the Cc list, they will add themselves.
At this point, there may be a good deal of back-and-forth, or there may be no activity. Be prepared to defend your decisions and your code, and you may be sent back for rewrites or additions. Make sure to keep the patch current; if the Rails trunk changes significantly in the meantime, you should rebase your patch so that it still applies cleanly (with no fuzz). If a patch is well-tested, it can be rebased and verified simply by repeating the preceding steps (updating, running tests, and creating a new diff).
Every month, the Rails Trac system sees thousands of actions (tickets opened, closed, or commented on). In order to manage this flow, there is a barrier to entry for contributors so that the core team doesn't have to deal with patches that are outdated, untested, or that break obvious functionality.
Generally, to get attention for a patch, you have to find
three reviewers to assert that the patch applies cleanly, passes
the automated tests, and "works" for them. They do this by
making an appropriate comment (as simple as "+1 for me") in the
ticket's comments. Once you have three reviewers, it is up to
you to add the verified
keyword to the ticket to indicate that review is
complete.
At this point, your ticket will show up in the "verified" report (http://dev.rubyonrails.org/report/12). Patches in this queue are usually acted upon quickly, whether the action is acceptance, rejection, or sending it back for more discussion.
Rails contributors use ticket status and resolutions as a way to deal with the large volume of incoming tickets. It is common for a ticket to be opened and closed a few times before being accepted or rejected. Don't worry if your ticket is closed as untested; that just means it needs tests (or, alternatively, a very good reason why it can't or shouldn't be tested). Then it can be sent back.
Also, don't be discouraged if your patch is ultimately rejected. There are many features that used to be in core Rails that have been removed or turned into plugins in Rails 2.0. The Rails core team is focusing on keeping Rails smaller and more agile; most frameworks tend to accumulate features without limit unless their growth is kept in check. The Rails plugin system was designed so that almost anything in Rails can be changed at runtime. In this way, the core can be kept simple without hampering the development of new features.
Many Rails contributors spend time in the #rails-contrib
IRC channel on irc.
freenode.org, and there is good discussion about Rails
internals there. Most contributors also subscribe to the
rubyonrails-core mailing list at Google Groups (http:// groups.google.com/group/rubyonrails-core),
which has more visibility and permanence than the IRC channel. Both
are good places to look for patch reviewers, and it is a good idea to
ask around on IRC and the mailing list before diving into any major
new feature work; there may be people who have started (or even
completed) similar work, and it is good to find other developers who
may have ideas about your plans.
The Rails framework is built in a modular fashion; Rails 2.0 comprises ActionPack, ActiveRecord, ActionMailer, Active Resource, ActiveSupport, and RailTies. As such, each component is tested separately. This causes a few minor issues (as the full functionality of one module may depend partially on another, so they can never be completely independent), but for the most part it increases flexibility, and the benefits outweigh the drawbacks.
Unit tests for most components are self-contained. Each component has a
Rakefile defining its testing strategy and any other Rake tasks it
requires. With the exception of ActiveRecord, all Rails components can be tested by changing to their directory
and executing rake
. If all is well,
you will see a large number of periods (representing successful
tests), and it will end with no failures or errors:
Finished in 1.444231 seconds. 704 tests, 5475 assertions, 0 failures, 0 errors
ActiveRecord is a bit more difficult to test, due to its
dependency on many external databases. The default task, if no task is
provided to rake
, is to run the
tests for MySQL, PostgreSQL, SQLite (version 2), and SQLite3.
In Rails 2.0, the lesser-used connection adapters have been moved out of Rails trunk and into gems so that they can be installed if needed. The connection adapters for Firebird, FrontBase, OpenBase, Oracle, Microsoft SQL Server, and Sybase now live under /adapters in the Rails repository.
The gems for these connection adapters are hosted at the Rails
gem server (http://gems.rubyonrails.org) and
are named activerecord-
(for example, dbname
-adapteractiverecord-oracle-adapter
or activerecord-sqlserver-adapter
), so they
can be installed with a command such as the following:
$ gem install activerecord-sybase-adapter –source \ http://gems.rubyonrails.org
Incidentally, IBM maintains its own DB2 adapter for ActiveRecord, so the old ActiveRecord DB2 connection adapter is gone in Rails 2.0.
In order to run the ActiveRecord unit tests, you will need to create test databases. Thankfully, there are some Rake tasks that automate this process. For a full test of the "big four" connection adapters from the ground up, follow these steps:
Install and configure the database servers: MySQL, PostgreSQL, SQLite, and SQLite3. The client libraries also need to be installed; these are installed with the server binaries but can be installed separately if the server is on a remote machine.
For MacPorts users, SQLite 2 can be installed with sudo port install sqlite2
(even though
its files are named sqlite
for
historical reasons, the port name is now sqlite2
). If you have previously
installed the sqlite
port, you
should uninstall it before installing sqlite2
and sqlite3
xs.
Install the Ruby database libraries with RubyGems:
$ sudo gem install mysql $ sudo gem install postgres $ sudo gem install sqlite-ruby $ sudo gem install sqlite3-ruby
Installing the mysql
library is not strictly necessary; Rails includes a pure-Ruby
MySQL library in
activerecord/lib/active_record/vendor/mysql.rb.
This implementation will be used if no native mysql extension is
found, which is useful on systems like Windows, where a Ruby
interpreter can be more accessible than a C compiler.
Additionally, the postgres
gem
can be replaced by the postgres-pr
gem, which is also pure
Ruby.
MacPorts users may have some difficulty with these steps;
the default configuration scripts look in the /usr and
/usr/local trees for the client libraries installed in
step 1, while MacPorts installs into
/opt/local. This can be fixed by passing
configuration parameters into gem
, which are preceded by -- so they
are not parsed as options to gem
itself:
$ sudo gem install sqlite3-ruby -- --with-sqlite-dir=/opt/local
Verify the database connection information for the unit tests. ActiveRecord tests require two database connections per adapter, to verify that ActiveRecord is able to properly manage multiple simultaneous connections. If needed, modify the connection specifications in activerecord/test/connections/native_adaptername/ connection.rb.
Create the unit test databases for MySQL and PostgreSQL. The SQLite and SQLite3 databases will be automatically created upon first run, as they are backed by a single file per database. For a default configuration (no modifications to the above connection specifications), run the following Rake tasks from the activerecord directory:
$ rake build_mysql_databases $ rake build_postgresql_databases
For more complicated configurations, create the databases specified in the preceding connection files, and ensure that the user specified in those files has full access to those databases, or you will get permission errors.
Change to the activerecord directory
and run rake
. The tests should
run on all four connection adapters with no failures or errors.
Tests for individual adapters can be run with separate Rake tasks
such as test_mysql;
run
rake-T
for a list of all
recognized tasks.