Thursday, January 31, 2008

Generator: Stage

Update: Stage has become a gem and project details can be found at stonean.com

This post was updated on 04-27-08 to reflect the improved design. I'm using the RubyOnRails methods as this is where Stage was born, but Stage is available for Merb as well.

I've just completed a new generator called stage. This is a direct copy/modification of the scaffold generator included with Rails 2.0.2.

Stage was born out of a desire to remove as much code as possible from the view. I didn't want to create a new markup, I wanted to use the existing architecture more efficiently.

To use Stage, you first need to grab it:
sudo gem install stage

Then you use stage just like scaffold:
./script/generate stage author first_name:string last_name:string
exists app/models/
exists app/controllers/
exists app/helpers/
create app/views/authors
exists app/views/layouts/
create app/views/data/
create app/views/authors/index.html.erb
create app/views/authors/show.html.erb
create app/views/authors/new.html.erb
create app/views/authors/edit.html.erb
create app/views/authors/_form.html.erb
create app/views/authors/_data.html.erb
dependency model
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/author.rb
create test/unit/author_test.rb
create test/fixtures/authors.yml
create db/migrate
create db/migrate/001_create_authors.rb
create app/controllers/authors_controller.rb
create app/helpers/authors_helper.rb
route map.resources :authors


So, what's different? With the exception of the index view, the contents have changed quite a bit.

Edit:
<h1>Editing Author</h1>

<%= render :partial => "form" %>

<%= link_to 'Show', @author %> |
<%= link_to 'Back', authors_path %>

New:
<h1>New Author</h1>

<%= render :partial => "form" %>

<%= link_to 'Back', authors_path %>

Show:
<%= render :partial => "data" %>
<%= link_to 'Edit', edit_author_path(@author) %> |
<%= link_to 'Back', authors_path %>


And theres a new partial called form. As you can see this is called by edit and new:
<%
submit_label = "Update"
submit_label = "Create" if @author.new_record?
-%>

<%= error_messages_for :author %>

<% form_for(@author) do |f| %>
<%= render :partial => "data" %>
<p> <%= f.submit submit_label %> </p>
<% end %>

We've now removed the duplication in new, edit and show by having them all reference the data partial.

I've had this construct for a while, but the data partial always ended up with too much code. If I'm in edit mode, show the input tag, else show the text, etc..., etc...

It could get rather ugly. So, I decided to really start using the helpers. I have used them before when there would have been "too much" code in the view. I guess I've changed my definition of "too much".

Here's the data partial:

<p>
<b>First name</b><br/>
<%= author_first_name_value %>
</p>
<p>
<b>Last name</b><br/>
<%= author_last_name_value %>
</p>

As you can see I am calling a <model>_<attribute>_value method which will give me back the correct form or show value. This construct is used to avoid naming collisions. I had initially designed it without namespacing the methods with the model name, but quickly discovered that was a bad idea.


The @action_name instance variable determines how I'm to render the information. Luckily both Merb and Rails have this same variable.

These methods are defined in the Helper:

module AuthorsHelper
def author_first_name_value
if @action_name == "show"
h @author.first_name
else
text_field_tag "author[first_name]", @author.first_name
end
end

def author_last_name_value
if @action_name == "show"
h @author.last_name
else
text_field_tag "author[last_name]", @author.last_name
end
end
end

I opted on creating a method for each attribute and a clearly outlined "if" block to make modifications easier.

I hope you find this useful and it fits your idea of view structure.

Btw, thanks to Greg Houston for his source formatting tool. It made this post look a lot better than it would have without it.

Wednesday, January 30, 2008

REST: Active Record Resource CRUD

I need to point out that I am new to REST and these posts are simply me thinking through these topics to obtain a better understanding. If you find something I've said to be incorrect, please let me know.

Simply put, REST design is about resources. To go further, REST design is about providing access to those resources in simple CRUD fashion. What is CRUD? Well it stands for Create, Read, Update and Delete. The basic actions one can perform on a resource.

Well, we obviously have more than those four methods. In fact the functional CRUD actions are: index, show, new, edit, create, update and destroy. This does not mean that each one of these actions will be seen in the path of the request.

Using a User model as an example:


Action

Listing users

Show user

New user

Create users

Edit user

Update user

Delete user


Path

/users

/users/1

/users/new

/users

/users/1/edit

/users/1

/users/1


HTTP Method

GET

GET

GET

POST

GET

PUT

DELETE




As you can see, some of the paths are the same. For example, users/1 can be for showing, updating or deleting. That does seem a little odd, but it's all about the HTTP method. The method determines the action to be taken.

These actions are part of the design consideration for a REST application. What does that mean? Well, when I am thinking about designing a REST application, I think in terms of accessing the data via a process, not user interaction. This forces me to design the resources in the simplest of manners. At least, that's my goal. Don't take this to mean this should ALWAYS be done. I don't apply an ALWAYS rule to anything, that leads to trouble.

As I encounter more REST related issues I'll post what I've learned. I had started to make a series on REST design, but I didn't like that idea. It felt like I was restricting myself to continue the topic instead of writing about my most recent discoveries. I'm gonna chalk that up to another lesson learned on blogging.

Note: This is an updated version of the original post.

Friday, January 4, 2008

RSpec boolean values in views

Here's an annoying little issue. Annoying because I didn't think it through.

Here's the index.html.erb_spec.rb:

require File.dirname(__FILE__) + '/../../spec_helper'

describe "/folders/index.html.erb" do
include FoldersHelper

before(:each) do
folder_98 = mock_model(Folder)
folder_98.should_receive(:name).and_return("MyString")
folder_98.should_receive(:top_level).and_return(false)
folder_99 = mock_model(Folder)
folder_99.should_receive(:name).and_return("MyString")
folder_99.should_receive(:top_level).and_return(false)

assigns[:folders] = [folder_98, folder_99]
end

it "should render list of folders" do
render "/folders/index.html.erb"
response.should have_tag("tr>td", "MyString", 2)
response.should have_tag("tr>td", false, 2)
end
end

This kept failing with the following message:

1)
'/folders/index.html.erb should render list of folders' FAILED
2.
<false> is not true.

This was followed by the stack trace pointing me to the following line:

response.should have_tag("tr>td", false, 2)

To fix the issue change the line to read:

response.should have_tag("tr>td", "false", 2)

Notice the quotes around false.