Halaman

Tampilkan postingan dengan label rails. Tampilkan semua postingan
Tampilkan postingan dengan label rails. Tampilkan semua postingan

Minggu, 14 November 2010

[shared] rails user role system

https://github.com/timcharper/role_requirement

installation:
- ruby script/generate roles Role User

installation will do:

  • Generates habtm table, creates role.rb with the habtm declaration. Adds declaration in user.rb (scans the code for "class User < ActiveRecord::Base", and puts the new code right after it.



  • Creates an admin user in users.yml, with a role named admin in roles.yml, including a fixture to demonstrate how to relate roles to users in roles_users.yml



  • Modify the user.rb (or corresponding user model) file, add the instance method has_role?



  • Generates RoleRequirementSystem against for the corresponding user model.



  • Generates a migration to make the necessary database changes



  • Scans ApplicationController, inserts the lines "include AuthenticatedSystem", and "include RoleRequirementSystem", if not already included.



  • Scans test_helper.rb and adds "includes RoleRequirementTestHelpers", if not already included.




Usage:
- add require_role "role_name" in controller, similar to before_filter

[shared] rails exception logger

https://github.com/defunkt/exception_logger

installation:
- ruby script/generate exception_migration
- rake db:migrate
- in application_controller.rb add
include ExceptionLoggable
rescue_from Exception, :with => :exception_handler

private

def exception_handler exception
log_exception exception
render "/500.html"
end


the default controller and view using prototype.js instead of jquery so i created my own controller view under admin namespace. the database pretty simple and similar to rails exception notification

for rails 3:
https://github.com/QuBiT/exception_logger

Kamis, 04 November 2010

Mysql::Error: query: not connected

MySQL 5.1 client library doesn't play well with Rails

download:
http://instantrails.rubyforge.org/svn/trunk/InstantRails-win/InstantRails/mysql/bin/libmySQL.dll

source:
http://forums.aptana.com/viewtopic.php?f=20&t=7563&p=27407&hilit=libmysql.dll#p27407

Sabtu, 30 Oktober 2010

named scope conditions eager loaded warning

http://jitu-blog.blogspot.com/2009/07/looking-into-rails-namedscope.html

Senin, 25 Oktober 2010

[shared] range overlap check

overlap = !((end2 < start1) || (start2 > end1))

r = Range.find_all(:conditions => ["NOT ((end < ?) || (start > ?))", new_start, new_end])

overlap = r.present?
number_of_overlaps = r.length

Kamis, 21 Oktober 2010

dynamic form with accept_nested_attributes

http://railsforum.com/viewtopic.php?id=28447

Senin, 18 Oktober 2010

add javascript files to rails default

environment.rb

# reset:
ActionView::Helpers::AssetTagHelper::JAVASCRIPT_DEFAULT_SOURCES = [
'jquery-1.4.3', 'jquery-ui-1.8.6.custom', 'jrails',
'jquery.extended_helper_2.0', 'date-id', 'jquery.gritter'
]
ActionView::Helpers::AssetTagHelper::reset_javascript_include_default

# adding:
ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'extended_helper', 'date'

Senin, 11 Oktober 2010

[shared] I18n pluralization patch

translate patch



module I18n
module Backend
module Base
def translate(locale, key, options = {})
raise InvalidLocale.new(locale) unless locale
return key.map { |k| translate(locale, k, options) } if key.is_a?(Array)

entry = key && lookup(locale, key, options[:scope], options)

if options.empty?
entry = resolve(locale, key, entry, options)
else
count, default = options.values_at(:count, :default)
values = options.except(*RESERVED_KEYS)
entry = entry.nil? && default ?
default(locale, key, default, options) : resolve(locale, key, entry, options)
end

raise(I18n::MissingTranslationData.new(locale, key, options)) if entry.nil?
entry = entry.dup if entry.is_a?(String)

entry = pluralize(locale, entry, count)
entry = interpolate(locale, entry, values) if values
entry
end
end
end
end


pluralize patch



module I18n
module Backend
class Simple
def pluralize(locale, entry, count)
if !defined?(count) || count.blank?
key = :none
elsif count == 0 && (entry.respond_to?(:has_key?) && entry.has_key?(:zero))
key = :zero
end
key ||= count == 1 ? :one : :other
unless entry.respond_to?(:has_key?) && entry.has_key?(key)
entry
else
entry[key]
end
end
end
end
end

Rabu, 06 Oktober 2010

[shared] netbeans rails additional code template

Ruby


1. model

#--
# constants
#++

#--
# require
#++

#--
# include
#++

#--
# attr
#++

#--
# extra capabilities
#++

#--
# belongs_to
#++

#--
# has_one
#++

#--
# has_many
#++

#--
# accepts_nested_attributes_for
#++

#--
# validation
#++

#--
# callbacks
#++

#--
# named_scope
#++

#--
# other definition such as alias_method
#++

#--
# public class methods
#++

#--
# public instance methods
#++

private
#--
# private class methods
#++

#--
# private instance methods
#++

protected
#--
# protected class methods
#++

#--
# protected instance methods
#++


2. resource

before_filter :prepare_account, :only => [:show, :edit, :update, :destroy]

# GET new_account_url
def new
# return an HTML form for describing the new account
end

# POST account_url
def create
# create an account
end

# GET account_url
def show
# find and return the account
end

# GET edit_account_url
def edit
# return an HTML form for editing the account
end

# PUT account_url
def update
# find and update the account
unless @account.update_attributes params[:account]
# failure code here..
end
end

# DELETE account_url
def destroy
# delete the account
@account.destroy
end

private

def prepare_account
@account = Account.first
end


3. resources

before_filter :prepare_messages, :only => [:index]
before_filter :prepare_message, :only => [:show, :edit, :update, :destroy]

# GET messages_url
def index
# return all messages
end

# GET new_message_url
def new
# return an HTML form for describing a new message
end

# POST messages_url
def create
# create a new message
end

# GET message_url(:id => 1)
def show
# find and return a specific message
end

# GET edit_message_url(:id => 1)
def edit
# return an HTML form for editing a specific message
end

# PUT message_url(:id => 1)
def update
# find and update a specific message
unless @message.update_attributes params[:message]
# failure code here..
end
end

# DELETE message_url(:id => 1)
def destroy
# delete a specific message
@message.destroy
end

private

def prepare_messages
@messages = Message.all
end

def prepare_message
@message = Message.find params[:id]
end


4. vf

validates_format_of :${1 default="attribute"}


5. vn

validates_numericality_of :${1 default="attribute"}


Ruby HTML (RHTML)


1. script




2. style


Kamis, 30 September 2010

include in rails 2.1+ not always generate single query

"..for some situations, the monster outer join becomes slower than many smaller queries..."

http://akitaonrails.com/2008/05/25/rolling-with-rails-2-1-the-first-full-tutorial-part-2
in Optimized Eager Loading

Kamis, 23 September 2010

[shared] paypal recurring with activemerchant

add this file:
from http://github.com/rayvinly/active_merchant/blob/master/lib/active_merchant/billing/gateways/paypal_express_recurring.rb


read:
- https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_WPRecurringPayments
- and PP_RecurringPaymentsAPI.pdf

Credit Card







allowed credit card type (not all country support all type):
  • Visa
  • MasterCard
  • Discover
  • Amex
  • Maestro
  • Solo


test credit card obtained from:
http://www.darkcoding.net/credit-card-numbers/
you can generate it yourself using MOD 10 algorithm (Luhn formula) or so they said =P
http://www.darkcoding.net/credit-card/luhn-formula/

Error! DPRP is disabled for this merchant ?
- Only US & UK sandbox business accounts can be enabled for WPP
- use preconfigured account using Website Payments Pro
- or read https://www.x.com/docs/DOC-1603

Paypal Account





Additional



ActiveMerchant::Billing::CreditCard patch to support paypal recurring:

Rabu, 04 Agustus 2010

[shared] pac file for developing site using subdomain

saat ini saia mengerjakan site yg menggunakan subdomain tersendiri untuk setiap usernya, karenanya setiap saia meregistrasi user baru saia harus menambahkan line baru di hosts file saia:

127.0.1.1 localhost
127.0.1.2 mysite.local
127.0.1.3 www.mysite.local
127.0.1.5 user1.mysite.local
127.0.1.6 user2.mysite.local
...

hal tersebut cukup merepotkan terutama untuk testing.

sedikit googling saia menemukan site yang menjelaskan penggunaan pac (proxy auto-config) file untuk mempermudah develop subdomain site:
http://www.taylorluk.com/2009/08/hey-pac-man-sup-subdomains

tambahan, isi pac file yg saia gunakan saat ini untuk rails:

function FindProxyForURL(url, host) {
if (shExpMatch(host, "*.local")) {
return "PROXY 127.0.0.1:3000";
}
if (shExpMatch(host, "*.example.com")){
return "PROXY 127.0.0.1:3000";
}
return "DIRECT";
}

Selasa, 25 Mei 2010

accept online payment for users (Part 1a: PayPal Website Payments Standard)

dah lama ga post, kali ini saia mao post artikel yg rasanya bakal lumayan panjang ;) tentang integrasi beberapa payment gateway (paypal, authorize.net, sagepay, google-checkout) buat nerima online payment untuk user (bukan untuk site owner).
jadi intinya site hanya sebagai perantara antara user dengan payment gateway.

ok mulai dari yg paling banyak digunakan: PayPal

persiapan yg dibutuhkan:
1. rails project dengan users (saia asumsikan dsini nama modelnya User)
2. developer account buat paypal (biar bisa nyoba) register di: https://developer.paypal.com/

siapkan model untuk menyimpan data paypal tiap user dan data notifikasi:

ruby script/generate model Paypal
ruby script/generate model Ipn


buat relasi user dengan paypal dan paypal dengan ipn, agar flexible saia menggunakan relasi polymorphic tapi relasi biasa juga dapat digunakan (saia anjurkan relasi paypal dengan ipn polymorphic agar nantinya kita mudah menambahkan gateway lain):

### paypal.rb ###
belongs_to :payable, :polymorphic => true
has_many :ipns, :as => :gateway, :dependent => :destroy


### ipn.rb ###
belongs_to :gateway, :polymorphic => true


### user.rb ###
include PaymentGatewayRelation


### /lib/payment_gateway_relation.rb ###
module PaymentGatewayRelation
def self.included(recipient)
recipient.class_eval do
has_one :paypal, :as => :payable, :dependent => :destroy

def payments
[self.paypal].compact
end

def payment_available?
!self.payments.empty?
end

def payment
self.payments.first
end

def payment= value
return unless value.respond_to? :payable
klass = value.class
self.payments.each{|x| x.destroy if x.class != klass}
self.reload
self.send("#{klass.to_s.underscore}=", value)
end
end
end
end

payment_gateway_relation.rb berisi relasi untuk semua gateway dan method2 untuk membantu akses.
mungkin akan lebih saia perjelas nanti saat gateway lain ditambahkan.

kemudian, tambahkan data paypal user yang dibutuhkan pada migration:

class CreatePaypals < ActiveRecord::Migration
def self.up
create_table :paypals do |t|
t.references :payable, :polymorphic => true
t.string :business, :limit => 70
t.string :return_url
t.timestamps
end
end

def self.down
drop_table :paypals
end
end


class CreateIpns < ActiveRecord::Migration
def self.up
create_table :ipns do |t|
t.references :gateway, :polymorphic => true
t.string :trans_id, :limit => 30
t.string :description, :limit => 50
t.timestamps
end
end

def self.down
drop_table :ipns
end
end

- business: paypal business account (email) dari user.
- return_url: url redirect untuk visitor ke suatu site (bisa di luar site kita) setelah pembayaran selesai
- trans_id: transaction id untuk mencegah duplikasi pembayaran
- description: optional, untuk menyimpan catatan ato suatu referensi

lanjut, di paypal.rb tambahkan attr_accessible dan beberapa validasi untuk keamanan ;)

include UriValidator

alias_attribute :account, :business

attr_accessible :business, :account, :return_url

validates_presence_of :account, :return_url
validates_uniqueness_of :business, :scope => :payable_type, :message => "account has already been used.", :case_sensitive => false
def validate
errors.add(:return_url, 'format invalid.') unless self.return_url.blank? || validate_http(self.return_url)
end

saia menggunakan alias attribute account untuk business agar lebih mudah dimengerti.

hal yang sama di ipn.rb

validates_presence_of :trans_id
validates_uniqueness_of :trans_id, :scope => :gateway_type, :allow_nil => true, :allow_blank => true

attr_accessible :trans_id, :description, :user, :gateway


buat ruby file /lib/uri_validator.rb:

module UriValidator

def validate_http uri
require 'uri'

return false if uri.blank?

uri = "http://" + uri unless uri.index("http") == 0

uri = URI.parse(uri)
if uri.class != URI::HTTP
return false
end

true
rescue URI::InvalidURIError
return false
end
end


fiuh.. urusan model2an beres..
migrate lalu selanjutnya controller dan view..
..
di post berikutnya biar kentang (sbenernya males nulis blog panjang2) =P

Jumat, 16 April 2010

[shared] to_xml_params with attributes from hash

sejauh yang saia tau, rails .to_xml dari hash tidak dapat menghasilkan xml tag dengan attribute selain type.
tetapi saia membutuhkan hal tersebut pada project yang saia kerjakan. karena itu saia membuat method to_xml_params untuk mengubah hash menjadi xml string:

def self.to_xml_params(data)
if data.is_a? Hash
data.collect do |key, value|
tag_attr = []
if key.is_a? Array
tag = key.first
key[1..-1].each do |key_attr|
key_attr.each do |k, v|
tag_attr << "#{k}=\"#{v}\""
end
end
else
tag = key
end
tag = tag.to_s.tr('_', '-')
result = "<#{tag}#{" #{tag_attr.join(' ')}" unless tag_attr.empty?}>"
result << to_xml_params(value)
result << "</#{tag}>"
result
end.join('')
elsif data.is_a? Array
data.inject(''){|result, v|
result << to_xml_params(v)
}
else
data.to_s
end
end


contoh penggunaan:


to_xml_params :a => "b"
# -> b
to_xml_params :a => {:b => "c"}
# -> c
to_xml_params [:a,{:x => :y}] => {:b => "c"}
# -> c
to_xml_params [:a,{:x => :y, :z => "w"}] => {[:b, {:p => "q"}] => "c"}
# -> c
to_xml_params :a => {:b => "c", :d => "e"}
# -> ec
to_xml_params :a => [{:b => "c"}, {:d => "e"}]
# -> ce sama dengan diatas

Selasa, 06 April 2010

[shared] "whenever" - cron simplified

karena beberapa waktu lalu saia menyadari kalau rufus-scheduler bermasalah bila digunakan di passenger maka saia beralih kembali menggunakan cron.
namun cron mengharuskan saia untuk meng-update crontab setiap kali saia ingin menambahkan task baru, karenanya saia mencoba mencari alternatif lain yg reliable dan mudah digunakan..

http://github.com/javan/whenever/

sebagai alternatif rufus-scheduler gem ini cukup mudah untuk digunakan, walaupun sedikit lebih "repot".

saia rasa dokumentasi di github sudah cukup jelas jadi saia hanya akan membahas implementasi yg saia gunakan.

yang menyusahkan dari gem ini, saia diharuskan meng-convert schedule.rb ke cron dengan menggunakan

whenever --update-crontab MyApp

setiap kali saia melakukan perubahan di schedule.rb
dokumentasi di github menunjukkan cara integrasi dengan capistrano, tetapi saia tdk mau direpotkan dengan menginstall gem tambahan, jadi yg saia lakukan hanya menambahkan bbrp baris di script untuk deploying pada staging dan production server:

.... svn up, rsync, etc ....

cd /home/rails/myapp && whenever --update-crontab MyApp --set environment=staging
cd /home/rails/myapp/script && chmod 755 runner

baris 1 untuk meng-update crontab. --set environment digunakan untuk merubah environment dari default production
baris 2 untuk memberi ijin execute pada script runner, karena saia menggunakan model method bukan rake untuk task2 nya

contoh schedule.rb yg saia gunakan:

every 30.minutes do
runner "QueuedEmail.deliver_all"
end

every 10.minutes do
runner "Message.destroy_all_expired"
end

every :day do
runner "ReferData.give_rewards!"
runner "SystemMailer.deliver_scheduler_check"
end

Jumat, 05 Februari 2010

[shared] Directory Traversal in Rails

beberapa saat yang lalu di project yang sedang saia kerjakan, saia menyadari sebuah security hole yang bisa dibilang cukup berbahaya (untungnya sebelum terjadi hal2 yang tidak diinginkan). yang menyebabkan masalah, saia membuat sebuah page yang isinya berasal dari partial berdasarkan params, isi view-nya kurang lebih seperti ini:

# views/frontpages/show.html.erb
<h3>Title</h3>
<%= render :partial => params[:page] || "home" %>

jadi isi dari page tersebut tergantung dari params page. yang jadi masalah ketika user memasukkan suatu url seperti
http://..../frontpages/show?page=%2fadmin%2fusers%2flist
(url dapat dengan mudah dicari menggunakan dictionary attack atau sejenisnya)
maka partial yang di-load akan berasal dari halaman di admin::users.
walaupun besar kemungkinan user tidak akan dapat melihat data di halaman tersebut karena akses tidak melalui controller, tetapi hal tersebut tetap tidak seharusnya diperbolehkan. apalagi terkadang dengan alasan 'malas' atau lainnya kita melakukan pengambilan data tidak di dalam controller tetapi langsung di view atau helper dimana data tersebut digunakan (ini salah satu alasan mengapa pengambilan data harus berada di controller bukan di view)

untuk mengatasi hal tersebut saia memproses params page td sebelum digunakan di view, misalkan:

# /controllers/frontpages_controller.rb
# ...
def show
@partial = (params[:page].blank? ||
params[:page].try('include?', '/')) ?
'home' : params[:page]
end
# ...


# views/frontpages/show.html.erb
<h3>Title</h3>
<%= render :partial => @partial %>

atau lebih baik lagi dengan tidak meng-implementasi partial seperti ini dan membuat action terpisah untuk setiap page.

yah situasi di atas mungkin merupakan hal bodoh yang seharusnya tidak terjadi selama kita mengikuti konvensi dari rails, tp ada kalanya karena berbagai alasan kita melanggar konvensi dan menggunakan implementasi kita sendiri, karenanya sebaiknya kita berpikir 1000x (lebay) sebelum melakukan hal itu.

oh ya directory traversal sendiri bukan hanya dapat digunakan pada situasi seperti di atas, tetapi banyak situasi lain yang bisa dimanfaatkan dengan directory traversal ini. karena itu, berhati-hatilah, inget kata bang napi (halah..)

ok segitu aja, tadinya mao nulis lanjutan dari svn branching tapi ditunda buat next post ^_^

Senin, 01 Februari 2010

[shared] nested layout

sesuai dengan yang saia janjikan di post sebelumnya, skarang saia akan membahas soal nested layout.
untuk memudahkan, saia akan mengambil contoh kasus dari post saia sbelumnya, yaitu untuk membuat menu yg berbeda2 tergantung controller yg aktif.


# layouts/application.html.erb
# ...
<div class="menu">
<%= render :partial => "main_menu" %>
</div>
<div class="content">
<%= yield %>
</div>
# ...


diubah menjadi:


# layouts/application.html.erb
# ...
<div class="menu">
<% begin %>
<%= render :partial => "layouts/#{controller.controller_path}_menu" %>
<% rescue ActionView::MissingTemplate %>
<% begin %>
<%= render :partial => "layouts/#{controller.controller_path.sub(/#{controller.controller_name}$/,'default_menu')}" %>
<% rescue ActionView::MissingTemplate %>
<%= render :partial => "main_menu" %>
<% end %>
<% end %>
</div>
<div class="content">
<%= yield %>
</div>
# ...


kode diatas akan mencari layout menu dengan path dan nama controller yg sedang aktif, bila tidak ditemukan default_menu partial dalam path controller aktif akan digunakan, bila tidak ada juga maka main_menu sebagai default akan digunakan. contoh:
  • UsersController akan menggunakan "layouts/_users_menu" || "layouts/_default_menu" || "layouts/_main_menu"
  • Admin::UsersController akan menggunakan "layouts/admin/_users_menu" || "layouts/admin/_default_menu" || "layouts/_main_menu" 
  • Admin::HomeController akan menggunakan "layouts/admin/_home_menu" || "layouts/admin/_default_menu" || "layouts/_main_menu" 
jadi untuk menggunakan menu yang sama pada smua controller untuk admin, buat partial "layouts/admin/_default_menu" dan tentu saja bila dibutuhkan tetap dapat di override dengan "layouts/admin/_[nama_controller]_menu"

dengan menggunakan nested layout, kita cukup menggunakan hanya 1 application layout. walaupun lebih tidak flexible daripada menggunakan content_for yg dapat diatur bergantung pada action. tp menurut saia jauh lebih rapih karena mengurangi kode yg ditulis pada controller.

Rabu, 20 Januari 2010

[shared] Using content_for from controller

biasanya content_for digunakan untuk passing suatu blok text dr view ke layout, contohnya

layout/application.html.erb

# ...
<div class="menu">
<%= yield :menu %>
</div>
<div class="content">
<%= yield %>
</div>
# ...


homepages/show.html.erb

# ...
<% content_for :menu, generate_menu %>
# ...


nah, terkadang dibutuhkan content_for yg dapat dipanggil di controller. misalkan untuk mengurangi pengulangan dengan menggunakan before_filter (dapat jg menggunakan nested layout, akan saia bahas di post lain).
untuk itu setelah saia coba ternyata hal tersebut dapat diselesaikan dengan mudah, hanya dengan mengisi instance variable dengan nama @content_for_#{nama} dengan contoh diatas berarti:

homepages_controller.rb

# ...
@content_for_menu = generate_menu
# ...


Warning: cara tersebut sudah dicoba di rails 2.3.3 tapi ada kmungkinan deprecated untuk versi rails yang lebih baru.. kalo ada cara yg lebih baik tolong kasi tau saia via comment, thanks ^_^

Jumat, 15 Januari 2010

[shared] Simple scheduled emails using rufus-scheduler

Baru2 ini di project yg sedang saia kerjakan, saia diminta untuk mengimplementasikan automated email. Intinya mengirim email secara otomatis pada saat user melakukan sesuatu. Sekilas biasa saja, hanya menggunakan ActionMailer untuk mengirim email pada event tertentu, namun masalahnya ada beberapa email yang dikirim bukan pada saat event dijalankan, ada delay waktu bbrp hari sebelum email dikirim.

Untuk itu saia memutuskan untuk membuat mailer yang dapat dijadwalkan, selain dapat mengirimkan email pada tanggal dan waktu tertentu, email jg dikirimkan secara berkala pada jangka waktu yg ditentukan (asumsi saia akan dibutuhkan nantinya untuk email laporan berkala.)

Buat Scheduler

Pertama, install rufus-scheduler

gem install rufus-scheduler

lalu dalam folder config/initializers buat file ruby baru, misal "task_scheduler.rb"

scheduler = Rufus::Scheduler.start_new

scheduler.every("10m") do
QueuedEmail.deliver_all
end

yang akan menjalankan deliver_all pada model QueuedEmail setiap 10 menit.

Buat Model utk menyimpan jadwal

generate model baru, misal queued_email
migration:

class CreateQueuedEmails < ActiveRecord::Migration
def self.up
create_table :queued_emails do |t|
t.string :from, :limit => 100
t.string :recipients, :limit => 100
t.string :subject, :limit => 100
t.text :content
t.integer :duration
t.datetime :send_at
t.datetime :last_sent_at
t.timestamps
end
end

def self.down
drop_table :queued_emails
end
end

model:

class QueuedEmail < ActiveRecord::Base
validates_presence_of :from
validates_length_of :from, :within => 6..100

validates_presence_of :recipients
validates_length_of :recipients, :within => 6..100

def recurring?
!self.duration.blank?
end

def deliver!
puts "Sending email: #{self.inspect}"
UserMailer.deliver_custom_mail self.attributes
if recurring?
puts "set sent at: #{Time.now.utc}"
self.update_attribute :last_sent_at, Time.now.utc
else
puts "destroy"
self.destroy
end
end

def self.deliver_all
mails = self.all :conditions => ["(send_at <= :current_time) AND (duration IS NULL OR duration = \"\" OR last_sent_at IS NULL OR TIME_TO_SEC(TIMEDIFF(:current_time, last_sent_at)) >= duration)", {:current_time => Time.now.utc}]
mails.each do |mail|
mail.deliver!
end
end
end

cara kerja:
- duration: bila diisi, setelah email dikirim, email tidak akan langsung dihapus, tetapi akan dikirimkan kembali setiap duration detik
- send_at: menentukan tanggal dan waktu email akan dikirim atau dikirim pertama kalinya bila duration ditentukan.
- last_sent_at: tidak perlu diisi, digunakan secara internal untuk email berkala
- lainnya: rasanya sudah cukup jelas, sama seperti actionmailer

Tambahkan custom_mail di mailer 

Saia menggunakan UserMailer < ActionMailer::Base untuk mengirimkan email, tambahkan kode berikut di dalamnya:

def custom_mail(options)
@recipients = options["recipients"]
@from = options["from"]
@subject = options["subject"]
@sent_on = Time.now
@body[:content] = options["content"]
end

dan buat custom_mail.erb di viewnya:

<%= @content %>


Contoh penggunaan

Misalkan untuk mengirimkan survey email 2 hari setelah user register. Tambahkan kode berikut di users_controller create:

# ....
# bila registrasi sukses daftarkan email untuk dikirim setelah 2 hari
QueuedEmail.create(
:send_at => 2.days.since(Time.now.utc),
:from => "\"Site Keren\" ",
:recipients => "#{@user.email}",
:subject => "Survey supaya keren" ,
:content => "Halo #{@user.name}, isi survey berikut supaya kamu makin keren gitu loh ....."
)
# ....

untuk email berkala, tentukan duration dalam detik, misal email yg dikirim tiap hari

:duration => 24.hours


Dan segitu saja, mungkin bukan implementasi yg terbaik, tp paling tidak bisa digunakan hehe..