20190522のRubyに関する記事は15件です。

RubyでGoogle Natural Language APIを使う

概要

RubyでGoogle Natural Language APIを使うための準備をメモりました。このAPIは普段PythonやPHPで用いられることが多く、Rubyで使おうとするとなかなか参考にできるものが少ないと思ったので。最後にテストで感情分析を行っています。

Google Natural Language APIとは?

Googleが提供する自然言語処理用のAPIです。自然言語処理を行うAPIはいくつか存在しますが、日本語テキストに対応しているのが強みです。基本無料で使用できますが、本人確認のためにクレジットカードが必須です。自然言語処理を行うクラウドサービスを比較している記事があったので、こちらを参考ににどうぞ。

テキストの感情分析をするためのクラウドサービス比較

Google Natural Language APIを使う準備

本題です。まずはGoogle Cloud Platform(このAPIを含むプラットホーム)で登録を済ませましょう。

1. 新しいプロジェクトの作成(任意)

ホーム画面の左上に「My First Project」という欄があるはずです。

スクリーンショット 2019-05-22 18.44.25.jpeg

これをクリックし、出てきた画面の右上にある「新しいプロジェクト」を選択、好きなプロジェクト名を付けて「作成」をクリックしてください。
スクリーンショット 2019-05-22 18.47.31.jpeg

元からあるMy First Projectのままでも作業はできますので、この設定は任意です。何個かプロジェクトを作るつもりの人や、好きな名前でプロジェクトを始めたい人は以上の設定をするといいと思います。

2. gcloudコマンドラインでの設定(多分必須)

Google Cloud Platformには備え付けのコマンドラインがあります。次はそれを使ってRubyでこのAPIを使えるように設定しましょう。
まず、ホーム画面で右上の赤枠のアイコンをクリックし、「Start Cloud Shell」をクリック。

スクリーンショット 2019-05-22 18.44.25.png

そうすると、画面下にコマンドラインが生成されたと思います。ここで、諸々の設定をしていきます。
まず、作業するプロジェクトが、目的(自分が作ったプロジェクト)のものになっているか確認します。コマンドライン上で、

gcloud config list

と打ちます。これは、現在作業しているプロジェクトを参照するコマンドです。返ってきた文章の中に

[core]
account = [自分のgmailアドレス]
project =[現在作業中のプロジェクトのID]

とあると思います。このプロジェクトIDが、作業したいプロジェクトのIDと一致しているか確認してください。もし違うようなら、

gcloud config set project [作業したいプロジェクトのID]

を実行しましょう。

Updated property [core/project].

と返ってくれば、作業するプロジェクトが変更できたことになります。

3. APIを有効にする(必須)

gcloudコマンドラインで次のコマンドを打ちます。

gcloud services enable language.googleapis.com

ここでエラーが起きる人は、作業2が正常にできていない場合があります。

4. サービスアカウントの作成(必須)

APIのリクエストを認証するためには、サービスアカウントというものを作る必要があります。詳しくは以下のリンクをご覧ください。

サービスアカウントの作成と管理

ここではプラットフォームからサービスアカウントを作成する方法を書きます。
まず、ホーム画面左の「IAMと管理」→「サービスアカウント」→「+ CREATE SERVICE ACCOUNT」をクリックします。

スクリーンショット 2019-05-22 18.44.25.png

スクリーンショット 2019-05-22 20.33.39.png

作成画面で、好きなサービスアカウント名を入力します。この後、

  • ②このサービスアカウントにプロジェクトへのアクセスを許可する

は省略してOKです。複数人が関わるプロジェクトにおいて、特定の人に何らかの権限を与える必要があるときは記入してください。次に、③ユーザーにこのサービスアカウントへのアクセスを許可 のところで、「キーの作成(オプション)」という欄があります。これは、プログラムを動かす際に必要な秘密鍵を含むファイルをダウンロードするものです。「+ キーを作成」を押し、キーのタイプをJSONにし、作成をクリックしましょう。これで秘密鍵ができました。なお、ページにも書いてある通り、このファイルは復元できないので注意してください。秘密鍵なので扱いも慎重に。

5. 環境変数を設定(必須)

環境変数GOOGLE_APPLICATION_CREDENTIALSを設定します。OSのコマンドラインで次のようなコマンドを打ちます。

  • Mac or Linux
export GOOGLE_APPLICATION_CREDENTIALS="[PATH]"

例)
export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/[FILE_NAME].json"
  • Windows(コマンドプロンプト)
set GOOGLE_APPLICATION_CREDENTIALS=[PATH]

6. gemをインストール(必須)

OSのコマンドラインで

gem install google-cloud-language

これでRubyでGoogle Natural Language APIを使う準備ができました。

いざ使ってみるぞ

こんなファイルを用意します。今回はAPIの中の一つの機能である「感情分析」を使ってみました。ソースコードはこんな感じです。

test.rb
require 'google/cloud/language' #APIを使う

language = Google::Cloud::Language.new
response = language.analyze_sentiment content: "今日も1日楽しかった。", #ここに分析したい文章
                                      type: :PLAIN_TEXT

sentiment = response.document_sentiment

score = sentiment.score.to_f.round(1) #float型に変換,少数第2位を四捨五入
magnitude = sentiment.magnitude.to_f.round(1)
puts "Score: #{score}"
puts "Magnitude: #{magnitude}"

出力結果

Score: 0.4
Magnitude: 0.4
  • score: -1.0~1.0の中で大きいほど正の感情(ポジティブ)、小さいほど負の感情(ネガティブ)を抱いていることを示す。
  • magnitude: 感情の強さ。0~infで、大きいほど感情的であることを示す。scoreが0.0であったとき、magnitudeが大きいなら「感情的であり、ポジティブとネガティブが打ち消しあっている」ことを示し、小さいなら「ニュートラルな文章である」ことを示す。

感想

意外と大変だな〜という印象でした。ただ、どの機能を使うにしてもほぼ同じような作業でできそうなので、一気にいろんなことを覚えなくてもいいのかもしれません。もう少し勉強していきたいなあと思います。

最後に

不明な点やおかしな点などございましたら気軽にコメントしていただけると嬉しいです。(もしかしたら間違っていることもあるかもしれませんが、暖かい目で見てから指摘くださると助かります!)

それでは!

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

Sinatraを支えるRuby記法 - 処理編

はじめに

ここまで、パスへのリクエストに対する処理の登録サーバ起動のからくりについて見てきました。最後に実際にリクエストが来たときにどのような処理が行われるのかについて見ていきましょう。

Sinatra::Base.call(クラスメソッド)

前回も少し説明しましたが、Rackハンドラに渡されているのはApplicationクラスオブジェクトです。Rubyはクラスもオブジェクトなのでcall(env)というメソッド(クラスなのでクラスメソッド=特異メソッド)さえあればRackハンドラは文句は言いません。
というわけで、読解の起点となるのはこのcallです。

sinatra/base.rbより抜粋
      def call(env)
        synchronize { prototype.call(env) }
      end

prototypeは前回見たようにインスタンスを作っておくメソッドです。というか名前から作っておいたインスタンスをテンプレに処理を行ってそうです。
synchronizeは想像通りなものなので省略。ちなみにデフォルトは排他制御オフのようです。

Sinatra::Base#call(インスタンスメソッド)

prototypeで作られるのはミドルウェアが設定されたアプリケーションオブジェクトをさらにくるんだWrapperオブジェクトですが、起動編で前置きしたようにそこは無視してApplicationオブジェクトのcallインスタンスメソッドに進みます。

sinatra/base.rbより抜粋
    # Rack call interface.
    def call(env)
      dup.call!(env)
    end

dupした上でcall!メソッドが呼ばれています。つまり、テンプレをコピーして個々のリクエストを処理しているようです。

call!に進む。invoke { dispatch! }が処理本体のようです。
なお登録編でもコメントしましたがSinatraでは「!」をinternal的な意味で使っているように思われます。

sinatra/base.rbより抜粋
    def call!(env) # :nodoc:
      @env      = env
      @params   = IndifferentHash.new
      @request  = Request.new(env)
      @response = Response.new
      template_cache.clear if settings.reload_templates

      @response['Content-Type'] = nil
      invoke { dispatch! }
      invoke { error_block!(response.status) } unless @env['sinatra.error']

      unless @response['Content-Type']
        if Array === body and body[0].respond_to? :content_type
          content_type body[0].content_type
        else
          content_type :html
        end
      end

      @response.finish
    end

invokeメソッド

invokeメソッドに進む。

sinatra/base.rbより抜粋
    # Run the block with 'throw :halt' support and apply result to the response.
    def invoke
      res = catch(:halt) { yield }

      res = [res] if Integer === res or String === res
      if Array === res and Integer === res.first
        res = res.dup
        status(res.shift)
        body(res.pop)
        headers(*res)
      elsif res.respond_to? :each
        body res
      end
      nil # avoid double setting the same response tuple twice
    end

ここでポイントとなるのは次の二点です。

  • Sinatraはthrow-catchの仕組みを使っているようだ。なおJavaとかに慣れてる人には誤解を与えると思いますがRubyのthrow-catchは例外処理ではなく大域脱出を行う仕組みです。
  • というわけで大域脱出して返された値をもとにレスポンスを設定している様子。ここで使われているstatusやbodyについては後ほど触れます。

dispatch!メソッド

invokeメソッドでyieldされているものは「invokeメソッドにブロックとして渡されたもの」、つまり、dispatch!メソッド呼び出しです。

sinatra/base.rbより抜粋
    # Dispatch a request with error handling.
    def dispatch!
      @params.merge!(@request.params).each { |key, val| @params[key] = force_encoding(val.dup) }

      invoke do
        static! if settings.static? && (request.get? || request.head?)
        filter! :before
        route!
      end
    rescue ::Exception => boom
      invoke { handle_exception!(boom) }
    ensure
      begin
        filter! :after unless env['sinatra.static_file']
      rescue ::Exception => boom
        invoke { handle_exception!(boom) } unless @env['sinatra.error']
      end
    end

なんとまたinvokeメソッドが呼ばれています。ここがどう動いているのかについては後で詳しく説明するとしてroute!に進みます(static!やfilter!は名前通りのことをやっているので飛ばします)

route!メソッド

登録編で見た「routeクラスメソッド」は登録処理で、「route!インスタンスメソッド」は実際のリクエストの処理というのはなかなか名前付けがいまいちに思うわけですがroute!の定義です。

sinatra/base.rbより抜粋
    # Run routes defined on the class and all superclasses.
    def route!(base = settings, pass_block = nil)
      if routes = base.routes[@request.request_method]
        routes.each do |pattern, conditions, block|
          returned_pass_block = process_route(pattern, conditions) do |*args|
            env['sinatra.route'] = "#{@request.request_method} #{pattern}"
            route_eval { block[*args] }
          end

          # don't wipe out pass_block in superclass
          pass_block = returned_pass_block if returned_pass_block
        end
      end

      # Run routes defined in superclass.
      if base.superclass.respond_to?(:routes)
        return route!(base.superclass, pass_block)
      end

      route_eval(&pass_block) if pass_block
      route_missing
    end

今までに比べると少し複雑ですが、雰囲気で読むと以下のようになっていると思われます。

  1. baseで示されるオブジェクト(デフォルトはsettings)から登録されてる処理の一覧を取り出して、
  2. process_routeメソッド呼び出してリクエストパスに対応する処理かチェック、
  3. 対応する処理ならroute_evalが呼び出される。

一つずつちゃんと確かめていきましょう。

settings

まずはsettingsです。「デフォルト値として設定されているsettingsってなんだ?」と思われるかもしれませんが実はメソッドです。Rubyはデフォルト引数としてメソッド呼び出しが書ける言語となっています。

というわけで「settingsメソッド」を探すと以下のようになっています。要するにApplicationクラスオブジェクトですね。登録編で処理が「Applicationクラスの@routes」に登録されていたのを覚えているでしょうか。そこから取り出してマッチングが行われているわけです。

sinatra/base.rbより抜粋
    # Access settings defined with Base.set.
    def settings
      self.class.settings
    end

    # Access settings defined with Base.set.
    def self.settings
      self
    end

process_route

次はprocess_routeです。

sinatra/base.rbより抜粋
    # If the current request matches pattern and conditions, fill params
    # with keys and call the given block.
    # Revert params afterwards.
    #
    # Returns pass block.
    def process_route(pattern, conditions, block = nil, values = [])
      route = @request.path_info
      route = '/' if route.empty? and not settings.empty_path_info?
      route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/')
      return unless params = pattern.params(route)

      params.delete("ignore") # TODO: better params handling, maybe turn it into "smart" object or detect changes
      force_encoding(params)
      original, @params = @params, @params.merge(params) if params.any?

      regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? {|subpattern| subpattern.is_a?(Mustermann::Regular)} )
      if regexp_exists
        captures           = pattern.match(route).captures.map { |c| URI_INSTANCE.unescape(c) if c }
        values            += captures
        @params[:captures] = force_encoding(captures) unless captures.nil? || captures.empty?
      else
        values += params.values.flatten
      end

      catch(:pass) do
        conditions.each { |c| throw :pass if c.bind(self).call == false }
        block ? block[self, values] : yield(self, values)
      end
    rescue
      @env['sinatra.error.params'] = @params
      raise
    ensure
      @params = original if original
    end

これも少し長いですが、http://localhost:4567 にアクセスされたとすると結局実行されるのは以下となります。

process_route超抜粋
    def process_route(pattern, conditions, block = nil, values = [])
      yield(self, values)
    end

route!に戻って、process_routeからyieldされるブロックを改めて見てみましょう。

route!の一部
        routes.each do |pattern, conditions, block|
          returned_pass_block = process_route(pattern, conditions) do |*args|
            env['sinatra.route'] = "#{@request.request_method} #{pattern}"
            route_eval { block[*args] }
          end

*argsとして渡されるのは、selfとvaluesです。これを登録されているblockに渡す・・・、[]で囲んでいるのがこれは、block([*args])と解釈されるようです。つまり、*argsと受け取った残余引数を展開し、改めて配列として渡す、意味あるのかな。昔はこう書かないと動かなかったとか?

さて、このblockが何者かというのは登録編の復習になりますが以下となります。

compile!の一部
        wrapper                 = block.arity != 0 ?
          proc { |a, p| unbound_method.bind(a).call(*p) } :
          proc { |a, p| unbound_method.bind(a).call }

つまり、self(Applicationオブジェクト)がunboundなメソッド(オブジェクトに結び付いてないメソッド。ここでは登録編でインスタンスメソッドとして登録されたパスに対するリクエスト処理のブロック)バインドされ、呼び出されています。

route_eval

上で説明した「リクエストに対する処理」はroute_evalにブロックとして渡されるので、route_evalでyieldされないと実際には実行されません。というわけでroute_evalを見てみましょう。

sinatra/base.rbより抜粋
    # Run a route block and throw :halt with the result.
    def route_eval
      throw :halt, yield
    end

渡されたブロックがyieldされ、その戻り値(リクエストの処理ブロックからの戻り値)を引数にthrowが行われています。

invoke再訪

route_evalではthrowが行われていました。これは対応する(同じシンボルを指定して待っている)catchまで呼び出しを一気に戻るという仕組みになっています。:haltで待っているのはどこかというとinvokeメソッドです。

sinatra/base.rbより抜粋
    # Run the block with 'throw :halt' support and apply result to the response.
    def invoke
      res = catch(:halt) { yield }

      res = [res] if Integer === res or String === res
      if Array === res and Integer === res.first
        res = res.dup
        status(res.shift)
        body(res.pop)
        headers(*res)
      elsif res.respond_to? :each
        body res
      end
      nil # avoid double setting the same response tuple twice
    end

ただし、ここまで見てきた中でinvokeは二回呼び出されていました。call!からroute_evalに至るまでの呼び出しは以下のようになっています。

call!
  invoke
    dispatch!
      invoke
        route!
          process_route
            route_eval

この場合、内側のinvokeまで戻るということになります。その後、dipatch!では(登録されていたら)後処理が実行され、外側のinvokeにはnilが返されるので何もしないというようになっているようです。何故このようなややこしいことしているのかについては「歴史的経緯」な気がしますがともかくthrow-catchの大域脱出を使うことで処理を一気に巻き戻すということが行われています。

Sinatra::Helpers

最後に複線しておいたstatusやbodyについて。Sinatraではステータスコードやボディーを戻り値として返す以外に以下のように書くことも可能です。個人的には宣言的に書けるので好きです。

get '/' do
  status 200
  body   'Hello world!'
end

大体察しはつくと思いますがこのstatusやbodyはApplicationオブジェクトのインスタンスメソッドです。もっとも「DSLとして」Sinatraを使う人にはインスタンスメソッドであるということは意識せずに書けるようになっています。
これらのメソッドはSinatra::Helpersモジュールに定義されており、Baseクラスがインクルードする、リクエストの処理ブロックは先に見たようにApplicationオブジェクトのインスタンスメソッドのため、インクルードされているメソッドが使える(それらを介してインスタンス変数を操作する)というからくりになっています。

sinatra/base.rbより抜粋
  # Methods available to routes, before/after filters, and views.
  module Helpers
    # Set or retrieve the response status code.
    def status(value = nil)
      response.status = Rack::Utils.status_code(value) if value
      response.status
    end

処理編まとめ

以上ここまでリクエストが来たときにどのように処理が行われるのかについて見てきました。登録編、起動編に比べると少し複雑でした。

  • クラスはオブジェクトなので、呼び出し側の指定を満たすメソッドが定義していれば特異メソッドでも問題ない(呼ばれる側として渡すことができる)
  • リクエストが来るとあらかじめ作成しておいた「アプリケーションオブジェクト」を複製し、リクエストに割り当てる。再利用とかを行っていないのは「そこまでパフォーマンスを追求してないから」と思われる。
  • Rubyではデフォルト引数にメソッド呼び出しを書くことができる。
  • 大域脱出を行うためにthrow-catchが使われている(例外処理ではない)

おわりに

三回にわたってSinatraの中身がどうなっているかを見てきました。「DSLをどう実現してるんだろ?(あと、マイクロフレームワークと呼ばれる類の実装を見てみたい)」という興味から読んでみましたがとてもRubyを使い倒したコードになっていたように思います。明日書くコードの役には立たなそうですが参考になりました(笑)

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

Sinatraを支えるRuby記法 - 起動編

はじめに

前回はリクエストに対する処理(getで書いたブロック)がどう登録されるのかを見てきました。今回は起動編、つまり、

ruby myapp.rb

とした場合に何が起こっているのかを見ていきます。

あらためてmyapp.rbを見てみましょう。

myapp.rb
require 'sinatra'

get '/' do
  'Hello world!'
end

myapp.rbにはサーバを起動するコードは書かれていません。が、先の通りにmyapp.rbを実行するとサーバが起動します。今回はこのからくりを探るのが目的です。

Sinatra::Application(sinatra/main.rbの方)

sinatra/main.rbに書かれているコードのうち前回無視した方を見てみます。

sinatra/main.rbより抜粋
module Sinatra
  class Application < Base

    # we assume that the first file that requires 'sinatra' is the
    # app_file. all other path related options are calculated based
    # on this path by default.
    set :app_file, caller_files.first || $0

    set :run, Proc.new { File.expand_path($0) == File.expand_path(app_file) }

    # オプション処理
  end

  at_exit { Application.run! if $!.nil? && Application.run? }
end

setやcaller_files等はSinatra::Baseに書かれていますが大体予想通りのものなので省略します。
上記のコードで注目すべきは下から2行目、at_exitです。リファレンスにあるようにこのメソッドで登録したブロックはインタプリタが終了するときに実行されます。これが「サーバ起動を書いてないのにサーバが起動するからくり」です。

Sinatra::Base.run!

以上終わり、となるわけはもちろんなく、Application.run!の中に入っていきます。前回も見たようにApplicationにはあまり処理は書かれておらず、run!メソッドはBaseクラスの方に書かれています。

sinatra/base.rbより抜粋
      # Run the Sinatra app as a self-hosted server using
      # Thin, Puma, Mongrel, or WEBrick (in that order). If given a block, will call
      # with the constructed handler once we have taken the stage.
      def run!(options = {}, &block)
        return if running?
        set options
        handler         = detect_rack_handler
        handler_name    = handler.name.gsub(/.*::/, '')
        server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
        server_settings.merge!(:Port => port, :Host => bind)

        begin
          start_server(handler, server_settings, handler_name, &block)
        rescue Errno::EADDRINUSE
          $stderr.puts "== Someone is already performing on port #{port}!"
          raise
        ensure
          quit!
        end
      end

detect_rack_handlerと関連コード

detect_rack_handlerはrun!のコメントに書かれているようなことが行われているわけですがRubyに慣れてないとわかりにくいと思うので説明します。

sinatra/base.rbより抜粋
      def detect_rack_handler
        servers = Array(server)
        servers.each do |server_name|
          begin
            return Rack::Handler.get(server_name.to_s)
          rescue LoadError, NameError
          end
        end
        fail "Server handler (#{servers.join(',')}) not found."
      end

「で、serverはどうなってるの?」と探すと以下のようになっています。

sinatra/base.rbより抜粋
    set :server, %w[HTTP webrick]

    if ruby_engine == 'macruby'
      server.unshift 'control_tower'
    else
      server.unshift 'reel'
      server.unshift 'puma'
      server.unshift 'mongrel'  if ruby_engine.nil?
      server.unshift 'thin'     if ruby_engine != 'jruby'
      server.unshift 'trinidad' if ruby_engine == 'jruby'
    end

run!のコメントと違ってるようなw
それはともかく、このsetやif文がどこに書かれているかというと、SinatraモジュールのBaseクラス直下です。
Rubyは(Pythonもですが)「クラス定義に普通の実行文が書ける。それらの実行文はクラスが定義される際に実行される(そもそもメソッド定義なども実行文である)」という特徴があります。ここら辺がJavaなどとは大きく異なる点です。

start_server

サーバの起動はstart_serverメソッドで行われます。
ここで重要なのはRackハンドラに渡しているのはApplicationクラスオブジェクト1という点です。つまり、リクエストが届いてハンドラから呼び出される場合、Applicationクラスのクラスメソッドのcallが呼び出されることになります。call後の流れについては詳しくは処理編で説明します。

sinatra/base.rbより抜粋
      # Starts the server by running the Rack Handler.
      def start_server(handler, server_settings, handler_name)
        # Ensure we initialize middleware before startup, to match standard Rack
        # behavior, by ensuring an instance exists:
        prototype
        # Run the instance we created:
        handler.run(self, server_settings) do |server|
          unless suppress_messages?
            $stderr.puts "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
          end

          setup_traps
          set :running_server, server
          set :handler_name,   handler_name
          server.threaded = settings.threaded if server.respond_to? :threaded=

          yield server if block_given?
        end
      end

prototypeメソッド

start_serverでは初めにprototypeというメソッドが呼び出されています。起動編の最後にこのメソッドについて見てみましょう。

sinatra/base.rbより抜粋
      # The prototype instance used to process requests.
      def prototype
        @prototype ||= new
      end

prototypeはクラスメソッドなので、newとはつまり、「Application.new」ということです。

「つまり、ここでApplicationクラスのインスタンスが作られてるんだな」と普通は考えると思いますが、話はもう少し複雑です(複雑でなければ取り上げません)
以下のコードを見てください。

sinatra/base.rbより抜粋
      # Create a new instance without middleware in front of it.
      alias new! new unless method_defined? :new!

      # Create a new instance of the class fronted by its middleware
      # pipeline. The object is guaranteed to respond to #call but may not be
      # an instance of the class new was called on.
      def new(*args, &bk)
        instance = new!(*args, &bk)
        Wrapper.new(build(instance).to_app, instance)
      end

なんじゃこりゃ( ゚д゚)
そう、なんとRubyはインスタンスを作るnewメソッドを差し替えることができるのです。つまり以下のことが行われています。

  • 本来のnew→new!にエイリアスすることで保存
  • newを再定義

buildメソッドではコメントにあるようにミドルウェアの初期化が行われ、それがWrapperインスタンスにくるまれます。まあそこら辺については本題ではないので次のリクエスト処理編では無視して読み進めていきます。

ここまでのまとめ

以上ここまでサーバ起動について見てきました。大事なのはat_exitです。他にも「自分で書くことはないかもしれないけどライブラリ読む際には出てくるかもしれない記法」がいくつかありました。

  • Sinatraでサーバ起動コードを書かなくてもサーバが起動するのはat_exitにサーバ起動が書かれているから。
  • Rubyではクラス定義直下に実行文が書ける。うまく利用すると環境に応じた処理を記述することなどができる。
  • クラスメソッド中で単に「new」と書かれている場合、「そのクラスのインスタンスを生成する(self.new。selfはクラスオブジェクト)」という意味である。
  • Rubyではnewメソッドを再定義することが可能である(本来のnewはaliasを使うことで保存できる)

処理編に続く。


  1. 書かれているのはBaseクラスですが普通にアプリを動かす際にはBaseクラスを継承したApplicationクラスがselfです。 

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

Sinatraを支えるRuby記法

はじめに

SinatraはWebアプリケーションを記述するためのDSL(を実装するライブラリ)です。
READMEでは以下のようにアプリケーションを記述し、

myapp.rb
require 'sinatra'

get '/' do
  'Hello world!'
end

このrbファイルを実行すればすぐにサーバを立ち上げることができると書かれています(もちろんこれだけじゃ実用的ではないというところは置いといて)

ruby myapp.rb

この記事の目的はSinatraの使い方を説明することではなく、上のように書いたDSLが「どうやって動いているのか(動かされているのか)」を調べることで、明日役に立つ(ことがあるかもしれない)知識を学ぶことにあります。

Sinatraを読んでいくにあたって以下の三段階に分けて説明していきます。

  • Sinatraはルーティングをどのように登録しているのか(この記事)
  • Sinatraはサーバをどのように起動しているのか
  • Sinatraはリクエストをどのように処理しているのか

なお、読解対象とする(記事でリンクを張る)Sinatraのバージョンは2.0.5とします。

sinatra/main.rbとextend

アプリケーションのファイルがrequireしているsinatra.rbは、sinatra/main.rbをrequireしています。さらにsinatra/main.rbはsinatra/base.rbをrequireしており、このsinatra/base.rbが一番核となるファイルなのですがまずはsinatra/main.rbを見てみましょう。以下の記述があります(上の方に書いてある部分は起動編で説明します)

sinatra/main.rbより抜粋
# include would include the module in Object
# extend only extends the `main` object
extend Sinatra::Delegator

Sinatra::Delegatorがextendされています。extendは「モジュールに定義されたメソッドをオブジェクトの特異メソッドとして追加する(オブジェクトのクラス全部にではなく特定のオブジェクトのみにメソッドを追加する)」という動作をします。
上記ではextendはオブジェクトを指定することなく書かれています。実はRubyのトップレベル(class内とかじゃない単にputsとか書くところ)はmainというオブジェクトがself(メソッドのレシーバー)になっています。

つまり、「extend Sinatra::Delegator」により、Delegatorに定義されているメソッドがトップレベルにメソッドとして定義されます。先に書いておくとこのDelegatorにgetなどのルーティングメソッドが定義されています。
Delegator(移譲者)という名前から想像できるようにDelegatorには単純にメソッドが定義されているわけではありません。というわけで次にsinatra/base.rbに入りSinatra::Delegatorを見てみましょう。

Sinatra::Delegator

Sinatra::Deletatorの定義はsinatra/base.rbの最後の方にあります。

sinatra/base.rbより抜粋
  # Sinatra delegation mixin. Mixing this module into an object causes all
  # methods to be delegated to the Sinatra::Application class. Used primarily
  # at the top-level.
  module Delegator #:nodoc:
    def self.delegate(*methods)
      methods.each do |method_name|
        define_method(method_name) do |*args, &block|
          return super(*args, &block) if respond_to? method_name
          Delegator.target.send(method_name, *args, &block)
        end
        private method_name
      end
    end

    delegate :get, :patch, :put, :post, :delete, :head, :options, :link, :unlink,
             :template, :layout, :before, :after, :error, :not_found, :configure,
             :set, :mime_type, :enable, :disable, :use, :development?, :test?,
             :production?, :helpers, :settings, :register

    class << self
      attr_accessor :target
    end

    self.target = Application
  end

Delegatorの動作についてはコメントに書かれている通りです。日本語に訳すと「このモジュールをオブジェクトにミックスする(Ruby的に言うとextendする)とそのメソッド呼び出しはSinatra::Applicationクラスのメソッド呼び出しに移譲される」となります。

Delegatorメソッドで行われている処理は以下のようになります。難解なのは「moduleでselfってどういうこと?」ということだと思います。module~endの間でのselfはモジュールオブジェクトになります。

  • target属性の定義と初期値設定
    • attr_accessorを使用し、target属性(ゲッターメソッドとセッターメソッド)を定義する。「class << self」とあるがここではクラスとは関係なくメソッドが実行されるコンテキストをself(=Delegatorモジュール)にするという意味。つまり、Delegatorモジュールにクラス変数的1にtarget属性が追加される。このような書き方になっているのはattr_accessorがprivateメソッド(レシーバを指定してのメソッド呼び出しができないメソッド)のため。
    • targetの初期値としてApplicationクラス(Sinatra::Application)を設定。
  • delegateメソッドの定義と実行
    • delegateメソッドを定義する。これは「self.delegate」なのでDelegatorモジュールの特異メソッド(クラスで言うとクラスメソッド)になる。このメソッドはextendしてもオブジェクトにメソッドは追加されない。
    • 定義したdelegateメソッドを呼び出す。delegateメソッドではdefine_methodを使い「targetで設定されたクラスのメソッドを呼び出す移譲メソッド」が定義されている。つまり、例えばgetメソッドを呼び出すとSinatra::Applicationクラスのgetクラスメソッドが呼び出されるようになる。なおここで定義されるメソッドはextend時にオブジェクトに追加される。

トップレベル(グローバル)でルーティングを定義するのだから裏側にオブジェクトがいてそいつに登録されるのだろうなとは思ってましたが、予想よりもかなり複雑なことをしているなという印象です。
さて次はgetメソッドが定義されているSinatra::Applicationですね。と言いたいところですが、Applicationはあまり処理が書かれておらず、Sinatraの本体と言えるのはその親クラスのSinatra::Baseです。

Sinatra::Base(登録処理周り)

Sinatra::Baseは長い(ほぼ1000行)のでこの記事ではルーティング登録に関する部分のみを説明します。それ以外の部分については起動編、処理編で説明します。

getメソッドは以下のようになっています。routeメソッドでルーティングが設定されているのだろうなと予測できます。

sinatra/base.rbより抜粋
    class << self
      # Defining a `GET` handler also automatically defines
      # a `HEAD` handler.
      def get(path, opts = {}, &block)
        conditions = @conditions.dup
        route('GET', path, opts, &block)

        @conditions = conditions
        route('HEAD', path, opts, &block)
      end

routeメソッド(念のためですが、これ以降もすべて特異メソッド=クラスメソッドです)。compile!メソッドを呼び出して処理を行ったうえで@routesに追加しています。@routesにはHTTPメソッド(動詞)ごとに処理が登録されているということがわかります。

sinatra/base.rbより抜粋
      def route(verb, path, options = {}, &block)
        enable :empty_path_info if path == "" and empty_path_info.nil?
        signature = compile!(verb, path, block, options)
        (@routes[verb] ||= []) << signature
        invoke_hook(:route_added, verb, path, block)
        signature
      end

compile!に進む。ちなみにSinatraコードの特徴として、メソッドに「!」を付けているものが多く、Rubyのお決まり的には「オブジェクト状態が変わるメソッドは!を付ける」だと思いますが、読んでる分にinternal的な意味で付けているように思います。

sinatra/base.rbより抜粋
      def compile!(verb, path, block, **options)
        # Because of self.options.host
        host_name(options.delete(:host)) if options.key?(:host)
        # Pass Mustermann opts to compile()
        route_mustermann_opts = options.key?(:mustermann_opts) ? options.delete(:mustermann_opts) : {}.freeze

        options.each_pair { |option, args| send(option, *args) }

        pattern                 = compile(path, route_mustermann_opts)
        method_name             = "#{verb} #{path}"
        unbound_method          = generate_method(method_name, &block)
        conditions, @conditions = @conditions, []
        wrapper                 = block.arity != 0 ?
          proc { |a, p| unbound_method.bind(a).call(*p) } :
          proc { |a, p| unbound_method.bind(a).call }

        [ pattern, conditions, wrapper ]
      end

      def compile(path, route_mustermann_opts = {})
        Mustermann.new(path, mustermann_opts.merge(route_mustermann_opts))
      end

compileメソッドではパターンマッチングに使うMustermannオブジェクトが作成されています。今回の本題ではないのでこちらは置いといて、generate_methodに進みます。

sinatra/base.rbより抜粋
      def generate_method(method_name, &block)
        define_method(method_name, &block)
        method = instance_method method_name
        remove_method method_name
        method
      end

書いてあるままですが何をしているかというと、

  1. 渡されたブロック(getメソッドに渡されるブロック。つまり、パスに対する処理)を「インスタンスメソッドとして」定義する。
  2. 定義したメソッド(メソッドオブジェクト)を取得する。なおcompile!での受け取り側変数が示すようにここで取得されるのはUnboundMethod(特定のインスタンスに結び付いていないメソッド)
  3. 定義したメソッドを削除する。なお「クラスのインスタンスメソッド一覧」から削除されるだけで一つ前で「オブジェクト参照」が取得されているのでガーベジコレクトされることはありません。ちなみに削除しているのは「一覧からは消す」ためと思われます。

最後にcompile!の一部を再掲します。ここで作られているprocが実行されるのは実際にパスに対するリクエストが来たときですが、unboundなメソッドにインスタンスを関連付けて実行しているということが推測できます。

compile!の一部
        wrapper                 = block.arity != 0 ?
          proc { |a, p| unbound_method.bind(a).call(*p) } :
          proc { |a, p| unbound_method.bind(a).call }

うーんだいぶややこしい。「ブロックをインスタンスのコンテキストで実行してるのかな」と思ってはいたのですが、それを実現するにはこんな風に書かないといけないのですね・・・

ここまでのまとめ

以上ここまでルーティングの登録処理について見てきました。「普通のオブジェクト」以外のselfについて理解することがここまでを読み解く鍵と言えるでしょう。

  • DSLで使うメソッドはSinatra::Delegatorに定義されており2、mainオブジェクトがSinatra::Delegatorをextendすることでトップレベルに定義される。
  • module~end内のselfはモジュールオブジェクトになる。このselfを利用し、モジュールオブジェクトにクラス変数的に属性を定義したり、クラスメソッド的にメソッドを定義したりできる。
  • 渡されたブロックはSinatra::Baseクラスのインスタンスメソッドとして登録される(ただしメソッド一覧からはすぐに削除される)。リクエスト処理時にインスタンスをバインドして実行すると推測される。

起動編へ続く。


  1. 文法的にはクラス変数ではなく「モジュールオブジェクト(インスタンス)のインスタンス変数」ですが実質的にはクラス変数みたいなものです。 

  2. 続く記事で触れますが全部のメソッドがSinatra::Delegatorの仕組みで動いているわけではありません。 

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

Sinatraを支えるRuby記法 - 登録編

はじめに

SinatraはWebアプリケーションを記述するためのDSL(を実装するライブラリ)です。
READMEでは以下のようにアプリケーションを記述し、

myapp.rb
require 'sinatra'

get '/' do
  'Hello world!'
end

このrbファイルを実行すればすぐにサーバを立ち上げることができると書かれています(もちろんこれだけじゃ実用的ではないというところは置いといて)

ruby myapp.rb

この記事の目的はSinatraの使い方を説明することではなく、上のように書いたDSLが「どうやって動いているのか(動かされているのか)」を調べることで、明日役に立つ(ことがあるかもしれない)知識を学ぶことにあります。

Sinatraを読んでいくにあたって以下の三段階に分けて説明していきます。

なお、読解対象とする(記事でリンクを張る)Sinatraのバージョンは2.0.5とします。

sinatra/main.rbとextend

アプリケーションのファイルがrequireしているsinatra.rbは、sinatra/main.rbをrequireしています。さらにsinatra/main.rbはsinatra/base.rbをrequireしており、このsinatra/base.rbが一番核となるファイルなのですがまずはsinatra/main.rbを見てみましょう。以下の記述があります(上の方に書いてある部分は起動編で説明します)

sinatra/main.rbより抜粋
# include would include the module in Object
# extend only extends the `main` object
extend Sinatra::Delegator

Sinatra::Delegatorがextendされています。extendは「モジュールに定義されたメソッドをオブジェクトの特異メソッドとして追加する(オブジェクトのクラス全部にではなく特定のオブジェクトのみにメソッドを追加する)」という動作をします。
上記ではextendはオブジェクトを指定することなく書かれています。実はRubyのトップレベル(class内とかじゃない単にputsとか書くところ)はmainというオブジェクトがself(メソッドのレシーバー)になっています。

つまり、「extend Sinatra::Delegator」により、Delegatorに定義されているメソッドがトップレベルにメソッドとして定義されます。先に書いておくとこのDelegatorにgetなどのルーティングメソッドが定義されています。
Delegator(移譲者)という名前から想像できるようにDelegatorには単純にメソッドが定義されているわけではありません。というわけで次にsinatra/base.rbに入りSinatra::Delegatorを見てみましょう。

Sinatra::Delegator

Sinatra::Deletatorの定義はsinatra/base.rbの最後の方にあります。

sinatra/base.rbより抜粋
  # Sinatra delegation mixin. Mixing this module into an object causes all
  # methods to be delegated to the Sinatra::Application class. Used primarily
  # at the top-level.
  module Delegator #:nodoc:
    def self.delegate(*methods)
      methods.each do |method_name|
        define_method(method_name) do |*args, &block|
          return super(*args, &block) if respond_to? method_name
          Delegator.target.send(method_name, *args, &block)
        end
        private method_name
      end
    end

    delegate :get, :patch, :put, :post, :delete, :head, :options, :link, :unlink,
             :template, :layout, :before, :after, :error, :not_found, :configure,
             :set, :mime_type, :enable, :disable, :use, :development?, :test?,
             :production?, :helpers, :settings, :register

    class << self
      attr_accessor :target
    end

    self.target = Application
  end

Delegatorの動作についてはコメントに書かれている通りです。日本語に訳すと「このモジュールをオブジェクトにミックスする(Ruby的に言うとextendする)とそのメソッド呼び出しはSinatra::Applicationクラスのメソッド呼び出しに移譲される」となります。

Delegatorメソッドで行われている処理は以下のようになります。難解なのは「moduleでselfってどういうこと?」ということだと思います。module~endの間でのselfはモジュールオブジェクトになります。

  • target属性の定義と初期値設定
    • attr_accessorを使用し、target属性(ゲッターメソッドとセッターメソッド)を定義する。「class << self」とあるがここではクラスとは関係なくメソッドが実行されるコンテキストをself(=Delegatorモジュール)にするという意味。つまり、Delegatorモジュールにクラス変数的1にtarget属性が追加される。このような書き方になっているのはattr_accessorがprivateメソッド(レシーバを指定してのメソッド呼び出しができないメソッド)のため。
    • targetの初期値としてApplicationクラス(Sinatra::Application)を設定。
  • delegateメソッドの定義と実行
    • delegateメソッドを定義する。これは「self.delegate」なのでDelegatorモジュールの特異メソッド(クラスで言うとクラスメソッド)になる。このメソッドはextendしてもオブジェクトにメソッドは追加されない。
    • 定義したdelegateメソッドを呼び出す。delegateメソッドではdefine_methodを使い「targetで設定されたクラスのメソッドを呼び出す移譲メソッド」が定義されている。つまり、例えばgetメソッドを呼び出すとSinatra::Applicationクラスのgetクラスメソッドが呼び出されるようになる。なおここで定義されるメソッドはextend時にオブジェクトに追加される。

トップレベル(グローバル)でルーティングを定義するのだから裏側にオブジェクトがいてそいつに登録されるのだろうなとは思ってましたが、予想よりもかなり複雑なことをしているなという印象です。
さて次はgetメソッドが定義されているSinatra::Applicationですね。と言いたいところですが、Applicationはあまり処理が書かれておらず、Sinatraの本体と言えるのはその親クラスのSinatra::Baseです。

Sinatra::Base(登録処理周り)

Sinatra::Baseは長い(ほぼ1000行)のでこの記事ではルーティング登録に関する部分のみを説明します。それ以外の部分については起動編、処理編で説明します。

getメソッドは以下のようになっています。routeメソッドでルーティングが設定されているのだろうなと予測できます。

sinatra/base.rbより抜粋
    class << self
      # Defining a `GET` handler also automatically defines
      # a `HEAD` handler.
      def get(path, opts = {}, &block)
        conditions = @conditions.dup
        route('GET', path, opts, &block)

        @conditions = conditions
        route('HEAD', path, opts, &block)
      end

routeメソッド(念のためですが、これ以降もすべて特異メソッド=クラスメソッドです)。compile!メソッドを呼び出して処理を行ったうえで@routesに追加しています。@routesにはHTTPメソッド(動詞)ごとに処理が登録されているということがわかります。

sinatra/base.rbより抜粋
      def route(verb, path, options = {}, &block)
        enable :empty_path_info if path == "" and empty_path_info.nil?
        signature = compile!(verb, path, block, options)
        (@routes[verb] ||= []) << signature
        invoke_hook(:route_added, verb, path, block)
        signature
      end

compile!に進む。ちなみにSinatraコードの特徴として、メソッドに「!」を付けているものが多く、Rubyのお決まり的には「オブジェクト状態が変わるメソッドは!を付ける」だと思いますが、読んでる分にinternal的な意味で付けているように思います。

sinatra/base.rbより抜粋
      def compile!(verb, path, block, **options)
        # Because of self.options.host
        host_name(options.delete(:host)) if options.key?(:host)
        # Pass Mustermann opts to compile()
        route_mustermann_opts = options.key?(:mustermann_opts) ? options.delete(:mustermann_opts) : {}.freeze

        options.each_pair { |option, args| send(option, *args) }

        pattern                 = compile(path, route_mustermann_opts)
        method_name             = "#{verb} #{path}"
        unbound_method          = generate_method(method_name, &block)
        conditions, @conditions = @conditions, []
        wrapper                 = block.arity != 0 ?
          proc { |a, p| unbound_method.bind(a).call(*p) } :
          proc { |a, p| unbound_method.bind(a).call }

        [ pattern, conditions, wrapper ]
      end

      def compile(path, route_mustermann_opts = {})
        Mustermann.new(path, mustermann_opts.merge(route_mustermann_opts))
      end

compileメソッドではパターンマッチングに使うMustermannオブジェクトが作成されています。今回の本題ではないのでこちらは置いといて、generate_methodに進みます。

sinatra/base.rbより抜粋
      def generate_method(method_name, &block)
        define_method(method_name, &block)
        method = instance_method method_name
        remove_method method_name
        method
      end

書いてあるままですが何をしているかというと、

  1. 渡されたブロック(getメソッドに渡されるブロック。つまり、パスに対する処理)を「インスタンスメソッドとして」定義する。
  2. 定義したメソッド(メソッドオブジェクト)を取得する。なおcompile!での受け取り側変数が示すようにここで取得されるのはUnboundMethod(特定のインスタンスに結び付いていないメソッド)
  3. 定義したメソッドを削除する。なお「クラスのインスタンスメソッド一覧」から削除されるだけで一つ前で「オブジェクト参照」が取得されているのでガーベジコレクトされることはありません。ちなみに削除しているのは「一覧からは消す」ためと思われます。

最後にcompile!の一部を再掲します。ここで作られているprocが実行されるのは実際にパスに対するリクエストが来たときですが、unboundなメソッドにインスタンスを関連付けて実行しているということが推測できます。

compile!の一部
        wrapper                 = block.arity != 0 ?
          proc { |a, p| unbound_method.bind(a).call(*p) } :
          proc { |a, p| unbound_method.bind(a).call }

うーんだいぶややこしい。「ブロックをインスタンスのコンテキストで実行してるのかな」と思ってはいたのですが、それを実現するにはこんな風に書かないといけないのですね・・・

ここまでのまとめ

以上ここまでルーティングの登録処理について見てきました。「普通のオブジェクト」以外のselfについて理解することがここまでを読み解く鍵と言えるでしょう。

  • DSLで使うメソッドはSinatra::Delegatorに定義されており2、mainオブジェクトがSinatra::Delegatorをextendすることでトップレベルに定義される。
  • module~end内のselfはモジュールオブジェクトになる。このselfを利用し、モジュールオブジェクトにクラス変数的に属性を定義したり、クラスメソッド的にメソッドを定義したりできる。
  • 渡されたブロックはSinatra::Baseクラスのインスタンスメソッドとして登録される(ただしメソッド一覧からはすぐに削除される)。リクエスト処理時にインスタンスをバインドして実行すると推測される。

起動編へ続く。


  1. 文法的にはクラス変数ではなく「モジュールオブジェクト(インスタンス)のインスタンス変数」ですが実質的にはクラス変数みたいなものです。 

  2. 続く記事で触れますが全部のメソッドがSinatra::Delegatorの仕組みで動いているわけではありません。 

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

Cloud9 エラー: No space left on device の解決 (実際にスペースがない場合)

No space left on device というエラー。容量がないことを示すエラーだが、容量が余裕なのにこのエラーがでる怪奇現象もある見たい。今回は、ちゃんと容量がない時の対処法を書く。

ファイル容量を確認
$df -h

Filesystem Size Used Avail Use% Mounted on
/dev/sda1 15G 15G 0 100% /
none 312M 0 312M 0% /dev/shm

100%容量使っています。
解決方法: 容量を増やします。

解決の流れ

https://console.aws.amazon.com/
上記AWSコンソールから操作する。

1 インスタンス停止

公式 https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/Stop_Start.html
右側のナビゲーターペインで、、

インスタンスを選択 → 容量を増やしたいワークスペースのアベイラビリティ(us-east-xx)を確認し、選択して、アクション→状態→停止 をしておく

2 ヴォリュームデタッチ

右側のナビゲーターペインで、、

Volumeを選択 → 容量を増やしたいワークスペースのアベイラビリティ(us-east-xx)を確認し、選択して、アクション→ボリュームのデタッチ

*あとで使うのでボリュームIDをコピーする

3 ボリュームのスナップショットをとる

右側のナビゲーターペインで、、

スナップショットを選択 → スナップショットの作成
ボリュームIDをペースト、コメントは任意だけどワークスペース名とか入れると作業しやすい。

4 スナップショットを使って新しいボリュームを作る

そのままスナップショットの画面で、スナップショットを選択し、アクションから、ボリュームの作成を選択。
大きさはスナップショットより大きくなるようにする。
アベイラビリティゾーンは、該当ワークスペースのものを選択する。

5 スナップショットのアタッチ

公式 https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/ebs-restoring-volume.html
右側のナビゲーターペインで、、

ボリュームを選択 → 製作したスナップショットを選択し、アクションからボリュームのアタッチをする。このときのインスタンスIDは、アタッチ先のもので。手順1で訪れたインスタンスの画面から確認できる。

注 この時重要なのが、デバイスがデフォルトで/dev/sdfとなっているが、Cloud9なら、/dev/xvdfとしないと、インスタンスの起動に失敗する。



状態がin-use になったら、ワークスペースにアクセスしてインスタンスを起動できるはず。

参考
https://stackoverflow.com/questions/6151695/ec2-instance-on-amazon-and-i-am-greeted-with-no-space-left-on-the-disk
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails Tutorial(2週目)-3-

静的なページの作成

動的なページとはデータベースと連携しているページ
静的なページとはHTMLファイルだけで構成されているページ

コントローラの生成

$rails generate controller コントローラ名(キャメルケース) アクション名1 アクション名2
アクションと同時にそれに対応するビューも生成される。

キャメルケース(英: camel case)は、複合語をひと綴りとして、要素語の最初を大文字で書き表すことをいう。キャメルケースとは、大文字が「らくだのこぶ」のように見えることからの命名である。

スネークケースとはコンピューターで、スペース(空白)を入れずに複合語を表記する際、下線記号(アンダースコア)を用いる方式。プログラミング言語やファイル名、ハッシュタグなどに用いられる。名称は、文字列がヘビ(スネーク)のように見えることから。

$ git add -A
$ git commit -m "Add a Static Pages controller"
$ git push -u origin static-pages
以降は、$git pushのみで同じ事ができる

失敗した場合にもとに戻す

controllerの場合
$rails generate controller StaticPages home help
$rails destroy controller StaticPages home help

マイグレーションの場合
$ rails db:migrate
一つ前に戻るのは
rails db:rollback
最初の状態に戻したいときは
$ rails db:migrate VERSION=0
マイグレーションにはバージョン番号が付与されるため、バージョンを指定すればその状態まで復元できる。

HTTPメソッドについて

GETは最も頻繁に使われるHTTPリクエストで、主にWeb上のデータを読み取る (get) ときに使われます。「ページを取得する (get a page)」という意味のとおり、ブラウザはhttp://www.google.com/やhttp://www.wikipedia.org/などのWebサイトを開くたびにGETリクエストをサイトに送信します。

POSTは、GETの次によく使用されるリクエストで、ページ上のフォームに入力した値を、ブラウザから送信する時に使われます。例えばRailsアプリケーションでは、POSTリクエストは何かを作成するときによく使われます (なお本来のHTTPでは、POSTを更新に使ってもよいとしています)。例えばユーザー登録フォームで新しいユーザーを作成するときは、POSTリクエストを送信します。

他にも、PATCHと DELETEという2つの操作があり、それぞれサーバー上の何かを更新したり削除したりするときに使われます。これら2つの操作は、GETやPOSTほどは使われていません。これは、ブラウザがPATCHとDELETEをネイティブでは送信しないからです。しかし、Ruby on Railsなどの多くのWebフレームワークは、ブラウザがこれらの操作のリクエストを送信しているかのように見せかける技術 (偽装) を駆使して、PATCHとDELETEという操作を実現しています。結果として、Railsはこの4つのHTTPリクエスト (GET・POST・PATCH・DELETE) を全てサポートできるようになりました。

テスト駆動開発について

テスト駆動開発 (TDD) とは、テストの手法の1つで最初に「正しいコードがないと失敗するテスト」を書き、次に本編のコードを書いてそのテストがパスするようにする方法。

TDDのメリット

・テストが揃っていれば、機能停止に陥るような回帰バグ (Regression Bug: 以前のバグが再発したり機能の追加/変更に副作用が生じたりすること) を防止できる

・テストが揃っていれば、コードを安全にリファクタリング (機能を変更せずにコードを改善すること) ができる

・テストコードは、アプリケーションコードから見ればクライアントとして動作するので、アプリケーションの設計やシステムの他の部分とのインターフェイスを決めるときにも役に立つ。

TDDか一括テストかの目安

・アプリケーションのコードよりも明らかにテストコードの方が短くシンプルになる (=簡単に書ける) のであれば、「先に」書く
・動作の仕様がまだ固まりきっていない場合、アプリケーションのコードを先に書き、期待する動作を「後で」書く
・セキュリティが重要な課題またはセキュリティ周りのエラーが発生した場合、テストを「先に」書く
・バグを見つけたら、そのバグを再現するテストを「先に」書き、回帰バグを防ぐ体制を整えてから修正に取りかかる
・すぐにまた変更しそうなコード (HTML構造の細部など) に対するテストは「後で」書く
・リファクタリングするときは「先に」テストを書く。特に、エラーを起こしそうなコードや止まってしまいそうなコードを集中的にテストする

埋め込みRuby

app/views/static_pages/home.html.erb
<% provide(:title, "Home") %>
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
  </head>
  <body>
    <h1>Sample App</h1>
    <p>
      This is the home page for the
      <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
      sample application.
    </p>
  </body>
</html>

<% ... %>と書くと、中に書かれたコードを単に実行するだけで何も出力しない。<%= ... %>のように等号を追加すると、中のコードの実行結果がテンプレートのその部分に挿入される

ブロックとyieldについて

ブロックとはメソッドの引数として渡すことができる処理のかたまり
例えば…

numbers = [1, 2, 3]
sum = 0
numbers.each do |n|
  sum += n
end

というコードでは、eachメソッドが配列numbersから要素を一つずつ取り出し、その値をdo以降のブロックに渡している。

yieldはブロックに引数を渡したり、ブロックの戻り値を受け取ったりできる。

def greeting 
  puts 'aa'
  text = yield 'bb' 
  puts text
  puts 'cc'
end

greeting do |nn|
  nn * 2
end

=> aa
   bbbb
   cc

上の処理では,

①puts 'aa'
②bbはブロック引数として、ブロックに渡され、yieldにより、ブロックの処理を行う
③bbがブロック引数nnに代入されブロック内の処理が行われる
④ブロックの戻り値をyieldが受け取り、それが変数textに代入
⑤puts text
⑥puts 'cc'
の流れ

個人的な理解としてはyieldはブロックを呼び出し、その戻り値を返す処理。

provideとyieldの関係

以上を整理した上で、先程のprovideとyieldが出てくるコードを見ると
provideヘルパは、:title というラベルと "Home"という文字列を関連付けており、yieldによってそれを呼び出している形。
あまり、ブロック処理のときのyiledとは関係ないのかな?

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

Rails6 のちょい足しな新機能を試す21(html_safe編)

はじめに

Rails 6 に追加されそうな新機能を試す第21段。 今回のちょい足し機能は、 String#html_safe 編です。
Rails 6.0 では、 html_safe?true な文字列から [] でインデックスを指定して抜き出した部分文字列も html_safe? が true となるようになりました。

Ruby 2.6.3, Rails 6.0.0.rc1 で確認しました。Rails 6.0.0.rc1 は gem install rails --prerelease でインストールできます。

$  rails --version
Rails 6.0.0.rc1

Rails プロジェクトを作る

$ rails new rails6_0_0rc1
$ cd rails6_0_0rc1

Controller と View を作る

controller と View を作ります。

$ bin/rails g controller html_safe index

HtmlSafeController#index を修正する

controller で、html_safe な文字列を作成し、そこから一部を抜き出します。

app/controllers/html_safe_controller.rb
class HtmlSafeController < ApplicationController
  def index
    str = '<em>This is HTML safe string first part</em><strong>This is HTML safe string second part</strong>'.html_safe
    i = str.index('<strong>')
    @html_safe_str1 = str[0...i]
    @html_safe_str2 = str[i..]
  end
end

View を修正する

index.html.erb で controller で抜き出した一部の文字列を表示します。

app/views/html_safe/index.html.erb
<h1>HtmlSafe</h1>
<p>
  @html_safe_str1 = <%= @html_safe_str1 %>
</p>
<p>
  @html_safe_str2 = <%= @html_safe_str2 %>
</p>

rails server を実行して表示する

bin/rails s を実行してブラウザから http://localhost:3000/html_safe/index にアクセスします。
html safe な文字列として処理されていることがわかります。

2019-05-19-091744_414x128_scrot.png

Rails 5 では?

Rails 5.2.3 では、抜き出した文字列の html_safe?false になるため、以下のようにエスケープ処理されてしまいます。
2019-05-19-091825_466x63_scrot.png

試したソースは以下にあります。
https://github.com/suketa/rails6_0_0rc1/tree/try021_html_safe

参考情報

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

error Command "webpack-dev-server" not found. info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

$ bin/webpack-dev-server

で出るタイトルのエラー

error Command "webpack-dev-server" not found. info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

解決コマンド

yarnのアップグレード

$ brew upgrade yarn

webpackerインストール

$ bundle exec rails webpacker:install

webpack-dev-server立ち上げ

$ bin/webpack-dev-server
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Serverspecで正規表現が使えない場合のor条件テスト

概要

Ansible Playbookの動作確認にServerspecを利用しています。
設定量が多いためServerspecでサーバの状態を可視化しています。

Ansibleでディレクトリの作成を行うのですが、roleによって
ディレクトリの権限がuidである状態と、ユーザ名である状態に分かれてしまいます。

一つのServerspecのroleで上記の差分を吸収したいのですが、Specinfraの仕様上、ファイル等の権限確認に正規表現が使えません。

specinfra/lib/specinfra/command/base/file.rb
    def check_is_owned_by(file, owner)
      regexp = "^#{owner}$"
      if owner.is_a?(Numeric) || (owner =~ /\A\d+\z/ ? true : false)
        "stat -c %u #{escape(file)} | grep -- #{escape(regexp)}"
      else
        "stat -c %U #{escape(file)} | grep -- #{escape(regexp)}"
      end
    end

なので、無理やりSpecinfraを使うことで対応しました。
ほかに方法あったらご教授お願いします。

対応策

  • パラメータを初期化
spec/spec_helper.rb
require 'net/ssh'
require 'serverspec'
require 'specinfra'

include Specinfra::Command

set :host, ENV['TARGET_HOST']

# :ssh | :exec
set :backend, :exec

# backendにsshを利用する場合
set :ssh_options, Net::SSH::Config.for(ENV['TARGET_HOST']).update({
    :user => 'user'
    :keys => '/home/user/.ssh/id_ecdsa'
})

# sudoユーザの場合
set :sudo_password, 'password' # 複合化処理等は省略
set :require_pty, true
  • Specinfraでのコマンド実行結果で条件分岐を行う
spec.rb
require 'spec_helper'

describe file('/tmp/testuser/') do
    # コマンドを取得
    cmd = Command::Base::File.check_is_owned_by('/tmp/testuser/', 'testuser')
    # 終了ステータスコードを取得
    rc = Specinfra::Runner.run_command(cmd).exit_status

    if rc != 0
        it { should be_owned_by 9999 }
    else
        it { should be_owned_by 'testuser' }
    end
end

実践

  • Rakefileを準備
Rakefile
require 'rake'
require 'rspec/core/rake_task'

task :defeult => :spec

RSpec::Core::RakeTask.new(:spec) do |task|
    ENV['TARGET_HOST'] = 'example'
    task.pattern = 'spec.rb'
end
  • 構成
./Rakefile
./spec/spec_helper.rb
./spec.rb
  • ディレクトリを作成、ユーザ権限をuidで設定
mkdir /tmp/testuser && chown 9999 $_
  • テスト実行
rake
  • uidマッピング
useradd -u 9999 testuser
  • テスト実行
rake

上記の2通りのテストが通るはずです。

バージョン情報

  • chefdk-0.6.2-1
  • serverspec-2.18.0
  • specinfra-2.35.1
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby・Ruby on Railsの環境構築(Mac向け)

はじめに

こんにちは!
今回はRuby・Ruby on Railsを勉強し始めようかな〜と思っている方に向けて、簡単な環境構築の方法をご紹介しようと思います。

そもそもRuby・Ruby on Railsとは

Rubyというのはまつもとゆきひろ氏が開発したオブジェクト指向スクリプト言語であり、JavaやC言語と違ってコンパイルする必要がなく、初心者でもすぐにプログラムを実行できるようになっています。
Rubyは「楽しさ」に焦点を当てて作られているようで、まつもと氏もこう語っています。
Ruby には Perl や Python とは決定的に違う点があり、それこそが Ruby の存在価値なのです。それは「楽しさ」です。私の知る限り、Ruby ほど「楽しさ」について焦点を当てている言語は他にありません。Ruby は純粋に楽しみのために設計され、言語を作る人、使う人、学ぶ人すべてが楽しめることを目的としています。しかし、ただ単に楽しいだけではありません。Ruby は実用性も十分です。実用性がなければ楽しめないではありませんか。

そして、Ruby on Railsは先ほどご紹介したRubyを用いてWebアプリケーションを開発するためのフレームワークであり、比較的簡単にバリエーション豊富なWebアプリケーションを作ることができます。Ruby on Railsを使って作られたサービスにはCookPadや価格.comなどがあり、どれも有名なものばかりです。Ruby on Railsを使ってWebアプリを開発し起業することも夢じゃないかも??

それでは実際に環境構築をしていきましょう!!

Rubyの環境構築

まず「ターミナル」「テキストエディタ」を用意しましょう。
テキストエディタはAtomなどいろいろありますが、お好きなものを使っていただいて大丈夫です。
これからRubyをインストールしていくのですが、実際の開発環境では複数のバージョンのRubyを扱うことが多く、それらの管理には「rbenv」というツールが欠かせないものになります。では、実際に手順を踏んでインストールしましょう。

①まずHomebrewをインストールします。ターミナルにこのようなコードを打ち込みましょう。

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

これを打ち込むとEnterキーの入力が求められますのでEnterキーを押します。次に端末のパスワードを入力します。
スクリーンショット 2019-05-22 11.23.21.png
パスワードを入力してもターミナル上には何も表示されませんが、それで正解です。気にせず入力しましょう。
しばらくするとインストールが完了します。

brew -v

と入力して「Homebrew 1.X.X」のように表示されたら完了です!

②次にrbenvをインストールしていきます。

以下のようにコードを入力しましょう。

brew install rbenv ruby-build

しばらくするとインストールが完了するので

rbenv -v

と入力して先ほどのようにバージョンが表示されたらOKです!
そして、これからRubyのインストールに移っていくのですが、その前にrbenvの設定を済ませます。ターミナルに

echo 'eval "$(rbenv init -)"' >> ~/.bash_profile

と入力してEnterキーを押します。何も表示されなければ大丈夫です。
次は

source ~/.bash_profile

と入力します。これでも何も表示されません。以上で設定完了です!

③最後はいよいよRubyのインストールです。

今回インストールするのはバージョン2.5.0のRubyです。

rbenv install 2.5.0

と入力して待ちます。結構長いです。。。
終了したら

rbenv versions

と入力しましょう。ちゃんとインストールできていたら2.5.0と表示されます。
今回インストールしたRubyを使えるようにするために

rbenv global 2.5.0

と入力します。これで2.5.0のRubyが使えるようになりました!お疲れ様です。

Ruby on Railsの環境構築

Rubyが導入できた興奮冷めやらぬうちにRuby on Railsの導入も済ませてしまいましょう。もっと言うと、サーバーの起動まで済ませます。

①まずは以下のようなコードを入力します。

gem install rails -v 5.0.3

これでRails自体のインストールは完了です。

②ではアプリケーションを新規作成しましょう。

今回はひとまずテストということで

rails new test_app

と入力します。これでテスト用のアプリケーションは作れました。
テキストエディタで先ほど作ったtest_appを開きましょう。すると、そのフォルダ内に「Gemfile」という名前のファイルがあると思います。これを開き

gem 'sqlite3'

と書かれている行を

gem 'sqlite3', '~> 1.3.6'

に書き換えます。この状態で一旦セーブします。
再びターミナルに戻って

cd test_app

でtest_appフォルダに移動し、

bundle update

を実行します。しばらく待って
「Bundle updated!」と表示されたら成功です!

③最後にサーバーを起動してみます!

rails s

と入力して実行すると文章がダーーーっと表示され、最後に「Use Ctrl + C to stop」と書かれていたらOKです。
起動したサーバーにアクセスしてみます。
ブラウザを開いてurlバーに

localhost:3000

と入力して検索します。
すると、、、
スクリーンショット 2019-05-22 11.51.00.png
このような画面が表示されると思います。サーバー起動成功です!

ちなみにサーバーをストップさせたい時は「control + C」を押します。

終わりに

以上が一連の流れです。
僕も最近Rubyを勉強し始めて環境構築でつまづいたので、同じようにつまづいている方の参考になれば幸いです。これから勉強を進めて何かしらWebサイトを作れたら嬉しいな〜なんて思っています。

それではまた次の記事で!

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

モデルに書いた定数を参照する

environment.rb

config/environments/は環境単位の設定ファイルを格納するディレクトリ(Railsドキュメント参照)
で簡単にまとめると、よく使う定数を置いて置ける場所という認識

でも共通でこの定数は使わないよって場合はモデルに書いた方が見やすいのでモデルに定数を書く

モデル

モデルに書くときも書き方は変わらないが、呼び出し方が違う

expert_collection_achieved.rb
  COMPLETE_STATUS = {
    :not_achieved => 0,
    :achieved => 1
  }

書き方としてはこんな感じで

environment.rbに書いたものを呼び出すときはCOMPLETE_STATUS[:achieved]1が表示される

モデルに書いたものだと、モデル名::COMPLETE_STATUS[:achieved]で呼び出す

ex) ExpertCollectionAchieved::COMPLETE_STATUS[:achieved]

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

Rails: 検索フォームでLike演算子を使うときはsanitize_sql_likeを使おう

備忘録

Webアプリを作るとき、必ずどこかで検索フォームを実装するときがあるかと思う。
その際に、ただ何も考えずに今までは、Like演算子を使ってきたがそれだとまずいということが分かったので備忘録として残しておく

railsではsanitize_sql_likeを使うことで検索でLIKE演算子のワイルドカードである_や%が使われてもエスケープすることができる

じゃあどのように書くのか?

User.where(user_type_id: 2).where(['second_prefecture_code LIKE ? or specialized_field LIKE ?', "%#{sanitize_sql_like(searchFirst)}%", "%#{sanitize_sql_like(searchSecond)}%"])

一例だが、上記のように書くことで、ワイルドカードである_や%が使われてもエスケープできるようになるはずである。

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

一番わかりやすいパンくずの実装(gem 'gretel')

今回はパンくずの実装についての記事を分かり易く説明します。

パンくずとは?

下記の画像のように画面遷移してきて、自分がどの位置にいるのか視覚的に分かり易く表示しているこれらをパンくずと総称しています。
また、リンクも持たせられるので、画面を戻る際にクリックしたら遷移するのでUI的にも優しいです。

下記メルカリの参考画像です。
スクリーンショット 2019-05-22 0.31.56.png

導入手順

使用gemは下記です。

gem 'gretel'

:writing_hand: _breadcrumbs.html.haml(パンくずview)の説明

パンくずを表示させるViewを部分テンプレートに分けておく。
(分けないでも良いが、変更や調整の際にメンテナンスを容易にするため)
下記以外の方法もあるが、私の場合は分かり易く呼び出されたコントローラーと、その先のアクション名でパンくずの表示を切り替えれるようにcase文を使い組んでいる。

case文については過去の記事を参考にしてください。
case文での条件分岐方法

case params.values_at :controller, :actionの部分は
binding.pryでデバックした際にコントローラー名とアクション名が入っているのを確認できるので、
when ['users', 'show']とすることでusersコントローラーのshowアクションに遷移したい際にこちらが参照される。

breadcrumb :user_showの部分は次々項で説明するconfig/breadcrumbs.rb内のメソッドを呼び出すためのメソッド名(readcrumbs.rb側のメソッド名)。
少し紛らわしいが、先にこちらでメソッド名を命名してしまってreadcrumbs.rb側に当てがった方がスムーズなので私の場合はこのような手順で進めています。

末尾にあるに下記のコードは、表示を区切る際に間に入る記号などを指定できる。
見慣れない文字が入っている部分はHTMLの特殊文字になる。
下記のサイトのように様々な記号を当てれる。
https://www.benricho.org/symbol/tokusyu_02_arrow.html

= breadcrumbs separator: " &nbsp;&nbsp;&#12297;&nbsp; "

上記の結果が下記のように変換される。
>の前後に空白を入れている。
= breadcrumbs separator: "   〉  "

app/views/shared/_breadcrumbs.html.haml
- case params.values_at :controller, :action

- when ['users', 'show']
  - breadcrumb :user_show

- when ['user_chat_rooms', 'index']
  - breadcrumb :user_chat_rooms_index

= breadcrumbs separator: " &nbsp;&nbsp;&#12297;&nbsp; "


:writing_hand: 部分テンプレート反映の準備

上記の部分テンプレートをパンくずを表示させた場所に下記のようにおく。
大体の場合はヘッダーの下に置かれていることがほとんどだと思うので、
ヘッダーの部分テンプレート中に組み込ませた方が効率は良いと思う。

 =render 'shared/breadcrumbs'

:writing_hand: gretel(パンくず)メソッド作成

次にconfig下にbreadcrumbs.rbを作成し下記のように記述。
linkの後に続く部分に画面に表示させたいパンくず名を入力する。
後にroot_pathやuser_chat_rooms_path(user_id:current_user.id)とあるが、
こちらはパンくずの表示部がクリックされた際に画面遷移するためのpathなので、クリックして遷移の必要がなければ記述しなくてのよい。
parent :rootの部分は親のメソッド名を記述する。

config/breadcrumbs.rb
crumb :root do
  link 'ホーム', root_path
end

crumb :user_show do
  link "#{current_user.name}"
  parent :root
end

crumb :user_chat_rooms_index do
  link "メッセージ一覧",user_chat_rooms_path(user_id:current_user.id)
  parent :user_show
end

完成

そして最終的には下記のようになる。
スクリーンショット 2019-05-22 1.16.31.png

次回は少し複雑なパンくずの表示方を記事にします。

``

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

[初心者]プログラミングを約250時間学んで感じたこと

はじめに

スクールでプログラミングを学び始めて、約1ヶ月が経ちます。
細かく言えば、今日で25日目になります(笑)
言語としては、
・Ruby ・Rails 
・HTML ・CSS  
を学んできました。
決め事として、1日最低10時間以上を毎日続けるようにしています!
単純計算で、約250時間です。
ということで今回は、全くの初心者が250時間勉強して感じたことを書いていこうと思います。

予想以上のキツさ

多数の言語を短期間に学ぶのは大変ですが、本気でやれば各言語の基礎は学ぶことができました。

ただ、正直かなりキツイです。
スクールは毎日通っていて、25日間1日も欠席はしていません!!!
新しい知識ばかり頭に入ってくる中で、毎日が格闘です(笑)

しかしそのおかげで、全くの無知だった私ですが、今ではTwitterの簡易版のようなアプリであれば自力で作成することができるようになりました!

正直初めは、「これ、本当に自分でも理解できるのか・・・?」
と思ってました。(笑)
ですが、絶対サボらず、折れずに毎日頑張っていたらいつの間にかアプリが作れるようになってました!
人生の中で1番頑張ったといっても過言ではないかもしれせん・・・(笑)

多くの衝撃事実

プログラミングを少しずつ学んでいく中で、「え!?」と思うことがいくつかありました。

まず1つは、
「プログラミングは暗記ではない」
ということです。

私は最初、プログラミングはありったけのコードをたくさん暗記しなきゃいけないと思ってました。(笑)

しかし、学んでいく中で
「わからないことがあったらすぐに調べて、それを適応させる能力」
がとても大事なのだと気付かされました。

「プロのエンジニアでもGoogleは必須なんだよ」と言われてから、私の勉強法は8割がアウトプット作業ばかりです。

2つ目は、
エラーの多さ、難しさです。

アプリを作成する中で、エラー、エラー、エラー、エラー・・・・・。
どれだけ調べても解決できないものだったり、本当に折れそうになります。(笑)

各言語における仕組み等を理解していないとエラーが解決できないため、
エラーが起きるたびに土台の脆さを痛感させられました。

ですが、数をこなすうちに段々と理解できて、簡単なエラーなら1人で解決できるようになることができました^^

しかし、実際まだたくさんの種類のエラーがあると思うので、勉強して数をこなしていくしかないですね・・・

おわりに

まだまだ、ほんの一部を学んだだけです。

もっといえば、基礎も全て完璧とはいえず、まだまだ知識不足だといえます。
これからもコツコツ努力して、一人前のプログラマーになれるよう日々精進です・・・

明日からは、「Javascript」「jQuery」を学んでいこうと思います!

ありがとうございました^^

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