ためすう

Rails に sorbet をインストールしてみる

2019-08-06

やったこと

Rails に sorbet を導入します。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

調査

Gemfile

gem 'sorbet', :group => :development
gem 'sorbet-runtime'
$ bundle install

sorbet の初期化

$ bundle exec srb init
# typed: true
class ApplicationController < ActionController::Base
  extend T::Sig

  private
  sig {params(x: Integer).returns(String)}
  def sorbet_init(x)
    # x.to_s
  end
end

型チェックの結果

$ bundle exec srb tc
app/controllers/application_controller.rb:20: Returning value that does not conform to method result type https://srb.help/7005
    20 |  def sorbet_init(x)
    21 |    # x.to_s
    22 |  end
  Expected String
    app/controllers/application_controller.rb:20: Method sorbet_init has return type String
    20 |  def sorbet_init(x)
          ^^^^^^^^^^^^^^^^^^
  Got NilClass originating from:
    app/controllers/application_controller.rb:20:
    20 |  def sorbet_init(x)
          ^^^^^^^^^^^^^^^^^^
Errors: 1

参考

Python の dir を使ってみる

2019-08-05

やったこと

dir 関数を使ってみます。

確認環境

$ ipython --version
6.1.0
$ jupyter --version
4.3.0
$ python --version
Python 3.6.2 :: Anaconda custom (64-bit)
import numpy as np
np.__version__
'1.13.1'

調査

引数がない場合、現在のローカルスコープにある名前のリストを返します。引数がある場合、そのオブジェクトの有効な属性のリストを返そうと試みます。

class Shape:
    def __dir__(self):
        return ['area', 'perimeter', 'location']

s = Shape()
print(dir(s))

出力結果

['area', 'location', 'perimeter']

参考

Ruby で hashie (gem) を使ってみる

2019-08-05

やったこと

今回、gem の hashie を使ってみます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

調査

インストール

$ gem install hashie

Coercion

データの型を強制します。

require 'hashie'

class BaseCoercionHash < Hash
  include Hashie::Extensions::Coercion
  include Hashie::Extensions::MergeInitializer
end

class DummyJob < BaseCoercionHash
end

class Job < BaseCoercionHash
  coerce_key :name, String
  coerce_key :year, Integer
end

class User < BaseCoercionHash
  coerce_key :name, String
  coerce_key :age, Integer
  coerce_key :job, Job
end


user = User.new(name: 'hogehoge', age: 20)
p user

user2 = User.new(name: 'hogehoge', age: 'ああああ')
p user2

user3 = User.new(name: 'hogehoge', age: 35, job: Job.new(name: 'free'))
p user3


user4 = User.new(name: 'hogehoge', age: 35, job: {name: 'free2'})
p user4

user5 = User.new(name: 'hogehoge', age: 35, job: DummyJob.new)
p user5

user6 = User.new(name: 'hogehoge', age: 35, job: 99999)
p user6

出力結果

ruby-hashie.rb

$ ruby ruby-hashie.rb
{:name=>"hogehoge", :age=>20}
{:name=>"hogehoge", :age=>0}
{:name=>"hogehoge", :age=>35, :job=>{:name=>"free"}}
{:name=>"hogehoge", :age=>35, :job=>{:name=>"free2"}}
{:name=>"hogehoge", :age=>35, :job=>{}}
Traceback (most recent call last):
	8: from ruby-hashie.rb:39:in `<main>'
	7: from ruby-hashie.rb:39:in `new'
	6: from /Users/abc/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/hashie-3.6.0/lib/hashie/extensions/merge_initializer.rb:20:in `initialize'
	5: from /Users/abc/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/hashie-3.6.0/lib/hashie/extensions/merge_initializer.rb:20:in `each'
	4: from /Users/abc/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/hashie-3.6.0/lib/hashie/extensions/merge_initializer.rb:21:in `block in initialize'
	3: from /Users/abc/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/hashie-3.6.0/lib/hashie/extensions/coercion.rb:38:in `set_value_with_coercion'
	2: from /Users/abc/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/hashie-3.6.0/lib/hashie/extensions/coercion.rb:180:in `block in build_coercion'
	1: from /Users/abc/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/hashie-3.6.0/lib/hashie/extensions/coercion.rb:180:in `new'
/Users/abc/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/hashie-3.6.0/lib/hashie/extensions/merge_initializer.rb:20:in `initialize': undefined method `each' for 99999:Integer (NoMethodError)
	6: from ruby-hashie.rb:39:in `<main>'
	5: from ruby-hashie.rb:39:in `new'
	4: from /Users/abc/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/hashie-3.6.0/lib/hashie/extensions/merge_initializer.rb:20:in `initialize'
	3: from /Users/abc/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/hashie-3.6.0/lib/hashie/extensions/merge_initializer.rb:20:in `each'
	2: from /Users/abc/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/hashie-3.6.0/lib/hashie/extensions/merge_initializer.rb:21:in `block in initialize'
	1: from /Users/abc/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/hashie-3.6.0/lib/hashie/extensions/coercion.rb:37:in `set_value_with_coercion'
/Users/abc/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/hashie-3.6.0/lib/hashie/extensions/coercion.rb:40:in `rescue in set_value_with_coercion': Cannot coerce property :job from Integer to Job: undefined method `each' for 99999:Integer (Hashie::CoercionError)

DummyJob クラスが Hash クラスを継承していると、通ってしまいました。

気をつけないと嵌るかもしれません。

参考

numpy.ravel を使ってみる

2019-08-04

やったこと

Numpy で numpy.ravel を使ってみます。

配列を1次元配列にします。

確認環境

$ ipython --version
6.1.0
$ jupyter --version
4.3.0
$ python --version
Python 3.6.2 :: Anaconda custom (64-bit)
import numpy as np
np.__version__
'1.13.1'

調査

import numpy as np
x = np.array([[9, 8, 7], [1, 2, 3], [4, 5, 6]])
print(x.ravel())
print(np.ravel(x))
print(np.reshape(x, -1))

出力結果

[9 8 7 1 2 3 4 5 6]
[9 8 7 1 2 3 4 5 6]
[9 8 7 1 2 3 4 5 6]

参考

numpy.linspace を使ってみる

2019-08-04

やったこと

Numpy で numpy.linspace を使ってみます。

確認環境

$ ipython --version
6.1.0
$ jupyter --version
4.3.0
$ python --version
Python 3.6.2 :: Anaconda custom (64-bit)
import numpy as np
np.__version__
'1.13.1'

調査

import numpy as np
x = np.linspace(0, 20, 10)
x

0 ~ 20 までの区間で、等間隔に10点サンプリングします。

出力結果

array([ 0.        ,  2.22222222,  4.44444444,  6.66666667,  8.88888889,
       11.11111111, 13.33333333, 15.55555556, 17.77777778, 20.        ])

参考

Rails で member ルーティングを使ってみる

2019-08-04

やったこと

Rails で member ルーティングを使ってみます。

以下は Rails ガイドからの引用です。

デフォルトで作成されるRESTfulなルーティングは7つですが、7つでなければならないということはありません。必要であれば、コレクションやコレクションの各メンバーに対して適用されるリソースを追加することもできます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

調査

コード追加

config/routes.rb

Rails.application.routes.draw do

  resources :hello do
    get 'index', action: :index
    get '(/:id)/show2', action: :show2
    get 'show3', on: :member
  end
end

app/controllers/hello_controller.rb

class HelloController < ApplicationController
  def index
  end

  def show2
    render :json => {ver: 2, id: params[:hello_id]}
  end

  def show3
    render :json => {ver: 3, id: params[:id]}
  end
end

結果を確認する

ルーティングを確認してみる

$ rails routes | grep -E "/hello" | grep "show."
                          GET    /hello/:hello_id(/:id)/show2(.:format)                                                   hello#show2
              show3_hello GET    /hello/:id/show3(.:format)                                                               hello#show3

パラメータの取得方法が変わっています。

member ルーティングでは id となっています。

下記の URL にアクセスすると、パラメータが取得できていることが確認できます。

http://localhost:3000/hello/1000/show2

http://localhost:3000/hello/1000/show3

参考

Rails で find_each、find_in_batches を使ってみる

2019-08-04

やったこと

Rails で大量のデータを処理するときに使う

  • find_each
  • find_in_batches

を使ってみます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

調査

find_each

$ rails c
...

irb(main):005:0> Task.find_each(batch_size: 1){ |task| p task }
  Task Load (0.1ms)  SELECT  "tasks".* FROM "tasks" ORDER BY "tasks"."id" ASC LIMIT ?  [["LIMIT", 1]]
#<Task id: 4, created_at: "2019-06-12 12:50:53", updated_at: "2019-07-14 16:01:58", name: "name2">
  Task Load (0.1ms)  SELECT  "tasks".* FROM "tasks" WHERE "tasks"."id" > ? ORDER BY "tasks"."id" ASC LIMIT ?  [["id", 4], ["LIMIT", 1]]
#<Task id: 5, created_at: "2019-06-12 12:52:31", updated_at: "2019-06-12 12:52:31", name: "name1">
  Task Load (0.1ms)  SELECT  "tasks".* FROM "tasks" WHERE "tasks"."id" > ? ORDER BY "tasks"."id" ASC LIMIT ?  [["id", 5], ["LIMIT", 1]]
=> nil

find_in_batches

$ rails c
...

irb(main):006:0> Task.find_in_batches(batch_size: 1){ |task| p task }
  Task Load (0.1ms)  SELECT  "tasks".* FROM "tasks" ORDER BY "tasks"."id" ASC LIMIT ?  [["LIMIT", 1]]
[#<Task id: 4, created_at: "2019-06-12 12:50:53", updated_at: "2019-07-14 16:01:58", name: "name2">]
  Task Load (0.1ms)  SELECT  "tasks".* FROM "tasks" WHERE "tasks"."id" > ? ORDER BY "tasks"."id" ASC LIMIT ?  [["id", 4], ["LIMIT", 1]]
[#<Task id: 5, created_at: "2019-06-12 12:52:31", updated_at: "2019-06-12 12:52:31", name: "name1">]
  Task Load (0.1ms)  SELECT  "tasks".* FROM "tasks" WHERE "tasks"."id" > ? ORDER BY "tasks"."id" ASC LIMIT ?  [["id", 5], ["LIMIT", 1]]
=> nil

参考

json の テストで gem の json_expressions を使ってみる

2019-07-29

やったこと

rspec で json_expressions を使ってみます。

利用シーンとしては、Web API で返却される json の型をチェックする時などです。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

$ gem list | grep rspec-rails
rspec-rails (3.8.2)

調査

インストール

Gemfile

gem 'json_expressions'
$ bundle install

テスト対象の処理

app/controllers/users_controller.rb

class UsersController < ApplicationController
  def show
    data = {
      user: {
        id: 1,
        type: 'userType1',
        type2: 'userType2',
        point: 2300,
        is_valid: true
      }
    }
    render :json => data
  end
end

rspec を書く

spec/controllers/users_controller_spec.rb

require 'rails_helper'
require 'json_expressions/rspec'

module Boolean; end
class TrueClass; include Boolean; end
class FalseClass; include Boolean; end

RSpec.describe UsersController, type: :controller do
  describe "GET #show" do
    it "returns a user" do
      pattern = {
        user: {
          id: Integer,
          type: String,
          point: Integer,
          is_valid: Boolean,
        }.ignore_extra_keys!
      }

      get :show
      expect(response.body).to match_json_expression(pattern)
    end
  end
end

ignore_extra_keys! を指定することでテスト対象のキーを絞り込むことができます。

Boolean かどうかのテストをするための対応も必要でした。

参考

RSpec で shared_context を使ってみる

2019-07-29

やったこと

rspec で shared_context を使ってみます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

$ gem list | grep rspec-rails
rspec-rails (3.8.2)

調査

実験

spec/models/task10_spec.rb

require 'rails_helper'

shared_examples 'Example Login' do
  it 'should be hogehoge' do
  end
end

RSpec.describe Task, type: :model do

  context '例) ログインしているユーザー' do
    it_behaves_like 'Example Login'
  end
end

出力結果

$ rspec spec/models/task10_spec.rb

Task
  例) ログインしているユーザー
    behaves like Example Login
      should be hogehoge

Finished in 0.0028 seconds (files took 3.85 seconds to load)
1 example, 0 failures

エイリアス

  • shared_examples_for
  • shared_context

これらのメソッドは、shared_examples のエイリアスです。

参考

rspec で and_call_original を使ってみる

2019-07-27

やったこと

rspec で and_call_original を使ってみます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

$ gem list | grep rspec-rails
rspec-rails (3.8.2)

調査

spec/models/task9_spec.rb

require 'rails_helper'

class Sample
  attr_reader :count
  def initialize
    @count = 0
  end

  def increment
    @count += 1
  end
end

RSpec.describe Task, type: :model do
  it '呼び出しているかどうかのテスト' do
    sample = Sample.new
    expect(sample).to receive(:increment)

    expect(sample.count).to eq(0)
    # 実際に呼び出してはいない
    sample.increment
    expect(sample.count).to eq(0)
  end

  it '実際に呼び出すテスト' do
    sample = Sample.new
    expect(sample).to receive(:increment).and_call_original

    expect(sample.count).to eq(0)
    sample.increment
    expect(sample.count).to eq(1)
  end
end

出力結果

$ rspec spec/models/task9_spec.rb

Task
  呼び出しているかどうかのテスト
  実際に呼び出すテスト

Finished in 0.00736 seconds (files took 1.75 seconds to load)
2 examples, 0 failures

and_call_original を使うと実際のメソッドが呼び出されていることが分かります。

参考