Friday, August 7, 2009

Ramaze, Sequel, and Search (Round 2)

After my last post, Jeremy Evans had a suggestion that I felt was worth passing on. He points out that you probably shouldn't be using datasets in models and views and instead should move them to the models and let the model handle it. So ... here's the new controller controllers/main_controller.rb

# controllers/main_controller.rb
#
# The mainController has a two methods index and search_results. First map to /
# for the view. Next, set the layout to page so that we use the
# layout/page.xhtml for our layout.
class MainController < Ramaze::Controller

# The Admin controller will be accessed using "admin" as in:
# http://localhost:7000/admin.
map '/'

# Use page.xhtml in the layout directory for layout
layout :page

# You can access it now with http://localhost:7000/
def index
@title = "Search Example"
end

# Calculate and display the search results.
def search_results
@title = "Search Example - Results"

# Grab the search_string from the request hash. This is generated when the user inputs
# something into the Search box in the view/index.xhtml file.
@search_string = request[:search]
@search_response = Book.search(@search_string.split)
end
end



and the new models/models.rb

#
# This is the model for the book and is backed by the :book table in the
# database.
#
# Create the Book model.
class Book < Sequel::Model
def self.search(terms)
Book.grep([:title, :description], terms.map{|x| "%#{x} %"}).all
end
end


Not too big a change, but it does make things a bit easier to use and test.

As always, let me know if you have questions.

Ramaze, Sequel, and Search

If you've been following my emails on the Ramaze and Sequel lists, you've probably realized that I've been working on a Ramaze application for a library. This project came about when our company decided that we needed a library and we started looking for software. There wasn't anything that really met our needs so I decided to take a stab at writing it myself. It's a pretty good sized application now and still not "finished", but I thought in these next few posts, I'd lay out some of the things that I've learned while working on it in smaller pieces than the whole application.

The first thing I'd like to show is the search function. This is much more simple than I thought it would be. What we'll do is create some books with titles and descriptions. We'll add a page that allows the user to input some search terms and then another page that displays these results. The results will be based on whether any of the search terms are found in either the title or the description.

First let's create the database with our migration. Here's the code for this. Note that we're adding the data as well as creating the database so we don't have to create the data using another page.

# dbMigration/001_SearchMigration.rb
#
# This is the "new" way to do migrations by using the Class.new form.
# It means that a new class name is not required and so there is no chance
# of creating a second class in the migration files that is the same as the
# first causing problems.
Class.new(Sequel::Migration) do
def up

create_table(:books) do
primary_key :id
String :title
String :description
end

from(:books).insert(:title => 'Programming Ruby', :description => 'A great Ruby book')
from(:books).insert(:title => 'Agile Web Developement with Rails', :description => 'A book about Ruby on Rails')
from(:books).insert(:title => 'Cryptonomicon', :description => 'A book about a Unix sys admin')
from(:books).insert(:title => 'The C Programming Language', :description => 'A book about programming in C')

end

def down
drop_table(:books)
end
end


We've seen this before. We create the table called "books" (note the plural) with text columns for the title and the description. Our down method, just drops the table. In the middle we add four books with their titles and descriptions.

Next let's look at our models. Jeremy Evans, the Sequel maintainer/guru, recommends having a single models.rb in our models directory to make it easier to use irb to test things. I've taken to doing this and it works quite well. Here our model is very simple (empty). We name the model Book (singular of the table books) and derive it from Sequel::Model. This will give us access to books table. Here's the actual code:

#
# This is the model for the book and is backed by the :book table in the
# database.
#
# Create the Book model.
class Book < Sequel::Model
end


The controller, controllers/main_controller.rb is also quite simple. It has an index method that only sets the title and a second method search_results that "calculate" the results and save them to the @search_response variable for use in the view/search.xhtml view. We also go ahead and save the @search_string so we can display that we can display that also. The last line, that calculates the search_response, probably needs a bit of explanation.

We're going use the "grep" method on the Book dataset. We will pass an array with :title and :description to let the method know which columns of Book we're interested in. The next piece, we take the search_string and split it into an array. We then "map" the array generating something that will look like:

%ruby % %rails %

for a search string of "ruby rails". You can read about the grep function in a Sequel dataset here.

Here's the controller code:

# controllers/main_controller.rb
#
# The mainController has a two methods index and search_results. First map to /
# for the view. Next, set the layout to page so that we use the
# layout/page.xhtml for our layout.
class MainController < Ramaze::Controller

# The Admin controller will be accessed using "admin" as in:
# http://localhost:7000/admin.
map '/'

# Use page.xhtml in the layout directory for layout
layout :page

# You can access it now with http://localhost:7000/
def index
@title = "Search Example"
end

# Calculate and display the search results.
def search_results
@title = "Search Example - Results"

# Grab the search_string from the request hash. This is generated when the user inputs
# something into the Search box in the view/index.xhtml file.
@search_string = request[:search]
@search_response = Book.grep([:title, :description], @search_string.split.map{|x| "%#{x} %"}).all
end
end


The search.xhtml contains the form to type in the search term(s) and submit it. On the form we use the action attribute to send the results to the search_results method in the controller. We use a "get" method so the results are passed on the URL (allowing bookmarking as Gavin pointed out when I asked how to do this incorrectly in a Ramaze thread. I had he and Clive steer me in the right direction though). The URL will look something like http://localhost:7000/search_results?search=ruby+rails when you're searching type "ruby rails" in the text box. Here's the view/index.xhtml:

<!-- view/index.xhtml -->
<!-- Create the form for the search. We're going to set the action to
search_results so that method will get called when the form is
submitted and we'll use a "get" method to pass the parameters in
the URL.
-->

<form id="search" action="search_results" method="get">
<fieldset>
<legend> Search </legend>
<div>
<!-- for= goes with id=, the name= is placed in the request variable. -->

<!-- Input for the title. -->
<label for="search">Search:</label>
<input id="search" name="search" type="text" />
<br/>

<!-- Submit the edited book values. -->
<input type="submit" value="Search" />
</div>
</fieldset>
</form>


The page to display the search response, view/search_results.xhtml, is also pretty simple. It takes the results saved in @search_response by the search_results method in the controller, loops through them and displays each of the books and their descriptions. Here's the code:

#{flashbox}
<br/>
<?r if @search_response.each && @search_response.size > 0 ?>
Results found for "#{@search_string}." <br/><br/>
<?r @search_response.each do | book | ?>
Title: #{book.title}
<br/>
Description: #{book.description}
<br/><br/>
<?r end ?>
<?r else ?>
No results found for "#{@search_string}".
<?r end ?>
<br/>


Finally, here's the layout (layout/page.xhtml):

<!-- view/page.xhtml -->

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<!-- Use the page.css in the public directory and set title based on
what's set in the associated method.
-->

<link rel="stylesheet" type="text/css" href="/page.css"/>

<title>#@title</title>
</head>
<body>
<div id="header">
<h1>Search Example</h1>
</div>

<div id="content">
<!-- Display the actual content. This will come from the method or the
associated view/*.xhtml file
-->

#@content
</div>

<!-- Set the footer in the center of the screen. -->
<div id="footer" style="text-align: center;">
<h5> Powered by Ramaze </h5>
</div>
</body>
</html>


and the CSS file (not formatted):

/* Header CSS */
#header {
background:#9DA9EE;
color: white;
margin-bottom: 0;
padding: 0.25em;
}

/* Footer CSS */
#footer {
background:#9DA9EE;
color: black;
}

All told, pretty simple and easy after someone points you in the right direction anyway.

As always, let me know if you have questions in the comments and I'll do my best to answer them.

Wednesday, July 8, 2009

Creating a Poll with Ramaze and Sequel; Updated 2009-07-10

Jeremy Evans of Sequel had a few comments on the code:


  • add the :polls here: foreign_key :poll_id, :polls in the migration.

  • change :questions to :responses in the drop in the migration.
  • make the drop a single line since it will take multiple values and drop the :responses first as in drop_table(:responses, :polls)

  • don't use the -M on the sequel migration as going to the latest is the default as in
    sequel -m dbMigration/ sqlite://polls.db

  • load the models before the controllers in start.rb as the controllers will depend on the models

  • remove the requires in the models as they can't be loaded without the database anyway.



I've updated the code below to reflect this.

Because of their great accuracy, Internet based on-line polls are a popular way to gather information. OK, if HTML supported sarcasm tags, that previous sentence would have to be enclosed in them. Still a lot of sites feature polls and they can be fun even if they aren't particularly useful or reliable. We'll use Ramaze and Sequel here to create a very simple on-line polling system. With the proper enhancements (say putting a login/password on the admin page), you could use this in your site.

We're going to create a "Poll of the Day" site that allows an administrator to create a poll for a given day and give it a title, the question, and some responses. These will be saved in the database. The main poll site will allow a user to answer the poll question for the day and then will be redirected to the results page to see the current results. The user can also go directly to the results page if desired.

Let's start with the database. Here's the Sequel migration for it:


# dbMigration/001_PollMigration.rb
#
# This is the "new" way to do migrations by using the Class.new form.
# It means that a new class name is not required and so there is no chance
# of creating a second class in the migration files that is the same as the
# first causing problems.
Class.new(Sequel::Migration) do
def up
create_table(:polls) do
primary_key :id
String :title
String :question
Date :date
end

create_table(:responses) do
primary_key :id
String :response
Integer :count
foreign_key :poll_id, :polls
end
end

def down
drop_table(:responses, :polls)
end
end



Here we're going to create two tables, one for the poll and one for the responses to the poll. The first will contain a title, a question, and a date. The second a reponse and a count of the number of times that response was selected. There will be a one-to-many / many-to-one relationship between the polls and the responses. In other words, each poll can have many responses, but each response will have only a single poll.

To run this use:
sequel -m dbMigration/ sqlite://polls.db

This should create the two tables with their associated columns. You can check this using the sqlite manager for Firefox that's available here.

Once we've created the database, we can start on the actual code. Let's start with start.rb our main program.



# start.rb
#
# This is the main program for the example. It loads the Sequel database,
# loads the controllers and models, and then starts up Ramaze.
#
# The database should have been set up using the database migrations in
# the dbMigration directory.
require 'rubygems'
require 'ramaze'
require 'sequel'

# Open the polls database. This must be done before we access the models
# that use it.
DB = Sequel.sqlite("polls.db")

# Load the controllers and models.
require 'models/poll'
require 'models/response'
require 'controllers/main_controller'
require 'controllers/admin_controller'

Ramaze.start


There's nothing in here that's different from what we've done in previous examples. We load the database, load the controllers, load the models and finally start up Ramaze.

The models are also very simple. Here's the models/poll.rb:



# models/poll.rb
#
# This is the model for the Poll and is backed by the :polls table in the
# database.
#
# Create the Poll model.
class Poll < Sequel::Model
one_to_many :responses
end


There's nothing too much in here. We, as is usual derive from Sequel::Model and then note the one_to_many relationship with the responses table.


# models/response.rb
#
# This is the model for the Response and is backed by the :responses table in the
# database.
#
# Create the Response model.
class Response < Sequel::Model
many_to_one :polls
end



Also, nothing much. We derive from Sequel::Model and then have a many_to_one relationship with the polls table.

Next, let's look at the controllers. First we have the admin controller in controllers/admin.rb:

# controllers/admin_controller.rb
#
# The AdminController has a single method index. First map to /admin for
# the view. Next, set the layout to page so that we use the layout/page.xhtml for our
# layout. Then we use a helper for the :xhtml which will allow us to use "js" in the
# layout (we use this to generate a javascript link).
class AdminController < Ramaze::Controller
# The Admin controller will be accessed using "admin" as in:
# http://localhost:7000/admin.
map '/admin'

# Use page.xhtml in the layout directory for layout
layout :page

helper(:xhtml)

# You can access it now with http://localhost:7000/admin
def index
@title = "Poll of the Day - Administration"
@page_javascript = "admin"

# If this was from a post, we grab the information out of the request hash
# and create a new Poll and however many we receive responses. We add the responses
# to the poll, set the flash message, and then stay on this page in case the
# user wants to add more polls.
if request.post?
title = request[:title]
date = request[:date]
question = request[:question]
poll = Poll.create(:title => title, :date => date, :question => question)
responses = request[:response]
responses.each do | r |
poll.add_response(Response.create(:response => r, :count => 0))
end
flash[:message] = "New poll accepted"
redirect rs(:index)
end
end
end



Once again, everything here we've mostly seen before. The only "interesting" piece is the responses that we grab from an array (we'll see how to set that up when we look at the view). Basically, we grab the data from the request hash, create a Poll using said data, and then loop through the response array creating Responses and adding them to the Poll. Just a note, there's no need to pull out the title, question, and date separately. I just did that so that I could debug a bit easier while I was developing. Feel free to just put the request[] into the Poll.create code. After the poll and responses are created, we just set the flash message to let the user know the poll was created and then just redirect to the same page so they can enter another poll if desired.

Now, here's the view, view/admin/index.xhtml


#{flashbox}
<h2>Poll of the Day - Administration</h2>

<form id="new_poll" method="post">
<fieldset>
<legend> Poll Information </legend>
<div>
<!-- for= goes with id=, the name= is placed in the request variable. -->

<!-- Input for the title, question, and date. -->
<label for="title">Title:</label>
<input id="title" name="title" type="text" />
<br/>
<label for="question">Question:</label>
<input id="question" name="question" type="text" />
<br/>
<label for="date">Date(yyyy-mm-dd):</label>
<input id="date" name="date" type="text" />
<br/>
<!-- This div, responses, is to add new input text boxes for responses -->
<div id="responses">
<!-- We're going to use the square brackets ([]) to let Rack (I believe)
know that we want these to come across the in the request hash as
an array.
-->

<label for="response[]">Response:</label>
<input id="response[]" name="response[]" type="text" />
</div>
<br/>
<!-- Submit the new poll (this should result in it being saved in the database -->
<input type="submit" value="Submit New Poll" />
</div>
</fieldset>
</form>



As you can see, the first part of this is pretty straightforward. We simply put input text boxes for the title, question and date of the poll. Then we have a div for the responses followed by the response text box. Note that the label on the text box contains square brackets on it. This tells Rack (I believe it's not Ramaze) that we want to return this as an array and not as a single value. We need to take a look at the JavaScript to see how additional response text boxes get added.

$(document).ready(function() {
// For the responses div, find any input and if we hit a return (13), then
// clone the input text box and add a new input text box after this one.
$("#responses input").keypress(function(e) {
// If we receive an "Enter" instead of submitting
// the form, clone the input and return false to
// stop the submit.
if (e.which == 13) {
// We have and Enter, so clone this input text box and
// insert the new one after this one.
$(this).clone(true).insertAfter(this).val("hello");

// Return false so we don't do the submit of
// the form we'd normally do here.
return false;
}
});
});

This is JavaScript file contains some jQuery code to add a function all input tags under the responses div id. This function will look for the Enter key (value 13) to be hit and when it is will clone the current text box and add the new text box after itself. In this way, the user can add more responses without being limited to a set number. This function returns false so that when the Enter is hit, the form is not submitted as is normal. To submit the form, the user need to actually click the "Submit New Poll" button.

That's pretty much it on the admin side, let's take a look at the end user side. Here we have the main controller

# controllers/main_controller.rb
#
# This example shows how to do a simple poll web page. It has two
# methods (index and results). The index method will display the current day's
# poll and allow the user to vote. When the user does vote, their vote will be
# counted, saved, and they will be redirected to the results page. The results
# page will display the current results for today's poll. There is one private
# method that both methods call to get the Poll for today. It will try to find the
# poll based on today's date and if it can't find it, will return nil. If nil is
# returned to either method, it will set the flash value to "No poll ...".
class MainController < Ramaze::Controller
# Use page.xhtml in the layout directory for layout
layout :page

helper(:xhtml)

# You can access it now with http://localhost:7000/
def index
# Set the title for the page.
@title = "Poll of the Day"

# Get today's poll and set the flash message
# if there's not one.
if !(@today_poll = todays_poll)
flash[:message] = "No poll for today"
end

# If the user voted, it should come in a a post. The
# request[:choices] represents the value that was on the
# radio button in view/index.xhtml. We'll use that to get
# the Response from models/response.rb. We increment the
# count in the response and then save the new value. Finally,
# we'll redirect to the results page to show the user the
# current vote count.
if request.post?
response = Response[request[:choices]]
response[:count] = response[:count] + 1
response.save
redirect r(:results)
end
end

# You can access it now with http://localhost:7000/results
# This will show the user the results for the voting on the
# current poll.
def results
# Set the title for the page.
@title = "Poll of the Day - Results"

# Get today's poll and set the flash message
# if there's not one.
if !(@today_poll = todays_poll)
flash[:message] = "No poll for today"
end
end

private

# Get today's poll from the database using today's date to find it. This method
# is private so it can be used by the two methods above, but can't be reached
# from the outside.
def todays_poll
Poll.find(:date => Date.new(Time.now.year,Time.now.month,Time.now.day))
end
end



First up we have the layout and the xhtml helper discussed in the admin controller. Next we have the index which will get displayed if you don't request anything else (i.e. go straight to http://localhost:7000/). Since this is the default, we don't need a map command as we did in the admin controller. We set the title (as always), then check get today's poll (@todays_poll) using the todays_poll method (sorry for the confusing naming). If there is no poll, we simply set the flash message which will get displayed in the view. Next, if this was a post (someone selected something from the radio boxes in the view), we increment the count in the response database, save the count, and then redirect to the results page.

Next up we have the results method which is available directly at http://localhost:7000/results or when you "vote" on the main page. Here all we do is set the title, get today's poll or set the message if it is not there.

Finally, there's the todays_poll private method. It's private so you can't get to it via a URL. It simply returns today's poll by finding a poll by creating a date from the current date. It would be much cleaner if there were a Date.now method to match the one in Time.

Here's the view for index:


<!-- view/index.xhtml -->
<h2>Today's Poll</h2>
#{flashbox}
<!-- Main page. Display the title, the question, and then radio boxes
for the choices. When they select, it will be submitted back to
the index page.
-->

<?r if @today_poll ?>
<h3>#{@today_poll.title}</h3>
<h3>#{@today_poll.question}</h3>
<form id="new_poll" method="post">
<!-- for each of the possible responses in today's poll, create a
radio button with the id (this is the id in the Response database
table) as the value and the response
as the text
-->

<?r @today_poll.responses.each do |r| ?>
<input type="radio" name="choices" value=#{r.id}> #{r.response}<br>
<?r end ?>
<!-- Submit the user's choice in the poll -->
<input type="submit" value="Vote" />
</form>
<?r end ?>



Nothing too special in here. If there's a poll for today, we put the title and question using the @todays_poll value and then loop on the responses creating a radio button for each of them. Finally, there's a submit button labeled "Vote".

Here's the results view:


<!-- view/results.xhtml -->
<h2>Today's Poll Results</h2>
#{flashbox}
<!-- Results page. Using today_poll, Display the title, the question, and then
the reponses and their counts.
-->

<?r if @today_poll ?>
<h3>#{@today_poll.title}</h3>
<h3>#{@today_poll.question}</h3>
<?r @today_poll.responses.each do |r| ?>
#{r.response} : #{r.count}<br>
<?r end ?>
<?r end ?>



We simply check if there is a poll and if there is display the title, question, and then the counts for each of the reponses.

As always, let me know if you have any questions or comments.

Tuesday, June 23, 2009

Updated 2009-06-24: Ramaze and Etanni Templating (String Interpolation Version)

In a comment in my last post Phrogz suggested that perhaps I should talk a bit more about Ruby string interpolation which is used by Etanni in templating. We've only used it in very simple ways but you can put any Ruby code inside of the #{} and the result will have a to_s() called on it. We can use this to do some interesting things in Etanni without needing the form. Note that the examples I'm giving here are pretty contrived but should give you some ideas on the sorts of things you can (possibly not should) do.

Here's our start.rb:


require 'rubygems'
require 'ramaze'

class MainController < Ramaze::Controller

# Use page.xhtml in the layout directory for layout
layout :page

# You can access it now with http://localhost:7000/
# All we're going to do is set the title in here and
# everything else will come from our view/index.xhtml file.
def index
# The title is used by the layout/page.xhtml file to set the title
# for the page.
@title = "Index Page"

# We'll use this boolean to decide what to show.
@saw_dog = true

# The number of dogs we've seen.
@dog_count = 3
end

end

Ramaze.start



Pretty simple this time around. We set the title, whether we saw any dogs, and the number of dogs we saw. The dogs came from Phrogz' example and I thought in his honor, we should go ahead and stick with them.

Here's the view/index.xhtml code (where the interesting stuff is going on in this example):


<!-- String interpolation version with if/then/else. -->
<br/>
#{if @saw_dog then "I saw a dog!" else "I didn't see any dogs!" end}

<!-- Using the ternary operator -->
<br/>
#{@saw_dog ? "I saw a dog!" : "I didn't see any dogs!" }

<!-- Check the count, if the count is anything but 1 add an "s" at the end. -->
<br/>I saw #{@dog_count} dog#{"s" unless @dog_count==1} at the park

<!-- Put the number of dogs seen -->
<br/>
#{"dog<br/>"*@dog_count}

<!-- Put the number of dogs seen by creating a list. -->
<br/>
<ul>
#{ d=''; 1.upto(@dog_count) do d << "<li>dog</li>" end; d}
</ul>

<!-- Put the number of dogs seen by creating an array and using join (from ^manveru)-->
<br/>
<ul>
#{Array.new(@dog_count){ "<li>dog</li>" }.join}
</ul>




First off, we use an if/then/else to display whether we saw a dog or not. Through all of these examples, remember that Ruby will return the last thing calculated for the expression. In this case, either the result of the "then" or the "else". Next we have the same thing using the ternary operator. I first saw this in C and it's a pretty handy tool to have lying around. After this, we have Phrogz' example where we put the dog_count and then if it is not 1, we add the "s" on the end of "dog". He used the ":s" form (an identifier) of this, but this may be a bit more readable for the non-experts among us. Next we put out the number of dogs we saw using the "*" operator of a string. In this case we add the br tag to put each of them on a separate line. Finally, we do things the hard way. Instead of using our as we did in the last post, we use only string interpolation to create the list. Like I said, this is not the way I'd recommend doing it, but it may give you some ideas. Update: Even more finally, we create the same list by creating an array and doing a join on it. This comes from ^manveru and will probably be faster and use less memory than the previous version.

The other files, layout/page.xhtml and public/page.css are the same as earlier, so just grab them from our last post.

As always, let me know if you have any questions or comments.

Sunday, June 21, 2009

Etanni Templating

Etanni has replaced Ezamar as the default templating engine for Ramaze and if anything it is even simpler. There are only a very few lines of code for the entire thing shown here. Etanni really only supports two things. The first is standard Ruby interpolation such as #{@x} which can go in your template and also for sections where the result should not be put into the output stream. We've used both of these in previous posts, especially the interpolation, but we haven't really talked about them too much. I decided that I needed to learn a bit more about it after this series of emails on Etanni (ignore the Subject and especially read Michael's final post on the subject where he explains things pretty well (at least it helped me out quite a bit).

Here's a bit of code that uses both types. We'll start out with our start.rb code:


require 'rubygems'
require 'ramaze'

class MainController < Ramaze::Controller

# Use page.xhtml in the layout directory for layout
layout :page

# You can access it now with http://localhost:7000/
# All we're going to do is set the title in here and
# everything else will come from our view/index.xhtml file.
def index
# The title is used by the layout/page.xhtml file to set the title
# for the page.
@title = "Index Page"

# Some simple text to just place on the page in view/index.xhtml.
@text_1 = "Hello World"

# We'll put this text (text_2) out count (5) times.
@count = 5
@text_2 = "Goodbye World"

# We'll create a list with these elements.
@arr_title = "List Elements"
@arr = [ "some", "list", "elements" ]

# We'll use this boolean to decide what to show.
@bool = true
end

end

Ramaze.start



There are five sections in here. The first sets the title which will be interpolated in the layout/page.xhtml file. We've already seen this usage a number of times. The second piece just sets a text value to "Hello World" and this will be interpolated in the view/index.xhtml file (as will all of the rest of the pieces). In the next part we set two things a count equal to five and another string to "Goodbye World". We'll use these to print out "Goodbye World" five times in the output. Next we set an array title and an array with three elements which we'll use to generate a list in the output. Finally, we have a boolean, bool, set to true which will be used to decide which text to display.

Here's the view/index.xhtml file:


<!-- Put out the text in text_1 -->
#{@text_1} <br/> <br/>

<!-- Use count to put out text_2 count times. -->
<?r 1.upto(@count) do ?>
#{@text_2} <br/>
<?r end ?>
<br/> <br/>


<!-- Put out the array title and then each element in the array. -->
#{@arr_title} <br/>
<ul>
<?r @arr.each do |a| ?>
<li>#{a}</li>
<?r end ?>
</ul>

<!-- If the bool is true then print that out otherwise print out that it was false. -->
<?r if (@bool == true) then ?>
bool was true!
<?r else ?>
bool was false!
<?r end ?>



This is pretty straight forward and mirrors the start.rb from earlier. We first put out the text in text_1. Next we use count to put out text_2 five times and then we put the arr_title and then the arr as a list. Finally, we check bool and print out whether it is true or false.

Here's the layout/page.xhtml:


<!-- view/page.xhtml -->

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<!-- Use the page.css in the public directory and set title based on
what's set in the associated method.
-->

<link rel="stylesheet" type="text/css" href="/page.css"/>
<title>#@title</title>
</head>
<div id="header">
<h1>SteamCode</h1>
</div>
<body>
<!-- Display the actual content. This will come from the method or the
associated view/*.xhtml file
-->

#@content

<!-- Set the footer in the center of the screen. -->
<div id="footer" style="text-align: center;">
<h5> Powered by Ramaze </h5>
</div>
</body>
</html>


You can grab the public/page.css from an earlier post.

I'd definitely recommend reading the emails that started this with Michael's observations and also the actual code (both linked to above).

As always, if you have questions or comments, let me know.

Monday, June 8, 2009

Using the js method and the XHTML Helper

In my last post on Ajax, I had some JavaScript code in the layout, that really shouldn't have been there. After I started looking at it, I came up with a solution that would have worked, but after discussing it on the Ramaze list, I was pointed towards a nicer solution. This involves using the XHTML helper. There's not a lot of documentation around it, but you can find the source code here. I'm just going to show the use of the js method, but the usage of the css is simiar and there are also js_for and css_for that take arrays (I haven't tested these, so you might not want to take my word for it).

Here's our start.rb

# start.rb
#
# This is the main program for the example. It loads the controller and model,
# and then starts up Ramaze.
#
require 'rubygems'
require 'ramaze'

class MainController < Ramaze::Controller
# Use page.xhtml in the layout directory for layout
layout :page

# Add the xhtml helper so that we can use the js() method in our views.
helper(:xhtml)

# You can access it now with http://localhost:7000/
def index
# We'll set the page_javascript to be public/js/index.js
@page_javascript = "index"
@title = "Using the js method"
end

# You can access it now with http://localhost:7000/page1.
def page1
# We'll set the page_javascript to be public/js/page1.js
@page_javascript = "page1"
@title = "Using the js method - Page 1"
end
end

Ramaze.start



There's mostly just our normal things in there, setting the layout and a couple of methods. The one unusual, actually just new, is the helper(:xhtml). What this will do is allow us to use the js method in our views to create a JavaScript simply. The two methods, index and page1 really don't do anything except set the @title and the @page_javascript variables. These will get picked up and used in our page.xhtml layout. Here's the code for that:


<!-- view/index.xhtml -->

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<!-- Our jQuery is in the public/js directory -->
<script src="js/jquery-1.3.2.js" type="text/javascript"></script>

<!-- Use the js method from the xhtml helper to add the javascript for this page -->
#{ js @page_javascript }

<title>#@title</title>
</head>
<body>
#@content
</body>
</html>


Once again, this is quite simple. We do have a script tag to include the jQuery library. The next line is where we use the js method that we discussed earlier. What will be produced for this line from index is <script src="/js/index.js" type="text/javascript"></script>. It will be similar for the page1 line.

Our two views are also very simple. The only thing in them that we're really going to use is the <div id="page_container"></div> page_container div. Here is view/index.xhtml:


<!-- view/index.xhtml -->
<h2>Hello World</h2>
<div id="page_container"></div>


and view/page1.xhtml:


<h2>Page 1</h2>
<div id="page_container"></div>


Finally, we have our two JavaScript files. Once again, very little. All we're going to do is grab the page_container div and set it's html value. This isn't a very good use of JavaScript in general and jQuery specifically, but it should at least work.

Here's public/js/index.js:


// public/js/index.js

// For jQuery put everything inside of the document ready code.
$(document).ready(function() {
// Add markup to page_container
$("#page_container").html("Index from Javascript");
}
);


and public/js/page1.js:


// public/js/page1.js

// For jQuery put everything inside of the document ready code.
$(document).ready(function() {
// Add markup to page_container
$("#page_container").html("Page 1 from Javascript");
}
);

This really is pretty simple, but I did want to show how the js method could be used. As I noted above, there's also a similar command for css as well as the js_for and css_for noted above. Sorry about the lack of formatting on the JavaScript.

Let me know if you have any questions or comments.

Sunday, May 31, 2009

Ramaze, jQuery, and Ajax

This post tries to show how to use Ajax and jQuery with Ramaze. Since, as usual, I didn't really know anything about JavaScript, jQuery, or Ajax, this was quite a learning experience. Right up front, I need to thank the Ramaze email list is general and Gavin in particular for patiently answering my questions. The exchange that started this is documented here.

Also, this jQuery tutorial, provided me a convenient jumping off point and much of the code for the jQuery is taken directly from here or is based on this code and slightly modified.

What we're going to do in this example is some very simple Ajax which put up five numbers, let's you select one, and then prints the number you selected below along with the time you selected it at. You probably wouldn't normally do this with Ajax, but it does show how to use it.

Here's our start.rb file:


# start.rb

require 'rubygems'
require 'ramaze'

# You can access it now with http://localhost:7000/
class MainController < Ramaze::Controller

# Layout using page but not if it comes from an Ajax request.
layout(:page){ !request.xhr? }

def index
@title = "Ramaze / Ajax Example"
end

# This should only get called from an ajax request.
def choose
if request.xhr? # came from ajax request
# Create a JSON "object" of the choice that can in through the post
# request and the current time. For the time, we need to enclose it
# in double quotes so that it will be interpreted as a string.
"{ choice: #{request['choice']}, time: \"#{Time.now}\" }"
else
request['choice'].to_s
end
end
end

Ramaze.start



It's pretty straightforward but does have a couple of new items in it. First there's this:

layout(:page){ !request.xhr? }

This tells Ramaze to use page for layout accept in the case of an Ajax call. When you make an Ajax call, you want just the data returned (in our case some JSON) and not the entire formatted page. This took the help of the Ramaze email group (thanks Gavin!) to figure out. If you're searching you may find deny_layout. This has been removed in the latest Ramaze, so avoid it.

Our index method is very straightforward so we'll move on to the choice method. Here, we simply take in the data from the post in this case something that will look like {choice: 2} and create another JSON object that also contains the time from a Time.now call. Here, we need to enclose the time in double quotes, so that it will be interpreted as a string. Without the quotes, there is no way to interpret it correctly.

Our index.xhtml file is very simple


<!-- view/index.xhtml -->

<h2>#@title</h2>

<p>This example demonstrates the use of Ramaze and Ajax along with some JQuery and JSON.</p>

<!-- An empty container where we'll put the choices and the return values
values from the ajax/post call.
-->

<div id="ajaxContainer"></div>


The only interesting piece is the empty div element. We're going to put our ajax code into it via page.xhtml.

Finally, here's the page.xhtml


<!-- view/page.xhtml -->

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>#@title</title>
<link rel="stylesheet" type="text/css" href="/page.css"/>

<!-- Our jQuery is in the public/js directory -->
<script src="js/jquery-1.3.2.js" type="text/javascript"></script>

<script type="text/javascript">

// For jQuery put everything inside of the document ready code.
$(document).ready(function() {

// Create an array of text and links. We'll add this to the
// ajaxContainer using join()
var choiceMarkup = ["Please choose a number: "];
for(var i=1; i <= 5; i++) {
choiceMarkup[choiceMarkup.length] = "<a href='#'>" + i + "</a> ";
}
choiceMarkup[choiceMarkup.length] = (" Your choice will be printed below.");

// Grab the ajaxContainer so we can add the choiceMarkup to it.
var container = $("#ajaxContainer");

// Add markup to container
container.html(choiceMarkup.join(''));

// Add click handlers. Here we're going to get all of the anchor elements <a>
// and add the function to them.
container.find("a").click(function(e) {

// Don
't do the normal thing you'd do when clicking a link.
e.preventDefault();

// Send a post request to the choose method when a link is
// clicked. Pass in the JSON "choice: item" to the function.
// The callback routine gets a resultObject(JSON) and a status
// (not used and not actually returned). The choose() method
// will return JSON, the fourth parameter, to post.
$.post(
"/choose",
{choice: $(this).html()},
function(resultObject, resultStatus) {

// Take the resultObject and grab the choice and time from it and format it.
var result = [
"<br>Thanks for choosing. You chose: ", resultObject.choice, " at ", resultObject.time
];
// Add the result to the ajaxContainer at the bottom. This will create a new
// item each time one of the numbers is clicked.
$("#ajaxContainer").append(result.join(
''));
},
'json' );
});
});
</script>
</head>
<body>
<div id="header">
<h1>SteamCode</h1>
</div>
#@content
<!-- Set the footer in the center of the screen. -->
<div id="footer" style="text-align: center;">
<h5> Powered by Ramaze </h5>
</div>
</body>
</html>



The first new thing we see here is the inclusion of the jQuery library. I've put it into the public/js directory. If you use a different version other than 1.3.2 you will need to change this line. Next is our actual script. All jQuery code is put inside the document ready block. After that, we create some text, links for the five choices, and a bit more text and shove these into an array, choiceMarkup. Then we grab the ajaxContainer defined in our view/index.xhtml file and put the choiceMarkup into it.

Next up, we find all of the anchor, a, links and add a function to them when they're clicked. The first thing we do in this function is disable the normal response to a click and then add our own code. Here we do a post function, calling the choose method in our controller and pass JSON that looks like {choice: 3} (we talked about this above). When the function returns, we have our data in the result object and as also discussed, this contains the choice and the time. We format this and then add it to our ajaxContainer. This will add a new line each time something is clicked and continue until we do a refresh. The final parameter to the post is the 'json' which tells jQuery that the data returned will be (obviously) JSON.

Everything else we've seen before and I won't go over it again.

There's quite a few new things to see in here and there aren't too many examples on the web showing the use of Ajax, jQuery, and Ramaze. Here's a post, but I'm not sure that it will work with the new Ramaze. It may take some modifications to get it working. Here's a nice jQuery link that helped a bit. Finally, for JSON, try here. JSON is not complex, but you do have to follow the rules (I didn't originally and it took a while and some help to figure out).

Let me know of any questions or comments.