jamztang
5/20/2015 - 11:21 AM

WIDS 2015 - Intro to Rails for mobile apps

Prerequisitie:
Ruby 2.0.0-p481
Rails 4.1.6

[Step 1]
 
- rails new wids2015
 
[Step 2] Add following lines to Gemfile
gem 'devise' 
gem 'rails_admin'
 
gem 'grape', '~> 0.6.1'
gem 'grape-entity', '~> 0.4.0'
gem 'rack-contrib', '~> 1.1.0'
gem 'grape-swagger', '~> 0.7.2'
gem 'grape-swagger-rails',  '~> 0.0.8'
 
[Step 3] 
 
- bundle install
- rails g devise:install
- rails g devise User
- rails g model Watch name:string user:references
 
[Step 4] Edit app/models/user.rb
 
has_many :watches
 
[Step 5] 
 
- rake db:migrate
- rails g rails_admin:install
 
[Step 6] Uncomment config/initializer/rails_admin.rb
 
config.authenticate_with do
    warden.authenticate! scope: :user
end
config.current_user_method(&:current_user)
 
[Step 7] 
 
- rails s
 
------------------------------------------------------------------------------
You will be able to work with rails_admin and devise now.
 
http://localhost:3000/admin
------------------------------------------------------------------------------
 
[Step 8] 
 
- rails g model Token secret_id:string hashed_secret:string expires_at:datetime user:references
 
[Step 9] add after create table db/migrate/[date]_create_tokens
	
- add_index :tokens, :secret_id, name: "index_tokens_secret_id", unique: true
 
[Step 10] 
 
- rake db:migrate
 
[Step 11] Edit app/models/token.rb
 
  before_create :generate_secret
  attr_accessor :secret
 
  def expired?
  	DateTime.now >= self.expires_at
  end
 
  def self.find_authenticated secret_id, secret
    token = where(secret_id: secret_id).first
    token if token && token.has_secret?(secret)
  end
 
  def has_secret? secret
  	BCrypt::Password.new(self.hashed_secret) == secret
  end
 
  private 
  def generate_secret
  	begin 
  	  self.secret_id = SecureRandom.hex 8
  	end while Token.exists?(secret_id: self.secret_id)
  	@secret = SecureRandom.urlsafe_base64 32
  	self.hashed_secret = BCrypt::Password.create secret, cost: 10
 
  	self.expires_at = DateTime.now + 30.days
  end
 
[Step 12] Edit app/models/user.rb
 
  def generate_authentication_token
  	Token.create(user: self)
  end
 
[Step 13] Add lib/api/entities.rb
module API
  module Entities
    class EntityToken < Grape::Entity
      expose :secret_id
      expose :secret
      expose :created_at
      expose :updated_at
    end
  end
end
 
[Step 14] Add lib/api/entities.rb
 
module API
  module Entities
    class EntityWatch < Grape::Entity
      expose :id
      expose :name
      expose :created_at
      expose :updated_at
    end
  end
end
 
[Step 15] Add lib/api/v1/users.rb
 
module API
  module V1
    class Users < Grape::API
      version       'v1'
      format        :json
 
      resource :login do 
        desc "Return logged in user with access token"
        params do 
          requires :email, type: String, desc: "Email"
          requires :password, type: String, desc: "Password"
        end
 
        get do 
          user = User.find_by_email(params[:email].downcase)
 
          if user && user.valid_password?(params[:password])
            token = user.generate_authentication_token
            user.sign_in_count += 1
            if user.save
              present :token, token, with: API::Entities::EntityToken
            end
          else 
            error!(I18n.t("devise.failure.invalid", :authentication_keys => Devise.authentication_keys.join(", ")), 403)
          end
        end
      end
 
      resource :watches do
        desc "Return list of watches"
        params do
          requires :secret_id, type: String, desc: "Secret id"
          requires :secret, type: String, desc: "Secret"
        end
        get do 
          authenticate!
 
          present :watches, @user.watches, with: API::Entities::EntityWatch, size: params[:size]
        end
 
        desc "Add a watch"
        params do
          requires :secret_id, type: String, desc: "Secret id"
          requires :secret, type: String, desc: "Secret"
          optional :name, type: String, desc: "Name of watch"
        end
        get 'add' do 
          authenticate!
 
          watch = Watch.create!(name: params[:name], user: @user)
          watch.save!
 
          present :watch, watch, with: API::Entities::EntityWatch, size: params[:size]
        end
      end
    end
  end
end
 
[Step 16] Add lib/v1/root.rb
 
module API
  module V1
    class Root < Grape::API
      mount API::V1::Users
    end
  end
end
 
[Step 17] Add lib/api/root.rb
 
require 'grape-swagger'
module API
  class Root < Grape::API
    helpers do
      def authenticate!
          error!({message: I18n.t("devise.errors.messages.invalid_token"), code: -90001}, 403) unless current_user 
        end
 
      def current_user
        @user || find_user_by_secret
      end
 
      private 
      def find_user_by_secret
        secret_id = params[:secret_id].presence
        secret    = params[:secret].presence
          token     = secret_id && secret && Token.find_authenticated(secret_id, secret)
        @user     = token.user if token
        
          return @user
      end
    end
 
    mount API::V1::Root
  end
end
 
[Step 18] Edit config/locales/devise.en.yml for devise.errors.messages.invalid_token
 
invalid_token: "Invalid Token."
 
[Step 19] Edit config/initializer/rails_admin.rb
 
Rails.application.eager_load!
config.included_models = ActiveRecord::Base.descendants.map!(&:name)
 
[Step 20] Edit config/application.rb
 
config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir["#{config.root}/lib/**/"]
 
[Step 21] Edit config/routes.rb
 
mount API::Root => '/api', as: 'api'
 
------------------------------------------------------------------------------
You will be able to access the api now.
 
http://localhost:3000/api/v1/login.json?email=[your email]&password=[your password] 
------------------------------------------------------------------------------
 
[Step 22] Add config/initializer/grape_swagger_rails.rb
 
require 'grape-swagger-rails'
 
GrapeSwaggerRails.options.url      = "/api/swagger_doc.json"
GrapeSwaggerRails.options.app_name = 'wids2015'
GrapeSwaggerRails.options.app_url  = '/'
 
[Step 23] Edit config/routes.rb
 
mount GrapeSwaggerRails::Engine => '/apidoc'
 
[Step 24] Edit lib/api/root.rb 
 
add_swagger_documentation(
  base_path: "/api",
  api_version: "v1",
  hide_documentation_path: true
)
 
------------------------------------------------------------------------------
You will be able to access the api doc now.
 
http://localhost:3000/apidoc
------------------------------------------------------------------------------