【Rails4】json_spec で JSONのテスト 及び MultiJson::ParseError対応

json_spec導入

Gemfile

gem 'json_spec'


spec/spec_helper.rb

RSpec.configure do |config|
  #JSON検査
  config.include JsonSpec::Helpers
:
:


検証
以下のJSONを検証する(kaminari APIより抜粋)
"meta":{"current-page":1,"next-page":2,"prev-page":null,"total-pages":2,"total-count":9}}

#"meta":{"current-page":1,"next-page":2,"prev-page":null,"total-pages":2,"total-count":9}}
body = response.body

#metaが存在すること
expect(body).to have_json_path('meta')

#meta/total-countは7であること
expect(body).to be_json_eql(7).at_path('meta/total-count')

#meta/total-pagesはIntegerであること
expect(body).to have_json_type(Integer).at_path('meta/total-pages')

#metaは要素を5つ持つこと(Arrayに使う方がしっくりする...)
expect(body).to have_json_size(5).at_path('meta')

(注意)文字列をbe_json_eqlで検証する場合 MultiJson::ParseError が発生します。

names = %({"first_name":"Steve","last_name":"Richert"})
expect(names).to be_json_eql("Steve").at_path("first_name")
#MultiJson::ParseError:
#       795: unexpected token at 'Steve'

解決策: JSON同士の比較のため対象文字列をJSONにする必要があります。

names = %({"first_name":"Steve","last_name":"Richert"})
expect(names).to be_json_eql("Steve".to_json).at_path("first_name")

【Rails4】kaminariをAPIで利用する

ページネーション用Gemで有名なkaminari。
ただ、使い方を調べてもAPIでの利用方法が見つからなかったので記載。

前提
サンプルとしてTodoをJSONで渡すAPIを実装

下記Gem導入済み
・kaminari
・active_model_serializers

modelの出力フォーマットをactive_model_serializersを利用して設定

./app/serializers/todo_serializer.rb

class TodoSerializer < ActiveModel::Serializer
  attributes :id, :body, :memo, :complete, :user_id
end


kaminariの共通設定ファイルを定義

config/initializers/kaminari_config.rb

Kaminari.configure do |config|
  config.default_per_page = 7    #最大項目数
  # config.max_per_page = nil       #最大数/page
  # config.window = 4               
  # config.outer_window = 0
  # config.left = 0
  # config.right = 0
  # config.page_method_name = :page
  # config.param_name = :page
end
Contact GitHub API Training Shop Blog About


kaminariをAPIに適用させる

config/initializers/active_model_serializer.rb

ActiveModel::Serializer.config.adapter = :json_api


controllerを実装する

class TodosController < ApplicationController

  def index
    #deviceを利用していますが、ここは各自読み替え
    todos = current_user.todos.page(params[:page] ||= 1)
    
    #meta 以下が今回の設定を適用させる
    render json: todos, meta: pagination_dict(todos)
  end

#以下省略

出力確認

{
"data":[{"id":"11","type":"todos","attributes":{"body":"APIを実装する","memo":"","complete":false,"user-id":9}},  {"id":"12","type":"todos","attributes":{"body":"はてなに投稿する","memo":"","complete":false,"user-id":9}},{"id":"13","type":"todos","attributes":{"body":"記事を投稿する","memo":"","complete":false,"user-id":9}}],
"links":{},
"meta":{"current-page":1,"next-page":null,"prev-page":null,"total-pages":1,"total-count":3}
}

meta 及び links がnodeに存在すれば完了。

【Rails4】before_actionに引数を渡す

以下の様にbefore_actionに引数を渡した状態で定義してもエラーとなる。

before_action :test('hogehoge')

これは以下の様に修正することで解決できる。

before_action -> {
  test('hogehoge')
}


フィルターを設定したい場合は以下の様に追記。

before_action -> {
  test('hogehoge')
},only: [:index, :show]


少しハマったのでメモ。

【Rails4】Devise::MissingWarden エラーへの対処

RepecでControllerのテストを実行した際に発生。
以下エラー文

Devise::MissingWarden:
 Devise could not find the `Warden::Proxy` instance on your request environment.
Make sure that your application is loading Devise and Warden as expected and
 that the `Warden::Manager` middleware is present in your middleware stack.


原因
Device gem によるプロキシエラー。

対応

f you're using RSpec, you can put the following inside a file named spec/support/devise.rb or in your spec/spec_helper.rb (or spec/rails_helper.rb if you are using rspec-rails):

RSpec.configure do |config|
  config.include Devise::Test::ControllerHelpers, type: :controller
  config.include Devise::Test::ControllerHelpers, type: :view
end

とのことなので以下の様に設定を追加

spec/rails_helper.rb

RSpec.configure do |config|
  config.include Devise::Test::ControllerHelpers, type: :controller
  config.include Devise::Test::ControllerHelpers, type: :view
end

【Java】SSL通信を実装する

環境
作業対象ファイル: C:\ssl
JDKのパス: C:\Program Files\Java\jdk1.6.0_45\bin
対象のサーバー証明書名: sample.cer

keyStore と trustStoreとは?
keystore: サーバーに送信するクライアントの証明書を格納するファイル。
trustStore: クライアント側が認証するサーバー側の証明書を格納するファイル。

手順
KeyStoreとTrustStore保存用ディレクトリを作成(C:\ssl)

認証対象のサーバー証明書(sample.cer)を C:\ssl に配置

ターミナルをAdministrator権限で実行し、インストール済みのJDK(C:\Program Files\Java\jdk1.6.0_45\bin)に移動。

以下のコマンドでkeyStoreを新規作成 -storepass の引数がパスワードとなります。

$ keytool -genkey -keystore keystore -storepass hogehoge

※この際、色々と設定を聞かれますが、よしなに...


同じくtrustStoreを新規作成

$ keytool -genkey -keystore truststore -storepass hogehoge


続いて sample.cer を trustStore に以下のコマンドでインポート -alias の引数が管理名として登録されます。

$ keytool -import -trustcacerts -file C:/ssl/sample.cer -keystore "C:/ssl/truststore" -alias sample_ssl


インポートされているかを以下のコマンドで確認

$ keytool -v -list -keystore  "C:/ssl/truststore"


インポートされている証明書の一覧が出てくるので、今回追加した sample_ssl が存在していれば成功。

以下サンプルコード

    /**
     * 認証付SSLContext作成
     * @param key_path Keystoreファイルのpath
     * @param key_password Keystoreファイルのpassword
     * @param trust_path Truststoreファイルのpath
     * @param trust_password Truststoreファイルのpassword
     * @return
     * @throws Exception
     */
    private SSLContext ssl_auth(String key_path, String key_password, String trust_path, String trust_password) throws Exception{
        //KeyStore読み込み
        char[] key_pass_char = key_password.toCharArray();
        KeyStore key_store = KeyStore.getInstance("JKS");
        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");

        key_store.load(new FileInputStream(key_path), key_pass_char );
        kmf.init(key_store, key_pass_char);

        //TrustStoreの読み込み
        char[] trust_pass_char =TrustPass.toCharArray();;
        KeyStore trust_store = KeyStore.getInstance("JKS");
        TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");

        trust_store.load(new FileInputStream(TrustPath), trust_pass_char);
        tmf.init(trust_store);

        KeyManager[] key_managers = kmf.getKeyManagers();
        TrustManager[] trust_managers = tmf.getTrustManagers();

        SSLContext sslcontext = SSLContext.getInstance("TLS");
        sslcontext.init(key_managers, trust_managers, null);

        return sslcontext;
    }


     URL urlObj = new URL(url);
     HttpsURLConnection http = (HttpsURLConnection) urlObj.openConnection();
     http.setRequestMethod("POST");
     http.setDoOutput(true);
     http.setConnectTimeout(30000); 
     http.setReadTimeout(30000); 
     http.setSSLSocketFactory(ssl_auth("C:/ssl/keystore", "hogehoge", "C:/ssl/trustStore", "hogehoge").getSocketFactory());
     http.connect();

     OutputStream os = http.getOutputStream();
     PrintStream ps = new PrintStream(os);
     ps.print("送信するパラメータ");
     ps.close();

【javascript】ES6で導入されたclassにクラスメソッドを実装

ES6で導入されたclass記法は元々あった擬似クラス構文のシンタックスシュガー。
そのため、ES5時代の手法が そのまま転用できる。

class Person{

  constructor(){
     this.name = '山田太郎'
  }
  
  say(){
    console.log(this.name);
  }
}

今回定義したPersonクラスにクラスメソッドを追加する。

Person.method = function(){
   console.log("クラスメソッド");
}


これだけ。
実際に試してみる。

let tarou = new Person();

//インスタンスからクラスメソッドにアクセスできない
tarou.method()  //Uncaught TypeError

//インスタンスはインスタンスメソッドにアクセスできる
tarou.say()    //山田太郎

//クラスはインスタンスメソッドにアクセスできない
Person.say()    //Uncaught TypeError

//クラスはクラスメソッドにアクセスできる
Person.method()  //クラスメソッド


ES5での擬似クラス定義を理解していれば然程難しくはない。

【Rails4】rails_adminで楽に管理画面を作ってみた

rails_adminを追加。

gem 'rails_admin'

インストール後、設定ファイル生成。

$ bundle install
$ rails g rails_admin:install

設定ファイルの ==Device==の項目のコメントを外す。
config/initializers/rails_admin.rb

RailsAdmin.config do |config|

  ### Popular gems integration

  ## == Devise ==
  config.authenticate_with do
    warden.authenticate! scope: :user
  end
  config.current_user_method(&:current_user)

  ## == Cancan ==
  # config.authorize_with :cancan

  ## == Pundit ==
  # config.authorize_with :pundit

  ## == PaperTrail ==
  # config.audit_with :paper_trail, 'User', 'PaperTrail::Version' # PaperTrail >= 3.0.0

  ### More at https://github.com/sferik/rails_admin/wiki/Base-configuration

  config.actions do
    dashboard                     # mandatory
    index                         # mandatory
    new
    export
    bulk_delete
    show
    edit
    delete
    show_in_app

    ## With an audit adapter, you can add:
    # history_index
    # history_show
  end
end

以下のファイルをコピーして配置して日本語化。
https://gist.github.com/mshibuya/1662352#file_rails_admin.ja.yml
config/locales/rails_admin.ja.yml

以下のアドレスにアクセスし、管理画面が表示されれば成功。
http://localhost:3000/admin/


このままだと全ユーザーが管理画面にアクセスできるためroleを付与する。

cancancanを追加。
Gemfile

gem 'cancancan'

cancancanインストール後、設定ファイル生成。

$ bundle install
$ rails g cancan:ability

userテーブルにadminフィールドを追加しておく。
追加したadminを参照するように設定。

class Ability
  include CanCan::Ability

  def initialize(user)
    if user && user.admin?
      can :access, :rails_admin
      can :manage, :all
    end
  end
end


コンソールで管理者にしたいユーザーのadminフラグをtrueに更新。

$ user = User.first
$ user.admin = true
$ user.save

これで管理画面がadmin権限を持つユーザー以外のアクセスを遮断するようになる。