Monday, September 14, 2009

Cells are bad asssss

I'm researching Nick Sutterer's cells for rails plugin and loving it so far. But, love can be fickle and this is just infatuation. But again, so far, so good.

To me, the V in Rails' MVC is severely lacking. And by lacking, I mean it stinks. I wanted to reuse partials from other controllers, pass arguments around, that kind of thing. The stuff that makes an RIA easier to build (I just learned that acronym yesterday). Partials are slick for simple apps like blogs, but they really look butt ugly when they're thrown around inside a complex form. And I've heard that using a bunch of partials can be slow, which may or may not be a factor (YMMV). Cells claims to be fast, but at this point I don't care.

Cleaning out the Clutter

Now ... lets try out cells. First, I branch my code so it can be scrapped if I need to bail.

Here's what one of my views looked like before:

...

%h2 Categories
= render :partial => 'category', :locals => { :f => package_form }

%h2 Available Contents
= render :partial => 'shared/search_field', :locals => {:name => 'search', :value => @search}
= render :partial => 'potential_packages', :object => @package.potential_children, :locals => {:f => package_form}

%h2 Current Contents
= render :partial => 'child_links', :locals => {:f => package_form}

...

And here's that view on cells:

...

%h2 Categories
= render_cell(:categories, :selections, :form => package_form)

%h2 Available Contents
= render_cell(:search, :search_for_package)
= render_cell(:packages, :selection_list, :packages => @package.potential_children)

%h2 Current Contents
= render_cell(:packages, :package_children, :form => package_form)

...

It's a subtle difference, but to me a huge difference: it's cleaner and easier to read what's going on. Partials, with their hashes, really obscure the intention, IMO.

Here's the rest of the code for one of the cells. Here's the cell, located in file app/cells/packages.rb:

class PackagesCell < Cell::Base
def selection_list
render
end

def package_children
render
end

def package_child
render
end
end

No work to do, just the three "states" each with a simple render. By Rails-style convention, it knows where to find the file to render: app/cells/packages/package_children.html.haml:

- form = @opts[:form]
- children = form.object.children
- if children.empty?
%h3 -:- No contents -:-
- else
%table
%thead
%tr
%th Vendor Model
%th Min
%th Max
%th Default
%th Min Items
%th Max Items
%th % Discount
%th Remove?
%tbody
- form.fields_for :child_links do |nest_form|
= render_cell(:packages, :package_child, :form => nest_form)

Look -- nested cells for nested attributes!

Reuseable Cells

The real treat comes when I want to use those same searches and package lists elsewhere: I don't have to handle different cases, don't have to reference any directories, don't have to pass a :locals hash, and as a bonus, I get a cells "controller" class that can do some work with both the model AND the session, with an explicit render. Multiple render constraints are gone, finally. This isn't a treat, it's a freakin' desert buffet!

In addition, I get to delete many partials so my view/model directories are cleaner, with fewer files, and it's still easy to find the cells when you need to: just look into the view that's referencing the cell and there it is, the hierarchy laid out for all to see, easy to find in the app/cells directory.

Just to test all this theory out, I plunked a cell into another controller's form, changed the object.attribute reference (from @packages.potential_children to @contract.potential_packages), and the cells showed up, working. Not only that but I added the same cell a second time and changed the attribute (from potential_packages to potential_inventory_items) and it worked! Man, that's what I call DRY.

Hierarchical

Cells introduces a new directory, apps/cells, but IMO the layout is intelligent and well organized. It didn't take long to learn the DSL and organization (I was afraid it was going to be a long road to learn Yet Another Framework). This blog post by Mike Pence helped out a lot. Mike also gave a talk (aptly called Components Are Not a Dirty Word) along with Cell's author, Nick Sutterer, at RubyConf 2008.

Plus, it's Rails 2.3 compatible.

Thanks, Nick, for a beautiful plugin -- you've made me believe that V can be complete. I can't wait to put cells on apotomo for some dynamic interaction.

UPDATE: Russell has convinced me to not use cells. render :partials can be wrapped to reduce their noise, accept variables and be shared. I looked at the apotomo code and it's an exciting concept but looks very complex. I'm going to take a "wait and see" attitude for now, to see how Russell implements the JavaScript for the app.

UPDATE: I still like cells, but will wait until the app calls for one. So far, Russell's partial wrapper is okay but the view directories are getting kinda messy. But, it does put everything in one directory and we aren't using a lot of shared partials yet. Here's a link to a post that talks about presenters and cells.

Saturday, September 12, 2009

Envisioning the UI

My vision for the UI: ideally, it features reusable components or widgets that function within a view framework. The first such widget that come to mind is Search.

SEARCHING

I see Search being ubiquitous, used frequently. Users of the site, whether the public or admin side, will search for information.

I want Search to be the strong point of this application.

For an admin example: the user searches for a customer in order to get contact info or find a contract. The user searches for an inventory item in order to edit it or get information or add it to a contract.

For a public example: the user searches for an items to rent or buy. The user searches for a howto tutorial, or special offer.

So here's the deal:

1) On every page, there should be a search feature in the header that performs a general purpose search across many tables. For admin, those tables are users, packages, contracts, inventory|rental|supply|service_items and comments. For public, the tables are packages, inventory|rental|supply|service_items, (and resources like tutorials, which is a future feature). This header search component is in addition to any other search components described below.

2) On every index page, there is a search component at the top of the list. (Only on the admin side, the search box gains focus on load so the user can immediately start typing). The associated list is filtered as the user types.

3) On the contract form, there are two search components: one for users and one for packages. The search box gains the focus when it transitions from hidden to visible (I'm envisioning tabs to access users and packages). The list is filtered as the user types.

4) On the package form, there is a search component at the top of the potential children list. The list is filtered as the user types.

5) Index page searches should be persistent, allowing the user to return to the index with the previously sorted list.

6) A single click should clear the search.

SELECTING FROM THE LIST

On the contract form, assign a user with a single click.

On the contract form, add a package or inventory_item as a contract_line_item with a single click.

Deleting a contract_line_item should involve a single-click, no confirmation.

All actions without a page reload.

Other changes can require a form submission and a warning when navigating away from the page before saving.

The intention is to reduce the number of clicks and delay when creating or updating a contract. Eventually, we'll add versioning, so that changes can be undone.

IMPLEMENTATION IDEAS

Client side search through DOM using jQuery plugin QuickSearch.

Defer the downloading of full lists from the server until user begins search, especially for the package and contract forms.

Tuesday, September 1, 2009

Auditing and Versioning

[UPDATE: Versioning the associations needs to be done manually, since a one-size-fits-all is not possible given the business logic. So acts_as_revisable will likely be our choice because of the features mentioned below. ALSO, contracts will likely be versioned only when the contract is finalized, like then email is sent to the client with a link to their new (or updated) contract. This is necessary because the live subtotal update feature (totals update when a line item is added to the contract or quantities/discounts/dates change) means we'll be creating and updating a contract many times before it is locked in.]

We all make mistakes. And we'd all like to have do-overs. Which is unlikely in this lifetime.

But hey, maybe we can -- with versioning (and its sidekick, auditing).

First, we need to protect completed contracts (everything returned and paid for) from being altered. This can be accomplished simply (if not securely) by just displaying the contract without any form containers. (We will probably want a "modify" button so that a privileged user can override this protection.)

Second, for incomplete contracts (any contract not completed), we want to:
1. know who created them,
2. know who made any changes (the auditing part),
3. know exactly what they changed (the auditing part),
4. and be able to revert back (rollback those changes).

It may be asking a lot, but this ain't no blog. It's a tar pit in need of handrails. IOW, it's challenging.

Here's why:

A contract has_many contract_line_items and contract_line_item belongs_to package, inventory_item, rental_item, supply_item or service_item. We need to record all those associations plus the data for contracts and contract_line_items, plus the id of the user responsible for those changes.

There are some plugins that offer versioning. Here are the ones I've come across that look promising:

1. acts_as_audited
pros:
- supports Rails 2.1
- stores both previous and changed values
- records current_user
Cons:
- stores both previous and changed values
- does not support associations
Decision: no

2. acts_as_revisable
pros:
- requires Rails 2.3
- less magic: revision model has to be defined
- lots of callbacks
- supports revision grouping (several actions into one revision changeset)
- monitor specific columns to trigger revision
- turn revision tracking on/off (good for migrations)
- name revisions
- supports branching (nice for cloning and possibly for extending a contract)
- single table for versions
Cons:
- does not record current_user
- does not support associations (although this post describes how to do it)
Decision: maybe

3. has_versioning
pros:
- supports Rails 2.3.3 (in fact, requires it)
- supports associations
Cons:
- adds one column to each model table
- is really new
- does not record current_user
Decision: no

4. paper_trail
pros:
- compatible with Rails 2.3
- stores creates, modifies and destroys and stores the name of the action (event)
- allows destroyed records to be restored (nice)
- allows reverts even if schema changes (nice)
- records current_user
- can be turned on/off (so migrations don't pollute the version table)
- single shared table
- lots of tests
Cons:
- does not support associations
Decision: maybe


5. vestal_versions
pros:
- new plugin, so likely supports Rails 2.3
- Ryan Bates did a Rails Cast, so it's become popular
- it purports to be DRY, storing only the fields that changed, in yaml, in a common version table
- simple interface, supports version-on-creation and reverting to arbitrary versions which then become the new version
Cons:
- does not support associations
- does not record current_user
Decision: no

6. version_fu
Pros:
- supports Rails 2.1+
Cons:
- table needed per versioned model
- version table is mirror of model table, not DRY
- does not support current_user
- does not support associations
Decision: no

UPDATE: new plugin #7
7. versionator
Pros:
- new, so it should support Rails 2.3
- supports associations
Cons:
- new, so may be work in progress
- minimal (no) documentation
- does not support current_user
Decision: maybe

8. simply_versioned
This plugin appears to be abandoned and, like Rick Olsen's acts_as_versioned, is probably incompatible with Rails 2.1+.

NO CURRENT_USER SUPPORT
The plugins that do not support recording the current_user may be okay, there may be other ways to accomplish this:
- We can add a column to the versionable model table that records the current_user, although that may not work for paper_trail, which records the values prior to the change.
- (another solution?)
- We can possibly patch the plugin with this capability, along with timestamp if not already being recorded. This would be my least preferred solution.

NO ASSOCIATION SUPPORT
This may be a show stopper, if we really need this, and I think we do. We could add it somehow, and it seems like the version information will be in the versioning table, we just have to add the capability. Will have to look into this further. There is a way to do association versioning for acts_as_revisable.

CLONING
I add this plugins here, because it's kind of related:

deep_cloning is interesting because we often create a contract and then clone it and set new dates for the cloned contract. This is because a client will call and say, "You know that last rental I had? I need the exact same thing for this weekend."

Saturday, August 22, 2009

Cover your Assets (was CONTRACTABLES: WHY SO FRICKIN' COMPLEX?)

UPDATE 8/26/2009: Mike and I are very close to working out the database schema. I realize now that this part of the design had been too complicated for me to work out solo. I'm glad we're straightening it out now rather than later. Basically, we have individual classes for our physical assets (rental_items, supply_items, service_items), abstract inventory_items, packages and contract_line_items (where immutable data is stored).

UPDATE 8/31/2009: We continue to close in on a working schema. We're working on the relationship between package, inventory_item and rental_item. Next, we'll tackle the package domain logic (what's required, what's not, what's default, what's constrained, etc).

1) We rent physical items either individually or contained in a package. UPDATE 8/31/2009: PACKAGES CAN BE SET ASIDE FOR THE NEXT PHASE.

2) A package can contain another package as well as physical items.

3) A package can contain optional items and required items. Some optional items may be included by default. IOW, if an item is not required, it is optional and may be included by default.

For instance, a video camera package contains a camera (required) and batteries (not required) and 110V power supply (not required). It comes with batteries and power supply by default but a customer can choose not to take either of them to save a little money.

4) A contract line item can be a "Rental" item, a "Supply" item, or a "Service" item. A supply item is a consumable (AA battery, videotape, etc). A service item is a crew member (camera operator, sound engineer, etc).

5) We have multiples of the same physical item in our inventory (like 4 "Panasonic DVX100A" cameras, 12 "Sennhieser EW100 G2" wireless mics, and 24 "Sony F970" camera batteries). If a customer reserves a DVX100A camera, we don't care which one is assigned to the contract, they are all the same.

6) However, when we pull a DVX100A, we want to assign that specific DVX100A to the order. In other words, when the customer picks up the order, we want to record which camera went out (we'll do this by entering the asset tag number on the order).

7) When creating/updating the contract, the system must check availability of items based on a date range, inventory level and current reservations.

So here is the design, as-is. It can change as needed.

1) The contractables table implements Single Table Inheritence, holding all three items: Rental, Supply and Service. UPDATE 8/31/2009: CONTRACTABLES IS NOW SPLIT INTO FIVE TABLES: packages, inventory_items, rental_items, supply_items, and service_items. THE packages TABLE'S RELATIONSHIPS TO THE rental_items, supply_items, and service_items TABLES ARE ALL MANY-TO-MANY, THROUGH THE inventory_items TABLE.

2) The contractables table is self-referential, many-to-many, forming a directed acyclic graph (not a tree). This allows packages to contain packages that contain both packages and physical items. It is critical that no path be cyclical.

3) The current design has each terminating node of the DAG representing a physical item. UPDATE 8/31/2009: THIS IS YET TO BE HAMMERED OUT. CURRENTLY, THE packages TABLE HAS LEAFS THAT REFERENCE RECORDS IN THE inventory_items TABLE. HOW THAT REFERENCE IS IMPLEMENTED IS STILL AMBIGUOUS.

That's where it begins to smell, IMO.

We need an individual record for each physical piece of inventory to track things (like its purchase date, maintenance schedule, asset tag, damage, etc). But as mentioned earlier, an F970 battery is like any other F970 and it doesn't matter which one is reserved.

So looking at it now, we probably do need a separate, physical inventory table. Each terminating node of the DAG can reference many records in the physical inventory table.

Back to the design:

4) To check availability, we need to know two things:
-- how many physical items are required for each package
-- how many of those physical items are reserved during the date range

To speed up these queries, I created two tables:
-- a nested set to get the set of required physical items, given a package
-- a reservation table to match those physical items against
The nested set would be built from the DAG and modified when the DAG is modified (or completely rebuilt on demand). The reservation table would be built from the contracts and modified when a contract is modified (or completely rebuilt on demand). UPDATE 8/31/2009: WE WILL LEAVE THESE TABLES OUT FOR NOW UNTIL WE NEED THE OPTIMIZATION IT OFFERS.

That's the basic gist. I may be missing something but it's late and I really need to get to sleep. Lets discuss this and ask questions.

Here are earlier posts to go through this in more detail, but they may be out of date:

more-most-complicated-model-sets

equipment-collection

nested-equipment-sets-and-premature

Tuesday, August 18, 2009

Phase III -- new features

This is a good time to jot down our Phase III goals...

1. Public website launch (if not already launched).*

2. User registration (if not already finished).

(The rest of this list is not prioritized.)

3. Auditing -- ability to view changes to contracts, users and contractables and to roll back those changes, including undeleting a record. Ability to link changes to specific user. Potential candidates include paper_trail, acts_as_audited, acts_as_revisable, has_versioning, version_fu, and vestal_versions. I'll follow up with a post about each plugins merits.

4. Comments -- ability to comment on contracts, users and contractables.

5. Reports -- ability to view contracts, given user name and optional date range. Ability to see what contracts are active, late, unconfirmed, canceled, etc. Ability to see contractable activity, given contractable(s) and optional date range (is a contractable earning money). Ability to generate quarterly and annual tax reports. Ability to generate third-party owner activity by date range (monthly, quarterly or custom range).

6. Payments -- securely store people's credit card information for deposits and payments. This will be stored with the gateway processor. If needed, generate Google Checkout invoices. Track payments and link payments to contracts.

7. Cart, Quotes and Reservations -- this is a big one. Add a cart feature that allows user to specify a date range (or number of days), add items to the cart and get a real-time quote.

8. Users -- Allow registered user to submit their cart with a specific date range as a request for reservation (pending admin approval). Allow registered users who are identified as "trusted" to submit their cart with a specific date range as an automatic reservation (still pending admin approval). Trusted users (who've previously rented from us and have a minimum positive rating) get live indication of contractable availability for their dates and can then actually make, review and modify their reservations online (but submissions and changes will be held, pending final admin approval).

9. Search -- add acts_as_suggest plugin for search bar (public, contractables nesting, contract line items).

Geesh -- this is a big list!

* Before the website is launched, need to figure out how best to populate the database with contractables and supplies, maybe by importing CSV files.

Documenting changes

* Mike has replaced resource_controller with inherited_resources. Reason: inherited_resources is less magic and seems more up to date than our other candidates (despite the big bonus that Russell is a contributor to make_resourceful).

* Mike has replaced restful_authentication with authlogic. Reason: authlogic has better flexibility, better features and less clutter (IMO).

* Mike and I have switched users and contractables from attribute_fu to Rails 2.3 nested models.

* Mike has set up our initial unit, functional and integration tests for users.

* Russell is whipping our CSS files and HAML views into shape.

Everything is looking good! Love the tests and the view code. We are proceeding to contracts now. No further changes anticipated at this point.

Monday, August 3, 2009

Styleguide

Style guide has been moved to the GitHub wiki.