20200918のRubyに関する記事は18件です。

【Rails6.0】タグ検索機能(タグで絞り込み機能)を実装してみた【gem無し】

現在個人開発しているアプリにタグ検索機能を実装したのでここに備忘録としてまとめさせていただきます。
あえてgemを使っていないので本質的なところまで理解することができました。

【環境】
- windows10 Pro
- Rails: 6.0.3.2
- ruby: 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-linux]
- Docker for windows
- MySQL 5.7
- nginx:1.15.8

機能の概要

新規投稿フォームにタグを入力すると、入力したタグが投稿一覧とサイドバーに反映されます。
サイドバーに反映されたタグをクリックするとそのタグが付いた投稿を絞り込むことができるという機能です。

投稿フォームからタグを入力
tagu_qiita.png

↑で入力したタグがサイドバーと投稿一覧に表示される
サイドバーに表示されたタグをクリックするとそのタグを含む記事を絞り込むことができる。(今回はサンプルなので一件のみ)
tagu_qiita2.png

実装手順

Itemモデル及びPostモデルなど作成済みという前提で話しをします。(ここではitemモデルとします。各自置き換えてください。)

まず、Tagモデル、Tagmapモデルをいつものように作成します。

$ rails g model Tagmap item:references tag:references 
$ rails g model tag tag_name:string

TagmapはItemモデルとTagモデルに紐づくのでデータ型にreferencesを付けてます。
これをするとRailsが良きにはからってくれますので詳しく知りたい方はググってみてください。

マイグレーションファイル↓

class CreateTagmaps < ActiveRecord::Migration[6.0]
  def change
    create_table :tagmaps do |t|
      t.references :item, null: false, foreign_key: true
      t.references :tag, null: false, foreign_key: true

      t.timestamps
    end
  end
end

こちらはインデックスを付けます。

class CreateTags < ActiveRecord::Migration[6.0]
  def change
    create_table :tags do |t|
      t.string :tag_name, null: false

      t.timestamps
    end
    add_index :tags, :tag_name, unique: true
  end
end

その後いつものようにマイグレーションをします。

$ rails db:migrate

アソシエーションを設定します。

item.rb
has_many :tagmaps, dependent: :destroy
has_many :tags, through: :tagmaps
tag.rb
class Tag < ApplicationRecord
  has_many :tagmaps, dependent: :destroy
  has_many :items, through: :tagmaps
end

tagもtagmapsと関連付けさせています。↑

thoroughを使うことで、tagmaps経由でitemsにアクセスできるようになってます。

tagmap.rb
class Tagmap < ApplicationRecord
  belongs_to :item
  belongs_to :tag
end

↑references形にするとbelongs_toの部分をRailsが自動で記述してくれます!

items_controller.rb
 def index
    if params[:search].present?
      items = Item.items_serach(params[:search])
    elsif params[:tag_id].present?
      @tag = Tag.find(params[:tag_id])
      items = @tag.items.order(created_at: :desc)
    else
      items = Item.all.order(created_at: :desc)
    end
    @tag_lists = Tag.all
    @items = Kaminari.paginate_array(items).page(params[:page]).per(10)
  end

こちらのコードの解説をします。
indexアクションでif文を使い3パターン場合分けしていて、この結果によりitem変数に代入される値を変えています。
1.検索フォームに入力があった場合
2.paramsで:tag_idを受け取った場合(クリックしたときにパラメータでtag_idを受け取ったときに発動する。タグで絞込む機能)
3.普通にページを表示させた場合
ページネーション機能も設定しているので、item変数に格納される変数が3パターンになり、それぞれ最適化されたものが表示できるようになっています。
ページネーションではKaminariというgemを使っております。

items_controller
def create
    @item = Item.new(item_params)
    tag_list = params[:item][:tag_name].split(nil)
    @item.image.attach(params[:item][:image])
    @item.user_id = current_user.id
    if @item.save
       @item.save_items(tag_list)
      redirect_to items_path
    else
      flash.now[:alert] = '投稿に失敗しました'
      render 'new'
    end
  end

itemsコントローラーのcreateアクションでのポイントは3行目のtag_list変数への代入部分です。
rubyのString#split は第 1 引数(今回はnil)で指定されたセパレータによって文字列を分割し、結果を文字列の配列で返します。ブロックを指定すると、配列を返す代わりに分割した文字列でブロックを呼び出します。
また、1 バイトの空白文字 ' 'で区切られたタグデータをフォームから送ると先頭と末尾の空白を除いたうえで、空白文字列で分割します。今回はこれを利用します。

新規投稿フォームから送られてきたパラメーターを空白文字(nil)でセパレイトして配列化し、Itemクラスで定義したsave_itemsクラスメソッドを使い、データベースに保存します。(フォームにはタグをスペース区切りで入力してもらうように使用者にお願いする。)

item.rb
 #検索メソッド、タイトルと内容をあいまい検索する
 def self.items_serach(search)
   Item.where(['title LIKE ? OR content LIKE ?', "%#{search}%", "%#{search}%"])
 end

 def save_items(tags)
    current_tags = self.tags.pluck(:tag_name) unless self.tags.nil?
    old_tags = current_tags - tags
    new_tags = tags - current_tags

    # Destroy
    old_tags.each do |old_name|
      self.tags.delete Tag.find_by(tag_name:old_name)
    end

    # Create
    new_tags.each do |new_name|
      item_tag = Tag.find_or_create_by(tag_name:new_name)
      self.tags << item_tag
    end
  end

↑タグデータを保存するとき、フォームから送られてきたタグデータのうち、すでに存在するタグネームがひとつでもあった場合はtagsテーブルのtag_nameカラムからpluckメソッドを使い一旦すべてのデータを引っ張ってきてcurrent_tagsに代入します。(すべて新しいものだった場合はnilになる。)
そしてすでに存在するタグデータの集合であるcurrent_tagsから、コントローラーから引数で渡ってきたtagsの配列を引くと古いタグ(old_tags)を定義することができます。
rubyでは配列の引き算をすると共通する要素を取り出すことができるためです。

具体例:

tags(tag_list = params[:item][:tag_name].split(nil)でコントローラーから渡ってくる配列) = ["Rails" "ruby" "React"]
current_tags(現在DBに存在しているタグデータ) = ["Rails" "ruby" "Vue.js"]

old_tags = ["Rails" "ruby" "Vue.js" "Docker"] -  ["Rails" "ruby" "React"] 
old_tags = ["Rails" "ruby"] 
↑二つの配列の共通の要素が残る(すでに存在している古いタグを算出することができる)

サイドバーのビュー一部抜粋(CSSフレームワークにUIkitというのを使ってます)

index.html.haml
%li.search_friend_by_categorize
  .uk-text-secondary.uk-text-bold
    タグで探す
  %ul.uk-flex.uk-flex-wrap
    - @tag_lists.each do |list|
      %li
        = link_to list.tag_name, items_path(tag_id: list.id), class: 'tag_list'

投稿記事一一覧へのタグの表示

index.html.haml
 %p.tag_list_box
   - item.tags.each do |tag|
     = link_to "##{tag.tag_name}", items_path(tag), class: 'smaller tag_list'

viewはこのような感じになります。特に難しいことはないですね!

最後まで読んでいただきありがとうございます!

少し駆け足で書いたので間違いなどあるかもしれません。もしなにかご指摘や感想などあればコメントいただけると嬉しいです!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コメント機能に削除機能を追加したい

【概要】

1.結論

2.どのように実装したのか

3.今後していきたいこと

1.結論

❶commentコントローラーのdestroyアクションに、投稿したidを紐づけて削除できるようにする。

❷ネスト化したpathにlink_toで削除ボタン押下できるようにする。


2.どのように実装したのか

<開発環境>
Ruby: 2.6.5
Rails: 6.0.3.3
controllers/comments_controller.rb
  def destroy
    redirect_to root_path unless user_signed_in? || current_user.id == @time.user_id && current_user.id == comment.user_id #---(1)
    @comment = Comment.find(params[:time_id])
    if @comment.destroy  #---(2)
      redirect_to time_path #---(3)
    else
      redirect_to root_path #---(4)
    end
  end

❶commentコントローラーのdestroyアクションに、投稿したidを紐づけて削除できるようにしました。
(1)ではログアウトしてる人や、自分以外の投稿者が自分の投稿のコメントを削除される(直接URLの入力して削除される)のを防ぐためです。
(2)は自分が投稿したコメントの削除を実行します。
(3)は成功すればコメント投稿画面に戻ります。
(4)はコメントの削除に失敗すればトップページに戻ります。

view/time/show.html.erb
      <% if @comments %>#---(1)
        <% @comments.each do |comment| %>#---(2)
          <p>
           <%= comment.user.name %>:#---(3)
            <% if user_signed_in? && current_user.id == @time_report.user_id && current_user.id == comment.user_id %>
              <%= link_to '❌', time_report_comment_path(comment.id), method: :delete %> #---(4)
               <%= comment.content %> #---(5)
            <% end %>
           </p>
        <% end %>
      <% end %>

❷ネスト化したpathにlink_toで削除ボタン押下できるようにしました。
(1)は"もしコメントがあれば"ということで、インスタンス変数でtimeコントローラーから持ってきています。
(2)User➡︎commentsはhas_manyなのでたくさんあるコメントを持ってきています。
(3)その中でも紐づけられたコメントしたユーザー名だけ表示しています。
(4)ルーティングでコメントをネスト化しているので自分で投稿した自分のコメントを自分で削除できるようにしています。
(5)コメントの内容を表示しています。


3.今後していきたいこと

SPAを実装したいです。吹き出しマークをクリックしたら、非同期的(ページ遷移がなく)にコメントが表示され、そこで編集や削除ができるようなアプリケーションにしたいです。そのためにはNuxt.js/Vue.jsの知識が必要になってくるのでコツコツ勉強していきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Git管理中のRailsプロジェクトをCloud9に環境構築

きっかけ

元々MacOS環境で作成していたRailsアプリをWindowsでも作業したいと思ったが、WindowsでやるならAWSでやってみたら簡単じゃないかということで久しぶりにCloud9で環境構築をした。

環境

AWS Cloud9
Ruby 2.7.0
Ruby on Rails 6.0.3
Chrome(ブラウザは何でもいい)

Cloud9にインスタンスを作成

AWSへのIAMユーザーでサインインは済ませておく。(これがまずよくわからない問題。)
サービスの中の下の方にCloud9があるのでクリック。

image.png

環境を作るボタンをクリック
image.png

Nameに好きなアプリ名を入れる。すでにGit管理されているプロジェクトの名前でいいでしょう。
今回はSample_appとしました。その後Next step。
image.png

セッティングの中のPlatformはUbuntu Serverを選択。その他はデフォルトでOK。
Rails6を使うときはSQLite3のバージョンの都合でAmazon Linuxでは無理なため決して選ばないように。
image.png
そしてNext step。

確認画面に移動するので、Crreate environment!
しばらくすると以下のような画面になってまずはプログラムを置く場所を作りました。このタブは不要なので消してOK。
image.png

プログラムをクローンする

+ボタンをクリックしてNew Terminalを選択。ターミナルが立ち上がります。
以下を行うことでドキュメントのダウンロードを行わないので、インストールが早まるそうです。

$ echo "gem: --no-document" >> ~/.gemrc

クローン!

$ git clone GitリポジトリのURL

クローン出来ました。
では、クローンしたプログラムのディレクトリに移動します。

$ cd プログラムのディレクトリ名

そうすると、以下のように注意を促されます。

Required ruby-2.7.0 is not installed.
To install do: 'rvm install "ruby-2.7.0"'

Ruby 2.7.0が必要だよ!

Ruby 2.7.0のインストールを行う

今回使用したいRubyのバージョンは2.7.0、Railsは6.0.2。開発中のプログラムに合わせる。
デフォルトでインストールされているRubyとバージョンが違うので、この環境にインストールします。
素直に行きます。

$ rvm install "ruby-2.7.0"

image.png
OK。

YARNのダウンロードとインストール

$ source <(curl -sL https://cdn.learnenough.com/yarn_install)
$ yarn install --check-files

上記がうまくいったら下のコマンドはスキップしてBundlerのインストールへ。
これでうまくいかないこともあるので、以下でも可能。

$ curl -o- -L https://yarnpkg.com/install.sh | bash

以下のような画面が出て、インストールが完了。新しいターミナルを開いたら使えるよって書いてありますね。
image.png

では、新しいターミナルを+ボタンを押して開いてみます。

$ yarn --version

1.22.5と出たので、インストールOK。

Railsなどのgemをインストール

まずはBundler のインストールから。プログラムのGemfileの内容によってちょっと時間かかります。

$ bundle install

Railsがインストールされているか確認します。

$ rails --version

6.0.3.1と出たのでOK。

Rails6 から特有のwebpackerをインストールします。

$ rails webpacker:install

何々?コンフリクトだと?YでOK。
image.png
image.png
よし。OK。

Cloud9の開発環境で画面にアクセスできるようにする

以下のファイルに追記します。画面確認のためにはブラウザでCloud9の環境にアクセスする必要があるので。
config/environments/development.rb
一番下のendの一段上。

config.hosts.clear

image.png

既存のプロジェクトなので以下のコマンドを忘れずに。

rails db:migrate

ついにプログラム起動!

$ rails s

image.png

画面を確認するには上の方のPreviewタブ>Preview Running Applicationをクリック。
その後赤枠部分をクリックするとブラウザが別タブで起動します。
image.png
よし!この画面が今回利用したプログラムのデフォルト画面です。これが出たので、この調子で開発が進められます!
image.png

以下のサイトを運営しています。

https://dokusyo-no-wa.com/
こちらの開発をMacとWindowsPCと両方でやれたら便利だなと思ったのがきっかけ。

参考記事

https://skillhub.jp/courses/134/lessons/785 (Cloud9でRails6の準備)
https://happy-teeth.hatenablog.com/entry/2019/09/17/110208 (SQLite3を利用するにはAmazon Linuxではできない。)
https://qiita.com/8zca/items/175efb0612070530d186 (CentOS環境でRails6.0をSQLite3 (>=3.8)で動かす)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

instagramのクローンアプリを作る④

はじめに

タイトルの通り、簡易版instagramのアプリを作っていきます。
下記の工程に分けて記事を執筆していきますので、順を追って読んでいただけたらなと思います。

アプリ作成〜ログイン機能の実装
写真投稿機能の実装
ユーザーページの実装
フォロー機能の実装 ←イマココ
⑤投稿削除機能の実装

モデル作成

※以下、アプリケーションのディレクトリで

ターミナル
rails g model follow user:belongs_to target_user:belongs_to

マイグレーションファイルを修正します。
target_userforeign_keyfalseに。

db/migrate/2020**********_create_follows.rb
class CreateFollows < ActiveRecord::Migration[6.0]
  def change
    create_table :follows do |t|
      t.belongs_to :user, null: false, foreign_key: true
      t.belongs_to :target_user, null: false, foreign_key: false

      t.timestamps
    end
  end
end

修正できたらrails db:migrateを実行します。

リレーションの設定

followモデルにリレーションを設定していきます。
target_userclass_nameforeign_keyを指定します。

app/models/follow.rb
class Follow < ApplicationRecord
  belongs_to :user
  belongs_to :target_user, class_name: 'User', foreign_key: 'target_user_id'
end

userモデルにもリレーションを設定していきます。
active_relationships
passive_relationships
を記述していきます。

app/models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_many :active_relationships, class_name: 'Follow', foreign_key: 'user_id'
  has_many :passive_relationships, class_name: 'Follow', foreign_key: 'target_user_id'
  has_many :followings, through: :active_relationships, source: :target_user
  has_many :followers, through: :passive_relationships, source: :user
  has_many :photos
end

これでリレーションは完成です。

followsコントローラ作成

ターミナル
rails g controller follows

ルーティングの設定も忘れずに行います。

routes.rb
Rails.application.routes.draw do
  root 'homes#index'

  devise_for :users

  resources :photos
  resources :users do
    resource :follow # ←ここ
  end
end

これでURLはuser/user_id/followという感じになると思います。

次に、followsコントローラを編集していきます。

folows_controller.rb
class FollowsController < ApplicationController
  before_action :authenticate_user!

  def create
    current_user.active_relationships.create(target_user_id: params[:user_id])

    redirect_to [:user, {id: params[:user_id] }]
  end

  def destroy
    current_user.active_relationships.find_by(target_user_id: params[:user_id]).destroy

    redirect_to [:user, {id: params[:user_id] }]
  end
end

before_action :authenticate_user!とすることで、
ログインしているユーザーのみ「フォロー/フォロー解除」することができます。

createアクションでフォローの動きを設定します。
リダイレクト先は[:user, {id: params[:user_id] }]として、
userのuser_id、つまり ユーザーの詳細ページに遷移するよう設定しています。

destroyアクションでフォロー解除の動きを設定します。
createではなく、まずfind_byでフォロー中のレコードを取得します。
そして、destroyで取得してきたレコードを破壊します。
リダイレクト先は同じで大丈夫だと思います。

viewファイルにフォロー/フォロー解除リンクを作成

app/views/users/show.html.erb
<h3><%= @user.email %></h3>

# ↓↓↓↓↓↓↓↓↓↓ ここから ↓↓↓↓↓↓↓↓↓↓
<% if current_user.active_relationships.exists?(target_user_id: @user.id) %>
  <%= link_to 'unfollow', [@user, :follow], method: :delete %>
<% else %>
  <%= link_to 'follow', [@user, :follow], method: :post %>
<% end %>
# ↑↑↑↑↑↑↑↑↑↑ ここまで ↑↑↑↑↑↑↑↑↑↑

<div>
  <%= link_to 'followings', [@user, :followings] %>
</div>
<div>
  <%= link_to 'followers', [@user, :followers] %>
</div>

<% @user.photos.each do |photo| %>
  <div>
    <p><%= photo.caption %></p>
    <%= image_tag photo.image %>
  </div>
<% end %>

条件分岐で、
current_userがユーザーを既にフォローしていた場合は
フォロー解除用のリンクを表示させます。→ method: delete
まだフォローしていない場合はフォロー用のリンクを表示させます。→ method: post

ここまでで一度ブラウザで確認してみます。
下記のようになっていれば成功です。
ユーザー詳細ページにとび、followを押すとフォロー完了。
そして、followunfollowに変わります。
unfollowを押すとフォロー解除となり、followに戻ります。
Image from Gyazo

ただ、この状態だとログインしていないユーザーが
URLに直接アクセスするとエラーが起きてしまいます。↓
Image from Gyazo

これは条件分岐でcurrent_userとしているためです。
ですので、更に条件分岐を追加していきます。
先ほどの条件分岐を↓で挟みます。
<% if user_signed_in? && current_user != @user %>
<% end %>

app/views/users/show.html.erb
<h3><%= @user.email %></h3>

<% if user_signed_in? && current_user != @user %> # ←ここ
  <% if current_user.active_relationships.exists?(target_user_id: @user.id) %>
    <%= link_to 'unfollow', [@user, :follow], method: :delete %>
  <% else %>
    <%= link_to 'follow', [@user, :follow], method: :post %>
  <% end %>
<% end %> # ←ここ

<div>
  <%= link_to 'followings', [@user, :followings] %>
</div>
<div>
  <%= link_to 'followers', [@user, :followers] %>
</div>

<% @user.photos.each do |photo| %>
  <div>
    <p><%= photo.caption %></p>
    <%= image_tag photo.image %>
  </div>
<% end %>

ユーザーがサインインしている

current_userがユーザー詳細ページのユーザーでないこと

という条件分岐を追記したことになります。

これで、サインインしていないと「フォロー/解除」のリンクが表示されないようになります。
また、自分の詳細ページに飛んだ時も表示されないようになります。

これでフォロー機能はほとんど完成ですが、
最後に、フォローされているユーザーの一覧を表示させたいと思います。

followings/followersコントローラの作成

ターミナル
rails g controller followings
ターミナル
rails g controller followers

ルーティングも忘れずに設定します。

routes.rb
Rails.application.routes.draw do
  root 'homes#index'

  devise_for :users

  resources :photos
  resources :users do
    resource :follow
    resources :followings # ←ここ
    resources :followers # ←ここ
  end
end

コントローラにもそれぞれ記述していきます。

folowings_controller.rb
class FollowingsController < ApplicationController
  def index
    @followings = User.find(params[:user_id]).followings
  end
end
folowers_controller.rb
class FollowersController < ApplicationController
  def index
    @followers = User.find(params[:user_id]).followers
  end
end

そしてviewを作成していきます。
それぞれlink_tofollowing/followeremailを表示して、
ユーザー詳細ページへのリンクを作成しています。

app/views/followings/index.html.erb
<% @followings.each do |following| %>
  <div>
    <%= link_to following.email, [following] %>
  </div>
<% end%>
app/views/followers/index.html.erb
<% @followers.each do |follower| %>
  <div>
    <%= link_to follower.email, [follower] %>
  </div>
<% end%>

pathの指定は
[following]user_followings_path
[follower]user_followers_path
でも良いのでわかりやすい方で。

最後にユーザー詳細ページにリンクを作成します。

app/views/users/show.html.erb
<h3><%= @user.email %></h3>

<% if user_signed_in? && current_user != @user %>
  <% if current_user.active_relationships.exists?(target_user_id: @user.id) %>
    <%= link_to 'unfollow', [@user, :follow], method: :delete %>
  <% else %>
    <%= link_to 'follow', [@user, :follow], method: :post %>
  <% end %>
<% end %>

# ↓↓↓↓↓↓↓↓↓↓ ここから ↓↓↓↓↓↓↓↓↓↓
<div>
  <%= link_to 'followings', [@user, :followings] %>
</div>
<div>
  <%= link_to 'followers', [@user, :followers] %>
</div>
# ↑↑↑↑↑↑↑↑↑↑ ここまで ↑↑↑↑↑↑↑↑↑↑

<% @user.photos.each do |photo| %>
  <div>
    <p><%= photo.caption %></p>
    <%= image_tag photo.image %>
  </div>
<% end %>

表示させるページのpathは
@userfollowings
@userfollowers
となります。

これで動作を確認してみます。
以下のようになっていれば成功です。
Image from Gyazo

速くててわかりづらいんですが、sampleでログインしている状態から始まっています。
sampleexampleの詳細ページに飛びフォローする。
②sampleの詳細ページでfollowingsのリンクへ飛ぶと、今フォローしたexampleが確認できる。
③exampleの詳細ページでfollowersのリンクへ飛ぶと、sampleが確認できる。


以上です。お疲れ様でした。

 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Grapeを使ってRESTfulAPIサービスを作成する

概要

grapeを使用してRESTfulなAPIサービスを作成したのでまとめる。
また作成後はPostmanを使用して挙動を確認した。

環境

ruby 2.6.5

Rails 5.2.4

実施したこと

・任意のUserをSeedsファイルに作成。gemはgrapeを使用。APIモードにて作成する。

・Postmanを使ってクライアントからアクセスし、データの登録、更新、削除。データの取得を行う
・アプリケーションには一般的なCRUD機能とデータの取得をJSONを用いて取得できるよう作成した

まずアプリケーションを作成

console
$ rails new sample_app

今回はアプリ名「sample_app」とした。

まずはgemをインストール

下記を追記

Gemfile
gem grape’ # rails、sinatraなどのRackアプリケーションで、RESTfulAPIを簡単に構築できる
gem 'grape-entity # grapeのみだと、値が全て表示されてしまう。grape-entityを使うと、表示項目の制限、整形をすることができる。
gem grape_on_rails_routes  #使用すると、Grape APIによって作成されたルートを簡単に読み書きできる。

続い bundle instalする。

$ bundle install --path vendor/bundle

DBの構成

Usermodelを生成する

console
$ bundle exec rails g model User name:string email:string age:integer
$ rails db:migrate

サンプル用にデータを用意します。

db/seeds.rbを下記の内容で作成します。

db/seeds.rb
User.create(name: 'Sony', email: 'sony@sample.com', age: '40')
User.create(name: 'Fredo', email: 'fred@sample.com', age: '38')
User.create(name: 'Tom', email: 'tom@sample.com', age: '35')
User.create(name: 'Michael', email: 'michael@sample.com', age: '33')
User.create(name: 'Connie', email: 'connie@sample.com', age: '30')

DBにファイルを読み込ませます。

$ bundle exec rake db:seed

ディレクトリ構成

下記のディレクトリ構成にします。

~/sample_api/
  ├ app/api/
  │    └ api.rb
  │    └ resources/
  │          └ v1/
  │            └ root.rb
  │        └ user.rb
  │       └ entities/
  │      └ v1/
  │        └ root_entity.rb
  │        └ user_entity.rb

autoloadの設定

app/apiの下のファイルがautoloadされるようにします。

config/application.rb
module SampleApi
  class Application < Rails::Application
    #..  
    # app/api以下のrbファイルをautoload
   + config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
   + config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]
  end
end

APIの作成

下記の図のような[親-子-孫-ひ孫]の関係でマウントさせます。

config/routes.rb  # [親]ルーティングの設定
  └ app/api/api.rb # [子]API親クラス
      └ app/api/resources/v1/root.rb # [孫]API v1の親クラス
          └ app/api/resources/v1/users.rb # [ひ孫]userAPIのクラス

[親] config/routes.rb

ルーティングの設定をします。

config/routes.rb
Rails.application.routes.draw do
# app/api/api.rbをマウント
+ mount API => /' #この方法では、基本クラスがクラスの各アクションをポイントするため、各アクションにルートを追加する必要はありません。
end

[子] app/api/api.rb

API親クラスを作成します。

app/api/api.rb
class API < Grape::API
  # urlの第1セグメント名 ex) http://localhost/api/
  prefix 'api'
  # app/api/resources/v1/root.rbをマウント
  mount Resources::V1::Root
end

[孫] app/api/resources/v1/root.rb

API v1の親クラスを作成します。

app/api/resources/v1/root.rb
module Resources
  module V1
    class Root < Grape::API
      version 'v1'
      format :json
      content_type :json, 'application/json'

      # app/api/resources/v1/users.rbをマウント
      mount Resources::V1::Users
    end
  end
end


続いてuserAPIのクラスを作成する前にEntityファイルを作成します。
これを作成しないとデータ取得を行った際に作成時間や更新時間など、表示したくないデータが表示されてしまいます。
*全てのデータを取得したい場合は作成不要。

Entityの親クラスを作成

app/api/entities/v1/root_entity.rb
module Entities
  module V1
    class RootEntity < Grape::Entity

    end
  end
end

userAPIのEntityファイルを作成

name, email, ageのみ表示するようにします。

(created_at、updated_atは表示させない)

app/api/entities/v1/user_entity.rb
module Entities
  module V1
    class UserEntity < RootEntity
      # name, email, ageのみ表示する
      expose :name, :email, :age
    end
  end
end

userAPIのクラスを作成します。

app/api/resources/v1/user.rb
module Resources
  module V1
    class Users < Grape::API
      resource :users do
        desc "user list"
        get do
          present User.all, with: Entities::V1::UserEntity  # with:〜〜とすることでentityファイルの内容を適用させます
        end

        desc "create new user"
        params do
          requires :name, type: String
          requires :email, type: String
          requires :age, type: Integer
        end
        post do
          User.create!({ name: params[:name], email: params[:email], age: params[:age] })
        end

        desc "Update user"
        route_param :id do 
          put do
            User.find(params[:id]).update({ name: params[:name], email: params[:email], age: params[:age] })
          end
        end

        desc "show user"
        params do
          requires :id, type: Integer, desc: "user id"
        end
        get ":id" do
          present User.find(params[:id]), with: Entities::V1::UserEntity # こちらのデータ取得も同様です。
        end

        desc "Delete user"
        route_param :id do
           delete do
             user = User.find(params[:id])
             user.destroy
           end
        end
      end
    end
  end
end
$ rails grape:routes or rake grape:routes
        GET  |  /api/:version/users(.:format)      |  v1  |  user list      
       POST  |  /api/:version/users(.:format)      |  v1  |  create new user
        PUT  |  /api/:version/users/:id(.:format)  |  v1  |  Update user    
        GET  |  /api/:version/users/:id(.:format)  |  v1  |  user_show      
     DELETE  |  /api/:version/users/:id(.:format)  |  v1  |  Delete user

上記のコマンドを実行すると、グレープAPIルートのリストを確認できます。

ここまででAPIを作成できました。ここからはPostmanを使用することで実際にCRUD機能、データ取得を正しく行えるか確認していきます。

rails起動後上記URIを入力すれば取得の確認が行える。

Postmanを使ってリクエスト、レスポンスの確認

PostmanのURL
https://www.postman.com/
スクリーンショット 2020-09-18 17.38.25.png

Postmanを起動
登録は指示に従っていけば簡単に行えます。

ローカルサーバー起動

$ rails s

データを作成(Create)

Bodyに登録するデータをJSON形式で設定して、実行する

メソッド:Post

URL:http://localhost:3000/api/v1/users

Body: raw JSON(application/json)

Body
{
  name: “サンプルさん”
  email: sample@example.com
  age: 19
}

Postmanを上記に設定変更しsendボタンを押す
スクリーンショット 2020-09-18 16.20.13.png

下記欄に6番目のidで新たにuserが登録されていることが分かる。

データの編集(update)

登録した情報を更新する。
Body要素に変更する情報を設定する。

メソッド:Put

URL:http://localhost:3000/api/v1/users/:id
(:idはdbに登録されているid値 ここでは6を設定)

Body:raw JSON(application/json)

Body
{
  name: “サンプル”
  email: sample1@example.com
  age: 20
}

スクリーンショット 2020-09-18 16.24.30.png

updateが行われている。

データの削除(destroy)

登録している情報を削除する

メソッド:Delete

URL:http://localhost:3000/api/v1/users/:id

(:idはdbに登録されているid値 ここでは6を設定)

スクリーンショット 2020-09-18 16.29.27.png

先程作成したデータを削除することに成功した。

データの取得(index)

登録した情報をすべて取得する。Seedsデータとして登録されている5つのデータを取得してみる。

メソッド:Get

URL:http://localhost:3000/api/v1/users

スクリーンショット 2020-09-18 16.20.50.png

下記に登録されているデータが確認できる。

データをid別に取得(show)

登録した情報からidを指定して、特定の情報のみを取得する(削除される前のid:6のデータ情報を取得しています。)

メソッド:Get

URL:http://localhost:3000/api/v1/users/:id

スクリーンショット 2020-09-18 16.24.56.png

CRUD機能の挙動については確認することができた。Postmanの機能についてはまだ勉強不足であるので深めていく。

またgrape gemについてもまだ様々な機能があるのでそこも学習し、使いこなせるようにしていく。

*参考  API開発・テスト便利ツール Postmanの使い方メモ
https://qiita.com/zaburo/items/16ac4189d0d1c35e26d1

*参考  Postmanを使ってAPIの確認をしてみる
https://qiita.com/me-654393/items/b14824dd9b37de0da163

*参考  RESTFUL API USING GRAPE IN RAILS
https://www.nascenia.com/restful-api-using-grape-in-rails/

*参考  grapeのgithub
https://github.com/ruby-grape/grape

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】複雑なルーティング構成まとめ

今回は、Usersリソースを中心に、追加構成としてAPIディレクトリを追加してみたり、新たなメソッドとしてsortメソッドを追加したときのパスの状態などを設定するパターンで見ていく

早見表

種類 パス クラス ファイルパス
scope /api/users UsersController /users_controller.rb
namespace /api/users Api::UsersController /api/users_controller.rb
module /users Api::UsersController /api/users_controller.rb
member /users/:id/sort
collection /users/sort

Controllersのディレクトリ構成と、ルーティング

scope

routes.rb
# /api/users

scope :api do
  resources :users
end
ディレクトリ構成
/controllers--
            |
            |--application.rb
            |
            |--users_controller.rb
controller.rb
class UsersController < ApplicationController
end

namespace

routes.rb
# /api/users

namespace :api do
  resources :users
end
ディレクトリ構成
controllers/-
            |
            |--application.rb
            |
            |--api/-
                   |
                   |--users_controller.rb
controller.rb
class Api::UsersController < ApplicationController
end

module

routes.rb
# /users

scope module: :api do
  resources :users
end
ディレクトリ構成
controllers/-
            |
            |--application.rb
            |
            |--api/-
                   |
                   |--users_controller.rb
controller.rb
class Api::UsersController < ApplicationController
end

まったく新しいパスを作る

member

routes.rb
# idを伴うパス /users/:id/sort

resources :users do
  member do
    get :sort
  end
end

省略形

routes.rb
resources :users do
  get :sort, on: :member
end

collection

routes.rb
# idを伴わないパス /users/sort

resources :users do
  collection do
    get :sort
  end
end

省略形

routes.rb
resources :users do
  get :sort, on: :collection
end

参考記事

https://qiita.com/senou/items/f1491e53450cb347606b

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SentryでRailsアプリのエラーをトラッキング

契約

以下より契約
https://sentry.io/signup/

契約時にプロジェクトを作成することになり、プロジェクト作成後の画面にセットアップ方法が書かれているので、手順通りに進めていけば問題無いはず。ただ念のため自分ようにまとめなおします。

基本的な使い方

Gemfile
gem "sentry-raven"
$ bundle install
config/application.rb
module AppName
  class Application < Rails::Application
    # 省略
    Raven.configure do |config|
      config.dsn = "https://#{ENV['SENTRY_KEY']}@#{ENV['SENTRY_SECRET']}.sentry.io/#{ENV['SENTRY_ID']}"
    end
  end
end

Sentryのサイトで表示されるセットアップ方法には、ID等が全て直書きされているので、環境変数にしまいました。

パラメーターやsession情報を拾えるようにする

app/controllers/application.rb
class ApplicationController < ActionController::Base
  before_action :set_raven_context

  private

  def set_raven_context
    Raven.user_context(id: session[:current_user_id]) # or anything else in session
    Raven.extra_context(params: params.to_unsafe_h, url: request.url)
  end
end

パスワードを平文で記録しない

$ touch config/initializers/sentry.rb
config/initializers/sentry.rb
Raven.configure do |config|
  config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s)
end

参考

その他

UIがめっちゃSlack笑
 2020-09-18 16.23.44.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsチュートリ

7.4.2から

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

第4回 SmartHRのライブラリkijiを使ってe-Govを動かす(動作確認編)

ドライバーからSmartHRのライブラリkijiを呼び出し、署名付きxmlを生成します。さらにcurlコマンドで検証環境に署名付きxmlを送信して、e-Govのレスポンス等を確認します。

curlコマンドでは、以下のパラメタ、リクエストURIを指定して、コマンドプロンプトから起動します。

No パラメタ、リクエストURI 説明
1 -X GET又はPOST
2 -d @署名付きxmlファイル名 POSTコマンド時のみ指定
3 -H "x-eGovAPI-AccessKey アクセスキー" ユーザ認証で発行されたキーを指定
4 -H "x-eGovAPI-SoftwareID: ソフトウェアID"
5 -H "Authorization: Basic認証用情報" Basic認証用ID:パスワードのBase64形式でエンコードした値
6 リクエストURI 検証環境向けのhttps形式URI

最後にe-Govの問題点を定義した上で、マイナポータルAPIで改善できるか考察します。

1 ユーザ認証

署名付きxmlを生成した上で、ユーザ認証を行います。

1.1 署名付きxmlを生成する

ドライバーを実行して、register.xmlを生成します。

ユーザ認証向け署名付きxml生成
>ruby make_register_xml.rb
>dir register.xml
2020/08/23  15:30             2,682 register.xml
               1 個のファイル               2,682 バイト
               0 個のディレクトリ     631,570,432 バイトの空き領域

1.2 ユーザ認証を行う

検証環境ではBasic認証が必要です。Basic認証用IDとBasic認証用パスワードを半角コロンで連結し、Base64でエンコードした値を用意し、その値をHTTPリクエストのヘッダ部のAuthorizationに設定します。

curlコマンドより、POSTコマンドでリクエストURIにアクセスすると、次の応答が返ってきます。

curl
>curl -X POST -d @register.xml -H "x-eGovAPI-SoftwareID: ************"
 -H "Authorization: Basic ****************************" 
 https://*****************/****/*/**************/login

<?xml version="1.0" encoding="UTF-8"?>
  <DataRoot>
    <Result>
      <Code>0</Code>
      <Message></Message>
    </Result>
    <ApplData>
    <UserID>*********</UserID>
    <AccessKey>
78zTKYZQrWdLYgC5cP4oTmalTQE8T7uPF+UDzUEYKZnM4o+teUfNgBGtQsEj6OLsLJX8bW72/B1C
me9RmoZLt/YjnLey59EVKzGKVmv/aHQ9ZCCh9zV6gP1h2B8A4/rQOdiXlS9bQ6YGoahaiJzoooW4
n7dF27OCt71+2yTgG6vMnTify8TYVX7ftazfNUfCPm/XPKTg+4PUnSlcXxPrN8dgRiuv6dSeTZFN
6juFvO+PsXIcxr//WtGvMP0V4cFP
    </AccessKey>
    <LastAuthenticationDate>20200702213554</LastAuthenticationDate>
  </ApplData>
</DataRoot>

<AccessKey>タグにはアクセスキーが設定されています。以後、各種APIを呼び出す際、httpリクエストのヘッダにアクセスキーを必ず指定します。また、<LastAuthenticationDate>タグは前回の認証におけるタイムスタンプです。

1.3 署名付きxmlの内容を確認する

ドライバーで生成したregister.xmlを確認します。ハッシュ計算及び署名が行われていることが分かります。

register.xml
<?xml version="1.0" encoding="UTF-8"?>
<DataRoot>
  <ApplData Id="ApplData">
    <UserID>*********</UserID>
  </ApplData>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#" Id="20200721144359">
    <SignedInfo>
      <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
      <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/> 
        <Reference URI="#ApplData">
          <Transforms>
            <Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/> 
          </Transforms>
          <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/> 
          <DigestValue>pfQDAIAVdZvrFvZoCTsCp5juOrxd7G1jS168/dFBm7Y=</DigestValue>
        </Reference>
      </SignedInfo>
      <SignatureValue>
Zrm+z6efKMPj2ugb/jFPPAOFasMSRgJDAFv63Qz2XeKnAc5pLPq1F3HXqrcZRY0zueyVkC2ceB9N
3EO+b/cXvpS2egSp4NC2LiFMx02r0qnpgHaxwCEebb5szCq6MgVYE6tL8xzIdP2ToSigMr+16vu9
AkWORFQX0JVyP4J3Fpl3/FvHdPzlId2fwIpZ1GfCdDhgp35oS85RUcP/JLVnqJ9b9V688LQZkP5g
29LqHbsj8626VX6TD6zYQNlr1rTtdoBIGvNs1Ve51ichNfHF4HeA8x7CG/vTN2AsF73dBLBzY0u4
sMD4zEM7nUBbKeuV831DE+2fbxZq+h6r+ThSdg==
      </SignatureValue>
      <KeyInfo>
        <X509Data> 
          <X509Certificate>
MIIEizCCA3OgAwIBAgIEWCsMRzANBgkqhkiG9w0BAQsFADAuMQswCQYDVQQGEwJKUDERMA8GA1UE
CgwIRGVtb01pbjExDDAKBgNVBAsMA0NBMTAeFw0xOTEwMjUwMTE2NTZaFw0yNDEwMjQxNDU5NTla
MEkxCzAJBgNVBAYTAkpQMREwDwYDVQQKDAhEZW1vTWluMTEMMAoGA1UECwwDQ0ExMRkwFwYDVQQD
DBBJY2hpcm8gTWFkb2d1Y2hpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsD81VFFm
GM26HYdeGrGzbPbadeBzU4WxIE1r6qeZLxjz7DiV42tjo8QFulWZk6hmCeb5j9ChKp+BA/9Yj6us
ccKHZrPQmbcXUZkXRXz9z/7CLxp8b1zAzJakZ0g7o9iU3a3TpyeP6V+GFtkO8YqfthEsJsYzx82d
Ui85Bm4l5pnztozPdXRZd2VJpdwzGqtqw6N9PKzMNWux5C0jklkaPeP5P6NHLfeN51+phkY2xkdJ
lry629PeKjZAwN0Z8Xu0JdubCvA95UctoZgBoOjpgDtWImV3o9hDpwu+1tMlEBhNZIuKOr2G38qR
Z9iJUyoK+XhbzyBUu0A2cwCj0MshbwIDAQABo4IBlDCCAZAwDgYDVR0PAQH/BAQDAgbAMBEGCWCG
SAGG+EIBAQQEAwIGQDBOBgNVHRIERzBFpEMwQTELMAkGA1UEBhMCSlAxHjAcBgNVBAoMFeaooeaT
rOawkemWk+iqjeiovOWxgDESMBAGA1UECwwJ77yj77yh77yRMB8GA1UdIAEB/wQVMBMwEQYPAoM4
ho4xCAEBAAKMmyVkMGgGA1UdEQRhMF+kXTBbMQswCQYDVQQGEwJKUDEeMBwGA1UECgwV5qih5pOs
5rCR6ZaT6KqN6Ki85bGAMRIwEAYDVQQLDAnvvKPvvKHvvJExGDAWBgNVBAMMD+eqk+WPo+OAgOS4
gOmDjjBQBgNVHR8ESTBHMEWgQ6BBpD8wPTELMAkGA1UEBhMCSlAxETAPBgNVBAoMCERlbW9NaW4x
MQwwCgYDVQQLDANDQTExDTALBgNVBAMMBENSTDEwHwYDVR0jBBgwFoAUFNPnlvRSx8XFOnRlLumW
95h4I18wHQYDVR0OBBYEFD6UH9Exye9dEpz9MHJ3sbUDpoZSMA0GCSqGSIb3DQEBCwUAA4IBAQBb
CKyFGsqMv6+HkrY0OK+4v40PJQAa/KbOC3JTKooLfNCNXTiTwtWAl1sGN+Ow8pIp8Yvj16VcYpi8
zO4TmNe8NT+u/e2OvBXwJ9OxOs9UNI2m/mXGcSSJ7eXMR3aVCniDU7IaQeicquQttLP9IOk9Ao1W
+BM35y5bITA/BMO5tzgaimp4G484QtF/XLi40rGhaZAHGEfvl0abJXPumjajhnGv7SCkjw4+9qdz
5Dtp6kl+GVshQgo6ofpEWhVzdhfqKhNy8dNRL7C/gOTYm+M9SAFk9syL5xKXRyMUGDOheypiJrW/
QyOjrxs6cFa5VqaZWcRIq8yVPwABCpGG/hjU
          </X509Certificate>
       </X509Data>
    </KeyInfo>
  </Signature>
</DataRoot>

<DigestValue>タグで挟まれている部分がハッシュ値であり、<ApplData>タグ全体をsha256でハッシュ計算した結果です。ハッシュ計算では、ハッシュ値から元の内容を求める事が困難であり、また元の内容を変更するとハッシュ値が変化するという性質があります。

<SignatureValue>タグで挟まれている部分が署名値であり、<SignedInfo>タグ全体を秘密鍵で署名したものです。

<X509Certificate>タグで挟まれている部分が証明書(公開鍵)です。この内容については、検証環境テスト用電子証明書(e-GovEE01_sha2.pfx)の公開鍵の部分と一致しています。

e-Govでは、署名付きxmlを解析し、署名値を公開鍵で復号する事で、送信者本人である事を確認します。さらにハッシュ値が一致する事で改ざんが無いことを確認します。

1.4 ハッシュ値を検証する

ハッシュ値の計算対象は、ユーザIDを含む<ApplData>タグ全体であり、これをSHA-256のアルゴリズムで計算した結果です。この結果が正しいかを確認するため、データ変換ツールを使って検証しました。

データ変換ツールを立ち上げ、<ApplData>タグ全体を変換元(入力)データに設定した上で、次の変換ルールにより変換しました。その結果を署名付きxmlのハッシュ値と比較したところ、結果が一致しました。

  • 入力形式(デコード):プレーンTEXT
  • 文字コード変換:変換不要
  • ハッシュ:SHA-256
  • 出力形式(エンコード):BASE64

2 標準形式による一括申請

curlコマンドを使って、認証、一括申請、送信案件一覧情報取得という順にe-Govにアクセスして、当該手続の状態が「到達」になるまでの各レスポンスを確認します。

2.1 署名付きxmlを生成する

ドライバーを実行して、標準形式による一括申請の署名付きxmlを生成します。

標準形式による一括申請の署名付きxml
>ruby make_zip_file_standard_format.rb

ドライバーを実行すると、zip形式ファイルが作成されます。これをデータ変換ツールでBase64形式エンコードに変換して、apply_data.xmlに取り込んで、申請データを作成します。

kousei.xmlの署名情報タグは次のようになります。

kousei.xml
<署名情報>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#" Id="20200822191454">
    <SignedInfo>
      <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
      <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
      <Reference URI="#%E6%A7%8B%E6%88%90%E6%83%85%E5%A0%B1">
        <Transforms>
          <Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
        <DigestValue>5OBpw3i4ksNiXkS0zjRaeTpdGpX9FqSK+DjyL3J6TPU=</DigestValue>
      </Reference>
      <Reference URI="900A01020000100001_01.xml">
        <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
        <DigestValue>Vb10zdwjOtrhECwy/TyswwTqSzEhWQKDc2B1y7j9v3M=</DigestValue>
      </Reference>
      <Reference URI="%E6%B7%BB%E4%BB%98%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB.docx">
        <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
        <DigestValue>OAjJfENMRUepWtYYSvAqGnSv6nxx4brjif93tCHopKk=</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>
fhdK4cIq4jAfrGM21nnerwL/U0vob2SMI6nkf1O7WgplbSAqFPooViJqcdk4UbulQlV3mR5WLd23
VphT/QWycbL6q4bav5qmesCE9AaGDexNTkn2Do+eWNAGJbFSSNhAwa+mY0QoMtgbR9tVlXxoYPQ1
9y8ak2WABQPSscAwPGqtb4BsDZMgOJKYqx5zPZlb+EnkC4sxnDMtEPqRjLUFAisQsVynbg3FOy0V
dQ6/psJc6GxCe3qAqCWFpUsLlC7WmiadwcITqgWWiDDBevUDVqLLEdYCcOJzQt4PPpGMNIaRUai+
Mec9e6KvSLnn/TGsnBf7z7nu7o4l1UomnPFUEw==
    </SignatureValue>
    <KeyInfo>
      <X509Data>
        <X509Certificate>
MIIEizCCA3OgAwIBAgIEWCsMRzANBgkqhkiG9w0BAQsFADAuMQswCQYDVQQGEwJKUDERMA8GA1UE
CgwIRGVtb01pbjExDDAKBgNVBAsMA0NBMTAeFw0xOTEwMjUwMTE2NTZaFw0yNDEwMjQxNDU5NTla
MEkxCzAJBgNVBAYTAkpQMREwDwYDVQQKDAhEZW1vTWluMTEMMAoGA1UECwwDQ0ExMRkwFwYDVQQD
DBBJY2hpcm8gTWFkb2d1Y2hpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsD81VFFm
GM26HYdeGrGzbPbadeBzU4WxIE1r6qeZLxjz7DiV42tjo8QFulWZk6hmCeb5j9ChKp+BA/9Yj6us
ccKHZrPQmbcXUZkXRXz9z/7CLxp8b1zAzJakZ0g7o9iU3a3TpyeP6V+GFtkO8YqfthEsJsYzx82d
Ui85Bm4l5pnztozPdXRZd2VJpdwzGqtqw6N9PKzMNWux5C0jklkaPeP5P6NHLfeN51+phkY2xkdJ
lry629PeKjZAwN0Z8Xu0JdubCvA95UctoZgBoOjpgDtWImV3o9hDpwu+1tMlEBhNZIuKOr2G38qR
Z9iJUyoK+XhbzyBUu0A2cwCj0MshbwIDAQABo4IBlDCCAZAwDgYDVR0PAQH/BAQDAgbAMBEGCWCG
SAGG+EIBAQQEAwIGQDBOBgNVHRIERzBFpEMwQTELMAkGA1UEBhMCSlAxHjAcBgNVBAoMFeaooeaT
rOawkemWk+iqjeiovOWxgDESMBAGA1UECwwJ77yj77yh77yRMB8GA1UdIAEB/wQVMBMwEQYPAoM4
ho4xCAEBAAKMmyVkMGgGA1UdEQRhMF+kXTBbMQswCQYDVQQGEwJKUDEeMBwGA1UECgwV5qih5pOs
5rCR6ZaT6KqN6Ki85bGAMRIwEAYDVQQLDAnvvKPvvKHvvJExGDAWBgNVBAMMD+eqk+WPo+OAgOS4
gOmDjjBQBgNVHR8ESTBHMEWgQ6BBpD8wPTELMAkGA1UEBhMCSlAxETAPBgNVBAoMCERlbW9NaW4x
MQwwCgYDVQQLDANDQTExDTALBgNVBAMMBENSTDEwHwYDVR0jBBgwFoAUFNPnlvRSx8XFOnRlLumW
95h4I18wHQYDVR0OBBYEFD6UH9Exye9dEpz9MHJ3sbUDpoZSMA0GCSqGSIb3DQEBCwUAA4IBAQBb
CKyFGsqMv6+HkrY0OK+4v40PJQAa/KbOC3JTKooLfNCNXTiTwtWAl1sGN+Ow8pIp8Yvj16VcYpi8
zO4TmNe8NT+u/e2OvBXwJ9OxOs9UNI2m/mXGcSSJ7eXMR3aVCniDU7IaQeicquQttLP9IOk9Ao1W
+BM35y5bITA/BMO5tzgaimp4G484QtF/XLi40rGhaZAHGEfvl0abJXPumjajhnGv7SCkjw4+9qdz
5Dtp6kl+GVshQgo6ofpEWhVzdhfqKhNy8dNRL7C/gOTYm+M9SAFk9syL5xKXRyMUGDOheypiJrW/
QyOjrxs6cFa5VqaZWcRIq8yVPwABCpGG/hjU
        </X509Certificate>
      </X509Data>
    </KeyInfo>
  </Signature>
</署名情報>

2.2 一括申請を行う

curlコマンドで一括申請を行います。「-X POST」「-d @apply_data.xml」等の必要なパラメタを指定します。

HTTPレスポンスでは、<SendNumber>タグに送信番号が設定されています。

curl
>curl -X POST -d @apply_data.xml -H "x-eGovAPI-AccessKey: 
78zTKYZQrWdLYgC5cP4oTmalTQE8T7uPF+UDzUEYKZnM4o+teUfNgBGtQsEj6OLsLJX8bW72/B1C
me9RmoZLt/YjnLey59EVKzGKVmv/aHQ9ZCCh9zV6gP1h2B8A4/rQOdiXlS9bQ6YGoahaiJzoooW4
n7dF27OCt71+2yTgG6vMnTify8TYVX7ftazfNUfCPm/XPKTg+4PUnSlcXxPrN8dgRiuv6dSeTZFN
6juFvO+PsXIcxr//WtGvMP0V4cFP"
 -H "x-eGovAPI-SoftwareID: ************"
 -H "Authorization: Basic ****************************"
 https://*****************/****/*/**************/apply

<?xml version="1.0" encoding="UTF-8"?>
<DataRoot>
  <Result>
    <Code>0</Code>
    <Message></Message>
  </Result>
  <ApplData>
    <SendNumber>202007231845376104</SendNumber>
    <SendDate>20200723184537</SendDate>
    <SendFileName>apply_data.zip</SendFileName>
    <SendApplyCount>1</SendApplyCount>
    <ErrorCount>0</ErrorCount>
  </ApplData>
</DataRoot>

2.3 送信案件一覧情報を取得する

curlコマンドで送信案件一覧情報を取得します。一括申請のレスポンスで取得した送信番号をリクエストURIのパラメタとして設定する点に留意します。

curl
>curl -X GET -H "x-eGovAPI-AccessKey: 
78zTKYZQrWdLYgC5cP4oTmalTQE8T7uPF+UDzUEYKZnM4o+teUfNgBGtQsEj6OLsLJX8bW72/B1C
me9RmoZLt/YjnLey59EVKzGKVmv/aHQ9ZCCh9zV6gP1h2B8A4/rQOdiXlS9bQ6YGoahaiJzoooW4
n7dF27OCt71+2yTgG6vMnTify8TYVX7ftazfNUfCPm/XPKTg+4PUnSlcXxPrN8dgRiuv6dSeTZFN
6juFvO+PsXIcxr//WtGvMP0V4cFP"
 -H "x-eGovAPI-SoftwareID: ************"
 -H "Authorization: Basic ****************************"
 https://*******************/*******/*/******/apply;id=202007231845376104

<?xml version="1.0" encoding="UTF-8"?>
<DataRoot>
  <Result>
    <Code>0</Code>
    <Message></Message>
  </Result>
  <ApplData>
    <SendNumber>202007231845376104</SendNumber>
    <SendDateFrom></SendDateFrom>
    <SendDateTo></SendDateTo>
    <PackageApplyCount>1</PackageApplyCount>
    <PackageApply>
      <No>1</No>
      <SendNumber>202007231845376104</SendNumber>
      <SendDate>20200723184537</SendDate>
      <WorkStatus></WorkStatus>
      <NormalCount>1</NormalCount>
      <AllCount>1</AllCount>
      <SupplementaryMessage></SupplementaryMessage>
      <ErrorCount>0</ErrorCount>
      <ErrorFile></ErrorFile>
      <ApplyCount>0</ApplyCount>
    </PackageApply>
  </ApplData>
</DataRoot>

この段階では、一括申請のデータ形式チェックが行われている状態で、申請案件一覧情報項目の<apply>タグが出力されていません。

しばらくして、もう一度、同じコマンドを実行すると、レスポンスの<Status>タグより、形式チェックが終わり、到達状態に遷移している事を確認できます。この時の到達番号については、<ArriveID>タグより「9002020000002128」であることが分かります。

curl
>curl -X GET -H "x-eGovAPI-AccessKey: 
78zTKYZQrWdLYgC5cP4oTmalTQE8T7uPF+UDzUEYKZnM4o+teUfNgBGtQsEj6OLsLJX8bW72/B1C
me9RmoZLt/YjnLey59EVKzGKVmv/aHQ9ZCCh9zV6gP1h2B8A4/rQOdiXlS9bQ6YGoahaiJzoooW4
n7dF27OCt71+2yTgG6vMnTify8TYVX7ftazfNUfCPm/XPKTg+4PUnSlcXxPrN8dgRiuv6dSeTZFN
6juFvO+PsXIcxr//WtGvMP0V4cFP"
 -H "x-eGovAPI-SoftwareID: ************"
 -H "Authorization: Basic ****************************"
 https://*******************/*******/*/******/apply;id=202007231845376104

<?xml version="1.0" encoding="UTF-8"?>
<DataRoot>
  <Result>
    <Code>0</Code>
    <Message></Message>
  </Result>
  <ApplData>
    <SendNumber>202007231845376104</SendNumber>
    <SendDateFrom></SendDateFrom>
    <SendDateTo></SendDateTo>
    <PackageApplyCount>1</PackageApplyCount>
    <PackageApply>
      <No>1</No>
      <SendNumber>202007231845376104</SendNumber>
      <SendDate>20200723184537</SendDate>
      <WorkStatus></WorkStatus>
      <NormalCount>1</NormalCount>
      <AllCount>1</AllCount>
      <SupplementaryMessage></SupplementaryMessage>
      <ErrorCount>0</ErrorCount>
      <ErrorFile></ErrorFile>
      <ApplyCount>1</ApplyCount>
      <Apply>
        <No>1</No>
        <ArriveID>9002020000002128</ArriveID>
        <MinistryName>デグレ省</MinistryName>
        <ProcName>
APIテスト用手続(労働保険関係手続)(通)0001/APIテスト用手続(労働保険関係手続)(通)0001
        </ProcName>
        <ProcFolderName>900A010200001000(1)</ProcFolderName>
        <ApplicantName>ほげ ほげ</ApplicantName>
        <CorporationtName></CorporationtName>
        <DepartmentName></DepartmentName>
        <Status>到達</Status>
        <StatusDate>20200723184907</StatusDate>
        <PayStatus>-</PayStatus>
        <PayWaitCount>0</PayWaitCount>
        <CorrectStatus>なし</CorrectStatus>
        <CommentCountNotRead>0</CommentCountNotRead>
        <CommentCount>0</CommentCount>
        <DocCountNotDownload>0</DocCountNotDownload>
        <DocCount>0</DocCount>
      </Apply>
    </PackageApply>
  </ApplData>
</DataRoot>

3 個別ファイル署名形式による一括申請

curlコマンドを使って、認証、一括申請、送信案件一覧情報取得、公文書・コメント一覧取得、公文書取得という順にe-Govにアクセスして、各レスポンスを確認します。さらにe-Govが発出する公文書の内容を確認します。

3.1 署名付きxmlを生成する

個別ファイル署名形式による一括申請の署名付きxml
>ruby make_zip_file_individual_signature_format.rb

ドライバーを実行すると、zip形式ファイルが作成されます。これをデータ変換ツールでBase64形式エンコードに変換して、apply_data.xmlに取り込んで、申請データを作成します。

kousei20200716142110000.xmlの署名情報タグは次のようになります。

kousei20200716142110000.xml
<署名情報>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#" Id="20200907161432">
    <SignedInfo>
      <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
      <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
      <Reference URI="#%E6%A7%8B%E6%88%90%E6%83%85%E5%A0%B1">
        <Transforms>
          <Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
        <DigestValue>fy/hXgyLyi66Vu8MmkmLVshFMOckyFz5FL4cXx88LFc=</DigestValue>
      </Reference>
      <Reference URI="950A10181002400001_01.xml">
        <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
        <DigestValue>R657a0a/AIViJFr/JMi7hl350io/wOr4hSuNRmVfcsQ=</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>
cccorxnQJsDjeLvf05LcpwX8E2VfowS0kDPXBi9KoSnC00C9VMEqXxJGDvABi5+rCm+TL49KGGVl
mTYSCBtbXIWScKf/ep1wNro33lWN99+Xtoqpbeb7pqja9XkFzcR9XTtRfJBtjVjPSlAg36s+0cUm
WAQmXNokYeiYcyLojuO18tiiywJxxV4605UjhSF67WVQ4HcEJlbqnb4VvlwJYsN8YDSJzCktBuHM
/bvM8HEi4DOGhJ+H7zmtJ6Gjzlm/SYyD5r8/kqNOMtTSx63X1B29dpC6hcKDuo+UQxsPFZbh06NU
vZYCDvIpzmNrrhwdj0dZaOliVfgaCtDUC3ed3g==
    </SignatureValue>
    <KeyInfo>
      <X509Data>
        <X509Certificate>
MIIEizCCA3OgAwIBAgIEWCsMRzANBgkqhkiG9w0BAQsFADAuMQswCQYDVQQGEwJKUDERMA8GA1UE
CgwIRGVtb01pbjExDDAKBgNVBAsMA0NBMTAeFw0xOTEwMjUwMTE2NTZaFw0yNDEwMjQxNDU5NTla
MEkxCzAJBgNVBAYTAkpQMREwDwYDVQQKDAhEZW1vTWluMTEMMAoGA1UECwwDQ0ExMRkwFwYDVQQD
DBBJY2hpcm8gTWFkb2d1Y2hpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsD81VFFm
GM26HYdeGrGzbPbadeBzU4WxIE1r6qeZLxjz7DiV42tjo8QFulWZk6hmCeb5j9ChKp+BA/9Yj6us
ccKHZrPQmbcXUZkXRXz9z/7CLxp8b1zAzJakZ0g7o9iU3a3TpyeP6V+GFtkO8YqfthEsJsYzx82d
Ui85Bm4l5pnztozPdXRZd2VJpdwzGqtqw6N9PKzMNWux5C0jklkaPeP5P6NHLfeN51+phkY2xkdJ
lry629PeKjZAwN0Z8Xu0JdubCvA95UctoZgBoOjpgDtWImV3o9hDpwu+1tMlEBhNZIuKOr2G38qR
Z9iJUyoK+XhbzyBUu0A2cwCj0MshbwIDAQABo4IBlDCCAZAwDgYDVR0PAQH/BAQDAgbAMBEGCWCG
SAGG+EIBAQQEAwIGQDBOBgNVHRIERzBFpEMwQTELMAkGA1UEBhMCSlAxHjAcBgNVBAoMFeaooeaT
rOawkemWk+iqjeiovOWxgDESMBAGA1UECwwJ77yj77yh77yRMB8GA1UdIAEB/wQVMBMwEQYPAoM4
ho4xCAEBAAKMmyVkMGgGA1UdEQRhMF+kXTBbMQswCQYDVQQGEwJKUDEeMBwGA1UECgwV5qih5pOs
5rCR6ZaT6KqN6Ki85bGAMRIwEAYDVQQLDAnvvKPvvKHvvJExGDAWBgNVBAMMD+eqk+WPo+OAgOS4
gOmDjjBQBgNVHR8ESTBHMEWgQ6BBpD8wPTELMAkGA1UEBhMCSlAxETAPBgNVBAoMCERlbW9NaW4x
MQwwCgYDVQQLDANDQTExDTALBgNVBAMMBENSTDEwHwYDVR0jBBgwFoAUFNPnlvRSx8XFOnRlLumW
95h4I18wHQYDVR0OBBYEFD6UH9Exye9dEpz9MHJ3sbUDpoZSMA0GCSqGSIb3DQEBCwUAA4IBAQBb
CKyFGsqMv6+HkrY0OK+4v40PJQAa/KbOC3JTKooLfNCNXTiTwtWAl1sGN+Ow8pIp8Yvj16VcYpi8
zO4TmNe8NT+u/e2OvBXwJ9OxOs9UNI2m/mXGcSSJ7eXMR3aVCniDU7IaQeicquQttLP9IOk9Ao1W
+BM35y5bITA/BMO5tzgaimp4G484QtF/XLi40rGhaZAHGEfvl0abJXPumjajhnGv7SCkjw4+9qdz
5Dtp6kl+GVshQgo6ofpEWhVzdhfqKhNy8dNRL7C/gOTYm+M9SAFk9syL5xKXRyMUGDOheypiJrW/
QyOjrxs6cFa5VqaZWcRIq8yVPwABCpGG/hjU
        </X509Certificate>
      </X509Data>
    </KeyInfo>
  </Signature>
</署名情報>

kousei20200716142110001.xmlの署名情報タグについては省略します。

3.2 一括申請を行う

curlコマンドで一括申請を行います。

curl
>curl -X POST -d @apply_data.xml -H "x-eGovAPI-AccessKey: 
RJc23F21t9vhjtaleH9N/Y9fhDJ9O8u0q+A5MHa50opasIe+cNVgM+8MJ9VVwR2gjiqIHH9Fr0oy
46lRRXZTaW6mbs4dWFvK6VUoj/tUwgd7XorYA7KtpGhQGVjc3E37Z6Di167pFycmB6+SjfalAgF6
eyQPWzvs9Kl7IgP68NqMbp0neFNCprL0qgUM1NxxuxlsJbkF1cbp4FJ3rt2VpPUEosfe1+Nmj8w9
sCvEQKSKBDTytDwpFCU6IzMuH3m3"
 -H "x-eGovAPI-SoftwareID: ************"
 -H "Authorization: Basic ****************************"
 https://*******************/*******/*/******/apply 

<?xml version="1.0" encoding="UTF-8"?>
<DataRoot>
  <Result>
    <Code>0</Code>
    <Message></Message>
  </Result>
  <ApplData>
    <SendNumber>202007252053026176</SendNumber>
    <SendDate>20200725205302</SendDate>
    <SendFileName>apply_data.zip</SendFileName>
    <SendApplyCount>1</SendApplyCount>
    <ErrorCount>0</ErrorCount>
  </ApplData>
</DataRoot>

3.3 送信案件一覧情報を取得する

curlコマンドで送信案件一覧情報を取得します。

curl
>curl -X GET -H "x-eGovAPI-AccessKey: 
NN1s3WgK2pcwv/5aELtNyRlcTjENG+pM4yAX3gU6NeSyCxACKwBXTUsdx4oxAmv75gXGsdvS6lFX
n2Ar5RNCUlwpjRhGbW8jZzoW5CCXQYOLLlNl21sffoKodYSMIr1GPMzJSeQTC+tT0gGFqAsyQxm6
Iethc/5AFJgdPX3bDAC9g2OvMDDNrECKY1/XaGCXDckIeI8H1SLz7mDi4Q1ZYofF5gDCFcSPsNSJ
A2fuEZoPRoHVBw/mzo7/qBFA1wWY"
 -H "x-eGovAPI-SoftwareID: ************"
 -H "Authorization: Basic ****************************"
 https://*******************/*******/*/******/apply;id=202007252053026176

<?xml version="1.0" encoding="UTF-8"?>
<DataRoot>
  <Result>
    <Code>0</Code>
    <Message></Message>
  </Result>
  <ApplData>
    <SendNumber>202007252053026176</SendNumber>
    <SendDateFrom></SendDateFrom>
    <SendDateTo></SendDateTo>
    <PackageApplyCount>1</PackageApplyCount>
    <PackageApply>
      <No>1</No>
      <SendNumber>202007252053026176</SendNumber>
      <SendDate>20200725205302</SendDate>
      <WorkStatus>処理待ち</WorkStatus>
      <NormalCount>0</NormalCount>
      <AllCount>1</AllCount>
      <SupplementaryMessage></SupplementaryMessage>
      <ErrorCount>0</ErrorCount>
      <ErrorFile></ErrorFile>
      <ApplyCount>0</ApplyCount>
    </PackageApply>
  </ApplData>
</DataRoot>

<WorkStatus>タグより、処理待ち状態です。しばらくして、もう一度、同じコマンドを実行すると、形式チェックが始まっている様子です。

curl
>curl -X GET -H "x-eGovAPI-AccessKey: 
NN1s3WgK2pcwv/5aELtNyRlcTjENG+pM4yAX3gU6NeSyCxACKwBXTUsdx4oxAmv75gXGsdvS6lFX
n2Ar5RNCUlwpjRhGbW8jZzoW5CCXQYOLLlNl21sffoKodYSMIr1GPMzJSeQTC+tT0gGFqAsyQxm6
Iethc/5AFJgdPX3bDAC9g2OvMDDNrECKY1/XaGCXDckIeI8H1SLz7mDi4Q1ZYofF5gDCFcSPsNSJ
A2fuEZoPRoHVBw/mzo7/qBFA1wWY"
 -H "x-eGovAPI-SoftwareID: ************"
 -H "Authorization: Basic ****************************"
 https://*******************/*******/*/******/apply;id=202007252053026176

<?xml version="1.0" encoding="UTF-8"?>
<DataRoot>
  <Result>
    <Code>0</Code>
    <Message></Message>
  </Result>
  <ApplData>
    <SendNumber>202007252053026176</SendNumber>
    <SendDateFrom></SendDateFrom>
    <SendDateTo></SendDateTo>
    <PackageApplyCount>1</PackageApplyCount>
    <PackageApply>
      <No>1</No>
      <SendNumber>202007252053026176</SendNumber>
      <SendDate>20200725205302</SendDate>
      <WorkStatus>処理待ち</WorkStatus>
      <NormalCount>0</NormalCount>
      <AllCount>1</AllCount>
      <SupplementaryMessage></SupplementaryMessage>
      <ErrorCount>0</ErrorCount>
      <ErrorFile></ErrorFile>
      <ApplyCount>0</ApplyCount>
    </PackageApply>
  </ApplData>
</DataRoot>

さらにしばらく待ち、もう一度、同じコマンドを実行すると、到達状態から審査終了に遷移しました。<ArriveID>タグには到達番号が設定されています。

curl
>curl -X GET -H "x-eGovAPI-AccessKey: 
NN1s3WgK2pcwv/5aELtNyRlcTjENG+pM4yAX3gU6NeSyCxACKwBXTUsdx4oxAmv75gXGsdvS6lFX
n2Ar5RNCUlwpjRhGbW8jZzoW5CCXQYOLLlNl21sffoKodYSMIr1GPMzJSeQTC+tT0gGFqAsyQxm6
Iethc/5AFJgdPX3bDAC9g2OvMDDNrECKY1/XaGCXDckIeI8H1SLz7mDi4Q1ZYofF5gDCFcSPsNSJ
A2fuEZoPRoHVBw/mzo7/qBFA1wWY"
 -H "x-eGovAPI-SoftwareID: ************"
 -H "Authorization: Basic ****************************"
 https://*******************/*******/*/******/apply;id=202007252053026176

<?xml version="1.0" encoding="UTF-8"?>
<DataRoot>
  <Result>
    <Code>0</Code>
    <Message></Message>
  </Result>
  <ApplData>
    <SendNumber>202007252053026176</SendNumber>
    <SendDateFrom></SendDateFrom>
    <SendDateTo></SendDateTo>
    <PackageApplyCount>1</PackageApplyCount>
    <PackageApply>
      <No>1</No>
      <SendNumber>202007252053026176</SendNumber>
      <SendDate>20200725205302</SendDate>
      <WorkStatus></WorkStatus>
      <NormalCount>1</NormalCount>
      <AllCount>1</AllCount>
      <SupplementaryMessage></SupplementaryMessage>
      <ErrorCount>0</ErrorCount>
      <ErrorFile></ErrorFile>
      <ApplyCount>1</ApplyCount>
      <Apply>
        <No>1</No>
        <ArriveID>9502020000056054</ArriveID>
        <MinistryName>APIステータス省</MinistryName>
        <ProcName>
APIテスト用手続(社会保険関係手続)(個)1006/APIテスト用手続(社会保険関係手続)(個)1006
        </ProcName>
        <ProcFolderName>950A102200039000(1)</ProcFolderName>
        <ApplicantName>松井 英夫</ApplicantName>
        <CorporationtName></CorporationtName>
        <DepartmentName></DepartmentName>
        <Status>審査終了</Status>
        <StatusDate>20200725210114</StatusDate>
        <PayStatus>-</PayStatus>
        <PayWaitCount>0</PayWaitCount>
        <CorrectStatus>なし</CorrectStatus>
        <CommentCountNotRead>0</CommentCountNotRead>
        <CommentCount>0</CommentCount>
        <DocCountNotDownload>1</DocCountNotDownload>
        <DocCount>1</DocCount>
      </Apply>
    </PackageApply>
  </ApplData>
</DataRoot>

3.4 公文書・コメント一覧を取得する

curlコマンドで公文書・コメント一覧を取得します。送信案件一覧情報取得のレスポンスで取得した到達番号をリクエストURIの一部に設定する点に留意します。

curl
>curl -X GET -H "x-eGovAPI-AccessKey: 
NN1s3WgK2pcwv/5aELtNyRlcTjENG+pM4yAX3gU6NeSyCxACKwBXTUsdx4oxAmv75gXGsdvS6lFX
n2Ar5RNCUlwpjRhGbW8jZzoW5CCXQYOLLlNl21sffoKodYSMIr1GPMzJSeQTC+tT0gGFqAsyQxm6
Iethc/5AFJgdPX3bDAC9g2OvMDDNrECKY1/XaGCXDckIeI8H1SLz7mDi4Q1ZYofF5gDCFcSPsNSJ
A2fuEZoPRoHVBw/mzo7/qBFA1wWY"
 -H "x-eGovAPI-SoftwareID: ************"
 -H "Authorization: Basic ****************************"
 https://*******************/*******/*/******/notice/9502020000056054

<?xml version="1.0" encoding="UTF-8"?>
<DataRoot>
  <Result>
    <Code>0</Code>
    <Message></Message>
  </Result>
  <ApplData>
    <ArriveID>9502020000056054</ArriveID>
    <ProcName>APIテスト用手続(社会保険関係手続)(個)1006/APIテスト用手続(社会保険関係手続)(個)1006</ProcName>
    <Official>
    <NoticeSubID>1</NoticeSubID>
    <AllowedDate>20200725210114</AllowedDate>
    <DocTitle>署名つき公文書</DocTitle>
      <File>
        <FileName>official_doc1.xml</FileName>
      </File>
      <DownloadExpiredDate>20201122000000</DownloadExpiredDate>
      <DownloadDate></DownloadDate>
      <Sign>あり</Sign>
      <ExpiredDateFlag>0</ExpiredDateFlag>
    </Official>
  </ApplData>
</DataRoot>

<DocTitle>タグより、サブID「1」の署名付き公文書が発出されています。また、<DownloadExpiredDate>タグより、取得期限が2020年11月20日までです。

3.5 公文書を取得する

curlコマンドで公文書を取得します。ここでも送信案件一覧情報取得のレスポンスで取得した到達番号「9502020000056054」及びサブID「1」をリクエストURIの一部に設定する点に留意します。

curl
>curl -X GET -H "x-eGovAPI-AccessKey: 
NN1s3WgK2pcwv/5aELtNyRlcTjENG+pM4yAX3gU6NeSyCxACKwBXTUsdx4oxAmv75gXGsdvS6lFX
n2Ar5RNCUlwpjRhGbW8jZzoW5CCXQYOLLlNl21sffoKodYSMIr1GPMzJSeQTC+tT0gGFqAsyQxm6
Iethc/5AFJgdPX3bDAC9g2OvMDDNrECKY1/XaGCXDckIeI8H1SLz7mDi4Q1ZYofF5gDCFcSPsNSJ
A2fuEZoPRoHVBw/mzo7/qBFA1wWY"
 -H "x-eGovAPI-SoftwareID: ************"
 -H "Authorization: Basic ****************************"
 https://*******************/*******/*/******/officialdocument/9502020000056054/1

<?xml version="1.0" encoding="UTF-8"?>
<DataRoot>
  <Result>
    <Code>0</Code>
    <Message></Message>
  </Result>
  <ApplData>
    <ArriveID>9502020000056054</ArriveID>
    <NoticeSubID>1</NoticeSubID>
    <Download>
      <FileData>
UEsDBBQACAAIAGCp+VAAAAAAAAAAAAAAAAARAAAAb2ZmaWNpYWxfZG9jMS54bWytV1uv4tYVfkfi
PxyRRzRjLoZjVxyi7SsGfMVcXyoDxgYDNrbBhqeBk2tn1KRJpukkVSfTROk0aSeJ0ksSpZ0f45xz
Jk/5C93AnAk5M4naKkiW9lp77W9967L3EoXnw8n4aKG73tCeniTS11OJI33as/vDqXGSqKvMNSxx
5PnatK+N7al+kpjaieeLhe2pa56/HOueqev+kb904J6vhz4SeuPEkenqg5OEPRgMe0Nt/Mu+3Utf
3248X4zHCpRIHjVopcaJwt4jVB7BX4FRREF9LOwUNZpUaLJdfPThR2ef3S4gl/KhiQp4CRqs4e5u
ebDHCywnMOKR2pbok0QK/q6l06l0orhdFpDH25fekUP3BUKk2kccdZKAbLfrxA+dtqt0rUTT6lFd
4Z4VKHJoDiEEsQgk7loqlS4ge/FwH6j0Y5KQXDqHpDNIZmsI9Yd26uGpnQYwTPHiozfPXnu/gGzX
V7YFwNPFb7648e3prwvITrhiUBIFUeEYsnj+4GEBeSId+ER+4LQA6mpJVJ5J45+vnd28c/H79Y8z

(省略)

DsArHMBXOICvcABf4QC+wgF8hQP4CgfwFQ7gKxzAVziAr3CAXuEAvcIBeoUD9AoH6BUO0CscoFc4
QK9wgF7hAL3CAX6FA/wKB/gVDvArHOBXOMCvcIBf4QC/wgF+hQP8Cgf7Kxzsr3Cwv8LB/goH+ysc
7K9wsL/Cwf4KB/M/8mMDv+zzv0r+/bwHAIH//Y4CcHBwsP+XB3zZHv/9zgO+LKv/iHN2NLCwNnH8
87jz1yGH7f/1yeavs4uBo/Nf73Igy8vmREKgoBCRE/2f9zvz/5fUmOQdTVyJ/8zoz3OfkrMN8Z8D
9t85s71siL9z/j9QSwcIOMaRTY3BAAAA0AAAUEsBAhQAFAAIAAgAYKn5UEytAV1HCgAAGBEAABEA
AAAAAAAAAAAAAAAAAAAAAG9mZmljaWFsX2RvYzEueG1sUEsBAhQAFAAIAAgAYKn5UIAs/hjbAgAA
hA4AABEAAAAAAAAAAAAAAAAAhgoAAG9mZmljaWFsX2RvYzEueHNsUEsBAhQAFAAIAAgAYKn5UDjG
kU2NwQAAANAAABEAAAAAAAAAAAAAAAAAoA0AAG9mZmljaWFsX2RvYzEucGRmUEsFBgAAAAADAAMA
vQAAAGzPAAAAAA==
      </FileData>
    </Download>
  </ApplData>
</DataRoot>

<FileData>タグで囲まれた部分が、Base64形式でエンコードされた公文書データです。これをデータ変換ツールを使ってデコードした上でバイナリファイルに出力します。出力されたファイルの拡張子をzipに変更して解凍すると、次の公文書が得られます。

  • official_doc1.pdf
  • official_doc1.xml
  • official_doc1.xsl

official_doc1.pdfは次の内容です。

公文書.png

4 まとめ

今回の試行を通して見えてきたe-Govの問題点を定義した上で、マイナポータルAPIで改善できるか考察します。

マイナポータルとは、政府が運営するオンラインサービスです。子育てや法人設立等に関する行政手続をワンストップで行えたり、行政機関からのお知らせの確認等が行えます。マイナポータルでは、申請API等が用意される予定であり、今後も、順次、機能拡充が行われます。

マイナポータルAPIの仕様書については、ソフトウェア開発業者向け専用ページに入手方法が記載されています。

4.1 e-Govの問題点を定義する

(1)HTTPレスポンスがXML形式であるため扱いにくい

HTTPレスポンスについては、一般的にはJson形式が多いです。一方、e-GovはXML形式です。XML形式だとテキストしか扱えず、integerやboolean、nullといった基本的な型すら存在しません。バイナリデータを含めるためには、Base64形式でエンコードする必要があるため、ソフトウェアサービス側で変換コードを記述する必要があり、扱いにくいです。

(2)公文書及びコメント通知の機械読みを想定していない

各府省が発出する公文書及びコメント通知については、機械読みを想定しておらず、形式も公開されていません。ファイルもxml+xsl、PDFとバラバラです。公文書及びメッセージを固めたZipファイルをBase64形式でエンコードした結果を返す方式であり、各府省が付与する情報をHTTPレスポンスから直接取り出すことができません。

各府省が発出する情報としては、次の情報があります。これらは一例であり、実際は沢山の情報があります。

  • 雇用保険に新規適用される事で付与される雇用保険適用事業所番号
  • 雇用保険の新規加入者に付与される雇用保険被保険者番号
  • 社会保険料に関する改定後の標準報酬月額
  • 申請に対する返戻理由等

現状は、これらの情報をソフトウェアサービスに反映させるため、ファイル展開をした後、人手で公文書及びコメント通知を確認して入力する必要があり、無駄な手間や入力ミス等が生じます。

公文書及びコメント通知については、公的機関が発出した書類であり、改ざんが出来ないようにする事及び改善されても後から分かる事を目的としているため、このような仕様になっていると考えます。

4.2 マイナポータルAPIで問題点が改善するか

マイナポータルAPIでは、HTTPレスポンスがJson形式になっており、e-Govの1つ目の問題点については、改善されます。一方、2つ目の問題点については、e-Govと同様にHTTPレスポンス内に公文書等のファイルをBase64エンコード形式で返す方式になっています。HTTPレスポンスから各府省が発出する情報を直接取得する事ができません。

マイナポータルAPIには、e-Govに存在しない機能として「自己情報所得API」が用意されています。「自己情報取得API」は、ユーザが行政所有の自己情報を確認するだけにとどまらず、Webサービス事業者に自己情報を提供することまで可能にします。

例えば、金融機関でローンを組む際、金融機関から所得証明書(課税証明書)の提出が求められるとします。現状は、ユーザが行政窓口に赴き、所得証明書(課税証明書)を発行してもらい、金融機関に提出します。一方、自己情報取得APIによるシステム間連携が実現すれば、金融機関がマイナポータルから所得情報を提供してもらう事が可能です。ユーザが行政窓口に赴く必要はなく、利便性が向上します。

この自己情報所得APIを使えば、行政が持つ自己の雇用保険被保険者番号や標準報酬月額等の情報を取得できます。今後、社労士等の代理人の操作により行政側の情報を取得する事が可能になれば、2つ目の問題点も改善できる可能性があります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

第3回 SmartHRのライブラリkijiを使ってe-Govを動かす(実行環境構築編)

kijiは、SmartHRに組み込まれているライブラリであり、GitHubで公開されています。このライブラリはRubyで作られたOSSであり、誰でも入手・改造・再頒布が可能です。
SmartHR kiji

ここでは、kijiを呼び出して署名付きxmlを生成するドライバーを開発します。

1.kijiのフォルダ構成

GitHubからkijiをダウンロードします。フォルダ構成は次のとおりです。

kiji-masterフォルダ構成
kiji-master/
        ├ bin/
        ├ lib/         # 開発ソース一式
        │  └ kiji/
        └ spec/        # テスト環境一式

kiji-master直下にあるlibに開発ソース一式、specにテスト環境一式があります。kiji-master直下にあるkiji.gemspecを確認すると、kijiが依存する他のgemパッケージのバージョンを確認できます。

後述しますが、開発ソースを一部修正する必要が生じたため、ローカル環境でkijiをビルドしました。kiji.gemspecのspec.filesよりGitHubからファイル一覧を取得するようになっていたので、ローカルフォルダーから取得するように変更しました。

lib内の開発ソースを以下に示します。

libフォルダ構成
lib/
  ├ kiji.rb
  └ kiji/
       ├ access.rb
       ├ api.rb
       ├ authentication.rb
       ├ client.rb
       ├ digester.rb
       ├ signer.rb
       ├ version.rb
       └ zipper.rb

kiji.rbを確認すると、アプリケーションが、version.rb、client.rb、zipper.rbを直接取り込んでいる事が分かります。

kiji.rb
require 'kiji/version.rb'
require 'kiji/client.rb'
require 'kiji/zipper.rb'

2.kijiの構造について

kijiは4つのクラスと2つのモジュールから構成されます。

kijiの構造
module kiji
  ├ class Zipper             # zipファイルに固める  
  ├ class Client             # e-Gov外部連携APIに送信  
  ├ class Signer             # 署名付きxml作成  
  ├ class Digester           # デジタル署名作成  
  ├ module Authentication    # 認証関係のユーティリティ  
  └ module Access            # e-Gov関係のユーティリティ

4つのクラスの階層としては、class Zipperとclass Clientが最上位にあり、その下にclass Signer、class Digesterと続きます。クラス図で書くと次のようになります。
kijiクラス関連図.png

e-Gov電子申請は、次の手順で行われます。

①署名付きxmlを作成する
②署名付きxml、添付ファイル等をzipファイルに固める
③zipファイルをBase64形式にエンコードして、送信データに格納する
④e-Gov外部連携APIのhttpリクエストボディに③を設定した上で送信する

class Zipperは①②、class Clientは③④の機能を持ちます。

今回は、class Zipperを利用して①②を行い、e-Govへの送信はcurlコマンドを利用する事にしました。これは、署名付きxmlの内容及びe-Govからのレスポンスを確認するためです。

3.windows環境の改行コードに起因するkijiの修正

zipper.rbにて、申請書のハッシュ値を計算する際、署名対象となるxmlを読み込む箇所に問題があり、コードを修正しました。

オリジナルのコード「app_doc = File.read(app_file_path)」では、xmlをテキストモードで読み込むため、windows形式の改行コード"\r\n"を内部で自動的に"\n"に置き換えてしまい、その結果に対してハッシュ値を計算していました。そのため、改行コードの置き換えをしないバイナリモードで読み込むように修正しました。

zipper.rb
      # 申請書のハッシュ値を求める
      app_file_paths.each do |app_file_path|
-       app_doc = File.read(app_file_path)
+       f = File.open(app_file_path, "rb")
+       app_doc = f.read
        app_file_name = File.basename(app_file_path)
        signer.digest_file!(app_doc, id: app_file_name)
      end

これによって、ローカルgemを作成する事になりました。kiji.gemspecのspec.filesをローカル先に変更した後、kijiをビルドして、ローカル環境にインストールしました。

kijiビルド
\kiji-master>rake build
kiji 0.2.2 built to pkg/kiji-0.2.2.gem.


\kiji-master>dir pkg
2020/08/21  11:42    <DIR>          .
2020/08/21  11:42    <DIR>          ..
2020/08/16  20:54            13,312 kiji-0.2.2.gem


\kiji-master>gem install pkg\kiji-0.2.2.gem
Successfully installed kiji-0.2.2
Parsing documentation for kiji-0.2.2
Installing ri documentation for kiji-0.2.2
Done installing documentation for kiji after 0 seconds
1 gem installed

4.テストデータを用意する

一括申請を行う際に必要になるテストデータを用意します。テストデータとしては、標準形式及び個別ファイル署名形式があります。この際、参照すべき公開資料としては、検証環境テスト用手続に関する資料です。公開資料「APIテスト用手続一覧」には、以下の情報が掲載されています。

APIテスト用手続一覧
手続情報
├ 手続識別子
├ 手続名称(構成管理XMLの「手続名称タグ」に記載する名称)
├ 様式ID
├ 申請書様式名称
├ 申請書様式仕様パターン
├ 受付行政機関ID
├ 分類
├ ひな形とした手続に関する情報
│    ├ 手続識別子
│    └ 手続名
├ 手続に関する条件
│    ├ 申請種別
│    ├ 申請書(手続識別子、申請書様式バージョン)
│    ├ 署名(有無、署名最大人数)
│    ├ 添付書類(有無、必須/任意/添付不可、書類名、固定/任意、書類名、送付方法)
│    ├ 提出先(提出先識別子、提出先名称、大分類、中分類、小分類)
│    ├ 単様式/複数様式
│    ├ 取下げ方法
│    ├ 府省照会有無
│    ├ 手数料種別
│    ├ 受付期間
│    ├ 様式
│    ├ 手続削除フラグ
│    └ ステータス自動遷移対象
└ 到達以降の可能処理
     ├ 取下げ(なし、依頼、申請)
     ├ 補正(なし、部分補正、再提出、補正申請)
     ├ 公文書発出(なし、あり)
     ├ 手数料登録(後納)(なし、あり)
     ├ 手続終了(通知、取下げ済み、再提出済み、公文書取得最終)
     └ コメント通知(なし、メッセージあり、ファイルあり)

また、公開資料「APIテスト用手続ステータス遷移一覧」には、新規申請、取下げ依頼、取下げ申請、補正申請毎に、以下の情報が掲載されています。

APIテスト用手続ステータス遷移一覧
ステータス遷移一覧
├ 手続識別子
├ 手続名
├ 申請に対する操作/処理
│    ├ 取下げ
│    ├ 補正申請
│    ├ コメント通知
│    ├ 公文書発出
│    └ 納付ステータス変更
├ 申請ステータスとアクション
│    ├ 申請の事前状態
│    │   ├ 対象の申請
│    │   └ 申請ステータス(申請サブステータス)
│    ├ アクション
│    │   ├ 利用者側の操作
│    │   └ e-Gov側処理の有無
│    └ 申請の事後状態
│         ├ 対象の申請
│         └ 申請ステータス(申請サブステータス)
└ 備考

これらの資料を参考にして、目的とするテストを実施できるようにテストデータを作成します。

4.1 標準形式

テストデータとして、手続識別子「900A010200001000」を取り上げます。この手続は「雇用保険被保険者資格取得届/電子申請」です。添付書類が必須であり、公文書の発出はありません。

必要なファイルは以下の通りです。ファイル名称については、公開資料「外部連携 API申請データ仕様 共通データ仕様書」で仕様が決められているので、その規則に従います。

No ファイル種別 ファイル名称 備考
1 構成管理XML kousei.xml
2 申請書XML 900A01020000100001_01.xml 様式ID(半角英数字18桁)+「_01」固定
3 添付書類 添付ファイル.docx

これらのファイルは次の階層になっています。

ファイル階層
kousei.xml
      ├ 900A01020000100001_01.xml
      └ 添付ファイル.docx

構成管理XML(kousei.xml)に必要な情報は、以下のとおりです。

No 項目 内容 備考
1 手続識別子 900A010200001000
2 手続名称 APIテスト用手続(労働保険関係手続)(通)0001/APIテスト用手続(労働保険関係手続)(通)0001 構成管理XMLの「手続名称タグ」に記載する名称
3 様式ID 900A01020000100001
4 申請書様式名称 APIテスト用手続(労働保険関係手続)(通)0001_01
5 申請書様式仕様パターン 0001 申請書XML構造定義【APIテスト用手続き】におけるパターン
6 受付行政機関ID 100900

構成管理XML(kousei.xml)に記載する手続きに関する条件については、以下のとおりです。

No 項目 内容 備考
1 申請種別 通常申請
2 申請書-手続識別子(申請書) 900A010200001000
3 申請書-申請書様式バージョン 0003
4 署名-有無 署名あり(単署)
5 署名-署名最大人数 1
6 添付書類-有無
7 添付書類-必須/任意 必須
8 添付書類-書類名固定/任意 書類名固定 書類名を「テスト添付書類1」とする
9 添付書類-書類名 テスト添付書類1
10 添付書類-送付方法 添付
11 提出先-提出先識別子 900API00000000001001001
12 提出先-提出先名称 総務省,行政管理局,API

これらの情報を構成管理XML(kousei.xml)のタグに埋め込みます。

kousei.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="999000000000000001.xsl" type="text/xsl"?>
<DataRoot>
  <様式ID>999000000000000001</様式ID>
  <様式バージョン>0001</様式バージョン>
  <STYLESHEET>999000000000000001.xsl</STYLESHEET>
  <構成情報 ID="構成情報">
    <管理情報>
      <手続番号>
        <受付行政機関ID>100900</受付行政機関ID>
        <手続ID>900A010200001000</手続ID>
      </手続番号>
      <手続名称>APIテスト用手続(労働保険関係手続)(通)0001/APIテスト用手続(労働保険関係手続)(通)0001</手続名称>
      <初回受付番号/>
      <申請種別>新規申請</申請種別>
      <申請者連絡先情報>
        <申請者情報>
          <氏名>ほげ ほげ</氏名>
          <氏名フリガナ>フリ ガナ</氏名フリガナ>
          <役職/>
          <法人団体名/>
          <法人団体名フリガナ/>
          <部門名/>
          <部門名フリガナ/>
          <郵便番号>1200001</郵便番号>
          <住所>東京都足立区大谷田</住所>
          <住所フリガナ>トウキョウトアダチクオオヤタ</住所フリガナ>
          <電話番号>12-232-1232</電話番号>
          <FAX番号/>
          <電子メールアドレス>zzz@zzz.zz</電子メールアドレス>
        </申請者情報>
        <連絡先情報>
          <氏名>漢 字</氏名>
          <氏名フリガナ>フリ ガナ</氏名フリガナ>
          <役職/>
          <法人団体名/>
          <法人団体名フリガナ/>
          <部門名/>
          <部門名フリガナ/>
          <郵便番号>1200001</郵便番号>
          <住所>東京都足立区大谷田</住所>
          <住所フリガナ>トウキョウトアダチクオオヤタ</住所フリガナ>
          <電話番号>12-232-1232</電話番号>
          <FAX番号/>
          <電子メールアドレス>zzz@zzz.zz</電子メールアドレス>
        </連絡先情報>
        <委任登録票添付情報>
          <発行番号/>
          <委任登録票名称/>
          <委任登録票ファイル名称/>
        </委任登録票添付情報>
      </申請者連絡先情報>
    </管理情報>
    <添付書類属性情報>
      <添付種別>添付</添付種別>
      <添付書類名称>テスト添付書類1</添付書類名称>
      <添付書類ファイル名称>添付ファイル.docx</添付書類ファイル名称>
      <提出情報>1</提出情報>
    </添付書類属性情報>
    <手数料情報>
      <手数料1>
        <手数料識別子/>
        <略科目コード/>
        <略科目名/>
        <振込金額/>
      </手数料1>
      <手数料2>
        <手数料識別子/>
        <略科目コード/>
        <略科目名/>
        <振込金額/>
      </手数料2>
      <手数料3>
        <手数料識別子/>
        <略科目コード/>
        <略科目名/>
        <振込金額/>
      </手数料3>
      <手数料4>
        <手数料識別子/>
        <略科目コード/>
        <略科目名/>
        <振込金額/>
      </手数料4>
      <手数料5>
        <手数料識別子/>
        <略科目コード/>
        <略科目名/>
        <振込金額/>
      </手数料5>
      <手数料6>
        <手数料識別子/>
        <略科目コード/>
        <略科目名/>
        <振込金額/>
      </手数料6>
    </手数料情報>
    <通信欄/>
    <府省照会情報>
      <府省照会1>
        <府省照会情報ラベル/>
        <府省照会情報/>
      </府省照会1>
      <府省照会2>
        <府省照会情報ラベル/>
        <府省照会情報/>
      </府省照会2>
      <府省照会3>
        <府省照会情報ラベル/>
        <府省照会情報/>
      </府省照会3>
      <府省照会4>
        <府省照会情報ラベル/>
        <府省照会情報/>
      </府省照会4>
      <府省照会5>
        <府省照会情報ラベル/>
        <府省照会情報/>
      </府省照会5>
      <府省照会6>
        <府省照会情報ラベル/>
        <府省照会情報/>
      </府省照会6>
      <府省照会7>
        <府省照会情報ラベル/>
        <府省照会情報/>
      </府省照会7>
      <府省照会8>
        <府省照会情報ラベル/>
        <府省照会情報/>
      </府省照会8>
      <府省照会9>
        <府省照会情報ラベル/>
        <府省照会情報/>
      </府省照会9>
      <府省照会10>
        <府省照会情報ラベル/>
        <府省照会情報/>
      </府省照会10>
    </府省照会情報>
    <提出先情報>
      <提出先識別子>900API00000000001001001</提出先識別子>
      <提出先名称>総務省,行政管理局,API</提出先名称>
    </提出先情報>
  <申請書属性情報><申請書様式ID>900A01020000100001</申請書様式ID><申請書様式バージョン>0003</申請書様式バージョン>
  <申請書様式名称>APIテスト用手続(労働保険関係手続)(通)0001_01</申請書様式名称><申請書ファイル名称>900A01020000100001_01.xml</申請書ファイル名称></申請書属性情報></構成情報>
</DataRoot>

申請書XML(900A01020000100001_01.xml)について、次のように作成します。

900A01020000100001_01.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="900A01020000100001.xsl" ?>
<DataRoot>
  <様式ID>900A01020000100001</様式ID>
  <様式バージョン>0003</様式バージョン>
  <STYLESHEET>900A01020000100001.xsl</STYLESHEET>
  <様式コピー情報>0</様式コピー情報>
  <Doctype>1</Doctype>
  <G00005-A-250045-001_1>
  <帳票種別>13101</帳票種別>
  <被保険者番号>
    <被保険者番号4桁></被保険者番号4桁>
    <被保険者番号6桁></被保険者番号6桁>
    <被保険者番号CD></被保険者番号CD>
  </被保険者番号>
  <取得>1</取得>
  <被保険者氏名>えーぴーあい 太郎</被保険者氏名>
  <被保険者氏名フリガナ>エーピーアイ タロウ</被保険者氏名フリガナ>
  <変更後の氏名></変更後の氏名>
  <変更後の氏名フリガナ></変更後の氏名フリガナ>
  <性別>男</性別>
  <生年月日>
    <年号>昭和</年号>
    <年>59</年>
    <月>5</月>
    <日>23</日>
  </生年月日>
  <事業所番号>
    <事業所番号4桁>1111</事業所番号4桁>
    <事業所番号6桁>222222</事業所番号6桁>
    <事業所番号CD>3</事業所番号CD>
  </事業所番号>
  <資格取得年月日>
    <年号>平成</年号>
    <年>27</年>
    <月>4</月>
    <日>9</日>
  </資格取得年月日>
  <被保険者となったことの原因>1</被保険者となったことの原因>
  <賃金>
    <支払の態様>1</支払の態様>
    <賃金月額>500</賃金月額>
  </賃金>
  <雇用形態>7</雇用形態>
  <職種>1</職種>
  <取得時被保険者種類区分/>
  <番号複数取得チェック不要/>
  <契約期間の定め>有</契約期間の定め>
  <契約期間の定めの有無>
    <契約期間の定め有>
      <契約期間開始>
        <年号>平成</年号>
        <年>27</年>
        <月>4</月>
        <日>9</日>
      </契約期間開始>
      <契約期間終了>
        <年号>平成</年号>
        <年>28</年>
        <月>3</月>
        <日>31</日>
      </契約期間終了>
      <契約更新条項有無>有</契約更新条項有無>
    </契約期間の定め有>
  </契約期間の定めの有無>
  <一週間の所定労働時間>
    <時間>40</時間>
    <分>0</分>
  </一週間の所定労働時間>
  <事業所名称>株式会社試験</事業所名称>
  <備考欄_申請者>
    <国籍/>
    <在留資格/>
    <在留期間>
      <年/>
      <月/>
      <日/>
    </在留期間>
    <資格外活動許可の有無/>
    <派遣請負労働者/>
    <備考/>
  </備考欄_申請者>
  <事業主>
    <住所>東京都</住所>
    <氏名>テストオーナー</氏名>
    <電話番号>
      <市外局番>03</市外局番>
      <市内局番>3030</市内局番>
      <加入者番号>3333</加入者番号>
    </電話番号>
  </事業主>
  <届出年月日>
    <年号>平成</年号>
    <年>27</年>
    <月>4</月>
    <日>1</日>
  </届出年月日>
  <あて先>品川</あて先>
  <社会保険労務士記載欄>
    <作成年月日>
      <年号>平成</年号>
      <年></年>
      <月></月>
      <日></日>
    </作成年月日>
    <提出代行者事務代理者の表示></提出代行者事務代理者の表示>
    <氏名></氏名>
    <電話番号>
      <市外局番></市外局番>
      <市内局番></市内局番>
      <加入者番号></加入者番号>
    </電話番号>
    <付記欄></付記欄>
  </社会保険労務士記載欄>
  <備考欄_職員>
    <備考></備考>
    <確認通知年月日>
      <年号>平成</年号>
      <年></年>
      <月></月>
      <日></日>
    </確認通知年月日>
  </備考欄_職員>
  <Xmit>0</Xmit>
</G00005-A-250045-001_1>
</DataRoot>

各タグにおけるデータの省略可否、文字数、文字種別等の制約を遵守すれば、形式チェック等は問題なく通ります。テストデータであるため、架空の内容で大丈夫です。

4.2 個別ファイル署名形式

テストデータとして、手続識別子「950A102200039000」を取り上げます。この手続は、「国民年金被保険者資格取得・種別変更・種別確認(第3号被保険者該当)届書/電子申請」です。添付書類が不要であり、公文書が発出されます。

必要なファイルは以下の通りです。

No ファイル種別 ファイル名称 備考
1 構成管理XML kousei.xml
2 申請書XML① 950A10220003900001_01.xml 様式ID(半角英数字18桁)+「_01」固定
3 申請書に対する構成情報XML① kousei20200716142110000.xml ‘kousei’+ yyyyMMddHHmmssSSS
4 申請書XML② 950A10220003900002_01.xml
5 申請書に対する構成情報XML② kousei20200716142115000.xml

申請書に対する構成情報XMLにおける「yyyyMMddHHmmssSSS」 は、申請案件フォルダ内でファイル名を一意とするために付加する当該ファイルの作成日付と時刻(100 分の 1 秒単位)です。これについても、公開資料「外部連携 API申請データ仕様 共通データ仕様書」に記載されています。

これらのファイルは次のような階層になっています。

ファイル階層
kousei.xml
      ├ 950A10220003900001_01.xml
      ├ kousei20200716142110000.xml
      ├ 950A10220003900002_01.xml
      └ kousei20200716142115000.xml

具体的なxmlの内容については、省略します。

5.データ変換ツールについて

今後、テストデータを作成して動作確認を行う際、データ変換ツールを利用します。このツールでは、ハッシュ値の計算、Base64形式エンコード又はデコード、エンコード又はデコード結果のファイルへの出力等を行う事ができます。次のケースでツールを利用します。

  • 署名付きxml生成ドライバーで計算したハッシュ値を検証する
  • 申請データを作成するため、Zip形式の申請データセットをBase64形式にエンコードする
  • e-Govよりダウンロードした公文書をBase64形式デコードしてファイルに出力する

6.署名付きxml生成ドライバーを作成する

今回は、ユーザ登録・認証及び一括申請のために、署名付きxml生成ドライバーをそれぞれ作成します。なお、一括申請では標準形式及び個別ファイル署名形式で署名対象ファイルが異なるため、それぞれに対応するドライバーを作成します。

6.1 ユーザ登録・認証

ユーザ登録・認証の場合、ひな形となるxmlを使わず、kousei.xmlの<ApplData>タグで囲まれた範囲に対して、ハッシュ値を計算し、そのハッシュ値に対して署名を行う点がポイントになります。

ツールを実行する前に、環境変数「EGOV_USER_ID」にe-Gov検証環境利用申請で指定した利用者IDを設定しておく必要があります。

署名付きxml生成ドライバーは次の通りです。

make_register_xml.rb
require 'kiji'

egov_env = {
  "KEY" => File.expand_path("./証明書/e-GovEE01_sha2.pfx"),
  "KEY_PASSWORD" => "gpkitest",
}

user_id = ENV["EGOV_USER_ID"]

pkcs12 = OpenSSL::PKCS12.new(File.open(egov_env["KEY"], "rb"),egov_env["KEY_PASSWORD"])

# ユーザIDを設定する
appl_data = Nokogiri::XML::Builder.new do |xml|
  xml.DataRoot {
    xml.ApplData(Id: 'ApplData') {
      xml.UserID user_id
    }
  }
end

doc = appl_data.to_xml(save_with:  0)

signer = Kiji::Signer.new(doc) do |s|
  s.cert = pkcs12.certificate
  s.private_key = pkcs12.key
  s.digest_algorithm           = :sha256
  s.signature_digest_algorithm = :sha256
end

signer.security_node = signer.document.root

# ダイジェスト値を計算する
signer.document.xpath('/DataRoot/ApplData').each do |node|
  signer.digest!(node, id: '#ApplData')
end

# 署名する
signer.sign!(issuer_serial: true)

# xmlに書き込む
File.write("register.xml", signer.to_xml)

6.2 一括申請

(1)標準形式

標準形式では、構成管理XMLが署名付きxmlになるのがポイントです。次の手順に従って、署名を行います。

  1. zipperインスタンスを生成する
  2. zipperの第1引数(署名出力先)に構成管理XML、第2引数(署名対象)に申請書XML(+添付ファイル)のファイルパスをそれぞれ渡して、署名付きの構成管理XMLを作成する
  3. 署名付きの構成管理XML及び申請書XML(+添付ファイル)をzip形式ファイルに固める
  4. 手続が複数あれば、各手続毎に2を繰り返してから、最後に3を行う

作成するドライバーについては、make_zip_file_standard_format.rbとします。
ドライバの入出力については、ドライバがあるフォルダに./zip_data/standard/があり、その下にin及びoutフォルダがあります。

inフォルダの中には、900A010200001000(1)フォルダがあり、以下3ファイルがあります。
・kousei.xml
・900A01020000100001_01.xml
・添付ファイル.docx

また、証明書ファイルについては、ドライバーがあるフォルダ直下に証明書フォルダを作成し、その中にe-GovEE01_sha2.pfxを配置します。

ドライバー実行前、outフォルダの中は空です。
ドライバーを実行すると900A010200001000(1)フォルダ、apply_data.zipが作成されます。900A010200001000(1)フォルダには、inフォルダと同じファイルがあり、ここにあるkousei.xmlは、署名付きxmlです。apply_data.zipについては、900A010200001000(1)フォルダをzip形式で固めたものです。

apply_data.zipについて、データ変換ツールを使ってBase64形式にエンコードします。エンコード結果をapply_data.xmlの<FileData>タグに挿入すると、e-Govに送信できる申請データが完成します。

apply_data_xml.png

ドライバ実行後、実行環境は次の状態になります。

標準形式の配置構成
実行環境/ 
     ├ make_zip_file_standard_format.rb
     ├ 証明書/
     │    └ e-GovEE01_sha2.pfx
     └ zip_data/
             └ standard/
                     ├ in/
                     │  └ 900A010200001000(1)/
                     │              ├ kousei.xml
                     │              ├ 900A01020000100001_01.xml
                     │              └ 添付ファイル.docx
                     └ out/
                         ├ 900A010200001000(1)/
                         │          ├ kousei.xml
                         │          ├ 900A01020000100001_01.xml
                         │          └ 添付ファイル.docx
                         └ apply_data.zip

署名付きxml生成ドライバーは次の通りです。

make_zip_file_standard_format.rb
require 'fileutils'
require 'zip'
require "cgi"
require 'date' 
require 'kiji'

# 署名ファイル、パスワード
Key = "./証明書/e-GovEE01_sha2.pfx"
password = "gpkitest"

# 入出力先パスを定義する
input_base_path = "./zip_data/standard/in/"
output_base_path = "./zip_data/standard/out/"

# 入出力データのファイルパスを定義する
Procedure = Struct.new(:folder, :kousei_xml, :application_xml, :attachment_file)
proc = Procedure.new("900A010200001000(1)","kousei.xml","900A01020000100001_01.xml","添付ファイル.docx")

input_path = "#{input_base_path}/#{proc.folder}"
output_path = "#{output_base_path}/#{proc.folder}"

signed_xml_path = "#{input_path}/#{proc.kousei_xml}"
style_file_path = "#{input_path}/#{proc.application_xml}"
attachment_file_path = "#{input_path}/#{proc.attachment_file}"
app_files_path = ["#{style_file_path}", "#{attachment_file_path}"]

# 出力先のフォルダ、ファイル等を消す
Dir.glob("#{output_base_path}/*") do |f|
  FileUtils.rm_r(f)
end

# Zipper生成
pkcs12 = OpenSSL::PKCS12.new(File.open(Key, "rb"),password)
zipper = Kiji::Zipper.new() do |s|
  s.cert = pkcs12.certificate
  s.private_key = pkcs12.key
end

# 署名を行う
signer = zipper.sign(signed_xml_path, app_files_path)

# 申請フォルダを作成する
FileUtils.mkdir_p(output_path)

# 署名付きxmlを書き出す
File.write("#{output_path}/#{proc.kousei_xml}", signer.to_xml)

# 申請書XML、添付ファイルをコピーする
app_files_path.each do |f|
  FileUtils.cp(f, output_path)
end

# 出力先にあるフォルダをzipに固める
zipper.write_zip(output_base_path, "#{output_base_path}/apply_data.zip")

(2)個別ファイル署名形式

個別ファイル署名形式では、「申請書①に対する構成情報XML」及び「申請書②に対する構成情報XML」が、それぞれ署名付きxmlになるのがポイントです(kousei.xmlは署名付きxmlになりません)。次の手順に従って、署名を行います。

  1. zipperインスタンスを生成する
  2. zipperの第1引数(署名出力先)に「申請書①に対する構成情報XML」、第2引数(署名対象)に「申請書②XML」のファイルパスを渡して、「申請書①に対する構成情報XML」に署名を付与する
  3. zipperの第1引数(署名出力先)に「申請書②に対する構成情報XML」、第2引数(署名対象)に「申請書②XML」のファイルパスを渡して、「申請書②に対する構成情報XML」に署名を付与する
  4. 構成管理XML、申請書①に対する構成情報XML、申請書①XML、申請書②に対する構成情報XML、申請書②XMLをzip形式ファイルに固める
  5. 手続が複数あれば、各手続毎に2,3を繰り返し、最後に4を行う

作成するドライバーについては、make_zip_file_individual_signature_format.rbとします。
ドライバの入出力については、ドライバがあるフォルダに./zip_data/indivisual/があり、その下にin及びoutフォルダがあります。
inフォルダの中には、950A102200039000(1)フォルダがあり、以下5ファイルがあります。
・kousei.xml // 構成管理XML
・950A10220003900001_01.xml // 申請書XML①
・kousei20200716142110000.xml // 申請書XML①に対する構成情報XML
・950A10220003900002_01.xml // 申請書XML②
・kousei20200716142115000.xml // 申請書XML②に対する構成情報XML

また、証明書ファイルについては、ドライバーがあるフォルダ直下に証明書フォルダを作成し、その中にe-GovEE01_sha2.pfxを配置します。

ドライバー実行前、outフォルダは空です。
ドライバを実行すると950A102200039000(1)フォルダ、apply_data.zipが作成されます。
950A102200039000(1)フォルダには、inフォルダと同じファイルがあります。ただし、ここにあるkousei20200716142110000.xml及びkousei20200716142115000.xmlは、署名付きxmlです。
apply_data.zipについては、950A102200039000(1)フォルダをzip形式で固めたものです。

apply_data.zipについて、データ変換ツールを使ってBase64形式にエンコードします。エンコード結果をapply_data.xmlの<FileData>タグに挿入すると、e-Govに送信できる申請データが完成します。なお、apply_data.xmlの書式については、標準形式と同じです。

ドライバ実行後、実行環境は次の状態になります。

個別ファイル署名形式の配置構成
実行環境/
     ├ make_zip_file_individual_signature_format.rb
     ├ 証明書/
     │    └ e-GovEE01_sha2.pfx
     └ zip_data/
             └ indivisual/
                    ├ in/
                    │  └ 950A102200039000(1)/
                    │            ├ kousei.xml
                    │            ├ 950A10220003900001_01.xml
                    │            ├ kousei20200716142110000.xml
                    │            ├ 950A10220003900002_01.xml
                    │            └ kousei20200716142115000.xml
                    └ out/
                       ├ 950A102200039000(1)/
                       │         ├ kousei.xml
                       │         ├ 950A10220003900001_01.xml
                       │         ├ kousei20200716142110000.xml
                       │         ├ 950A10220003900002_01.xml
                       │         └ kousei20200716142115000.xml
                       └ apply_data.zip

署名付きxml生成ドライバーは次の通りです。

make_zip_file_individual_signature_format.rb
require 'fileutils'
require 'zip'
require "cgi"
require 'date' 
require 'kiji'

# 署名ファイル、パスワードを定義する
Key = "./証明書/e-GovEE01_sha2.pfx"
password = "gpkitest"

# 入出力先パスを定義する
input_base_path = "./zip_data/indivisual/in/"
output_base_path = "./zip_data/indivisual/out/"

# 出力先のフォルダ、ファイル等を消す
Dir.glob("#{output_base_path}/*") do |f|
  FileUtils.rm_r(f)
end

# 入出力データのファイルパスを定義する
Procedure = Struct.new(:folder, :kousei_xml, 
                       :config_info_appl_xml, :application_xml, 
                       :config_info_appl_xml_2, :application_xml_2)
proc = Procedure.new("950A102200039000(1)","kousei.xml",
                     "kousei20200716142110000.xml","950A10220003900001_01.xml",
                     "kousei20200716142115000.xml","950A10220003900002_01.xml")

input_path = "#{input_base_path}/#{proc.folder}"
output_path = "#{output_base_path}/#{proc.folder}"

# 申請フォルダを作成する
FileUtils.mkdir_p(output_path)

# Zipper生成
pkcs12 = OpenSSL::PKCS12.new(File.open(Key, "rb"),password)
zipper = Kiji::Zipper.new() do |s|
  s.cert = pkcs12.certificate
  s.private_key = pkcs12.key
end

# 申請書1に対する構成情報XMLに対して署名を行う
signed_xml_path = "#{input_path}/#{proc.config_info_appl_xml}"
style_file_path = ["#{input_path}/#{proc.application_xml}"]
signer = zipper.sign(signed_xml_path, style_file_path)

# 署名付きxmlを書き出す
File.write("#{output_path}/#{proc.config_info_appl_xml}", signer.to_xml)

# 申請書2に対する構成情報XMLに対して署名を行う
signed_xml_path_2 = "#{input_path}/#{proc.config_info_appl_xml_2}"
style_file_path_2 = ["#{input_path}/#{proc.application_xml_2}"]
signer = zipper.sign(signed_xml_path_2, style_file_path_2)

# 署名付きxmlを書き出す
File.write("#{output_path}/#{proc.config_info_appl_xml_2}", signer.to_xml)

# コピーする申請書XML、添付ファイルをリスト化する
copy_files_path = ["#{input_path}/#{proc.kousei_xml}", style_file_path, style_file_path_2]

# 申請書XML、添付ファイルをコピーする
copy_files_path.each do |f|
  FileUtils.cp(f, output_path)
end

# 出力先リストをzipに固める
zipper.write_zip(output_base_path, output_base_path + "apply_data.zip")
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Ruby]求める年が閏年かどうか

問題

ある年のある月の日数を求めるメソッドを作ります。
問題となるのは2月が閏年かどうか。

閏年の判断基準

① その年が4で割り切れる場合、閏年の可能性がある。(これだけでは確定ではない)

② 年が100で割り切れて400で割り切れない場合は、閏年ではない。

作るプログラムの雛形

def get_days(year, month)
  # ここに問題の処理を書き加える
end

puts "年を入力してください"
year = gets.to_i
puts "月を入力してください"
month = gets.to_i

days = get_days(year, month)
puts "#{year}#{month}月は#{days}日間あります"

模範解答

def get_days(year, month)
  month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
  if month == 2
    if year % 4 == 0
      if year % 100 == 0 && year % 400 != 0
        days = 28
      else
        days = 29
      end
    else
      days = 28
    end
  else
    days = month_days[month - 1]
  end
  return days
end

puts "年を入力してください:"
year = gets.to_i
puts "月を入力してください:"
month = gets.to_i

days = get_days(year, month)
puts "#{year}#{month}月は#{days}日間あります"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby on Rails】ランキング表示(合計、平均値)

目標

ランキングの表示

開発環境

ruby 2.5.7
Rails 5.2.4.3
OS: macOS Catalina

前提

※ ▶◯◯ を選択すると、説明等が出てきますので、
  よくわからない場合の参考にしていただければと思います。

  • controllerとviewのみ変更
  • userモデル
  • postモデル(:body, :score)
  • いいね機能実装
  • Google Natural Language APIによるコメント感情分析導入済み(投稿の点数付け)

※今回の方法では、
数値がない(いいねが押されていない、平均値がない)ユーザーは
表示されません。

controller

コントローラーさえできればできたも同然です!

平均値のランキング:app/controllers/users_controller.rb
  def rank
    @users = User.
              find(Post.
                    group(:score).
                    order('avg(score) desc').
                    pluck(:user_id)
                  )
  end

補足
@users = User.find( ):@userにUserモデルから以下の条件で取得した値を入力
・Post.group(:score):Postモデルからscoreカラムでグループ分け
・order('avg(score) desc'):scoreの平均順で降順並び替え
・pluck(:user_id):上記値のuser_idがあるものを全て配列

合計のランキング:app/controllers/posts_controller.rb
  def rank
    @posts = Post.
              find(Favorite.
                    group(:post_id).
                    order('count(post_id) desc').
                    pluck(:post_id)
                  )
  end

補足
@posts = Post.find( ):@postにPostモデルから以下の条件で取得した値を入力
・Favorite.group(:post_id):Favoriteモデルからpost_idカラムでグループ分け
・order('count(post_id) desc'):post_idの個数で降順並び替え
・pluck(:post_id):上記値のpost_idがあるものを全て配列

view

下記のようにeach降順順で表示可能

<% @users.each do |user| %>
<% end %>
<% @posts.each do |user| %>
<% end %>

Kaminariを使用する場合

前にKaminari.paginate_array( )、後ろに.page(params[:page])をつければOK

@posts = Kaminari.paginate_array(Post.find(Favorite.group(:post_id).order('count(post_id) desc').pluck(:post_id))).page(params[:page])

参考サイト

Railsでお手軽ランキング機能
【Rails】ランキング機能の実装

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyで数値の絶対値を取得する方法

数値から絶対値を求める方法

そもそも絶対値とは? 原点0から見た距離 = 符号なしの数値
絶対値を取得するメソッドとして "abs" があります!(absolute valueの略)

absメソッド

対象とある数値の絶対値を取得することができる。
・正の数の場合は、そのまま。
・負の数の場合は、符号を取って正の数にした数値が取得できる。

正の数の場合。
num = 5.abs
#=> 5

負の数の場合。
num = (-5).abs
#=> 5

問題

3つの整数a b cが与えられた場合、
bまたはcがaとの差が1で、かつ bとcとの数値の差が2以上の場合は"True"。
それ以外は"False"と出力するメソッドを作る。

出力例

close_far(1, 2, 10) → True
close_far(1, 2, 3) → False
close_far(4, 1, 3) → True

模範解答

def close_far(a,b,c)
  x = (a-b).abs
  y = (a-c).abs
  z = (b-c).abs

  if x == 1 && z >= 2
    puts "True"
  elsif y == 1 && z >= 2
    puts "True"
  else
    puts "False"
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsを始める時に読んでおきたい入門書

はじめに

Railsで開発を行うにあたり読んで良かった入門書をまとめましたが、一番は公式ドキュメントを読む事です。
ただ公式ドキュメントだけだとなかなか理解が進まない事も多いかと思います。
そういった方に参考になれば幸いです。

Ruby

・プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで
https://www.amazon.co.jp/%E3%83%97%E3%83%AD%E3%82%92%E7%9B%AE%E6%8C%87%E3%81%99%E4%BA%BA%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AERuby%E5%85%A5%E9%96%80-%E8%A8%80%E8%AA%9E%E4%BB%95%E6%A7%98%E3%81%8B%E3%82%89%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA%E3%83%BB%E3%83%87%E3%83%90%E3%83%83%E3%82%B0%E6%8A%80%E6%B3%95%E3%81%BE%E3%81%A7-Software-Design-plus%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA/dp/4774193976/ref=sr_1_5?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=ruby&qid=1600388138&sr=8-5

・メタプログラミングRuby
https://www.amazon.co.jp/%E3%83%A1%E3%82%BF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0Ruby-%E7%AC%AC2%E7%89%88-Paolo-Perrotta/dp/4873117437/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=ruby+%E3%83%A1%E3%82%BF&qid=1600388179&sr=8-1

Ruby on Rails

・パーフェクト Ruby on Rails
https://www.amazon.co.jp/%E3%83%91%E3%83%BC%E3%83%95%E3%82%A7%E3%82%AF%E3%83%88-Ruby-Rails-%E3%80%90%E5%A2%97%E8%A3%9C%E6%94%B9%E8%A8%82%E7%89%88%E3%80%91-Perfect/dp/4297114623/ref=sr_1_6?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=rails&qid=1600388204&sr=8-6

Rspec

・Everyday Rails - RSpecによるRailsテスト入門
https://leanpub.com/everydayrailsrspec-jp

・Effective Testing with RSpec 3
https://www.amazon.co.jp/Effective-Testing-RSpec-Confidence-English-ebook/dp/B076VTMTV8/ref=sr_1_6?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=rspec&qid=1600388276&sr=8-6

React

・速習 React
https://www.amazon.co.jp/%E9%80%9F%E7%BF%92-React-%E9%80%9F%E7%BF%92%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-%E5%B1%B1%E7%94%B0%E7%A5%A5%E5%AF%9B-ebook/dp/B07GWFRCR1/ref=sr_1_6?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=react&qid=1600389000&sr=8-6
・りあクト! TypeScriptで始めるつらくないReact開発 第3版【Ⅰ. 言語・環境編】
https://booth.pm/ja/items/2368045
・りあクト! TypeScriptで始めるつらくないReact開発 第3版【Ⅱ. React基礎編】
https://booth.pm/ja/items/2368019
・りあクト! TypeScriptで始めるつらくないReact開発 第3版【Ⅲ. React応用編】
https://booth.pm/ja/items/2367992

jQuery

・jQuery本格入門
https://www.amazon.co.jp/%E6%94%B9%E8%A8%82%E6%96%B0%E7%89%88jQuery%E6%9C%AC%E6%A0%BC%E5%85%A5%E9%96%80-%E6%B2%96%E6%9E%97%E6%AD%A3%E7%B4%80-ebook/dp/B00MPDUSLG/ref=sr_1_10?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=jQuery&qid=1600388424&sr=8-10

BootStrap

・Bootstrap 4 フロントエンド開発の教科書
https://www.amazon.co.jp/Bootstrap-4-%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89%E9%96%8B%E7%99%BA%E3%81%AE%E6%95%99%E7%A7%91%E6%9B%B8-%E5%AE%AE%E6%9C%AC-%E9%BA%BB%E7%9F%A2/dp/4297100207/ref=sr_1_5?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=bootstrap&qid=1600388388&sr=8-5

webpack

・速習webpack
https://www.amazon.co.jp/%E9%80%9F%E7%BF%92webpack-%E9%80%9F%E7%BF%92%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-%E5%B1%B1%E7%94%B0%E7%A5%A5%E5%AF%9B-ebook/dp/B07CQLGGP9/ref=sr_1_6?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=webpack&qid=1600388657&sr=8-6

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WindowsデスクトップアプリケーションをRubyで作って実行可能ファイル(.exe)を配布する!

結論から

2018年3月現在、Windows向けデスクトップアプリケーションをRubyで書いて実行可能ファイル(.exe)を作って配布したいなら、採用するのはこれ択一!

  • Ruby-GNOME2 (GUIライブラリ)
  • OCRA (実行可能ファイル作成ツール)

手順

Ruby-GNOME2をインストール。Windows用GNOMEライブラリも自動でインストールされる。

gem install gtk2 -r

RubyスクリプトからWindows実行可能ファイルを作成するツールOCRAをインストール。

gem install ocra -r

OCRAを使ってRuby/GNOME2で書かれたデスクトップアプリケーションから実行ファイルを作成。

ocra desctopapplication.rb --no-autoload --add-all-core

生成された実行ファイルdesctopapplication.exeは75MBだった。

RubyでWindowsデスクトップアプリケーション(GUIアプリケーション)を作るためライブラリ比較

名前 活動状況 サポート環境 メリット デメリット
Ruby-GNOME2 活発 Windows, Linux, Mac 安定、情報豊富 メンテナンスのみ。機能開発はGNOME3
Ruby-GNOME3 活発 Windows, Linux, Mac 活発に開発中 情報が少ない、機能不足のところも
FXRuby 活発 Windows, Linux, Mac Rubyらしいコードが書ける 日本語入力に対応していない(表示は可能)
Shoes 活発 JRuby 簡素でRubyらしいコードで書ける JRubyが必要。将来pure Rubyになる?
Qt
wxRuby 7年間更新なし Windows, Linux, Mac - -
gem install gtk2 -r -p http://lv2west-proxy.nwdept:8080

結果

Fetching: pkg-config-1.2.9.gem (100%)
Successfully installed pkg-config-1.2.9
Fetching: native-package-installer-1.0.6.gem (100%)
Successfully installed native-package-installer-1.0.6
Fetching: cairo-1.15.11-x86-mingw32.gem (100%)
Successfully installed cairo-1.15.11-x86-mingw32
Fetching: glib2-3.2.1-x86-mingw32.gem (100%)
Successfully installed glib2-3.2.1-x86-mingw32
Fetching: atk-3.2.1-x86-mingw32.gem (100%)
Successfully installed atk-3.2.1-x86-mingw32
Fetching: cairo-gobject-3.2.1-x86-mingw32.gem (100%)
Successfully installed cairo-gobject-3.2.1-x86-mingw32
Fetching: gobject-introspection-3.2.1-x86-mingw32.gem (100%)
Successfully installed gobject-introspection-3.2.1-x86-mingw32
Fetching: pango-3.2.1-x86-mingw32.gem (100%)
Successfully installed pango-3.2.1-x86-mingw32
Fetching: gio2-3.2.1-x86-mingw32.gem (100%)
Successfully installed gio2-3.2.1-x86-mingw32
Fetching: gdk_pixbuf2-3.2.1-x86-mingw32.gem (100%)
Successfully installed gdk_pixbuf2-3.2.1-x86-mingw32
Fetching: gtk2-3.2.1-x86-mingw32.gem (100%)
Successfully installed gtk2-3.2.1-x86-mingw32
Parsing documentation for pkg-config-1.2.9
Installing ri documentation for pkg-config-1.2.9
Parsing documentation for native-package-installer-1.0.6
Installing ri documentation for native-package-installer-1.0.6
Parsing documentation for cairo-1.15.11-x86-mingw32
Installing ri documentation for cairo-1.15.11-x86-mingw32
Parsing documentation for glib2-3.2.1-x86-mingw32
Installing ri documentation for glib2-3.2.1-x86-mingw32
Parsing documentation for atk-3.2.1-x86-mingw32
Installing ri documentation for atk-3.2.1-x86-mingw32
Parsing documentation for cairo-gobject-3.2.1-x86-mingw32
Installing ri documentation for cairo-gobject-3.2.1-x86-mingw32
Parsing documentation for gobject-introspection-3.2.1-x86-mingw32
Installing ri documentation for gobject-introspection-3.2.1-x86-mingw32
Parsing documentation for pango-3.2.1-x86-mingw32
Installing ri documentation for pango-3.2.1-x86-mingw32
Parsing documentation for gio2-3.2.1-x86-mingw32
Installing ri documentation for gio2-3.2.1-x86-mingw32
Parsing documentation for gdk_pixbuf2-3.2.1-x86-mingw32
Installing ri documentation for gdk_pixbuf2-3.2.1-x86-mingw32
Parsing documentation for gtk2-3.2.1-x86-mingw32
Installing ri documentation for gtk2-3.2.1-x86-mingw32
Done installing documentation for pkg-config, native-package-installer, cairo, glib2, atk, cairo-gobject, gobject-introspection, pango, gio2, gdk_pixbuf2, gtk2 after 21 seconds
11 gems installed

ocra sample.rb C:\Ruby193\lib\tcltk\ --no-autoload --add-all-core

FXRuby

公式サイトであったはずのfxruby.orgが死んでいるけどgithub上の活動はそこそこあるので気にしない。READMEで代用

最新バージョンは1.6.39。2017年12月25日にリリースされている。

APIマニュアルはこちら

日本語入力ができない。非公式パッチの適評が必要。らしい。
http://magazine.rubyist.net/?0001-RubyGUI#l8

Shoes ... JRuby用

FXRuby ... 生きてる
公式サイト http://www.fxruby.org/ は死んでるし、
trunkは6年放置されているけど、最新版の1.6はまあまあ活発に活動されているので大丈夫。
最新のりりース
=== 1.6.39 / 2017-12-26
Fix FXGLVisual.supported and .supported?
Add support for RubyInstaller-2.5

Ruby/Tk
Ruby-GNOME2
QTRuby ... 最近怪しい
Ruby/FLTK ... 当時から開発が停止
wxRuby ... 7年前から更新なし

limelight ... 6年前から更新なし

FXRubyを使う

インストール

gem install fxruby -r -p http://lv2west-proxy.nwdept:8080

結果

Fetching: fxruby-1.6.39-x86-mingw32.gem (100%)
Successfully installed fxruby-1.6.39-x86-mingw32
Parsing documentation for fxruby-1.6.39-x86-mingw32
Installing ri documentation for fxruby-1.6.39-x86-mingw32
Done installing documentation for fxruby after 38 seconds
1 gem installed

参考

https://code.i-harness.com/ja/q/3fb29
https://code.i-harness.com/ja/q/1c32d
https://qiita.com/drumath2237/items/16ab7247f30000cb7583

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

form_withについて

そもそもform_withってなんですか?

情報を送信するためのヘルパーメソッドです!
Q.ヘルパーメソッドって何ですか?
A.railsにおける、主にビューでHTMLタグを出現させたりテキストを加工するために使用するメソッドの総称のことです。要は、railsでビューの加工をするメソッドといったもので覚えておくといいと思いました。(同じことを言ってますねこれ?)

form_withが何かわかりました!じゃあ、どのように使うのですか?

ビューファイルに記載していきます!
次のように記述します。

index.html.erb
<%= form_with url: "パス",local:true do |form| %>

上記のような記述ではデータベースには保存されません。

index.html.erb
<%= form_with(model: モデルクラスのインスタンス,local:true,) do |form| %>

で、保存するためのボタンを作成するには

index.html.erb
 <%= form.submit %>

と記述をすることで、ボタンを表示させ保存等を行うことができます。(ルーティングからコントローラーの設定ができていればの話ですが!)

まとめ

form_withは、要するに、ビューで使用するデータを受け渡すメソッドといった考えでいたらいいのかなと思っております。間違っていたら、ご指摘お願いします!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsチュートリ

7.1.4から

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails 6で認証認可入り掲示板APIを構築する #13 認証ヘッダの付与

Rails 6で認証認可入り掲示板APIを構築する #12 userとpostの関連付け

factoryの修正

factoryから直していきます。

一番多く飛んでいるのが

ActiveRecord::RecordInvalid:
        Validation failed: User must exist

というエラーですね。
これはcreate(:post)した時に、user_idがnilになることで発生しています。

factoryの修正で潰しましょう。

spec/factories/posts.rb
   factory :post do
     subject { "MyString" }
     body { "MyText" }
+
+    after(:build) do |obj|
+      obj.user = build(:user) if obj.user.nil?
+    end
   end

after(:build)は、buildやcreateをした後に実行されます。
post.userにbuildしたuserを入れることにより、User must existエラーを潰せます。

なお、if obj.user.nil?をすることでcreate(:post, user: user)のように特定ユーザーを渡して生成した際、内部処理で上書きされてしまうのを防いでいます。

実はもっとシンプルな方法で、とりあえず大半潰すことができたりします。

spec/factories/posts.rb
   factory :post do
     subject { "MyString" }
     body { "MyText" }
+    user
   end

ただしこの方法だとcreate(:post)した時はいいのですが、build(:post)した時にuserがnilで返ってくるため、前者の対応をしています。

request specとcontrollerの修正

spec/requests/v1/posts_request_spec.rb
   describe "POST /v1/posts#create" do
+    let(:authorized_headers) do
+      user = create(:user)
+      post v1_user_session_url, params: { email: user.email, password: "password" }
+      headers = {}
+      headers["access-token"] = response.header["access-token"]
+      headers["client"] = response.header["client"]
+      headers["uid"] = response.header["uid"]
+      headers
+    end
     let(:new_post) do
       attributes_for(:post, subject: "create_subjectテスト", body: "create_bodyテスト")
     end
     it "正常レスポンスコードが返ってくる" do
-      post v1_posts_url, params: new_post
+      post v1_posts_url, params: new_post, headers: authorized_headers
       expect(response.status).to eq 200
     end
     it "1件増えて返ってくる" do
       expect do
-        post v1_posts_url, params: new_post
+        post v1_posts_url, params: new_post, headers: authorized_headers
       end.to change { Post.count }.by(1)
     end
     it "subject, bodyが正しく返ってくる" do
-      post v1_posts_url, params: new_post
+      post v1_posts_url, params: new_post, headers: authorized_headers
       json = JSON.parse(response.body)
       expect(json["post"]["subject"]).to eq("create_subjectテスト")
       expect(json["post"]["body"]).to eq("create_bodyテスト")
     end
     it "不正パラメータの時にerrorsが返ってくる" do
-      post v1_posts_url, params: {}
+      post v1_posts_url, params: {}, headers: authorized_headers
       json = JSON.parse(response.body)
       expect(json.key?("errors")).to be true
     end
   end

ユーザーを生成し、そのユーザー情報を元にログイン。
レスポンスヘッダにある認証用3キーをheadersに加えてpostをすることで、create(:user)したユーザーとして認証された状態でアクセスをします。
ただしcontroller側をまだ直していないのでエラーのままです。

controllerの修正

app/controllers/v1/posts_controller.rb
     def create
-      post = Post.new(post_params)
+      post = current_v1_user.posts.new(post_params)
       if post.save

上記修正でテスト通過するようになるはずです。

挙動を説明すると、まずheadersで認証情報が渡ってきているため、controllerではcurrent_v1_userというメソッドが使えます。これはログイン中のユーザーインスタンスが返ってくるものです。
つまりcurrent_v1_user.posts.newは、ログイン中のユーザーに紐づくpostをインスタンス化しています。
それにより、ログインしているユーザーのpostが作られます。

rspecの認証済みヘッダ取得処理をhelperに移動

テストは通るようになったのですが、今後Punditを入れて認可を実装していくにあたり、認証済みヘッダを取得する処理を都度書いていては保守性が下がるので、spec用のhelperに移動します。

spec用helperは一般的にspec/supportに置いていくのでディレクトリを作ります。

$ mkdir spec/support
$ touch spec/support/authorization_spec_helper.rb

rspecにあった処理をごっそりこっちに持ってきます。

spec/support/authorization_spec_helper.rb
# frozen_string_literal: true

#
# 認証用ヘルパ
#
module AuthorizationSpecHelper
  def authorized_user_headers
    user = create(:user)
    post v1_user_session_url, params: { email: user.email, password: "password" }
    headers = {}
    headers["access-token"] = response.header["access-token"]
    headers["client"] = response.header["client"]
    headers["uid"] = response.header["uid"]
    headers
  end
end

spec/support下に配置しただけでは勝手に読み込んでくれないので、spec/rails_helper.rbを修正します。

spec/rails_helper.rb
-# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
+Dir[Rails.root.join("spec", "support", "**", "*.rb")].sort.each { |f| require f }
...
 RSpec.configure do |config|
...

+  config.include(AuthorizationSpecHelper, type: :request)
 end

コメントアウトされていたspec/support下を読みにいく処理を有効化するのと、AuthorizationSpecHelperをincludeします。上記のように書くことで、request specのみ有効になります。

spec/requests/v1/posts_request_spec.rb
...
 require "rails_helper"

 RSpec.describe "V1::Posts", type: :request do
+  let(:authorized_headers) do
+    authorized_user_headers
+  end
...
   describe "POST /v1/posts#create" do
-    let(:authorized_headers) do
-      user = create(:user)
-      post v1_user_session_url, params: { email: user.email, password: "password" }
-      headers = {}
-      headers["access-token"] = response.header["access-token"]
-      headers["client"] = response.header["client"]
-      headers["uid"] = response.header["uid"]
-      headers
-    end
...

あとは上記対応で完了。
テスト結果が変わらずgreenであればとりあえずOKです。

テストは全部通過するものの、そもそもテストコードが不十分。
認証されていない時に#createを叩くと500エラーになったり、そもそも自分以外の投稿を更新したり削除できてしまう現状の仕様は困るので、次次回でいよいよ認可を入れていきます。

次回はseedの整備を行います。
本日はここまで。

続き


連載目次へ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む