Archive for the ‘Ruby on Rails’ Category
- In: Ruby | Ruby on Rails
- 5 Comments
This morning I ran into a small roadblock working with the acts_as_state_machine gem. My code started like this:
class Communication < ActiveRecord::Base
include AASM
aasm_column :status
aasm_state :draft
aasm_state :pending
aasm_state :approved
aasm_state :rejected
aasm_event :submit do
transitions :to => :pending, :from => :draft
end
aasm_event :approve do
transitions :to => :approved, :from => [:pending, :rejected]
end
aasm_event :reject do
transitions :to => :rejected, :from => [:pending, :approved], :guard => :validate_rejection_reason
end
aasm_initial_state :draft
def validate_rejection_reason
if self.rejection_reason
true
else
raise AASM::InvalidTransition.new('A rejection reason is required')
end
end
end
This is a basic example using AASM to provide an approval process for my Communication model. I wanted the ability to just call instance.reject(“some reason”) or instance.reject!(“some reason”). I was really thinking to hard about it. The assm_event just creates some methods for me….so why not just decorate those methods using ruby aliases. This is what I ended up with:
include AASM
aasm_column :status
aasm_state :draft
aasm_state :pending
aasm_state :approved
aasm_state :rejected
aasm_event :submit do
transitions :to => :pending, :from => :draft
end
aasm_event :approve do
transitions :to => :approved, :from => [:pending, :rejected]
end
aasm_event :reject do
transitions :to => :rejected, :from => [:pending, :approved], :guard => :validate_rejection_reason
end
alias old_reject reject
alias old_reject! reject!
aasm_initial_state :draft
def reject(reason)
self.rejection_reason = reason
self.old_reject
end
def reject!(reason)
self.rejection_reason = reason
self.old_reject!
end
def validate_rejection_reason
if self.rejection_reason
true
else
raise AASM::InvalidTransition.new('A rejection reason is required')
end
end
Notice the use of alias for reject and reject!. Now you have to call reject with a rejection reason. There may be another way to get parameters in to the event call….but this is how I rolled mine
New has_class_attr plugin
Posted on: November 3, 2009
- In: Ruby | Ruby on Rails
- Leave a Comment
I wrote this as a plugin….but it really has nothing to do with rails. I guess it could be packaged as a gem, but its so small that it probably doesn’t make sense. You ever create class variables or class instance variables and then create getters for them for use as statics?
Old Way:
class MyClass
@items = [1,2,3]
def self.items
@items
end
end
MyClass.items #[1,2,3]
New Way:
class MyClass has_class_attr :items, :data => [1,2,3] end MyClass.items #[1,2,3]
Install:
.script/plugin install git://github.com/angelo0000/has_class_attr.git
New has_duration_field plugin
Posted on: November 1, 2009
- In: Ruby | Ruby on Rails
- Leave a Comment
Building on my previous post I created the has_duration_field plugin. As described, I wanted to represent a duration of time on a field in my model. Using the proxy_field plugin, I built the DurationField class and added it as the proxy for my columns tblk and tduty.
Before the has_duration_field plugin:
class Block < ActiveRecord::Base proxy_field [:tduty, :tblk], :as => DurationField end
I just implemented my DurationField class and threw it in my lib dir. As I did that I realized that really using proxy_field and the implementation of DurationField would make a neat little plugin.
With the plugin you can do this:
class Block < ActiveRecord::Base has_duration_field [:tduty, :tblk] end
The plugin just provides an implementation of the DurationField class for you and an easy way to make your column proxy to a DurationField.
Install:
./script/plugin install git://github.com/angelo0000/has_duration_field.git
New proxy_field plugin
Posted on: October 31, 2009
- In: Ruby | Ruby on Rails
- Leave a Comment
I spent some time this week and wrote some small plugins. I’ll do a small write for each to explain the purpose and usage. The first plugin is the proxy_field plugin. For Bidbuddy (personal project) I only have about 10 models, but for each model there are a ton of fields that each represent a time duration. For example a field may represent how long a specific aircraft is used for a given schedule. The time is a duration so I have to store the value in seconds in the database. What I found was that I had helpers that would convert those column values in to hours, minutes, days, etc. While this works, it doesn’t feel right. I would rather just get the value of the column and call to_hours on it. I could open the Integer class and add my to_hours method to it, but I would like something a bit more generic that could be reused for other situations. What I came up with was this syntax:
class Block < ActiveRecord::Base
proxy_field [:tduty, :tblk], :as => DurationField
end
class DurationField
def initialize(seconds)
@seconds = seconds
end
def to_hours
@seconds / 60 / 60
end
#Other useful methods would go here
#to_seconds, to_minutes, to_days, to_weeks, etc...
end
This allows me to proxy any ActiveRecord field into another object. It basically allows you to deserialize any column data.
Old Way:
def seconds_to_hours(seconds)
seconds / 60 / 60
end
b = Block.find(CONDITIONS_HERE)
puts seconds_to_hours(b.tduty)
New Way:
b = Block.find(CONDITIONS_HERE) puts b.tduty.to_hours
Install:
./script/plugin install git://github.com/angelo0000/proxy_field.git
I have not decided yet how to handle nil columns. Its not a technical challenge but more of a design decision. The plugin could return nil when you called the method to get the proxy: b.tduty would return nil. Or that could be up to the proxy object class to return nil for each method if the inializer got a nil. In my duration example to_hours method would nil if @seconds was nil. I’m not sure what I would prefer. I would love a suggestion…
Rendering views in rake tasks
Posted on: May 23, 2009
- In: Ruby | Ruby on Rails
- 3 Comments
Recently, I needed to create a background process to offload a long export process. The easiest approach was to just utilize a rake task that would be kicked off by the system command. I ran into an issue because I needed to capture the rendering of a view to create the export file. This is an example of the code that came out of the exploration:
task :run_export => :environment do
av = ActionView::Base.new(Rails::Configuration.new.view_path)
av.class_eval do
include ApplicationHelper
end
chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
file_name = "export_"
10.times { file_name << chars[rand(chars.size-1)] }
data = av.render(:partial => "shared/export", :locals => {:surveys => Survey.all})
FileUtils.mkdir_p "#{RAILS_ROOT}/public/system/exports}"
File.open("#{RAILS_ROOT}/public/system/exports/#{file_name}.xls", 'w') {|f| f.write(data) }
end
The above rake task will create an instance of ActionView so that we can call render. My partial also needed access to some helpers I have defined in my ApplicationHelper, so it gets included. Next we generate a psuedo random file name for our export. Then we create a directory for our export files. The directory and its parents are only created if they do not exist. Lastly, we write the result of our render to our file.
Not sure if this is the best approach to this..but seems to work for me.
The joy of helpers
Posted on: January 24, 2009
I’m a believer in coding for readability. I recently started back on building Comparative Agility. Before the holidays, we were in a bit of a crunch to get a functional app ready for a conference that Ken and Mike were attending. In the crunch, I did not create any helpers. In my last project with Mike I did just the opposite. I had time and was able to create helpers for a ton of stuff. The result was very readable. I can pick up the Userstories code and very quickly jump into adding features or fixing issues. The Comparative Agility project is much more ui intensive and helpers are going to be my savior. Now that the crunch is over, I can go refactor and clean up the ui. If you haven’t had a chance to check out Comparative Agility, you should….its a great concept, awesome site, and of course its Ruby