zhubert

a programming blog

Batman.js and Rails Part 1, Server Side

Similar to my last few posts, I’d like to explore Batman.js through building a simple CRUD app. Specifically my requirements are:

  • Simple form CRUD
  • Routing of resources - nested, with nav, pagination
  • Associations - has_many, belongs_to
  • Rails idiom and integration - shouldn’t be an impedance mismatch
  • Data binding - views change automatically when data does
  • View Composition - weak, strong…just get it done
  • No Framework Poison Pills - that would be bugs and things that would jeopardize a production app
  • HAML/Coffeescript Friendly

This first post is going to be really similar to getting ready for Ember, but has a few differences now that I’ve carried the Batman implementation all the way through and some master branches have changed (life on the edge). Here we go…

Start a new rails app (Rails 3.2.3):

1
$ rails new batman-rails-demo

And add the following to the Gemfile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# AMS, no longer requires a scope, woot!
gem "active_model_serializers", :git => "git://github.com/josevalim/active_model_serializers.git"

# shipping 0.8.0 (latest is 0.9.0), so just getting this for generators
gem 'batman-rails', git: 'git://github.com/Shopify/batman-rails.git'

# for sanity
gem 'thin'
gem 'quiet_assets'
gem 'bootstrap-sass', '~> 2.0.1', group: [:assets]
gem 'haml-rails'
gem 'haml_assets'
gem 'ejs'

# for faking data for dev
gem 'ffaker', group: [:development, :test]

And rebundle…

1
$ bundle update

Now let’s make our single resource and a serializer (serializer now made automatically!):

1
$ rails g resource post title:string content:string

Oddly, the serializer references some ApplicationSerializer which doesn’t exist at the time of writing, so change that to:

app/serializers/post_serializer.rb
1
2
3
class PostSerializer < ActiveModel::Serializer
  attributes :title, :content, :id
end

And put the basics into our posts_controller:

app/controllers/posts_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class PostsController < ApplicationController
  respond_to :json

  def index
    respond_with Post.all
  end

  def show
    respond_with Post.find(params[:id])
  end

  # for validation, can't use responders (batman expects errors to not have a root)
  def create
    @post = Post.new(params[:post])
    respond_to do |format|
      if @post.save
        format.json { render :json => @post }
      else
        format.json { render :json => @post.errors, :status => :unprocessable_entity}
      end
    end
  end

  # for validation, can't use responders (batman expects errors to not have a root)
  def update
    @post = Post.find(params[:id])
    respond_to do |format|
      if @post.update_attributes(params[:post])
        format.json { render :json => @post }
      else
        format.json { render :json => @post.errors, :status => :unprocessable_entity}
      end
    end
  end

  # batman can handle Rails 3 respond for this action
  def destroy
    respond_with Post.destroy(params[:id])
  end
end

And get our database up to speed…

1
2
$ rake db:create
$ rake db:migrate

Let’s put some dummy data into seeds.rb:

db/seeds.rb
1
2
3
4
5
6
foo = 0
25.times do
  sleep 1 # so we can have unique timestamps
  Post.create(:title => "Post #{foo.to_i}", :content => "#{Faker::Lorem.paragraph(5)}")
  foo = foo + 1
end

Seeding will take 25 seconds…

1
$ rake db:seed

Running the server and calling the API we see some serialized posts. Woot!

1
2
3
$ rails server
$ curl localhost:3000/posts.json # from another window
{"posts":[{"title":"Post 0","content":"Dolore suscipit et et consectetur necessitatibus sapiente consequatur. Ut laboriosam animi ducimus veniam delectus labore. Earum minima non architecto consequuntur minus. Delectus omnis saepe illum et quas consequatur. Consequatur vitae provident cum eaque dolor tenetur. Qui consequatur delectus odio aliquam tenetur ut."}...

Some routing changes in config/routes.rb:

1
2
3
4
BatmanRailsDemo::Application.routes.draw do
  resources :posts
  root to: 'main#index'
end

Make a landing page action to host the app:

1
$ rails g controller main index

Remove public/index.html, empty out main/index.html and if you’re HAML crazy like most folks I know, change your application.html.haml like so (oooh, the hints of Batman creep in!):

app/views/layouts/application.html.haml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
!!!
%html
  %head
    %meta{:charset => "utf-8"}
      %title CRUD Demo
      = stylesheet_link_tag 'application'
      = javascript_include_tag "application"
      = csrf_meta_tags
  %body
    .navbar.navbar-fixed-top
      .navbar-inner
        .container
          %a.brand{:href => '/'}
            CRUD
          %ul.nav
            %li{:data => {"foreach-nav" => "window.BatmanRailsDemo.navLinks", "addclass-active" => "window.BatmanRailsDemo.currentRoute.controller | matches nav.controller"}}
              %a{:data => {"bind" => "nav.text", "bind-href" => "nav.href"}}
    #main.container
      .content
        %p#flashnotice.alert.alert-success{:data => {"showif" => "window.BatmanRailsDemo.flash.success.length"}}
          %a.close{:href => "#", :onclick => "$('#flashnotice').hide(); return false"} ×
          %span{:data => {"bind" => "window.BatmanRailsDemo.flash.success"}}
        %p#flashwarning.alert.alert-error{:data => {"showif" => "window.BatmanRailsDemo.flash.error.length"}}
          %a.close{:href => "#", :onclick => "$('#flashwarning').hide(); return false"} ×
          %span{:data => {"bind" => "window.BatmanRailsDemo.flash.error"}}
        %div{:data => {"yield" => "main"}}
        %footer
          %p
            &copy; zhubert #{Time.now.year}

So now Rails is ready to play nice with Batman. Time for the fun!

Posted by Zack Hubert batmanjs, rails

Tweet

« Ember.js and Rails Part 5, Naive Real-time Batman.js and Rails Part 2, Batman Install »

Comments

gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.