Merb - First steps
Posted by Nucc
I’ve read a lot of Merb, so I decided to try it. First it seems to be very poor framework, and it’s true. It’s only a skeleton unlike rails. First I tried
script/generate model User name:string email:string
and I got a pure User class without Datamapper, ActiveRecord, or other ORM. After googling I found the solution, it need to set use_orm :datamapper in dependencies.rb. For datamapper install use this:
sudo gem install datamapper sudo gem install merb_datamapper sudo gem install do_mysql
It’s okay, we generate again our model file.
script/generate model User name:string email:string
The result:
-
class User < DataMapper::Base
-
property :name, :string
-
property :email, :string
-
end
Okay, it’s nice, but how can we migrate with the database? We have a config/database.sample.yml file like in rails, edit this. It’s very nice, in Rails we have to set username, pass, database for all environment, here we can use inheritance.
:development: &defaults :adapter: mysql :database: merb_test :username: root :password: :host: localhost :test: <<: *defaults #:database: sample_test: production: <<: *defaults #:database: sample_production
Okay, it’s nice, try to migrate. … I’ve need some time, to solve this very simple problem. In rails, we have rake db:migrate, but in merb it’s not, or not ready yet. Okay, no problem, I found this solution:
In config/merb_init.rb insert this:
-
DataMapper::Persistence.auto_migrate!
It will migrating our models with database before merb server start. Let’s look an example. If we want to add a column to our User table, we only need to set another property tag, like:
-
class User < DataMapper::Base
-
property :name, :string
-
property :email, :string
-
property :message, :string
-
end
After a restart Users table looks:
desc users; +---------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +---------+-------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | name | varchar(50) | YES | | NULL | | | email | varchar(50) | YES | | NULL | | | message | varchar(50) | YES | | NULL | | +---------+-------------+------+-----+---------+----------------+
Nice. But! I’ve some problems. How can we manage, when new columns have an initialize value? How can we rename row? It’s very good for smaller projects, but what we can do, if we have an already exists database with a lot of data? It’s not trivial for me?
I shouldn’t remove migrating, but change it. I forget the numbers before the migration name, and use creation date for order them. Every migration file should has a creation date, and migration script use it for order them. It’s only an idea.
Another problem in Merb, controllers don’t get Controller suffix in class name. It seems to comfortable, but what happens, if we have a User model, and we want to use an User controller to manage our model. It’s problem. Need to use different namespaces for models and controllers, or use suffix or prefix in controllers like rails.
Erubis
Posted by Nucc
I’ve been looking for memcache solutions, when I found merb. Merb is similar to Rails, it’s developed by Ezra Zygmuntowicz (author of Deploying Rails). Merb uses Erubis instead of ERB. Erubis is refinement of ERuby, three times faster than ERB, it has auto HTML escaping support, but the most important for me is the preprocessing ability.
I tried to install Erubis to Rails 2.0.2 for testing, but I had problems. I would like to share how to setup in Rails 2.0.2, because it’s not the same in 2.0.1 and 2.0.2
First, setup from gem
sudo gem install erubis --include-dependencies
We create /config/initializers/erubis.rb for loading on boot.
-
-
class ActionView::Base
-
private
-
def convert_template_into_ruby_code
-
# dummy
-
end
-
-
def delegate_compile_with_preprocessing(handler, template)
-
-
if ::Erubis::Helpers::RailsHelper.preprocessing
-
preprocessor = ::Erubis::Helpers::RailsHelper::PreprocessingEruby.new(template)
-
template = self.instance_eval(preprocessor.src)
-
if ::Erubis::Helpers::RailsHelper.show_src
-
logger.debug "** Erubis: preprocessed==<<’END’\n#{template}END\n"
-
end
-
end
-
-
delegate_compile_without_preprocessing(handler, template)
-
end
-
-
alias_method_chain :delegate_compile, :preprocessing
-
end
-
-
require ‘erubis’
-
require ‘erubis/helpers/rails_helper’
-
-
module ActionView
-
module TemplateHandlers
-
class Erubis < TemplateHandler
-
-
def compile(template)
-
klass = ::Erubis::Helpers::RailsHelper.engine_class
-
properties = ::Erubis::Helpers::RailsHelper.init_properties
-
klass.new(template, properties).src
-
end
-
end
-
end
-
end
-
-
ActionView::Base.register_default_template_handler :erb, ActionView::TemplateHandlers::Erubis
-
ActionView::Base.register_template_handler :rhtml, ActionView::TemplateHandlers::Erubis
-
-
# settings
-
Erubis::Helpers::RailsHelper.engine_class = Erubis::FastEruby
-
Erubis::Helpers::RailsHelper.show_src = true
-
Erubis::Helpers::RailsHelper.preprocessing = true
In settings, we have three options. I use FastEruby, and I turned on show_src to see precompiled html code in console. If you want to use preprocessing, need to set preprocessing true. Preprocessing is useful for loop, but the syntax changes, when you use it. Instead of <% %> you have to use [% %], and if you have variable, you have to use _?(’variable’). I tried to test in a view,
-
<% 1.upto 1000 do %><%= link_to "My post", _?(’@post link’) %>
with ERB it’s generate 49-61 req/sec, erbius without preprocessing is 51-74 req/sec, and with preprocessor 90-120 req/sec. If I have more time, I try to make test cases, and publish them.
One comment: If you use html escaping function, change every <%= %> tags to <%== %>
I’ve need some time to realize my mistake.
Slots plugin
Posted by Nucc
I’d like to share my last Rails plugin which add comfortable management for remote_calls. I have a work for developing a photo manager. It needs a lot of remote call for change for example the name of the album, upload photo, remove uploaded photos from the div tree, rotate image without reload the whole page. The currently solution in rails like this:
/app/view/photo/index.rhtml
-
<div id="photo_1"></div>
-
<div id="photo_2"></div>
-
<%= link_to_remote "Rotate Photo 1",
-
:url => {:action => "rotate", :id => "1"} %>
/app/controllers/photo_controller.rb:
-
class PhotoController < ApplicationController
-
def index
-
end
-
-
def rotate
-
@photo = Photo.find_by_id(params[:id])
-
@photo.rotate(90)
-
@photo.save!
-
render :update do |page|
-
page["photo_1"].replace_html :partial => "photo",
-
:object => @photo
-
end
-
rescue
-
end
-
end
If we have a lot of link_to_remote for different methods, we have to write render :update .. for each one. The other problem, what happens if one of my action try to call another action. For example, always when I rotate an image, I’d like to share with the user what happens (flash a div element, with “Photo rotated” message). If I write another method for flashing message, it would be double rendering. If I write a helper method, I should always pass the page parameter, and if the message use my params[], I should pass it too. I have a solution for this problem.
Install this plugin in your Rails framework.
script/plugin install https://svn.bteam.hu/plugins/slots/trunk/
Let’s look the changes
/app/view/photo/index.rhtml
-
<div id="message"></div>
-
<div id="photo_1"></div>
-
<div id="photo_2"></div>
-
<%= link_to_remote "Rotate Photo 1",
-
:url => {:action => "rotate", :id => "1"} %>
/app/controllers/photo_controller.rb:
-
class PhotoController < ApplicationController
-
slots :rotate, :message
-
-
def index
-
end
-
-
end
/app/helpers/photo_helper.rb
-
class PhotoHelper
-
def rotate
-
@photo = Photo.find_by_id(params[:id])
-
@photo.rotate(90)
-
@photo.save!
-
-
page["photo_1"].replace_html :partial => "photo",
-
:object => @photo
-
message("Photo was rotated by 90 degree")
-
#Or we can use
-
# message("Photo #{params[:id]} was rotated by 90 degree")
-
rescue
-
end
-
-
def message(msg)
-
page["message"].replace_html :partial => "message",
-
:object => msg
-
end
-
end
As you see, I put our remote called methods into helper. It’s necessary because only helpers can change our views. If we want to use the params hash in the message method, we can do this.
In controllers, when we call slots method, it generates a method with the same name as the slot, so we can use before_filter for this methods.
Enjoy it!