Automatically Saving Drafts Using Periodically_Call_Remote

on Fri Dec 05 15:05:37 GMT 2008 in Ruby on Rails and viewed 10533 times

If you write for a large article-driven web site, you will invariably accidentally lose a whole post. You then have a two basic options: you can write in a word processor software, or you could build your own system to automatically save drafts when writing. That’s what you’ll do here, using periodically_call_remote().


Imagine that you work for a large article site just like this. (Well okay, this isn’t very large) Either way, you will at some point find yourself, well, screwed in the fact that you were working on an article, no matter how large, and you’ll somehow delete it. Now, after that you’ll probably write your articles in Word or OpenOffice for a while (I did), but I’m here to offer you a much easier offer in the form of Ruby on Rails and my first Rails tutorial. What you’re going to do is take a simple scaffold generated application and transform it into an article writing machine that will automatically save your drafts of articles at an interval set by you.

You can see a demo of the whole application here.

The Idea

The idea is that you’re going to just do the basics with article addition. You can definately add more, but for now, keep it simple. There’s going to be a simple, scaffold-generated application, and you’re going to augment it with the call periodically_call_remote. Each article will automatically have its own draft. At least, each new article until it’s actually added. Once the article or draft is added, then the draft should be deleted. So to make each article have its own draft, you’re going to use the relations between tables.

The Bare Bones Application

What you first need is some tables in your application. Assuming that your rails app is already set up for database interaction (there are numerous tutorials on this), the next thing you need is the schema. So here is your migration.


class Draft < ActiveRecord::Migration
  def self.up
    create_table :posts, :force => true do |p|
      p.column :title, :string
      p.column :body, :text
      p.column :created_on, :date
    end

    create_table :drafts, :force => true do |d|
      d.column :title, :string
      d.column :body, :text
      d.column :created_on, :datetime
      d.column :post_id, :integer
    end
  end

  def self.down
    drop_table :posts
    drop_table :drafts
  end
end

Then run rake migrate to generate your tables.

Then you just need the very basics of the application, so run these two commands to generate the models and controller.


ruby script/generate scaffold Post Articles
ruby script/generate model Draft

Next you need to relate the two models. Edit draft.rb to say:


class Draft < ActiveRecord::Base
belongs_to :post
end

and then post.rb to say:


class Post < ActiveRecord::Base
has_one :draft
end

With only one extra line of code, the two tables are related. It’s very easy to understand it. Each post has_one draft, and the draft belongs_to a post.

And the ruby script/generate scaffold will generate all your views, the models for Post and Draft, and the controller Articles.

Starting To Build on It

The very first thing you need to do is add the javascript includes to your articles.rhtml in the app/views/layouts directory. It looks like this:

<%= javascript_include_tag :defaults %>

It’ll automatically include all the javascript libraries for scriptaculous, and prototype, and all that good stuff.

Before you modify the views, you need to create a draft to use for your article. Look in the new method in articles.rb in app/controllers. Add one line at the end of the method.


@draft = Draft.create

@draft = Draft.create will make a new Draft object and then save it to the database. And that makes the basic draft you’ll need for saving.

Now look in your _form.rhtml file in app/views/articles. It’ll say something like:


<!--[form:post]-->
<p><label for="post_title">Title</label><br/>
<%= text_field 'post', 'title'  %></p>

<p><label for="post_body">Body</label><br/>
<%= text_area 'post', 'body'  %></p>
<!--[eoform:post]-->

(I deleted the created on field just because you don’t really need that, it’s automatic.)

What you need to do is find a way to, every 30 or so seconds, make a call with the data from the two areas there to another action and save a draft. So add a call to periodically_call_remote somewhere in that file.


<%= periodically_call_remote( :update => 'results',
    :url => { :action => 'save_draft', :id => @draft.id },
    :complete => visual_effect(:highlight, 'results'), 
    :frequency => 30,
    :with => "'title=' + escape($F('post_title')) + '&body=' + escape($F('post_body'))") %>

There are 5 options specified here. :Update refers to the id of the element that will have the output of the action. So add a simple <div id="results"></div> element above or below the form. :Url refers to the url you’re going to make the remote call to. In this case it will be in the articles controller, the save_draft action, and with the id of the draft you just created. :Complete is just a simple visual effect to do when the action is completed. In this case, it’ll highlight the results <div> when the action is completed. :Frequency is the number of seconds to wait between calls. And then :with is the biggy here. This will add any other variables basically. Execute the call with these variables. In this case, it’s going to be title and body. The idea is to have it all surrounded in double quotes with single quotes around the variable calls. Then you concatenate all the strings together with escape($F(Field_id)). Escape just takes out any potentially dangerous characters. $F() takes the argument of an id of a form element to get the data from. So $F(‘post_title’) gets the information from the post_title field and feeds it into the escape function, which is then concatenated onto the :with string.

Not so bad, eh? Now you need the actual method to save the draft. So add this method into the controller.


 def save_draft
    @title = params[:title]
    @body = params[:body]
    @draft = Draft.find(params[:id])
      if @draft.update_attributes(:title => @title, :body => @body)
        render :text => 'Draft saved'
      else
        render :text => 'Draft was not saved; there was a problem'
      end
  end

It’s a pretty easy method. @title and @body both get their data from the parameters you passed in with periodically_call_remote. @draft is the database information found from the id parameter passed in with the call. Then it’s just a matter of seeing if you can update the draft’s attributes and displaying a message. The message will be displayed in the <div id="results"></div> you added in the form somewhere. It was specified in the :update option of periodically_call_remote.

Well, look what you’ve gotten so far! Test it out, try to add a new article, and be rewarded with that nice new “Draft saved!” highlighted message. Has to be one of the most rewarding things ever.

Finishing the Post Off

Instead of you leaving your post half-finished, say that you actually finished it in one sitting. Congratulations on that, I can barely do that. But after you’ve added the post, you still have this draft sitting in limbo cluttering your database (and soon your list page). But the solution is very simple.

Open your new.rhtml file in app/views/articles. It should have an <h1> element saying that there’s a new post, then a start form tag, a partial, a submit button, and finally and end of form. And then a link to the list action. You just need to add one thing to the start_form_tag.


<%= start_form_tag :action => 'create', :id => @draft.id %>

The :id => @draft.id basically takes the id of the @draft object created when the Draft was quickly created and saved in the controller, and adds it to the form to be taken into account for deletion when adding an article all in the same go.

Now Posts are saved to the database in the create method of the Articles.rb controller. So look for that method.


def create
    @post = Post.new(params[:post])
    if @post.save and Draft.find(params[:id]).destroy
      flash[:notice] = 'Post was successfully created.'
      redirect_to :action => 'list'
    else
      render :action => 'new'
    end
end

The code is all the same as the scaffold generated code, except for the statement after the and in the if statement. Draft.find(params[:id]).destroy deletes the Draft found from the id passed to it from the form on the “new” action with the form.

Listing The Drafts

Well, now that you have created a draft or two, you need to be able to see them and edit them to post them eventually. Luckily, this is actually pretty easy.

Go into your list.rhtml file in app/views/articles. There’s going to be a bit of code about the display of all the current articles, but we need to add some code to display the current drafts. So add this:


<h2>Drafts</h2>

<table>
<% for draft in @drafts %>
    <tr>
    <td><%=h draft.title %></td>
    <td><%=h(truncate draft.body,255)%></td>
    <td><%=h draft.created_on %></td>
    <td><%= link_to "Keep Editing", :action => 'edit_draft', :id => draft.id %></td>
    <td>
    <%= link_to "Delete Draft", { :action => 'delete_draft', :id => draft.id }, 
       :confirm => 'Are you sure?' %>
    </td>
    </tr>
<% end %>
</table>

This is some prety basic code. It adds a little header announcing the drafts table, and a table for all the incomplete articles you might write. There’s a loop to display the title of the draft, 255 character of the draft (which is what truncate does), the time and date it was created, and the links to keep editing the draft and to delete it. Now as for the @drafts array you need for all the drafts, add this line to the list method in articles.rb.


@drafts = Draft.find(:all, :order => 'id DESC')

That pretty self-explanatory. Look for all the drafts and order by the latest created. Easy. Before you move on, let’s just add the delete method. Add this method somewhere in articles.rb.


  def delete_draft
    if Draft.find(params[:id]).destroy
      redirect_to :action => 'list'
      flash[:notice] = "Draft Deleted" 
   else
      redirect_to :action => 'list'
      flash[:notice] = "There was a problem." 
   end
  end

The link you added back in the list.rhtml file to delete a draft now takes place here. It’s a pretty simple method. If the action can find and delete the Draft with the id supplied by the link, then it’ll redirect to the list action saying the draft was deleted. Otherwise, it’ll do the same but tell you that there was a problem. But now that’s done!

Editing Existing Drafts And Making Them Posts

Now that you have an existing draft, and you can find that draft, you need to be able to come back to the Draft and edit it so that you can finally publish it. But that won’t be too hard with the quick _form.rhtml partial the scaffolding provides for you. So all you need now is a method in the controller and the mostly easy view.

Go back into the controller (articles.rb), and add this simple action:


  def edit_draft
    @post = Draft.find(params[:id])
  end

It’s a pretty simple action. It just fetches the Draft (from the id in the url) into the @post variable. Why @post? Well, that lies in the _form.rhtml. Instead of creating another custom form just to have the object in a @draft variable, why not just leave it the same so that all the information will be instantly put into the forms. But before I talk about that, let’s look at edit_draft.rhtml


<h2>Edit a Draft</h2>

<%= start_form_tag :action => 'add_draft', :id => params[:id] %>
  <%= render :partial => 'form' %>
  <%= submit_tag "Post" %>
<%= end_form_tag %>

<%= link_to 'Back', :action => 'list' %>

Pretty easy, eh? It’s about the same as any of the other scaffold generated methods like this. It has a header, a start form, the partial, the submit tag, and the end of the form. For the start form, it’s pretty easy: just a direction towards the add_draft action with the id in the url of the current draft. And, as I said