Adding Components
Persistence Engine
Contributing an object persistence library is probably the most involved component to integrate with Padrino. For this guide, let us pretend that we would like to integrate Datamapper into Padrino.
Generators
First, let's add Datamapper to the project generator's available components in padrino-gen/generators/project.rb:
# padrino-gen/lib/padrino-gen/generators/project.rb
component_option :orm, "database engine", :choices => [:activerecord, :datamapper]
Here, we needed to append :datamapper
as an option for the :orm
component_option
in the project generator. Once we have defined Datamapper as
an option for the ORM component, let's actually define the specific integration
tasks for the generator in
padrino-gen/generators/components/orms/datamapper.rb:
# padrino-gen/lib/padrino-gen/generators/components/orms/datamapper.rb
# These are the steps to setup the persistence layer in the initial project such
# as requiring certain gems, constructing the database.rb configuration file and
# creating the models folder for the application
def setup_orm
require_dependencies 'data_objects', 'do_sqlite3', 'datamapper'
create_file("config/database.rb", DM)
empty_directory('app/models')
end
# These are the steps to generate the actual model file
# when the model generator is executed.
#
# e.g. create_model_file("account", ["username:string", "password:string"])
def create_model_file(name, fields)
# ...truncated...
create_file(model_path, model_contents)
end
# These are the steps to generate the model migration file
# when the model generator is executed.
#
# e.g. create_model_migration("create_accounts", "account", ["username:string"])
def create_model_migration(migration_name, name, columns)
# ...truncated...
end
# These are the steps to generate the db migration file
# when the migration generator is executed.
#
# e.g. create_migration_file("AddEmailToAccount", "AddEmailToAccount", ["email:string"])
def create_migration_file(migration_name, name, columns)
# ...truncated...
end
Rake Tasks
Next, if the persistence engine needs to include useful rake tasks (to migrate
or modify the database for instance), you can add these to the padrino-tasks
folder in the padrino-gen
gem. For Datamapper, there are a number of tasks
that should be available in
padrino-tasks/datamapper.rb:
# padrino-gen/lib/padrino-gen/padrino-tasks/datamapper.rb
if defined?(DataMapper)
namespace :dm do
namespace :migrate do
task :load => :environment do
# ...truncated...
end
desc "Migrate up using migrations"
task :up, :version, :needs => :load do |t, args|
# ...truncated...
end
end
end
end
Unit Tests
Next, let's add the appropriate unit tests to ensure our new component works as intended in padrino-gen/test/testprojectgenerator.rb:
# padrino-gen/test/test_project_generator.rb
...
it 'should properly generate default' do
out, err = capture_io { generate(:project, 'project.com', "--root=#{@apptmp}", '--orm=datamapper', '--script=none') }
assert_match(/applying.*?datamapper.*?orm/, out)
assert_match_in_file(/gem 'dm-core'/, "#{@apptmp}/project.com/Gemfile")
assert_match_in_file(/gem 'dm-sqlite-adapter'/, "#{@apptmp}/project.com/Gemfile")
assert_match_in_file(/DataMapper.setup/, "#{@apptmp}/project.com/config/database.rb")
assert_match_in_file(/project_com/, "#{@apptmp}/project.com/config/database.rb")
end
...
README
Finally for the generator integration, we should add the available option to the generator README file:
# padrino-gen/README.rdoc
orm:: none (default), mongomapper, mongoid, activerecord, sequel, couchrest, datamapper
With that update to the README, persistence support for the generator is complete. However, to be fully compliant, support for Padrino Admin should also be added. This will allow the admin dashboard to work properly with your persistence engine of choice and is highly recommended.
Padrino Admin Support
Adding padrino-admin
support for your persistence engine is actually fairly
straightforward. First, let's add Datamapper to the set of supported admin ORM
engines in
padrino-admin/generators/actions.rb:
# padrino-admin/lib/padrino-admin/generators/actions.rb
def supported_orm
[:activerecord, :mongomapper, :mongoid, :couchrest, :datamapper]
end
Next, we need to define the interaction methods available by our persistence engine on our models in padrino-admin/generators/orm.rb:
# padrino-admin/lib/padrino-admin/generators/orm.rb
module Padrino
module Admin
module Generators
class OrmError < StandardError; end
class Orm
attr_reader :klass_name, :klass, :name_plural, :name_singular, :orm
def initialize(name, orm, columns=nil, column_fields=nil)
# ...truncated...
end
# Defines access to a model's columns
def columns
@columns ||= case orm
when :activerecord then @klass.columns
when :datamapper then @klass.properties
else raise OrmError, "Adapter #{orm} is not yet supported!"
end
end
# Defines access to retrieving all existing records for a model.
def all
"#{klass_name}.all"
end
# Defines access for querying records for a model.
def find(params=nil)
case orm
when :activerecord then "#{klass_name}.find(#{params})"
when :datamapper then "#{klass_name}.get(#{params})"
else raise OrmError, "Adapter #{orm} is not yet supported!"
end
end
# Defines how to build a new record for a model.
def build(params=nil)
if params
"#{klass_name}.new(#{params})"
else
"#{klass_name}.new"
end
end
# Defines how to save a new record for a model.
def save
"#{name_singular}.save"
end
# Defines how to update attributes of a record for a model.
def update_attributes(params=nil)
case orm
when :activerecord then "#{name_singular}.update_attributes(#{params})"
when :datamapper then "#{name_singular}.update(#{params})"
else raise OrmError, "Adapter #{orm} is not yet supported!"
end
end
# Defines how to destroy a record for a model.
def destroy
"#{name_singular}.destroy"
end
end # Orm
end # Generators
end # Admin
end # Padrino
Next, we need to describe how the Account
model should be defined for our
persistence engine within
padrino-admin/generators/templates/account/datamapper.rb.tt:
# padrino-admin/lib/padrino-admin/generators/templates/account/datamapper.rb.tt
class Account
include DataMapper::Resource
include DataMapper::Validate
attr_accessor :password, :password_confirmation
# Define Properties
property :id, Serial
property :name, String
# ...truncated...
# Define Validations
validates_present :email, :role
# ...truncated...
# Callbacks
before :save, :generate_password
#
# This method is for authentication purpose
#
def self.authenticate(email, password)
account = first(:conditions => { :email => email }) if email.present?
account && account.password_clean == password ? account : nil
end
#
# This method is used from AuthenticationHelper
#
def self.find_by_id(id)
get(id) rescue nil
end
#
# This method is used for retrieve the original password.
#
def password_clean
crypted_password.decrypt(salt)
end
private
def generate_password
return if password.blank?
self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{email}--") if new?
self.crypted_password = password.encrypt(self.salt)
end
def password_required
crypted_password.blank? || !password.blank?
end
end
Finally, let's update the padrino-admin
README file at
padrino-admin/README.rdoc
to reflect our newly support component:
# padrino-admin/README.rdoc
Orm Agnostic:: Data Adapters for Datamapper, Activerecord, Mongomapper, Mongoid, Couchrest, Dynamoid
Contribute to Padrino
This completes the full integration of a persistence engine into Padrino. Once all of this has been finished in your GitHub fork, send us a pull request and assuming you followed these instructions properly and the engine actually works when generated, we will include the component into the next Padrino version crediting you for the contribution!
last updated: 2022-02-22
comments powered by Disqus