Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

542 lines (393 sloc) 22.993 kb

dm-rails

This gem provides the railtie that allows datamapper to hook into rails3 and thus behave like a rails framework component. Just like activercord does in rails, dm-rails uses the railtie API to hook into rails. The two are actually hooked into rails almost identically.

Creating new datamapper apps on rails3 from scratch is actually really easy. The following will guide you through the process.

Generating a new application from scratch

It's really easy to go from zero gems to a working rails3 app using datamapper. All you need is the latest rubygems release and rails

gem update —system # you will need rubygems ~> 1.3.6 for the command below to work
gem install rails --pre

Once you have rails and thus bundler installed, you can bootstrap a rails master branch application with a single command. Yes! A single command. Cool.

rails new project_name -m http://datamapper.org/templates/rails.rb

When run, the command will print out some options on how to proceed with your newly generated application.

rspec support

Unfortunately, I haven't yet been able to get rspec to work with the default sqlite3 database.yml setup. In order to get rspec working you would need to change to another database for now, like mysql. Edit your database.yml file to look something like this:

defaults: &defaults
  adapter: mysql
  username: root
  password:
  host: localhost

development:
  database: dm_rails3_app_development
  <<: *defaults

  # Add more repositories
  # repositories:
  #   repo1:
  #     adapter:  postgresql
  #     database: sample_development
  #     username: the_user
  #     password: secrets
  #     host:     localhost
  #   repo2:
  #     ...

test:
  database: dm_rails3_app_test
  <<: *defaults
production:
  database: dm_rails3_app_production
  <<: *defaults

and you should be good to and use rspec.

Developing dm-rails

Issue the following commands to get a fully functional development environment including datamapper and rails up and running within a minute.

gem install bundler # if this is new for you
bundle install

Whenever you want to update your dependencies because you want to make sure you develop against master branches, just issue

bundle install

again and bundler will go ahead and fetch the latest commits from the gems you depend on.

Sample Gemfile

Using bundler it's really easy to get an app going with datamapper and rails3. Just use a Gemfile like this, and bundler will pull in everything needed to run your app. Note that you also must add any additional datamapper plugin or any other gem that you'd like to use to the Gemfile. This makes sure that bundler is able to provide a complete environment containing all required dependencies for your app.

source 'http://rubygems.org'

RAILS_VERSION = '~> 3.0.0.beta4'

DM_VERSION    = '~> 1.0.0'

RSPEC_VERSION = '~> 2.0.0.beta.11'

gem 'activesupport',      RAILS_VERSION, :require => 'active_support'
gem 'actionpack',         RAILS_VERSION, :require => 'action_pack'
gem 'actionmailer',       RAILS_VERSION, :require => 'action_mailer'
gem 'railties',           RAILS_VERSION, :require => 'rails'

gem 'dm-rails',               DM_VERSION
gem 'dm-#{database}-adapter', DM_VERSION

# You can use any of the other available database adapters.
# This is only a small excerpt of the list of all available adapters
# Have a look at
#
#  http://wiki.github.com/datamapper/dm-core/adapters
#  http://wiki.github.com/datamapper/dm-core/community-plugins
#
# for a rather complete list of available datamapper adapters and plugins

# gem 'dm-sqlite-adapter',    DM_VERSION
# gem 'dm-mysql-adapter',     DM_VERSION
# gem 'dm-postgres-adapter',  DM_VERSION
# gem 'dm-oracle-adapter',    DM_VERSION
# gem 'dm-sqlserver-adapter', DM_VERSION

gem 'dm-migrations',        DM_VERSION
gem 'dm-types',             DM_VERSION
gem 'dm-validations',       DM_VERSION
gem 'dm-constraints',       DM_VERSION
gem 'dm-transactions',      DM_VERSION
gem 'dm-aggregates',        DM_VERSION
gem 'dm-timestamps',        DM_VERSION
gem 'dm-observer',          DM_VERSION

group(:test) do

  gem 'rspec',              RSPEC_VERSION
  gem 'rspec-core',         RSPEC_VERSION, :require => 'rspec/core'
  gem 'rspec-expectations', RSPEC_VERSION, :require => 'rspec/expectations'
  gem 'rspec-mocks',        RSPEC_VERSION, :require => 'rspec/mocks'
  gem 'rspec-rails',        RSPEC_VERSION

end

# ------------------------------------------------------------------------------

# These gems are only listed here in the Gemfile because we want to pin them
# to the github repositories for as long as no stable version has been released.
# The dm-core gem is a hard dependency for dm-rails so it would get pulled in by
# simply adding dm-rails. The dm-do-adapter gem is a hard dependency for any of
# the available dm-xxx-adapters. Once we have stable gems available, pinning these
# gems to github will be optional.

gem 'dm-core',              DM_VERSION
gem 'dm-do-adapter',        DM_VERSION
gem 'dm-active_model',      DM_VERSION

# Uncomment this line if you want to see application specific metric data

# gem 'rails_metrics', '~> 0.1', :git => 'git://github.com/engineyard/rails_metrics'

Sample database.yml files

DataMapper supports connecting to and working with multiple repositories easily. In order to be able to take full advantage of that feature in rails, you can configure as many repositories for your different environments as you wish. All you need to do is follow some simple naming conventions and you're good to go. Have a look at the #{adapter}_defaults declarations in the sample files below. That's the only convention you need to follow. Your default declarations should always end with “defaults”. This is necessary for dm-rails to not confuse these with any of your environment declarations. In fact, dm-rails looks at the content of your database.yml and rejects every key that matches /defaults/. The remaining entries represent the repository configurations for the available environments.

An example for setting up a single repository for every environment.

defaults: &defaults
  adapter: mysql
  username: root
  password:
  host: localhost

development:
  database: rails3_app_development
  <<: *defaults
test:
  database: rails3_app_test
  <<: *defaults
production:
  database: rails3_app_production
  <<: *defaults

An example for setting up multiple repositories for every environment.

mysql_defaults: &mysql_defaults
  adapter: mysql
  username: mysql_user
  password: mysql_sekret
  host: localhost

postgres_defaults: &postgres_defaults
  adapter: postgres
  username: postgres_user
  password: postgres_sekret
  host: postgres_host

oracle_defaults: &oracle_defaults
  adapter: oracle
  username: oracle_user
  password: oracle_sekret
  host: oracle_host

development:
  database: rails3_mysql_development
  <<: *mysql_defaults
  repositories:
    foo:
      database: rails3_postgres_development
      <<: *postgres_defaults
    bar:
      database: rails3_oracle_development
      <<: *oracle_defaults

test:
  database: rails3_mysql_test
  <<: *mysql_defaults
  repositories:
    foo:
      database: rails3_postgres_test
      <<: *postgres_defaults
    bar:
      database: rails3_oracle_test
      <<: *oracle_defaults

production:
  database: rails3_mysql_production
  <<: *mysql_defaults
  repositories:
    foo:
      database: rails3_postgres_production
      <<: *postgres_defaults
    bar:
      database: rails3_oracle_production
      <<: *oracle_defaults

Once you have defined your database.yml file, dm-rails's rake tasks will be able to create, drop, auto_migrate! and auto_upgrade! all your defined repositories.

Available generators

Since the new generators provide hooks that allow the dm-rails gem to provide generators that hook into parts like model, scaffolds or test generation, everything just works like it does with active_record.

The following generators are available to help you get started with the typical components of any rails application.

...
rails generate controller
rails generate generator
rails generate helper
rails generate integration_test
rails generate migration
rails generate model
rails generate observer
rails generate performance_test
rails generate plugin
rails generate resource
rails generate scaffold
rails generate scaffold_controller
rails generate session_migration
rails generate stylesheets
...

For a complete list run the following in your project's directory

rails generate

Available datamapper specific rake tasks

To get a list of all available rake tasks in your rails3 app, issue the usual

rake -T

Once you do that, you will see the following rake tasks among others. These are the ones that dm-rails added for us.

...
rake db:automigrate            # Perform destructive automigration of all repositories in the current Rails.env
rake db:autoupgrade            # Perform non destructive automigration of all repositories in the current Rails.env
rake db:create                 # Create the database(s) defined in config/database.yml for the current Rails.env - also creates the test database(s) if Rails.env.development?
rake db:create:all             # Create all the local databases defined in config/database.yml
rake db:drop                   # Drops the database(s) for the current Rails.env - also drops the test database(s) if Rails.env.development?
rake db:drop:all               # Drop all the local databases defined in config/database.yml
rake db:migrate                # Migrate the database to the latest version
rake db:migrate:down[version]  # Migrate down using migrations
rake db:migrate:up[version]    # Migrate up using migrations
rake db:seed                   # Load the seed data from db/seeds.rb
rake db:sessions:clear         # Clear the sessions table for DataMapperStore
rake db:sessions:create        # Creates the sessions table for DataMapperStore
rake db:setup                  # Create the database, load the schema, and initialize with the seed data
...

Configuring and introspecting dm-rails

Rails3 makes it easy to expose framework component specific configuration to application developers in a uniform and easy to use way. To build on this philosophy, dm-rails exposes its configuration via a single object that is used throughout dm-rails's code to store configuration relevant to datamapper and to rails. You can access it from within your application and of course alter it's settings in your config/application.rb or config/environments files. Here's a quick overview of the API the configuration object exposes. Expect this to grow as we come up with additional useful stuff to configure.

Rails::DataMapper.configuration
Rails::DataMapper::Configuration.for(database_yml_hash)

Rails::DataMapper::Configuration#raw
Rails::DataMapper::Configuration#environments
Rails::DataMapper::Configuration#repositories

As promised, you can easily inspect the configuration from your running rails application. Let's start a rails console and have a look what's configured for our app.

ree-1.8.7-2010.01 mungo:alfred snusnu$ rails console
Loading development environment (Rails 3.0.0.beta4)
ruby-1.8.7-p248 > require 'pp'
 => ["PP"]
ruby-1.8.7-p248 > pp Rails::DataMapper.configuration
#<Rails::DataMapper::Configuration:0x103b46460
 @raw=
  {"production"=>
    {"adapter"=>"mysql",
     "username"=>"root",
     "database"=>"alfred_production",
     "host"=>"localhost",
     "password"=>nil},
   "development"=>
    {"adapter"=>"mysql",
     "username"=>"root",
     "database"=>"alfred_development",
     "host"=>"localhost",
     "password"=>nil},
   "defaults"=>
    {"username"=>"root",
     "adapter"=>"mysql",
     "host"=>"localhost",
     "password"=>nil},
   "test"=>
    {"adapter"=>"mysql",
     "username"=>"root",
     "database"=>"alfred_test",
     "host"=>"localhost",
     "password"=>nil}},
 @repositories=
  {"production"=>
    {"default"=>
      {"adapter"=>"mysql",
       "username"=>"root",
       "database"=>"alfred_production",
       "host"=>"localhost",
       "password"=>nil}},
   "development"=>
    {"default"=>
      {"adapter"=>"mysql",
       "username"=>"root",
       "database"=>"alfred_development",
       "host"=>"localhost",
       "password"=>nil}},
   "test"=>
    {"default"=>
      {"adapter"=>"mysql",
       "username"=>"root",
       "database"=>"alfred_test",
       "host"=>"localhost",
       "password"=>nil}}},
 @root=#<Pathname:/Users/snusnu/projects/github/mine/alfred>>

 => nil

Additionally, you can reach the configuration object via the standard way that rails provides to expose configuration for framework components and plugins.

Rails.application.config.data_mapper

This will give you the exact same object we inspected in the previous pretty print output from rails console.

Extending dm-rails

It's easy to extend or adapt dm-rails to meet your specific needs. Thanks to the railties API it's possible to hook into any part of the initialization process. In order to customize dm-rails, all you need to do is define your own Rails::Railtie class that inherits from Rails::DataMapper::Railtie and require that instead of the standard dm-rails/railtie. During initialization of any rails plugin, the initializers defined by that plugin are run in the order specified. Since dm-rails defines every action that gets called by its initializers as a method on either the Railtie instance or the class, you can just go ahead and overwrite these in your subclass. All those methods get called with the running Rails::Application (always reachable via Rails.application) as single parameter, so you can customize depending on the app's state as much as you wish. Additionally, the initializers are all named, and you can hook your own additional initializers before or after any of the named rails (or dm-rails) initializers.

To give you an idea of what you get when inheriting from Rails::DataMapper::Railtie have a look at the list of methods provided by that object.

Rails::DataMapper::Railtie.configure_data_mapper(app)
Rails::DataMapper::Railtie.setup_i18n_support(app)
Rails::DataMapper::Railtie#setup_controller_runtime(app)
Rails::DataMapper::Railtie#setup_logger(app)

To complete the picture of dm-rails's initialization process, here's an overview of the defined initializers in the order they are called by rails during bootup. Note that every one of these initializers does one single thing; calling one of the methods listed above. This makes it easy to customize each of these steps by overwriting the respective method.

initializer 'data_mapper.configuration' do |app|
  configure_data_mapper(app)
end

initializer 'data_mapper.logger' do |app|
  setup_logger(app, Rails.logger)
end

initializer 'data_mapper.i18n_support' do |app|
  setup_i18n_support(app)
end

# Expose database runtime to controller for logging.
initializer "data_mapper.log_runtime" do |app|
  setup_controller_runtime(app)
end

# Preload all models once in production mode,
# and before every request in development mode
initializer "datamapper.add_to_prepare" do |app|
  config.to_prepare { Rails::DataMapper.preload_models(app) }
end

# Run setup code once in after_initialize to make sure all initializers
# are in effect once we setup the connection. Also, this will make sure
# that the connection gets set up after all models have been loaded,
# because #after_initialize is guaranteed to run after #to_prepare.
# Both production and development environment will execute the setup
# code only once.
config.after_initialize do |app|
  Rails::DataMapper.setup(Rails.env)
end

If you want to add additional rake tasks in your extension, you can do so by adding the following to your railtie.

rake_tasks do
  load 'path/to/your/tasks.rake'
end

Identity Map support

Activating the identity map is achieved by installing a middleware that wraps the whole request inside a block

DataMapper.repository { ... }

Note that this scopes every call to datamapper to the :default repository specified in your database.yml file. If you need to access a different repository from within your actions, just wrap the calls in another DataMapper.repository block. DataMapper stacks the repositories it uses and the innermost will always win.

In order to activate the Identity Map in your application, you need to explicitly use the provided middleware (or in fact any other middleware that does the job to your liking) in any of your controllers. For example, if you want to enable the Identity Map for all controllers, you would declare to use the middleware in your ApplicationController.

require 'dm-rails/middleware/identity_map'
class ApplicationController < ActionController::Base
  use Rails::DataMapper::Middleware::IdentityMap
  protect_from_forgery
end

If you've created your application using the official templates at datamapper.org/templates/rails.rb this has already been added for you. If for some reason you don't want to enable the Identity Map globally for all controllers, you can either just use the middleware in a few selected controllers (in case you don't have too many controllers needing it), or you can create a controller class that uses the middleware, and inherit from that controller in cases where you need Identity Map support.

# app/controllers/identity_map_controller.rb
require 'dm-rails/middleware/identity_map'
class IdentityMapController < ApplicationController
  use Rails::DataMapper::Middleware::IdentityMap
end

# app/controllers/people_controller.rb
class PeopleController < IdentityMapController
  # ...
end

Cucumber testing framework support

dm-rails ist working fine with the cucumber testing framework out of the box. In order to have transaction support in cucumber, so that the data generated by the different scenarios doesn't cause problems, create a file “RAILS_APP/features/support/datamapper.rb” with the following contents:

Before do
  repository(:default) do |repository|
    transaction = DataMapper::Transaction.new(repository)
    transaction.begin
    repository.adapter.push_transaction(transaction)
  end
end

After do
  repository(:default).adapter.pop_transaction.rollback
end

This simply wraps each scenario in a transaction which is rolled back.

If your underlying database doesn't support transactions, you can instead use this code to clean/ truncate the whole database after each scenario (not dm-rails specific):

require 'database_cleaner'
require 'database_cleaner/cucumber'
DatabaseCleaner.strategy = :truncation

Using adapter specific Resource naming conventions

Say you want to namespace your models and don't want your storage names to reflect your module nesting. DataMapper provides easy ways to use just the naming conventions you like. Basically, all you need to do is tell dm-rails and thus DataMapper that you either want to use a predefined naming convention

# in some config/initializers/file.rb
convention = DataMapper::NamingConventions::Resource::UnderscoredAndPluralizedWithoutModule
Rails::DataMapper.configuration.resource_naming_convention[:default] = convention

or that you want to use your own naming convention, that is implemented in e.g. a lambda

# in some config/initializers/file.rb
Rails::DataMapper.configuration.resource_naming_convention[:default] = lambda do |value|
  'tbl' + value.camelize(true)
end

For more detailed documentation about DataMapper naming conventions and the ones that are available by default, have a look at rdoc.info/projects/datamapper/dm-core and search for NamingConventions in the Class List.

Using additional datamapper plugins

In order to use additional plugins add them to the Gemfile and require them from inside a file in config/initializers. Once you've done that, update your bundle and you should be ready to use the plugin(s)

cd /path/to/your/app
# edit Gemfile
bundle install

Have a look at this application's Gemfile for an idea of how to use gems from git repositories.

Rails notification system

Currently dm-rails publishes the same benchmarking information like active_record does. This means that you will get output like this in your log files.

Completed in 9ms (Views: 7.6ms | Models: 0.6ms) with 200

Current Issues

  • migrations might not work perfectly

  • rspec-2 is not yet integrated

TODO (not necessarily in that order)

  • SPECS !!!

  • Think about a release strategy supporting both beta releases and master branch

  • Further README updates

  • More work on migrations

Credits

Big thanks to everyone working on datamapper, rails, bundler and open source in general. This will be (and actually already is) an awesome platform for developing web applications.

Note on Patches/Pull Requests

  • Fork the project.

  • Make your feature addition or bug fix.

  • Add tests for it. This is important so I don't break it in a future version unintentionally.

  • Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)

  • Send me a pull request. Bonus points for topic branches.

The dm-rails team

Thx to all contributors, every patch, big or small is very much appreciated!

Copyright

Copyright © 2010-2010 The dm-rails team. See LICENSE for details.

Jump to Line
Something went wrong with that request. Please try again.