Rails: Web API shaokun.wu@gmail.com Thanks to Leon Du and Rain Chen
You should start with... • ruby 1.9.2 Performance, Threads/Fibers, Encoding/Unicode... • rails 3.1.0 Asset pipeline, HTTP streaming, jQuery
Rails on Campus https://github.com/kudelabs/roc-demo1 https://github.com/kudelabs/roc-demo2
GZRuby http://groups.google.com/group/gzruby?lnk=srg
talk about... • API • API • API • •
Start Simple but Elegant http://localhost:3000/api/messages http://localhost:3000/api/messages/{id}
• $ git clone git://github.com/kudelabs/roc-demo2.git • $ cd roc-demo2 • $ bundle install • $ rake db:create • $ rake db:migrate • $ rails server • goto http://localhost:3000
• $ rails generate controller api::messages apps/controllers/api/messages_controller.rb test/functional/api/messages_controller_test.rb
apps/controllers/api/messages_controller.rb class Api::MessagesController < ApplicationController # GET /api/messages.json def index @messages = Message.all respond_to do |format| format.json { render json: @messages } end end # GET /api/messages/1.json def show @message = Message.find(params[:id]) respond_to do |format| format.json { render json: @message } end end end
curl http://localhost:3000/api/messages
Start Alone but Test is Your Partner rake test:functionals rake test:units rake test:integration
test/functional/api/messages_controller_test.rb class Api::MessagesControllerTest < ActionController::TestCase setup do @message = messages(:one) end test "should get index" do get :index, format: :json assert_response :success assert_not_nil assigns(:messages) end test "should show message" do get :show, format: :json, id: @message.to_param assert_response :success end end
API
built-in Namespaced controllers http://localhost:3000/api/v2/messages Rocweibo::Application.routes.draw do namespace :api do resources :messages namespace :v2 do resources :messages end end end
test/functional/api/v2/messages_controller_test.rb class Api::V2::MessagesController < Api::ApplicationController before_filter :authenticate, :only => :create ... # curl -X POST -H "Accept: application/json" --user shaokun.wu@gmail.com:a # http://localhost:3000/api/v2/messages -d "message[body]=abcdefg" def create @message = Message.new(params[:message]) @message.user = @current_user @message.save! render json: @message end private def authenticate if user = authenticate_with_http_basic { |u, p| User.authenticate(u, p) } @current_user = user else request_http_basic_authentication end end end
Keep Refactoring whenever You could Don’t Repeat Yourself & Decoupling
class Api::V2::MessagesController < Api::ApplicationController ... private def authenticate if user = authenticate_with_http_basic { |u, p| User.authenticate(u, p) } @current_user = user else request_http_basic_authentication end end end class Api::ApplicationController < ActionController::Base private def authenticate if user = authenticate_with_http_basic { |u, p| User.authenticate(u, p) } @current_user = user else request_http_basic_authentication end end end
/app/controllers/api/application_controller.rb class Api::ApplicationController < ActionController::Base end /app/controllers/application_controller.rb class ApplicationController < ActionController::Base helper_method :current_user, :logged_in? protect_from_forgery before_filter :require_logged_in ... end
Grape A opinionated micro-framework for creating REST-like APIs in Ruby.
class Rocweibo::V1::API < Grape::API prefix 'api' version 'v1' resource :messages do get do Message.limit(20) end get ':id' do Message.find(params[:id]) end end end Rocweibo::Application.routes.draw do mount Rocweibo::V1::API => "/" # mount API routes ... end
Where to HOST your app?
No Easy Way to Host in China :( • Amazon EC2 • Linode • DotCloud • Heroku
DotCloud $ dotcloud push {myapp} {myrepopath}/
• $ dotcloud create rocdemo • $ touch dotcloud.yml • $ dotcloud push rocdemo . • or $ dotcloud push -b mybranch rocdemo . Deployment finished. Your application is available at the following URLs www: http://rocdemo-limiru2n.dotcloud.com/
add mysql service # just update the content of your dotcloud.yml www: type: ruby $ dotcloud info rocdemo data: type: mysql data: config: mysql_password: ... instances: 1 type: mysql # also, update the content of your database.yml www: production: config: adapter: mysql2 rack-env: production encoding: utf8 ruby-version: 1.9.2 reconnect: false instances: 1 database: roc_demo2_production type: ruby pool: 5 url: http://rocdemo-limiru2n.dotclo username: root password: ... port: 14895 host: rocdemo-LIMIRU2N.dotcloud.com
• dotcloud logs rocdemo.www • dotcloud ssh rocdemo.www • dotcloud info rocdemo
Rails Admin
• Display database tables • Create new data • Easily update data • Safely delete data • Automatic form validation • Search and filtering • Export data to CSV/JSON/XML • Authentication (via Devise) • User action history
• add the line below to your Gemfile gem 'rails_admin', :git => 'git://github.com/sferik/rails_admin.git' • $ bundle install • $ rails g rails_admin:install
Cache & Daemon
def index @messages = Rails.cache.fetch('all_messages', :expires_in => 30.seconds) do Message.all end respond_to do |format| format.json { render json: @messages } end end
Started GET "/api/v2/messages.json" for 127.0.0.1 at 2011-09-15 17:29:53 +0800 Processing by Api::V2::MessagesController#index as JSON Message Load (0.1ms) SELECT "messages".* FROM "messages" Completed 200 OK in 46ms (Views: 11.4ms | ActiveRecord: 0.4ms) Started GET "/api/v2/messages.json" for 127.0.0.1 at 2011-09-15 17:29:58 +0800 Processing by Api::V2::MessagesController#index as JSON Completed 200 OK in 7ms (Views: 6.1ms | ActiveRecord: 0.2ms)
• Production • Cache • Production memcache
Rocweibo::Application.configure do # Settings specified here will take precedence over those in config/application.rb # Code is not reloaded between requests config.cache_classes = true ... # Use a different cache store in production # config.cache_store = :mem_cache_store ... # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation can not be found) config.i18n.fallbacks = true # Send deprecation notices to registered listeners config.active_support.deprecation = :notify end
Daemon how to run your Background Job rails runner lib/my_script.rb
puts %(There are #{Message.count} messages and #{User.count} users in our database.) count = Reply.delete_all(["body LIKE ?", "%fuck%"]) puts "#{count} replies contains "fuck" got deleted." count = User.delete_all( [ "created_at < ? AND email_confirmed = ?", Time.new - 2.days, false ] ) puts "#{count} not confirmed users within 2 days got deleted."
Delayed Job http://railscasts.com/episodes/171-delayed-job
still interesting... • http://martinciu.com/2011/01/mounting-grape-api-inside-rails- application.html • http://docs.dotcloud.com/services/ruby/ • http://railscasts.com/episodes/171-delayed-job • http://www.sinatrarb.com/
@shaokunwu http://weibo.com/shaokunwu

Rails web api 开发

  • 1.
    Rails: Web API shaokun.wu@gmail.com Thanks to Leon Du and Rain Chen
  • 2.
    You should startwith... • ruby 1.9.2 Performance, Threads/Fibers, Encoding/Unicode... • rails 3.1.0 Asset pipeline, HTTP streaming, jQuery
  • 3.
  • 6.
  • 7.
    talk about... • API •API • API • •
  • 8.
    Start Simple butElegant http://localhost:3000/api/messages http://localhost:3000/api/messages/{id}
  • 9.
    $ git clone git://github.com/kudelabs/roc-demo2.git • $ cd roc-demo2 • $ bundle install • $ rake db:create • $ rake db:migrate • $ rails server • goto http://localhost:3000
  • 10.
    $ rails generate controller api::messages apps/controllers/api/messages_controller.rb test/functional/api/messages_controller_test.rb
  • 11.
    apps/controllers/api/messages_controller.rb class Api::MessagesController < ApplicationController # GET /api/messages.json def index @messages = Message.all respond_to do |format| format.json { render json: @messages } end end # GET /api/messages/1.json def show @message = Message.find(params[:id]) respond_to do |format| format.json { render json: @message } end end end
  • 12.
  • 13.
    Start Alone but Testis Your Partner rake test:functionals rake test:units rake test:integration
  • 14.
    test/functional/api/messages_controller_test.rb class Api::MessagesControllerTest < ActionController::TestCase setup do @message = messages(:one) end test "should get index" do get :index, format: :json assert_response :success assert_not_nil assigns(:messages) end test "should show message" do get :show, format: :json, id: @message.to_param assert_response :success end end
  • 15.
  • 16.
    built-in Namespaced controllers http://localhost:3000/api/v2/messages Rocweibo::Application.routes.draw do namespace :api do resources :messages namespace :v2 do resources :messages end end end
  • 17.
    test/functional/api/v2/messages_controller_test.rb class Api::V2::MessagesController <Api::ApplicationController before_filter :authenticate, :only => :create ... # curl -X POST -H "Accept: application/json" --user shaokun.wu@gmail.com:a # http://localhost:3000/api/v2/messages -d "message[body]=abcdefg" def create @message = Message.new(params[:message]) @message.user = @current_user @message.save! render json: @message end private def authenticate if user = authenticate_with_http_basic { |u, p| User.authenticate(u, p) } @current_user = user else request_http_basic_authentication end end end
  • 18.
    Keep Refactoring whenever Youcould Don’t Repeat Yourself & Decoupling
  • 19.
    class Api::V2::MessagesController <Api::ApplicationController ... private def authenticate if user = authenticate_with_http_basic { |u, p| User.authenticate(u, p) } @current_user = user else request_http_basic_authentication end end end class Api::ApplicationController < ActionController::Base private def authenticate if user = authenticate_with_http_basic { |u, p| User.authenticate(u, p) } @current_user = user else request_http_basic_authentication end end end
  • 20.
    /app/controllers/api/application_controller.rb class Api::ApplicationController < ActionController::Base end /app/controllers/application_controller.rb class ApplicationController < ActionController::Base helper_method :current_user, :logged_in? protect_from_forgery before_filter :require_logged_in ... end
  • 21.
    Grape A opinionated micro-frameworkfor creating REST-like APIs in Ruby.
  • 22.
    class Rocweibo::V1::API <Grape::API prefix 'api' version 'v1' resource :messages do get do Message.limit(20) end get ':id' do Message.find(params[:id]) end end end Rocweibo::Application.routes.draw do mount Rocweibo::V1::API => "/" # mount API routes ... end
  • 23.
    Where to HOSTyour app?
  • 24.
    No Easy Wayto Host in China :( • Amazon EC2 • Linode • DotCloud • Heroku
  • 25.
    DotCloud $ dotcloud push{myapp} {myrepopath}/
  • 26.
    • $ dotcloudcreate rocdemo • $ touch dotcloud.yml • $ dotcloud push rocdemo . • or $ dotcloud push -b mybranch rocdemo . Deployment finished. Your application is available at the following URLs www: http://rocdemo-limiru2n.dotcloud.com/
  • 27.
    add mysql service #just update the content of your dotcloud.yml www: type: ruby $ dotcloud info rocdemo data: type: mysql data: config: mysql_password: ... instances: 1 type: mysql # also, update the content of your database.yml www: production: config: adapter: mysql2 rack-env: production encoding: utf8 ruby-version: 1.9.2 reconnect: false instances: 1 database: roc_demo2_production type: ruby pool: 5 url: http://rocdemo-limiru2n.dotclo username: root password: ... port: 14895 host: rocdemo-LIMIRU2N.dotcloud.com
  • 28.
    • dotcloud logsrocdemo.www • dotcloud ssh rocdemo.www • dotcloud info rocdemo
  • 29.
  • 30.
    Display database tables • Create new data • Easily update data • Safely delete data • Automatic form validation • Search and filtering • Export data to CSV/JSON/XML • Authentication (via Devise) • User action history
  • 31.
    • add theline below to your Gemfile gem 'rails_admin', :git => 'git://github.com/sferik/rails_admin.git' • $ bundle install • $ rails g rails_admin:install
  • 35.
  • 36.
    def index @messages = Rails.cache.fetch('all_messages', :expires_in => 30.seconds) do Message.all end respond_to do |format| format.json { render json: @messages } end end
  • 37.
    Started GET "/api/v2/messages.json"for 127.0.0.1 at 2011-09-15 17:29:53 +0800 Processing by Api::V2::MessagesController#index as JSON Message Load (0.1ms) SELECT "messages".* FROM "messages" Completed 200 OK in 46ms (Views: 11.4ms | ActiveRecord: 0.4ms) Started GET "/api/v2/messages.json" for 127.0.0.1 at 2011-09-15 17:29:58 +0800 Processing by Api::V2::MessagesController#index as JSON Completed 200 OK in 7ms (Views: 6.1ms | ActiveRecord: 0.2ms)
  • 38.
    Production • Cache • Production memcache
  • 39.
    Rocweibo::Application.configure do # Settings specified here will take precedence over those in config/application.rb # Code is not reloaded between requests config.cache_classes = true ... # Use a different cache store in production # config.cache_store = :mem_cache_store ... # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation can not be found) config.i18n.fallbacks = true # Send deprecation notices to registered listeners config.active_support.deprecation = :notify end
  • 40.
    Daemon how to runyour Background Job rails runner lib/my_script.rb
  • 41.
    puts %(There are#{Message.count} messages and #{User.count} users in our database.) count = Reply.delete_all(["body LIKE ?", "%fuck%"]) puts "#{count} replies contains "fuck" got deleted." count = User.delete_all( [ "created_at < ? AND email_confirmed = ?", Time.new - 2.days, false ] ) puts "#{count} not confirmed users within 2 days got deleted."
  • 42.
  • 44.
    still interesting... • http://martinciu.com/2011/01/mounting-grape-api-inside-rails- application.html • http://docs.dotcloud.com/services/ruby/ • http://railscasts.com/episodes/171-delayed-job • http://www.sinatrarb.com/
  • 45.