Friday, November 13, 2009

Event callbacks in AASM

Having written about AASM in the past, first on Chaining state transitions with Acts As State Machine (aasm), and then again in Conditional state transitions with AASM, I thought I had AASM down rather pat.

However, when I tried adding a callback to the aasm_event, I ran into trouble. Quite simply, the callback never worked. Here's the first code:


Notice that the callback registration is in the aasm_event code block. It seemed reasonable that this would be part of the transition. When I determined that this was not working, I had a colleague review the code and we ended up both scratching our heads.

So, I cracked open the library and poked around. I saw the successful aasm_state call displaying the callback registration in the options portion of the method, but the options of the aasm_event were blank. Hmm!

Here are the method definitions for your edification:
aasm_state(name, options={})
aasm_event(name, options = {}, &block)

Well, it was pretty clear how to fix the problem; move the callback registration after the name argument and before the block. Here's the corrected code:

That's it! Have fun.

Friday, October 30, 2009

Fast Online Documentation - GotAPI.com

When I'm coding and have an API question, I have one and only one demand from my documentation source -- speed. I want to be able to quickly find the information I seek. "Now. I want it now."

I use PDF versions of my favorite books and the generated Ruby 2.3.4 API documentation locally, which is all good, but I found something that may be even better. GotAPI has a clean interface, is wicked fast and utterly intuitive. It even holds your recent searches.

In addition to lightning fast Ruby and Ruby on Rails look ups, it has tabs for HTML, Javascript DOM and CSS; all the things I need during RoR development!

I've added it to my browser bar so I can get to it quickly and it's rapidly becoming my reference tool of choice. Highly recommended.

Saturday, October 10, 2009

Conditional state transitions with AASM

Also part of the same state machine I wrote about in "Chaining state transitions with Acts As State Machine (aasm)" required two different types of behavior for a state transition.

The task reviewer state was optional and would be triggered only if selected. I represented that state as a "use_reviewer" boolean column in the object. If use_reviewer == true, then the object transitions from task_created to "to_reviewer", otherwise it needed to transition to "to_writer"

The solution to this is rather straight forward. Use the :guard option in the aasm_event definition.

Here's the code:

aasm_event :created do
transitions :to => :to_reviewer, :from => [:task_created], :guard => Proc.new {|p| p.use_reviewer == true }
transitions :to => :to_writer, :from => [:task_created]
end

The event causes the object to transition to the "to_reviewer" state if the objects "use_reviewer" is true, otherwise it transitions to "to_writer". Here's the code evidence from script/console:

>> a = Assignment.new
=> (Assignment object)
>> a.use_reviewer = true
=> true
>> a.save
=> true
>> a.created!
=> true
>> a.aasm_current_state
=> :to_reviewer

Now we start over:
>> a = Assignment.new
=>(Assignment object)
>> a.save
=> true
>> a.created!
=> true
>> a.aasm_current_state
=> :to_writer

Chaining state transitions with Acts As State Machine (aasm)

Have you ever had state transition requirements that required automatic chaining of transition changes? I've got a system that transitions to a "to_reviewer" state after it has been created.

In the "to_reviewer" state, a group of reviewers are presented the assignment and they may accept the assignment, or a project manager may assign the assignment to a reviewer: in either case, as soon as there is a reviewer assigned, the assignment transitions to the "reviewer" state.

However, if the system already has a reviewer assigned at the "created" event, then it should cascade the state transition change and go directly to "reviewer" without staying in "to_reviewer"

Having selected the Rubyist AASM gem to implement my state machine in this Ruby on Rails project, I expected that I would be able to use the :after options of the aasm_state statement. I got that idea from the README.rdoc for version 2.1.1, which lists "event:after" in the callback chain.

However, the :after callback is called on an event (aasm_event) and I can't find documentation on how to hook into that callback. Digging into the the code, I found the following syntax works correctly in my Assignment object (derived from ActiveRecord). The solution is to use the :after_event option in the aasm_state definition statement.

This first part defines the states:
aasm_initial_state :task_created
aasm_state :task_created

aasm_state :to_reviewer, :after_enter => :check_reviewer
aasm_state :reviewer

This next part defines the events:
aasm_event :created do
transitions :to => :to_reviewer, :from => [:task_created]
end

aasm_event :task_review do
transitions :to => :reviewer, :from => [:to_reviewer]
end

This last part is the definition of the :after_enter method:
def check_task_reviewer
task_review unless reviewer_id.blank?
end


Now, when the object is created, the aasm_current_state is "task_created". If the object receives the "created" event, it transitions to the "to_reviewer" state. However, after the object enters the "to_reviewer" state, the "check_task_reviewer" method advances the state again if a reviewer as been assigned (indicated by the presence of the reviewer_id).

Checking the code in the console, we get:

>> a = Assignment.new
=> (Assignment object information)
>> a.reviewer_id = 5
=> 5
>> a.save
=> true
>> a.created!
=> true
>> a.aasm_current_state
=> :reviewer

And we can see that the object transitioned through the "to_reviewer" state an on into the "reviewer"state.

Thursday, October 08, 2009

The Ruby Toolbox

I've had the good fortune to stumble across The Ruby Toolbox.

This delightful site is filled with collections of helpful, nifty plugins and gems that help make the life of any Ruby and/or Rails developer much more pleasant. There are some 75+ different categories that deal with ActiveRecord, Backups, Code Metrics, CSS Frameworks, E-Commerce, Game Libraries, Gem Creation, and on, and on.

Not only are projects, gems & plugins listed, but they are rated in terms of how much attention the project is receiving. The most popular projects are listed at the top with an attractive scale along side to the right to help you compare the popularity of different options.

If you're doing Ruby and/or Rails development and haven't visited this site, then you really deserve to do yourself a favor. I'm headed there now!

Thursday, October 01, 2009

Rails Trick: Access an ActiveRecord field in a loop in a view

I came up with a little trick that I want to share, even though I'm sure there are better ways of doing this (perhaps someone will suggest them!)

OK, picture this: We have over half a dozen different types of workers -- Writers, Sr. Editors, Editors, etc. -- and we're going to pay them. So, in the database we have things like writer_payment, sr_editor_payment and editor_payment.

In the edit view, we have something like this (actually, there are many more cells, but I've omitted them for clarity):



And, I fully expect that we will be adding many more workers, so we can look forward to much cutting and pasting.

Well, I wanted to come up with a way of declaring an array, iterating through it and creating each row automagically.

My first thought was this:



But this doesn't work at all. The ActiveRecord attribute can't be built dynamically with string interpolation in the view. Similar efforts failed miserably.

But then I took at look at the ActiveRecord::Base class and noticed the attributes method. This got me a-thinkin' and here's what I came up with. Get the attributes from the ActiveRecord. Build a string with the array value and the "_payment", use straight array access to get the payment value



And, Holy Smakeral -- it works!

Sunday, September 27, 2009

Great Website for Printing Patents

It's been a busy day and I'm finishing it with some patent work. I wanted to share a really great discovery. PAT2PDF is a free site that seems to be somewhat supported by donations (I made one!). The site accepts a search phrase for the following types of items:
  • US Utility Patent
  • US Pre-Grant Publication
  • US Design Patent
  • US Plant Patent
  • US Reissue
  • US Statutory Invention Reg.
You can even issue multiple requests by separating terms with semicolons.

The site looks up your item, pulls it from the US Patent Office's website and assembles it into a single PDF for download. Sweet.

What I Learned About Garage Door Openers

A friend's garage door opener wasn't opening the door although the motor was running. I offered to try and repair it. After all, it already wasn't working; what was the harm?

After opening the cover the problem was obvious. The nylon gear which drove the shaft that drove the sprocket (which is a gear that is driven by or drives a chain), was split in half. I looked up the manufacturer on the 'Net, then the model number and ordered a replacement kit. I was so relieved that it came with a detailed instruction book!

Here's what I learned about this garage door opener:

Limit Switches

The limit switches are very simple. As the drive shaft turns, it drives a screw upon which is mounted a moving contact. As the screw turns, it moves the contact to one end or the other. One screw on each side of the moving contact has the other end of the contact. As soon as the moving contact meets the end contacts -- the door stops. Simple!

Rate Sensor

There is a rate sensor that measures the rate at which the motor is spinning. This was also simple. A cap on the motor shaft had a series of four slots and openings, which passed through a sensor with a tiny emitter and sensor. If the door gets blocked, the motor can't turn. If the motor can't turn and the unit has not hit one of the limit switches, and the door is moving down, the controller reverses direction. If the door is moving up, it stops. Simple!

Retaining Pins

There isn't any other way to get a retaining pin out other than with a hammer. The sprocket drive shaft holds the drive gear in place with two retaining pins. These fit tightly into place. I had to drive the lower pin out in order to slide the new drive gear into place and the only way to do that was with a hammer. As soon as the pin was flush with the shaft, I used a punch to continue driving the pin through the shaft. After the new drive gear was in place, it was hammering once again to get the pin back into position.

Disassembly & Reassembly

Check the orientation of things as you disassemble them. I made two good choice and missed on opportunity. Before unplugging drive motor power connectors, I confirmed that the diagram in the instruction booklet was accurate and took a picture of the setup with my phone. When re-assembling the unit, I had all the documentation that I needed. However, I didn't notice the tabs in the top plate that holds the drive shaft in position and ended up re-installing that plate rotated 1/3 around. I didn't notice the mistake until nearly the very last step of re-installing the protective plastic cap over the sprocket and drive chain. It didn't make sense to disassemble the entire unit at that point, so we're living with my error. Had I taken a picture of that unit before disassembling it, I would have had the necessary reference.

Clean Grease

Clean grease isn't so bad. Dirty grease is wicked icky, but the new clean grease was easy to put on and I didn't mind cleaning up afterwards.

End of the story: the garage door works perfectly now. And I had a lot of fun taking the durn thing apart and putting it together!

Configuring Drupal for McNiff Plumbing & Heating

As mentioned in my last post on Selecting Drupal for McNiff Plumbing & Heating, I wanted to talk about configuring Drupal for a mostly static site.

The requirements are:
  • Anonymous Users can view all content
  • The site is Page oriented; Home, Services, Comments, Contact Us
  • No navigation menu
  • No login options visible to anonymous users
  • No content titles, author information or posting dates on pages
  • No user comment links
To start with, I went to Administer >> Site Building >> Blocks and disabled nearly all the blocks. I changed the Navigation block to be visible only to Authenticated Users.

I then created the following pages: Home, Services & Comments and copied some of the content from the current site as a place holder. I also set the Menu Title Links in each of these pages to their base name, so that it could be referenced from a menu.

I used Administer >> Site Building >> Menus >> Primary Links to add a menu item for each page.

Next up was creating the Contact Us form. I enabled the Contact module in the Core - optional section, wen to "Administer By Module" and configured the permissions for the Contact Form and enabled "access site-wide contact form" for all users.

Back to the Menus to add a new Primary Link menu option for Contact by specifying the "contact" page as the target. After saving this new menu item, it was showing up as a new tab with the other menu pages. Great!

Let's check for an anonymous users. I opened up a different browser (I just hate logging out and in and out and in) and checked the development site. No joy in Mudville -- and no tab either.

Somewhere along the line, I was missing permissions. I checked and double checked that the menu was enabled and that anonymous users could access the contact form. I even typed the path into the other browser and the form displayed. Hmmm; not a permission problem because the anonymous user could see the form. The navigation link was simply not being displayed.

Back to Administer >> Site Building >> Contact Form >> Edit. There is a "Selected" option, which was set to "No". Changing that to "Yes" turned on the tab in the Anonymous User's view.

Yea!

Next up; clean up the pages to show only the pieces of information we want. This might require a custom View.

Selecting Drupal for McNiff Plumbing & Heating

I've been helping a local service provide, James McNiff of McNiff Plumbing & Heating improve his website. When we started, his site was a single page, over 60 keywords, and practically no inbound links. Website Grader gave it a 7 out of 100

My first work was to try and touch as little as possible and clean up the HTML so that the site was more search engine friendly. It was actually less time to re-implement the site in clean HTML with CSS, but it was still one page and still was lacking on the SEM side. Website grader raised it to an 8 out of 100.

Version 2 broke the single page into three pages, Home, Services, Comments and to add a rudimentary Contact form. I like working in Ruby on Rails, but his hosting provide charged extra for that application, so I used ROR to generate the web pages, captured them statically and uploaded the static content to his provider. Website grader raised it to a 12 out of 100.

Adding listings to his services and website to some business directories has helped, raising his score to 15.

However, Mr. McNiff didn't really like to content, which had been carried over from the original site and he wanted to be able to add pictures to his site. Given my interest in Drupal, I thought this was a perfect application for trying Drupal.

The hosting provide had a control panel installer for installing Drupal, which set up the MySQL database and installed Drupal 6.14. Here is a list of the modules that I added to begin configuring the site:
  • Admin Menu
  • Advanced Help
  • CCK
  • Devel
  • FCKEditor
  • Google Analytics
  • IMCE (for image upload)
  • INT Meta
  • Webform (although the Contact module looks sufficient)
Please note, that as of this posting, the Drupal site is hidden. As soon as it's ready, I'll swap Drupal to the HTML root and archive the current site.

Next up, a bit about configuring Drupal for a site with essentially static content.