Hacking In Article Voting in Mephisto

Posted about 1 year back at RailsJitsu

In this article I am going to give you a primer on adding AJAX, custom routes and controllers to Mephisto to extend it’s functionality. For this example we will add digg/dzone type voting to articles, which means we will be hacking the Content model and forcing the CachedPage to expire.

To start off we need to add a custom controller to our Mephisto code to handle the AJAX based voting. We also need to create a model for the votes and a migration to hold the vote data.

1
2
3
./script/generate controller votes vote
./script/generate model ContentVote
./script/generate migration add_voting_record

We will start with our migration file by adding our table as well as the code for reverse migration. We also need columns to track the content_id, ip_address of the voter, and whether it was an up or down vote, which we handle with a boolean value.

1
2
3
4
5
6
7
8
9
10
11
12
13
class AddVotingRecord < ActiveRecord::Migration
  def self.up
    create_table :content_votes, :force => true do |t|
      t.integer         :content_id
      t.string          :ip_address
      t.boolean         :up
    end
  end

  def self.down
    drop_table :content_votes
  end
end

Next we to add the code to handle the vote record.

The code works like this; we add a one to many relationship to our ContentVote model, as every Content object can have many ContentVotes. The vote method takes a direction (up, or down) and an ip_address to prevent double voting. If the user has already voted we just short circuit. If not then we create a vote record, then set the boolean based direction and ip_address, then save our new vote record.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Content < ActiveRecord::Base
  filtered_column :body, :excerpt
  belongs_to :user, :with_deleted => true
  belongs_to :site
  [:year, :month, :day].each { |m| delegate m, :to => :published_at }
  
  # Content Voting
  has_many :content_votes
  def vote( direction, ip_address )
    return if self.content_votes.collect(&:ip_address).include?(ip_address)
    vote = self.content_votes.new
    vote.up = (direction == 'up') ? true : false
    vote.ip_address = ip_address
    vote.save
  end
  
end

Now we add the relationship to our new ContentVote model.

1
2
3
class ContentVote < ActiveRecord::Base
  belongs_to :content
end

On to our the controller that will be handling the AJAX call. We need to know what article the vote is for, and then call our vote method on the Content model. Then we need to expire the article page so that other people will see the new vote. The expiry part gave me some trouble, so let me elaborate how the expire_cached_pages method works.

The first parameter of the expire_cached_pages method is the controller making the request, this is our controller so we reference self. Next we pass a message to be logged, then we find all pages referenced by the article (as an article can live in multiple places simultaneously).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class VotesController < ApplicationController
  session :off

  def vote
    @article = Content.find params[:id]
    @article.vote( params[:direction], request.remote_ip )
  
    #expire article page
    site.expire_cached_pages( 
      self, 
      "Expired pages referenced by #{@article.class} ##{@article.id}", 
      site.cached_pages.find_by_reference(@article)
    )
  end
  
end

In order for us to be able to call a custom controller in Mephisto we need to add a route. In a plugin this is handled by Mephisto::Plugin.add_route, but we aren’t making a plugin just yet, so we have to patch it into the /lib/mephisto/routing.rb file, inside the self.connect_with(map) method which handles the Mephisto routing scheme. Also, since I am a big fan of resources we will do it that way, which also means we will add a :member action to delegate the call to and designate the request a :post.

Note the position of our added route, if you put it in the wrong place it won’t work. I like to put my custom routes just below the Plugin custom routes code.

1
2
3
4
5
6
# Custom voting route
map.resources :votes, :member => { :vote => :post }

# Original Mephisto code below here
map.dispatch '*path', :controller => 'mephisto', :action => 'dispatch'
map.home '', :controller => 'mephisto', :action => 'dispatch'

Of course, if we actually want our Liquid template to have access to the value of the votes we need to add it to the drop. In your /app/drops/article_drop.rb file you need to add this just above the protected methods:

1
2
3
4
5
6
7
8
  # VOTING
  def up_vote_count
    @up_vote_count ||= liquify(*@source.content_votes.inject(0) {|sum,vote| sum + ((vote.up) ? 1 : 0) })
  end
  
  def down_vote_count
    @down_vote_count ||= liquify(*@source.content_votes.inject(0) {|sum,vote| sum + ((!vote.up) ? 1 : 0) })
  end

We are almost done! The last thing we need to do is implement this AJAX call in our Liquid template, and the rjs code to change the vote numbers. This part will vary based on what theme you are using. I will give my code and you will have to figure out where in your theme to put it.

Also, since these themes use Liquid, we cannot call our rails helpers, so what I did was create a temp rails app, insert the link_to_remote call, render it in the browser and then copy out the produced AJAX code.

1
2
3
4
<span id="votes_up">{{ article.up_vote_count }}</span>
<a onclick="new Ajax.Request('/votes/{{article.id}}/vote?direction=up', {asynchronous:true, evalScripts:true}); return false;" href="#"><img src="/images/vote_up.gif"></a><br/>
<span id="votes_down">{{ article.down_vote_count }}</span>
 <a onclick="new Ajax.Request('/votes/{{article.id}}/vote?direction=down', {asynchronous:true, evalScripts:true}); return false;" href="#"><img src="/images/vote_down.gif"></a>

And our vote.rjs file (placed into the /app/views/votes/vote.rjs):

1
2
page.replace_html 'votes_up', @article.content_votes.inject(0) {|sum,vote| vote.up ? sum+1 : sum}
page.replace_html 'votes_down', @article.content_votes.inject(0) {|sum,vote| !vote.up ? sum+1 : sum}

And that is it! Run the migration, copy the up and down images to your theme folder, restart your mongrel processes, and you have content voting in Mephisto (this can be applied to comments as well with only changes to the _comment.liquid as comments and articles are the same base model)! I hope you enjoyed the ride, if you have trouble post in the comments and I will try and help.

A final note on customizing Mephisto in this way; if you do an svn up your code will get blasted into oblivion. So what to do? The way I handle this is to use a local Git repository. Git is amazing at doing code merges.

So I have 2 folders, one where the Mephisto trunk lives, and stays the way the core team wants it, and then a blog folder where my customizations live. When the core team updates trunk I do an svn up there, and then a Git merge to my blog directory.

So far this has never made me make a hand-merge, and it has never eaten my customizations.

Hacking In Article Voting in Mephisto

Posted about 1 year back at RailsJitsu

In this article I am going to give you a primer on adding AJAX, custom routes and controllers to Mephisto to extend it’s functionality. For this example we will add digg/dzone type voting to articles, which means we will be hacking the Content model and forcing the CachedPage to expire.

In this article I am going to give you a primer on adding AJAX, custom routes and controllers to Mephisto to extend it’s functionality. For this example we will add digg/dzone type voting to articles, which means we will be hacking the Content model and forcing the CachedPage to expire.

To start off we need to add a custom controller to our Mephisto code to handle the AJAX based voting. We also need to create a model for the votes and a migration to hold the vote data.

1
2
3
./script/generate controller votes vote
./script/generate model ContentVote
./script/generate migration add_voting_record

We will start with our migration file by adding our table as well as the code for reverse migration. We also need columns to track the content_id, ip_address of the voter, and whether it was an up or down vote, which we handle with a boolean value.

1
2
3
4
5
6
7
8
9
10
11
12
13
class AddVotingRecord < ActiveRecord::Migration
  def self.up
    create_table :content_votes, :force => true do |t|
      t.integer         :content_id
      t.string          :ip_address
      t.boolean         :up
    end
  end

  def self.down
    drop_table :content_votes
  end
end

Next we to add the code to handle the vote record.

The code works like this; we add a one to many relationship to our ContentVote model, as every Content object can have many ContentVotes. The vote method takes a direction (up, or down) and an ip_address to prevent double voting. If the user has already voted we just short circuit. If not then we create a vote record, then set the boolean based direction and ip_address, then save our new vote record.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Content < ActiveRecord::Base
  filtered_column :body, :excerpt
  belongs_to :user, :with_deleted => true
  belongs_to :site
  [:year, :month, :day].each { |m| delegate m, :to => :published_at }
  
  # Content Voting
  has_many :content_votes
  def vote( direction, ip_address )
    return if self.content_votes.collect(&:ip_address).include?(ip_address)
    vote = self.content_votes.new
    vote.up = (direction == 'up') ? true : false
    vote.ip_address = ip_address
    vote.save
  end
  
end

Now we add the relationship to our new ContentVote model.

1
2
3
class ContentVote < ActiveRecord::Base
  belongs_to :content
end

On to our the controller that will be handling the AJAX call. We need to know what article the vote is for, and then call our vote method on the Content model. Then we need to expire the article page so that other people will see the new vote. The expiry part gave me some trouble, so let me elaborate how the expire_cached_pages method works.

The first parameter of the expire_cached_pages method is the controller making the request, this is our controller so we reference self. Next we pass a message to be logged, then we find all pages referenced by the article (as an article can live in multiple places simultaneously).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class VotesController < ApplicationController
  session :off

  def vote
    @article = Content.find params[:id]
    @article.vote( params[:direction], request.remote_ip )
  
    #expire article page
    site.expire_cached_pages( 
      self, 
      "Expired pages referenced by #{@article.class} ##{@article.id}", 
      site.cached_pages.find_by_reference(@article)
    )
  end
  
end

In order for us to be able to call a custom controller in Mephisto we need to add a route. In a plugin this is handled by Mephisto::Plugin.add_route, but we aren’t making a plugin just yet, so we have to patch it into the /lib/mephisto/routing.rb file, inside the self.connect_with(map) method which handles the Mephisto routing scheme. Also, since I am a big fan of resources we will do it that way, which also means we will add a :member action to delegate the call to and designate the request a :post.

Note the position of our added route, if you put it in the wrong place it won’t work. I like to put my custom routes just below the Plugin custom routes code.

1
2
3
4
5
6
# Custom voting route
map.resources :votes, :member => { :vote => :post }

# Original Mephisto code below here
map.dispatch '*path', :controller => 'mephisto', :action => 'dispatch'
map.home '', :controller => 'mephisto', :action => 'dispatch'

Of course, if we actually want our Liquid template to have access to the value of the votes we need to add it to the drop. In your /app/drops/article_drop.rb file you need to add this just above the protected methods:

1
2
3
4
5
6
7
8
  # VOTING
  def up_vote_count
    @up_vote_count ||= liquify(*@source.content_votes.inject(0) {|sum,vote| sum + ((vote.up) ? 1 : 0) })
  end
  
  def down_vote_count
    @down_vote_count ||= liquify(*@source.content_votes.inject(0) {|sum,vote| sum + ((!vote.up) ? 1 : 0) })
  end

We are almost done! The last thing we need to do is implement this AJAX call in our Liquid template, and the rjs code to change the vote numbers. This part will vary based on what theme you are using. I will give my code and you will have to figure out where in your theme to put it.

Also, since these themes use Liquid, we cannot call our rails helpers, so what I did was create a temp rails app, insert the link_to_remote call, render it in the browser and then copy out the produced AJAX code.

1
2
3
4
<span id="votes_up">{{ article.up_vote_count }}</span>
<a onclick="new Ajax.Request('/votes/{{article.id}}/vote?direction=up', {asynchronous:true, evalScripts:true}); return false;" href="#"><img src="/images/vote_up.gif"></a><br/>
<span id="votes_down">{{ article.down_vote_count }}</span>
 <a onclick="new Ajax.Request('/votes/{{article.id}}/vote?direction=down', {asynchronous:true, evalScripts:true}); return false;" href="#"><img src="/images/vote_down.gif"></a>

And our vote.rjs file (placed into the /app/views/votes/vote.rjs):

1
2
page.replace_html 'votes_up', @article.content_votes.inject(0) {|sum,vote| vote.up ? sum+1 : sum}
page.replace_html 'votes_down', @article.content_votes.inject(0) {|sum,vote| !vote.up ? sum+1 : sum}

And that is it! Run the migration, copy the up and down images to your theme folder, restart your mongrel processes, and you have content voting in Mephisto (this can be applied to comments as well with only changes to the _comment.liquid as comments and articles are the same base model)! I hope you enjoyed the ride, if you have trouble post in the comments and I will try and help.

A final note on customizing Mephisto in this way; if you do an svn up your code will get blasted into oblivion. So what to do? The way I handle this is to use a local Git repository. Git is amazing at doing code merges.

So I have 2 folders, one where the Mephisto trunk lives, and stays the way the core team wants it, and then a blog folder where my customizations live. When the core team updates trunk I do an svn up there, and then a Git merge to my blog directory.

So far this has never made me make a hand-merge, and it has never eaten my customizations.

RSpec and Inline RJS

Posted about 1 year back at Jonathan.inspect

Rails give us the ability to write inline RJS via render :update syntax, as in :

render :update do |page|
  page['addressPreviewStatus'].update 'Address Not Found'
end

Previous code will update the content of tag with “addressPreviewStatus” id with ‘Address Not Found’.

But how can we spec that out ? I needed to search a little as there seems to be very little examples.

First in rails there is a special assertion assert_select_rjs, merged from the assert_select plugin, it let you test your RJS with a syntax similar to RJS itself. RSpecOnRails has a special matcher wrapping assert_select_rjs : has_rjs.

You can use it on response to specify what should be generated, for exemple you can :

# Specify response should contains an update or insert of some kind
response.should have_rjs

# Specify response should contains an update or insert for the tag with given id
response.should have_rjs('id')

# Specify response should contains a specific update, insert, etc. for the given tag
response.should have_rjs(:replace, 'id')

You get the point. Now, a nice syntax allows you to write RJS this way :

render :update do |page|
  page['id'].update('replacement text')
end

So my first try was to use :

response.should have_rjs(:update, 'id', 'replacement text')

but this fail miserably with an error Unknown RJS statement type update. I tried different syntax but none worked.

Finally browsing through source of assert_select_rjs I found what I was looking for. When using this syntax you should use one of :chained_replace or :chained_replace_html depending what you want to test :replace or :update.

Now here is the solution :

response.should have_rjs(:chained_replace_html, 'id', 'replacement text')

RSpec and Inline RJS

Posted about 1 year back at Jonathan.inspect

Rails give us the ability to write inline RJS via render :update syntax, as in :

render :update do |page|
  page['addressPreviewStatus'].update 'Address Not Found'
end

Previous code will update the content of tag with “addressPreviewStatus” id with ‘Address Not Found’.

But how can we spec that out ? I needed to search a little as there seems to be very little examples.

First in rails there is a special assertion assert_select_rjs, merged from the assert_select plugin, it let you test your RJS with a syntax similar to RJS itself. RSpecOnRails has a special matcher wrapping assert_select_rjs : has_rjs.

You can use it on response to specify what should be generated, for exemple you can :

# Specify response should contains an update or insert of some kind
response.should have_rjs

# Specify response should contains an update or insert for the tag with given id
response.should have_rjs('id')

# Specify response should contains a specific update, insert, etc. for the given tag
response.should have_rjs(:replace, 'id')

You get the point. Now, a nice syntax allows you to write RJS this way :

render :update do |page|
  page['id'].update('replacement text')
end

So my first try was to use :

response.should have_rjs(:update, 'id', 'replacement text')

but this fail miserably with an error Unknown RJS statement type update. I tried different syntax but none worked.

Finally browsing through source of assert_select_rjs I found what I was looking for. When using this syntax you should use one of :chained_replace or :chained_replace_html depending what you want to test :replace or :update.

Now here is the solution :

response.should have_rjs(:chained_replace_html, 'id', 'replacement text')

make_resourceful 0.2.0

Posted about 1 year back at Blog Posts : Nex3

After many months of development, make_resourceful version 0.2.0 is finally ready for release. We’ve now got a full set of specs (i.e. tests) and full RDoc documentation. The code is cleaner, bugs are fixed, the filesize is smaller1.

Oh, and there are new features. New, awesome features.

Installation

Before we go into the features, though, let’s talk about installing the thing.

There are actually three different ways to install it. See, we didn’t just release 0.2.0 today; we released 0.2.2 as well2.

What’s the difference? 0.2.0 is Rails 1.2.3 compatible. 0.2.2 is not.

All this really means is that 0.2.2 uses the new-style URL helpers. Instead of child_path(@parent, @child), current_object will now call parent_child_path(@parent, @child). This is compatible with the Rails 2.0 API as well.

Now then. To install version 0.2.0, do

./script/plugin install http://svn.hamptoncatlin.com/make_resourceful/tags/rel_0-2-0
mv vendor/plugin/rel_0-2-0 vendor/plugin/make_resourceful

For version 0.2.0, do

./script/plugin install http://svn.hamptoncatlin.com/make_resourceful/tags/make_resourceful

Finally, if you want to take a bit of a risk, you can install from trunk:

./script/plugin install http://svn.hamptoncatlin.com/make_resourceful/trunk
mv vendor/plugin/trunk vendor/plugin/make_resourceful

Note that, like version 0.2.2, trunk is incompatible with Rails 1.2.3 and lower versions.

New Features

I talked about publish and serialization earlier. That’s probably the most visible change, but there’s plenty of other stuff as well.

For instance, there are several handy new accessors. save_succeeded? and save_failed? return whether or not a database update, like that done by create, update, and destroy, completed successfully. plural_action? and singular_action? return whether the current action is plural, like index, or singular, like show.

There are also a bunch of new additions that aren’t glamorous, like new declarations and accessors, but are important and useful all the same. For example, in 0.2.0 all the default responses have an empty Javascript response. This means that RJS templates will work automatically without any modifications of the controller.

Another useful tweak: all the callback declarations (before, after, response_for) now allow you to easily declare the same callback for multiple events. All you have to do is pass in multiple arguments, like so:

before :show, :index do
  @page_title = "Check out my resourceful controller." 
end

Singular Resources

Thanks to an idea of Peter Baker’s, make_resourceful 0.2.0 includes support for singular resources. These are resources defined by a has_one relationship. For instance, if Person has_one Hat, you’d make a HatController rather than a HatsController.

make_resourceful would notice that the controller was singular3 because of the singular controller name (no extra code required!), and act accordingly. This means that it wouldn’t define an index action, and for current_object, it would look up Person.find(params[:person_id]).hat instead of Hat.find(params[:id]).

URL Helpers

One of the handy things that was around in m_r 0.1.0 was the URL helpers: object_path and objects_path. They automatically managed all the hassles with Rails URL generation. A single call to object_path might be equivalent to a call to child_path(@grandparent, @parent, @child).

Unfortunately, these two helpers didn’t really cover all the URLs that one might have wanted to generate. What if you wanted a URL for the new action? They also were both _path methods: they returned the path of the resource, without the domain name or protocol.

Thus, in 0.2.0, we’ve added a whole slew of other URL helpers. There’s new_object_path and edit_object_path, which return the paths to the new and edit actions, respectively. Then, for each _path helper, there’s now a _url helper, which returns the domain name as well. For instance, on this blog’s PostsController, new_object_url returns “http://nex-3.com/posts/new”.

resourceful_scaffold

make_resourceful 0.2.0 improves on more than just the API. It also comes bundled with a generator in the style of the built-in scaffold_resource generator. The syntax is even the same:

./script/generate resourceful_scaffold ResourceName attribute1:type1 attribute2:type2 ...

This generates the model, views, tests, and of course made_resourceful controller for the given resource. It works out of the box with a make_resourceful installation.

It should be noted that the views it generates are Haml, not ERB, to encourage people to make their views as beautiful as their controllers will be. Haml is awesome; if you haven’t checked it out, you should4.

Deprecations

There are a few things that we’ve decided to deprecate or outright remove in make_resourceful 0.2.0. Most of these are accessors that didn’t make sense to keep separate from the methods that used them. They’d never make sense for users to directly call. Rather than overriding them, we now reccommend that you override the method that called them.

Note that some of these were only introduced after 0.1.0 was released. However, lots of people have been using trunk anyway, so I’ll mention them anyway.

The first of these accessors we removed was current_param. This just returned params[:id] and was only called by current_object. Now if you want to change which parameter is used, just change current_object.

The same is true for model_includes. It returned an empty hash, and was supposed to be overridden to provide a value that would be passed to the :include option of current_model.find in current_objects. This was inconsistent with our general “override current_objects to customize it” policy, so we chucked it.

The last accessor we deprecated was namespace_prefix. This returned a prefix for the URL-generating methods called by object_path and friends. The accessor is actually still around, but it’s been given a more general name: url_helper_prefix. Override that instead.

Finally, we removed the associated_with declaration. We did this because

associated_with :current_user, :source => :user

could in fact be done in one line using only the pre-existing declarations:

before(:create) { current_object.user = current_user }

Contributors

According to my records, the following people contributed patches or ideas that went into 0.2.0:

  1. Cristi Balan5
  2. Tom Stuart
  3. Don Petersen6
  4. Alex Ross6
  5. James Golick
  6. Mike Ferrier
  7. Jeff Hardy
  8. Hampton Catlin
  9. Myself

The Future

So what’s on the plate for 0.3.0? Our first order of business will be to add in a patch by Jonathan Linowes that will get rid of support for deeply nested resources and add support for polymorphic nesting. See his writeup for more information.

We’ll also add a more integrated exception handling mechanism, and some sort of better way of dealing with stuff like flash messages. And then… who knows?

If you have some feature you particularly want, don’t hesitate to let us know.

1 This is pretty cool. A Subversion-less install of make_resourceful 0.2.0 is roughly 250 kilobytes. 0.1.0 was a about 1.1 megabytes. The difference is even more striking if you install from Subversion.

2 We skipped 0.2.1 because we reserve odd version numbers for development versions. That way we can tell whether someone’s running trunk or not from looking at their VERSION file.

3 You can notice, too, via the singular? and plural? helpers.

4 Full disclosure: Hampton Catlin and myself are both Haml developers. We’re totally biased. But we’re biased because Haml is awesome.

5 Cristi pretty much single-handedly tracked down an insanely annoying bug, as well as supplying various other patches. Super awesome.

6 I’d link to these folks’ websites if I knew what they were.

Display Validation Errors For Your Ajaxified Form

Posted about 1 year back at Simplistic Complexity - Home

One of the sweet things Rails provides is practically free error notification on forms. With just a little bit of

<%= error_messages_for :post %>

validations on your model, and some logic in your controller, bam, instant error messaging. Very simple.

But what if your form is using AJAX to post? The page doesn't re-render, so how is our buddy, error_messages_for, going to work? Well its not, we're gonna have to use AJAX to display the errors. It turns out this isn't too touch, but as usual we want to keep it DRY.

The Controller

class PostsController < ApplicationController
  def create
    @post = Post.new(params[:post])
    @post.save!
 
    # automatically renders create.rjs.erb
 
  rescue ActiveRecord::RecordInvalid
 
    @model = @kit_item
    render :template => 'shared/validation_error.rjs.erb'
 
  end
end

We will attempt to create the Post. If all goes well, then the create rjs action will fire and all is well. But if the record fails validation, we catch it, assign the current model to @model, and render shared/validaiton_error.rjs.erb. This is our generic AJAX action for rendering errors for Models.

The AJAX

Create shared/validation_error.rjs and use this little snippit, and thats pretty much it for this part.

page.replace_html "errors_for_#{@model.class.name.underscore}", "Oops! <ul>" + @model.errors.collect{|k,v| "<li>The #{k} #{v}</li>"}.to_s + "</ul>"
page.visual_effect :highlight, "errors_for_#{@model.class.name.underscore}", :startcolor => "'#ff0000'", :endcolor => "'#aca2b6'"

So, the errors just get collected and printed out to a div in the page. Let's go add this fancy div.

The View

This part is pretty simple. You just need to open up your form partial, posts/_form.html.erb and add this little view helper to the form:

<%= error_div_for post %>

This does nothing but prints out a div with a specific ID to prepare for our RJS action, if it's needed. So let's go make the view helper.

module ApplicationHelper
 
  def error_div_for(model)
      %{<div id="errors_for_#{model.class.name.underscore}"></div>}
  end
 
end

Now we can just call this simple view helper and pass in any model and it will dynamically build a div with the appropriate ID inside.

And thats pretty much it. To go through it again, when the form is loaded, the error div will be placed in the page, waiting to be used. Once the form is submitted in the background, the controller's create action will attempt to save it. If the record is invalid with validation errors, then the universal validation_errror RJS template will be fired off and replace all the errors from the failed validation, into the div we placed in the page. Oh and of course a little red flash action to make sure the user sees the errors.

Display Validation Errors For Your Ajaxified Form

Posted about 1 year back at Simplistic Complexity - Home

One of the sweet things Rails provides is practically free error notification on forms. With just a little bit of

<%= error_messages_for :post %>

validations on your model, and some logic in your controller, bam, instant error messaging. Very simple.

But what if your form is using AJAX to post? The page doesn't re-render, so how is our buddy, error_messages_for, going to work? Well its not, we're gonna have to use AJAX to display the errors. It turns out this isn't too touch, but as usual we want to keep it DRY.

The Controller

class PostsController < ApplicationController
  def create
    @post = Post.new(params[:post])
    @post.save!
 
    # automatically renders create.rjs.erb
 
  rescue ActiveRecord::RecordInvalid
 
    @model = @kit_item
    render :template => 'shared/validation_error.rjs.erb'
 
  end
end

We will attempt to create the Post. If all goes well, then the create rjs action will fire and all is well. But if the record fails validation, we catch it, assign the current model to @model, and render shared/validaiton_error.rjs.erb. This is our generic AJAX action for rendering errors for Models.

The AJAX

Create shared/validation_error.rjs and use this little snippit, and thats pretty much it for this part.

page.replace_html "errors_for_#{@model.class.name.underscore}", "Oops! <ul>" + @model.errors.collect{|k,v| "<li>The #{k} #{v}</li>"}.to_s + "</ul>"
page.visual_effect :highlight, "errors_for_#{@model.class.name.underscore}", :startcolor => "'#ff0000'", :endcolor => "'#aca2b6'"

So, the errors just get collected and printed out to a div in the page. Let's go add this fancy div.

The View

This part is pretty simple. You just need to open up your form partial, posts/_form.html.erb and add this little view helper to the form:

<%= error_div_for post %>

This does nothing but prints out a div with a specific ID to prepare for our RJS action, if it's needed. So let's go make the view helper.


module ApplicationHelper

def error_div_for(model)
%{<div id="errors_for_#{Inflector.underscore(model.class)}"></div>}
end

end

Now we can just call this simple view helper and pass in any model and it will dynamically build a div with the appropriate ID inside.

And thats pretty much it. To go through it again, when the form is loaded, the error div will be placed in the page, waiting to be used. Once the form is submitted in the background, the controller's create action will attempt to save it. If the record is invalid with validation errors, then the universal validation_errror RJS template will be fired off and replace all the errors from the failed validation, into the div we placed in the page. Oh and of course a little red flash action to make sure the user sees the errors.

Prototype Window Login Form with Ajax/RJS

Posted about 1 year back at for i in infinity

Here is a simple tutorial to demonstrate the use of prototype Window login form with RubyOnRails and Ajax. The idea behind this is to override Ok event of Dialog.confirm() form and send Ajax request to controller. The demo application makes use of RJS for Ajax callbacks. See Live Demo To ...

Scrivener, the best writing tool since sliced bread

Posted about 1 year back at Cody Fauser

Have you ever wanted to write something, but felt so bogged down by the tools that you gave up? Even worse is if you don't even have the option of giving up. When I was writing RJS Templates for Rails using the O'Reilly Word template there were many moments when I thought that I may end it all. Thankfully, that didn't happen.

I have a profound hatred for Microsoft Word. I feel like its entire codebase exists solely to make my writing experience miserable. Capitalizing everything I need lowercase, formatting my paragraphs in ways that I don't want, just messing with my document to ruin my weekend. I'm sure it is possible to make Word do what I want, but I just don't feel like it is worth the effort. I don't want to waste my time figuring out how to make Word work for me, when what I want to do is really simple. I just want to write text. Is this a lot to ask for?

Thankfully, Tobias Lütke let me know about Scrivener. What can I say? Scrivener is fantastic. Scrivener not only gets out of my way when I'm writing, it actually has features that help with the process of writing. Scrivener helps in all the areas where it counts when it comes to writing. Organization, taking notes, keeping lists of reference material, storing research documents right in the project. Scrivener feels like a combination of a simple text editor with the power of an organizational tool like Omni Outliner built right in. You can create an outline in minutes, reorganize by dragging sections around, look at high level views of a section in a cork board view, and then turn off the entire UI when you're ready to get down to writing by entering the full screen typewriter mode.

There are many, many more killer features that I won't get into here. Do yourself a favour and get the demo. Better yet, just buy it. It only costs $34.99, which is less than you're going to spend next week at Starbucks anyway. You can get up to speed with all the features in less than an hour with the included tutorials.

ActiveMerchant PeepCode Screenshot

Scrivener, the best writing tool since sliced bread

Posted about 1 year back at Cody Fauser

Have you ever wanted to write something, but felt so bogged down by the tools that you gave up? Even worse is if you don't even have the option of giving up. When I was writing RJS Templates for Rails using the O'Reilly Word template there were many moments when I thought that I may end it all. Thankfully, that didn't happen.

I have a profound hatred for Microsoft Word. I feel like its entire codebase exists solely to make my writing experience miserable. Capitalizing everything I need lowercase, formatting my paragraphs in ways that I don't want, just messing with my document to ruin my weekend. I'm sure it is possible to make Word do what I want, but I just don't feel like it is worth the effort. I don't want to waste my time figuring out how to make Word work for me, when what I want to do is really simple. I just want to write text. Is this a lot to ask for?

Thankfully, Tobias Lütke let me know about Scrivener. What can I say? Scrivener is fantastic. Scrivener not only gets out of my way when I'm writing, it actually has features that help with the process of writing. Scrivener helps in all the areas where it counts when it comes to writing. Organization, taking notes, keeping lists of reference material, storing research documents right in the project. Scrivener feels like a combination of a simple text editor with the power of an organizational tool like Omni Outliner built right in. You can create an outline in minutes, reorganize by dragging sections around, look at high level views of a section in a cork board view, and then turn off the entire UI when you're ready to get down to writing by entering the full screen typewriter mode.

There are many, many more killer features that I won't get into here. Do yourself a favour and get the demo. Better yet, just buy it. It only costs $34.99, which is less than you're going to spend next week at Starbucks anyway. You can get up to speed with all the features in less than an hour with the included tutorials.

ActiveMerchant PeepCode Screenshot

 


1 2 3 4 5 6 ... 8