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

Rspecと Factoryの使い方

Rspecとは?

Rspecとは自動テストをしてくれる言語。になります。
UIテストをいちいちせずとも、
「ページの遷移」、「モデルバリデーション」、「if分の条件分岐による処理の確認」...
などなど、人間がやるには面倒なことを自動でやってくれるのがRspecになります!

準備

今回は比較的簡単な、
「コントローラーによる画面遷移確認テスト」
のコードを書いていこうと思います

また、ログイン後の処理定義をするコードを書きますが、そのログイン処理をDeviseを使ってやるものとします

導入するGemインストール

今回使うのはRspecFactory_botというGemを使います
内容については後ほど解説しますので、まずはインストールしましょう!!

Gemfile
group :development, :test do
  gem "rspec-rails"
  gem "factory_bot_rails"
end

rspecを設定・書くためのファイルを作成

$ bundle exec rails generate rspec:install

deviceログインの簡略化

controller_macros

spec/support/controller_macros.rbににログイン処理を簡略化させるためのコードを記載します
ファイルがない場合のファイルを作成してください

spec/support/controller_macros.rb
module ControllerMacros
  def login_admin(admin)
    @request.env["devise.mapping"] = Devise.mappings[:admin]
    sign_in admin
  end

  def login_user(user)
    controller.stub(:authenticate_user!).and_return true
    @request.env["devise.mapping"] = Devise.mappings[:user]
    sign_in user
  end
end

rails_helperで読み込み設定

先ほどのコードを読み込ませるために、spec/rails_helper.rbに以下文を追記します

spec/rails_helper.rb
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

実装

ここからファイルに必要なコード書いていきます

テストするためのモデルを作成

spec/fatoriesこのディレクトリに作成していモデルデータを記載していきます。
今回はユーザによる画面遷移テストを作りたいので、まずユーザーファイルを作成。

spec/fatories/users.rb

その後中身を書いていきます

**spec/fatories/users.rb**
FactoryBot.define do
  factory :user do
    name { "test" }
    email { "user@email.com" }
    password { "password" }
    password_confirmation { "password" }
  end
end

今回はユーザー必要最低限な情報だけにしておきます
ここはかくじ必要なカラム等追加していただければと思います!

テストコードの記載

ここから肝心なテスト内容を記載するところです

テストコードを書く場所は
spec/controllers/users_controller_spec.rb
ここになります!
コントローラー内での画面遷移テストしかできないので、他のモデルが絡むときは
随時それ対応するコントローラーでかきましょう!

spec/cotrollers/users_controller_spec.rb
require 'rails_helper'

describe UsersController, type: :controller do
  before do
    @user = FactoryBot.create(:user)
  end
  context 'ログイン後' do
    before do 
      login(@user)
    end

    it 'トップ画面にいく' do 
      get :index
      expect(response).to have_http_status "200"
    end
  end

  context '未ログインの場合' do
    it 'トップに行くと302(エラー)が返される' do 
      get :index
      expect(response).to have_http_status "302"
    end
    it 'トップに行くとuserログインページにリダイレクトされる' do 
      get :index
      expect(response).to redirect_to new_user_session_path
    end
  end
end

・describe
 →コントローラーしていし、typeでもコントローラーと指定する
 →モデルの場合typeはmodelになる

・before do
 →テスト実行前の準備する場所
 →今回で言うとここでユーザーの情報を作成して、@userに代入してuserのデータを扱えるようにしている

・contxt
 →実行する前提条件の定義を日本語わかりやすく、みやすくする
 →実行範囲内を定義する意味もある

・it
 →実際にテストする内容

・get: :index
 →ルーティングで定義てしてるgetメソッドindexアクションに遷移すると定義

・expect(response).to have_http_status "200"
 →要するに成功したら200って返ってくきてるよね??という確認
 →ここがtrueかfalseかでテスト結果を判定している

実行

$ rspec

以上で実行完了

まとめ

以上でコントローラーテストの簡易的な説明になります
まずは簡単なところからはじめて徐々にできる範囲を伸ばしていきましょう!!

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

【Ruby】mapメソッドのつかいかた(+別メソッドとの組み合わせの例など)

自己整理&備忘録です。

mapメソッドとは

mapメソッドとは、「配列の要素それぞれに対して一定の処理を行って、新しい配列をつくるメソッド」とされる。

 
基本的な型

配列.map { |変数| 実行させたい処理 }

名称は異なるがcollectメソッドもmapと同じ動きをする。

つかいかた(例)

1. 数式

sample_1.rb
x = [0, 100, 500]
y = x.map { |x| x * 2 }
p y

#=> [0, 200, 1000]

2.mapの要素にメソッドを与える(よく見る形)

基本の型

オブジェクト名.map(&:メソッド名)

sports = ["BASEBALL", "SOCCER"]
p sports.map(&:downcase) 

#=> ["baseball", "soccer"]

mapとmap!の違い

map  → 元の値に対して影響を与えない
map! → 元の値を書き換える

fruits = ["apple", "banana", "orange"]
p fruits
p fruits.map(&:upcase)
p fruits
p fruits.map!(&:capitalize)
p fruits

# 出力結果(4つ目のmap!後に元のfruitsを出力すると上書きされている)
["apple", "banana", "orange"]
["APPLE", "BANANA", "ORANGE"]  
["apple", "banana", "orange"]
["Apple", "Banana", "Orange"]
["Apple", "Banana", "Orange"] 

eachメソッドとの違い

新しい配列を作るので、空の配列array = []を作らずに書くことができる。

例.配列の頭文字の大文字にしたいときのeachmapの違い

rei.rb
fruits = ["apple", "banana", "orange"]

出力結果(理想形)

["Apple", "Banana", "Orange"]

 
eachのとき

each_ver.rb
fruits = ["apple", "banana", "orange"]
fruits_ini = []
fruits.each do |frt|
  fruits_ini << frt.capitalize
end
p fruits_ini

#=> ["Apple", "Banana", "Orange"]

 
mapのとき

map_ver.rb
fruits = ["apple", "banana", "orange"]
p fruits.map(&:capitalize)
#=> ["Apple", "Banana", "Orange"]

参考

Rubyのmap, map!メソッドの使い方
Ruby mapメソッドについて
【Rails入門】mapメソッドを完全攻略!配列操作の基礎を学ぼう

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

Railsチュートリアル 第4章

Railsコンソールの設定

Railsコンソールはirb(IRB:Interactive RuBy)を拡張して作られているため、Rubyの機能を全て使うことができる。

$ nano ~/.irbrc

下記の設定を書くと、irbのプロンプトが簡潔な表示に置き換わる。

IRB.conf[:PROMPT_MODE] = :SIMPLE
IRB.conf[:AUTO_INDENT_MODE] = false

後続ifとunless

if:条件式が"真"のときに実行される

puts "x is not empty" if !x.empty?

unless:条件式が"偽"のときに実行される

>> string = "foobar"
>> puts "The string '#{string}' is nonempty." unless string.empty?
The string 'foobar' is nonempty.
=> nil

メソッド

mapメソッド

配列の要素の数だけブロック内の処理を繰り返し、結果として作成された配列を返す。
map!は元の値を書き換える。

オブジェクト.map { |変数|
  # 実行したい処理
}
>> (1..5).map { |i| i**2 }
=> [1, 4, 9, 16, 25]
>> %w[a b c]      # %w で文字列の配列を作成
=> ["a", "b", "c"]
>> %w[a b c].map { |char| char.upcase }
=> ["A", "B", "C"]
>> %w[A B C].map { |char| char.downcase }
=> ["a", "b", "c"]

mapのブロック内で宣言した引数(char)に対してメソッドを呼び出している場合は、省略記法が一般的。

>> %w[A B C].map { |char| char.downcase }
=> ["a", "b", "c"]
>> %w[A B C].map(&:downcase)
=> ["a", "b", "c"]

to_a

配列に変換する

>> (0..9).to_a
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

%w

文字列の配列に変換

>> a = %w[foo bar baz quux]         # %wを使って文字列の配列に変換
=> ["foo", "bar", "baz", "quux"]
>> a[0..2]
=> ["foo", "bar", "baz"]

ブロック

下記では、範囲オブジェクトである(1..5)に対して、eachメソッドを呼び出している。
メソッドに渡されている{ |i| puts 2 * i }が、ブロックと呼ばれる部分。

>> (1..5).each { |i| puts 2 * i }

また、ブロックである事を示すには波カッコで囲むが、下記のようにdoとendで囲んで示すこともできる。
短い1行のブロックには波カッコを使い、長い1行や複数行のブロックにはdo..endを使う。

>> (1..5).each do |i|
?>   puts 2 * i
>> end

そう考えると、単体テストもブロックであることがわかる。
このtestメソッドは、文字列(説明文)とブロックを引数にとり、テストが実行されるときにブロック内の文が実行されている。

test "should get home" do
  get static_pages_home_url
  assert_response :success
  assert_select "title", "Ruby on Rails Tutorial Sample App"
end

ハッシュ

ハッシュは本質的には配列と同じだが、インデックスとして整数値意外のものも使える点が配列とは異なる。(そのため、他の言語ではハッシュを連想配列と呼ぶこともある)
ハッシュは、キーと値のペアを波カッコで囲んで表記する。
配列と似ているが、ハッシュでは要素の並び順が保証されないため、要素の順序が重量である場合は、配列を使う必要がある。

>> user = { "first_name" => "Michael", "last_name" => "Hartl" }
=> {"last_name"=>"Hartl", "first_name"=>"Michael"}

上記では、ハッシュのキーとして文字列を使っていたが、Railsでは文字列よりもシンボルを使うのが一般的。:nameのように表す。

>> user = { :name => "Michael Hartl", :email => "michael@example.com" }
=> {:name=>"Michael Hartl", :email=>"michael@example.com"}
>> user[:name]              # :name に対応する値にアクセスする
=> "Michael Hartl"
>> user[:password]          # 未定義のキーに対応する値にアクセスする
=> nil

また、シンボルとハッシュロケットの組み合わせを、下記のようにキーの名前の後にコロンを置く記法も同じように使える。

{ name: "Michael Hartl", email: "michael@example.com" }

CSSを追加する

ハッシュを学んだので、下記が理解できるようになっている。

<%= stylesheet_link_tag 'application', media: 'all',
                        'data-turbolinks-track': 'reload' %>

まずは、丸カッコがないが、Rubyでは丸カッコは使用しなくてもいいので、下記の2つの行は等価となる。

stylesheet_link_tag('application', media: 'all',
                    'data-turbolinks-track': 'reload')
stylesheet_link_tag 'application', media: 'all',
                    'data-turbolinks-track': 'reload'

次にmedia引数はハッシュだが、波カッコがない点が不思議。
ハッシュはメソッド呼び出しの最後の引数である場合は、波カッコを省略できるためであり、下記の2つの行は等価となる。

stylesheet_link_tag 'application', { media: 'all',
                    'data-turbolinks-track': 'reload' }
stylesheet_link_tag 'application', media: 'all',
                    'data-turbolinks-track': 'reload'

Rubyでは改行と空白を区別していないため、上記のように途中に改行が含まれていても問題ない。

以上の事から、stylesheet_link_tagメソッドは、2つの引数で呼ばれており、最初の引数はである文字列は、スタイルシートへのパスを示す。
次の引数であるハッシュには2つの要素があり、最初の要素はメディアタイプを示し、次の要素はturbolinksという機能をオンにしている。
最後に上記を読み込んで生成されたHTMLソースは下記となる。

<link data-turbolinks-track="true" href="/assets/application.css"
media="all" rel="stylesheet" />

クラス

クラスの継承

String ⇨ Object ⇨ BasicObject ⇨ nil(スーパークラスを持たないという事)
Array、Hashなどのクラスも上記と同様である。これが"Rubyではあらゆる物がオブジェクトである"という事。

>> s = String.new("foobar")
=> "foobar"
>> s.class
=> String
>> s.class.superclass
=> Object
>> s.class.superclass.superclass
=> BasicObject
>> s.class.s

クラス継承の例

>> class Word < String             # WordクラスはStringクラスを継承する
>>   # 文字列が回文であればtrueを返す
>>   def palindrome?
>>     self == self.reverse        # selfは文字列自身を表します
>>   end
>> end
=> :palindrome?

上記の様に、WordクラスはStringクラスを継承しているため、palindrome?メソッドだけでなく、Stringクラスが扱える全てのメソッドがWordクラスでも扱える様になる。

>> s = Word.new("level")    # 新しいWordを作成し、"level" で初期化する
=> "level"
>> s.palindrome?            # Wordが回文かどうかを調べるメソッド
=> true
>> s.length                 # WordはStringで扱える全てのメソッドを継承している
=> 5

selfキーワード

selfとは、オブジェクトそのものを指している。

attr_accesssorメソッド

クラスにインスタンス変数を読み書きするためのアクセサメソッドを定義するメソッド。
アクセサメソッドは、外部インスタンスのインスタンス変数を参照したり変更するために定義する。

initialize

User.newを実行すると自動的に呼び出されるメソッド。

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

Alpineベースのimageにruby2.2.3を入れる

本当はalpine-rubyをベースにするべきなんだろうけど諸事情でJava系のベースにする必要があったので、rbenvを自力で入れて構築することにしました。

出来上がったDockerfileはこちら。

FROM openjdk:8-jdk-alpine

RUN apk update && \
    apk upgrade && \
    apk add --update --no-cache \
    bash \
    vim \
    curl \
    tzdata

RUN cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
    apk del tzdata

RUN echo "http://dl-cdn.alpinelinux.org/alpine/v3.8/main" >> /etc/apk/repositories
RUN apk update && \
    apk upgrade && \
    apk add --update --no-cache --virtual=.build-dependencies \
    build-base \
    git \
    gcc \
    readline-dev \
    openssl-dev=1.0.2u-r0   \
    zlib-dev \
    libffi-dev

ENV PATH /usr/local/rbenv/shims:/usr/local/rbenv/bin:$PATH
ENV RBENV_ROOT /usr/local/rbenv
ENV CONFIGURE_OPTS "--disable-install-doc"

RUN git clone https://github.com/sstephenson/rbenv.git ${RBENV_ROOT} && \
    git clone https://github.com/sstephenson/ruby-build.git ${RBENV_ROOT}/plugins/ruby-build && \
    ${RBENV_ROOT}/plugins/ruby-build/install.sh
RUN echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> /etc/profile.d/rbenv.sh && \
    echo 'eval "$(rbenv init -)"' >> /etc/profile.d/rbenv.sh

RUN rbenv install 2.2.3 && \
    rbenv global 2.2.3

COPY Gemfile .
COPY Gemfile.lock .

RUN gem install -N bundler -v 1.16.2
RUN bundle install -j4 && \
    rm -rf /usr/local/bundle/cache/*.gem

RUN apk del .build-dependencies

大きな障害はは以下のようにopensslの1.0を取得することでした。

v3.8から1.0系のopensslを取得する

素直にapk add opensslでライブラリを入れるとrbenv install時に以下のようなエラーが発生する。

BUILD FAILED (Alpine Linux 3.9.4 using ruby-build 20200115-8-g73b926b)

Inspect or clean up the working tree at /tmp/ruby-build.20200123165940.26.gjbJCo
Results logged to /tmp/ruby-build.20200123165940.26.log

Last 10 log lines:
                              power_assert-0.2.2.gem
                              minitest-5.4.3.gem
installing rdoc:              /usr/local/rbenv/versions/2.2.3/share/ri/2.2.0/system
installing capi-docs:         /usr/local/rbenv/versions/2.2.3/share/doc/ruby
The Ruby openssl extension was not compiled.
ERROR: Ruby install aborted due to missing extensions
Configure options used:
  --prefix=/usr/local/rbenv/versions/2.2.3
  LDFLAGS=-L/usr/local/rbenv/versions/2.2.3/lib 
  CPPFLAGS=-I/usr/local/rbenv/versions/2.2.3/include 

エラーの原因としては、ruby2.4以下の場合はopenssl1.1系に対応していないため、openssl1.0を入れる必要があるからでした。
rbenv/ruby-build

The openssl extension of Ruby version before 2.4 is not compatible with OpenSSL 1.1.x.

ubuntuベースのimageで構築するならこちらの記事で言及されているように有志が開発したパッチを当てれば動きますが、alpineの場合は自力で1.0系を入れる必要があります。

現在openjdk:8-jdk-alpineではデフォルトでv3.9のリポジトリを参照しているが、v3.9ではopensslは1.1系の提供になっています。
こちらで調べると1.0系の取得には1つ前のv3.8のリポジトリから取得が必要のようです。
https://pkgs.alpinelinux.org/packages?name=openssl&branch=v3.8&repo=main

そこでapkから取得する前に参照先にv3.8のリポジトリを追加して上げる必要があります。

RUN echo "http://dl-cdn.alpinelinux.org/alpine/v3.8/main" >> /etc/apk/repositories

追加後にopenssl-dev=1.0.2u-r0とバージョンを指定することで1.0系のopensslを取得することができます。

また当初はgem install時にThe Ruby openssl extension was not compiled. Missing the OpenSSL lib?エラーが発生していたが、修正前のDockerfikeでlibssl1.0を入れていたことが原因でした。
おそらくssl系のライブラリ同士でバッティングしていたっぽい?

rbenvの前はruby-installを使用して軽量化を図っていましたが、上と同じOpenSSLのエラーがどうしても解消できなかったので諦めることに…
ruby-installで上手く行けば多分もう少し軽くできそう。

RUN wget -O - https://github.com/postmodern/ruby-install/archive/v0.7.0.tar.gz | tar xzvf - && \
    ( cd ruby-install-0.7.0 && \
      make install ) && \
    rm -rf ruby-install-*
RUN ruby-install --system --cleanup ruby 2.2.3

その他細かいポイントなど

docker軽量化系の記事では散々言及されているようなことですが、軽くまとめると
- ビルドに必要のないパッケージは残したくないので.build-dependenciesでまとめて最後の行で一括で削除しています。
- ENV CONFIGURE_OPTS "--disable-install-doc"を指定することで不要なドキュメントをインストールしないようにしています
- bundle install時にrm -rf /usr/local/bundle/cache/*.gemでgemのキャッシュを消すようにしています

今どき2.2.3を使うことはあまりないと思いますが、備忘録として残しておこうと思います。

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

【rails】ユーザー 登録時にnameカラムに自動的に@を付ける方法

はじめに

初学者がポートフォリオ作成でハマったことをメモします。
TiwtterのアカウントIDのように特定のカラムの最初に@を付ける方法です。ホートフォリオ作成時にユーザーの名前やアカウントIDの最初に@を付けたいという方は多いと思います。
初歩的な内容すぎて調べてもそのような記事がすぐ見つからなかったので残しておきます。

結論

before_saveをmodelに設定する。

コードと説明

formから送られてきた値を保存する前に加工する。
before_saveは、paramsをDBに保存する直前に指定したプログラム(今回の例では、:change_account_name)を実施するコールバック関数です。
before_saveを設定すると、createとupdateのどちらも場合にも、適用されます。

コールバック処理について
http://www.techscore.com/blog/2012/12/25/rails%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%AB%E3%83%90%E3%83%83%E3%82%AF%E3%81%BE%E3%81%A8%E3%82%81/

models/user.rb
before_save :change_account_name
  def change_account_name
    self.account_name = "@" + account_name
  end
views/users/new
#slim,bootstrap使用
= form_with model: @user, local: true do |f|
(中略)
  = f.label :account_name, 'アカウントID'
    .input-group
      .input-group-prepend
        .input-group-text @
          = f.text_field :account_name, class: 'form-control' , placeholder: "9文字まで" 

スクリーンショット 2020-01-23 18.52.24.png

終わりに

誰かのお役に立てれば幸いです。
間違えている部分があればコメントお願い致します。

参考にした記事
https://qiita.com/KeisukeYoshida0220/items/e1ce152ac8d89845aab3

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

【Ruby】ヒアドキュメントの初歩的なつかいかた

簡単に備忘録。

1. ヒアドキュメントとは

ヒアドキュメントは「文字列をシェルスクリプトやプログラミングに埋め込むためのもの」
""無しや改行&空白なども書いた通りに適用できる(長い文などに有効)。

2. 使い方

通常

puts "りんご"
puts "バナナ"
puts "メロン"
puts "いちご"

#=> 
りんご
バナナ
メロン
いちご

ヒアドキュメントの場合

puts <<~EOS
りんご
バナナ
メロン
いちご
EOS

#=> 
りんご
バナナ
メロン
いちご

参考

【3分でわかる】Rubyでヒアドキュメント
ヒアドキュメントとは・基本の使い方(ruby)

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

#Ruby / nested Array elements / map and delete / Broken modified original array / So use dup ( deep copy )

# GOOD
# dup element in map
arr = [[:A, :B], [:C, :D]]; changed_arr = arr.map { |a| x = a.dup; x.delete_at(-1); x }; p changed_arr; p arr
# [[:A], [:C]]
# [[:A, :B], [:C, :D]]

# BAD
# map and delete
arr = [[:A, :B], [:C, :D]]; changed_arr = arr.map { |a| a.delete_at(-1); a }; p changed_arr; p arr
#  [[:A], [:C]]
# [[:A], [:C]]

# BAD
# dup array and map and delete
arr = [[:A, :B], [:C, :D]]; dupped_arr = arr.dup; changed_arr = dupped_arr.map { |a| a.delete_at(-1); a }; p changed_arr; p arr
# [[:A], [:C]]
# [[:A], [:C]]


Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/2957

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

#Ruby で配列の添字・順序を文字列から探す ( find_index )

["A","B","C","C","D"].find_index("A")
# => 0

["A","B","C","C","D"].find_index("B")
# => 1

# 複数見つかる場合も最初のものが返る
["A","B","C","C","D"].find_index("C")
# => 2

["A","B","C","C","D"].find_index("D")
# => 4

["A","B","C","C","D"].find_index("E")
# => nil

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/2956

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

Railsチュートリアル 第3章

この章では、テスト駆動開発について学習。

テスト

Railsのテストには、コントローラーテスト、モデルテスト、統合テストの3種類ある。
ここでは、コントローラーテストについてメモ。

テストの例を2つほど

test "should get home" do
 get static_pages_home_url  ⇨GETリクエストをhomeアクションに対して発行
 assert_response :success   ⇨リクエストに対するレスポンスは"成功"になるはず
end
test "should get home" do
 get static_pages_home_url
 assert_response :success
 assert_select "title", "Home | Ruby on Rails Tutorial Sample App" ⇨titleタグ内に「Home | Ruby on Rails Tutorial Sample App」と言う文字列があるはず
end

setupメソッド

書くテストが実行される直前で実行されるメソッド。

provideメソッド

provideメソッドでパラメータを引き渡す

<% provide(:title, "Home") %>

yieldメソッドで受け取る

<title><%= yield(:title) %></title>

テストの便利な設定

テスト用の設定として、minitest reportersとGuardについてメモ。

minitest reporters

テストの結果をプログレスバーでパーセント表示したり、REDやGREENで表示してくれる設定。
minitest-reporters gemを利用。

Gemfile.rb
source 'https://rubygems.org'
.
.
group :test do
  gem 'rails-controller-testing', '1.0.2'
  gem 'minitest',                 '5.10.3'
  gem 'minitest-reporters',       '1.1.14'  ⇦これを追加
  gem 'guard',                    '2.13.0'
  gem 'guard-minitest',           '2.4.4'
end
.
.
test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require "minitest/reporters"  ⇦これを追加
Minitest::Reporters.use!    ⇦これを追加

class ActiveSupport::TestCase
  # Setup all fixtures in test/fixtures/*.yml for all tests
  # in alphabetical order.
  fixtures :all

  # Add more helper methods to be used by all tests here...
end

Guardによるテストの自動化

rails testコマンドを手動で打ち込まなくても、static_pages_test.rbファイルなどを変更すると自動的にテストを実行してくれるツール。

Gemfile.rb
source 'https://rubygems.org'
.
.
group :test do
  gem 'rails-controller-testing', '1.0.2'
  gem 'minitest',                 '5.10.3'
  gem 'minitest-reporters',       '1.1.14'
  gem 'guard',                    '2.13.0'  ⇦これを追加
  gem 'guard-minitest',           '2.4.4'   ⇦これを追加
end
.
.

初期化

$ bundle exec guard init

Cloud9を使っている場合は、tmuxをインストールする必要がある。

$ sudo yum install -y tmux

Guardfile編集

Guardfile.rb
# Guardのマッチング規則を定義
guard :minitest, spring: "bin/rails test", all_on_start: false do
  watch(%r{^test/(.*)/?(.*)_test\.rb$})
  watch('test/test_helper.rb') { 'test' }
  watch('config/routes.rb')    { integration_tests }
  watch(%r{^app/models/(.*?)\.rb$}) do |matches|
    "test/models/#{matches[1]}_test.rb"
  end
  watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches|
    resource_tests(matches[1])
  end
  watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches|
    ["test/controllers/#{matches[1]}_controller_test.rb"] +
    integration_tests(matches[1])
  end
  watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches|
    integration_tests(matches[1])
  end
  watch('app/views/layouts/application.html.erb') do
    'test/integration/site_layout_test.rb'
  end
  watch('app/helpers/sessions_helper.rb') do
    integration_tests << 'test/helpers/sessions_helper_test.rb'
  end
  watch('app/controllers/sessions_controller.rb') do
    ['test/controllers/sessions_controller_test.rb',
     'test/integration/users_login_test.rb']
  end
  watch('app/controllers/account_activations_controller.rb') do
    'test/integration/users_signup_test.rb'
  end
  watch(%r{app/views/users/*}) do
    resource_tests('users') +
    ['test/integration/microposts_interface_test.rb']
  end
end

# 与えられたリソースに対応する統合テストを返す
def integration_tests(resource = :all)
  if resource == :all
    Dir["test/integration/*"]  else
    Dir["test/integration/#{resource}_*.rb"]
  end
end

# 与えられたリソースに対応するコントローラのテストを返す
def controller_test(resource)
  "test/controllers/#{resource}_controller_test.rb"
end

# 与えられたリソースに対応するすべてのテストを返す
def resource_tests(resource)
  integration_tests(resource) << controller_test(resource)
end

Guard使用時のSpringとGitの競合を防ぐには、.gitignoreファイルにspring/ディレクトリを追加する。
そうすることで、指定したファイルはGitリポジトリに追加されなくなる。

.gitignore
# See https://help.github.com/articles/ignoring-files for more about
# ignoring files.
#
# If you find yourself ignoring temporary files generated by your
# text editor or operating system, you probably want to add
# a global ignore instead:
#   git config --global core.excludesfile '~/.gitignore_global'

# Ignore bundler config.
/.bundle

# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal

# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep

# Ignore Byebug command history file.
.byebug_history

# Ignore Spring files.
/spring/*.pid        ⇦これを追加

設定が完了したので実行

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

RubyでLeetCodeを解いてみた Palindrome Number

https://leetcode.com/problems/palindrome-number/

Determine whether an integer is a palindrome. An integer is a palindrome when it reads the same backward as forward.

# @param {Integer} x
# @return {Boolean}
def is_palindrome(x)
  return false if x.negative?
  if x.positive?
    arr = x.to_s.chars
    size = arr.size
    arr.each_with_index do |ele, idx|
      return false if ele != arr[size - idx - 1]
    end
    return true
  end
  return true if x.zero?
end

Coud you solve it without converting the integer to a string?

文字列に変換しないパターンは思いつかなかった?

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

rakeタスクに引数を渡したいとき

rakeタスクで引数を渡したい!
意外とまとまった記事がなかったので備忘。

タスク名とともに引数を書く

task :task_name, ['filter'] => :environment do |_task, args|
  p args
  p args[:filter]
  ~

filter の部分は任意の文字でOK

タスク実行方法

command: rake "task_name[arg]"

arg 部分に渡したい引数を書く。この場合だと "arg" が渡される。
タスク全体を "" で囲まないと、引数までタスクとして認識されない。

実行結果

=> #<Rake::TaskArguments filter: arg>
"arg"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】kaminari&ransackでページネーションと検索機能を実装

【ページネーション】
1ページあたり20件に設定。
スタイルはBootstrap4で実装します。

【検索機能】
投稿一覧から検索とブックマーク済み投稿一覧から検索の2パターン実装していきます。
検索条件はタイトルか本文の部分一致とします。

【事前準備】
・ブックマーク機能を実装しておいてください。
参考資料:【Rails】ブックマーク(お気に入り)機能
・bootstrap4を使用できるように設定しておいてください。
参考資料:BootstrapをRailsに導入してみよう!徹底解説!

環境

Rails 5.2.3
mysql 5.7.28
gem kaminari
gem ransack

実装

それでは実装していきます。

gemの追加

gem kaminari
gem ransack

Gemfileに追加し、bundle install

Gemfile
gem 'kaminari'
gem 'ransack'
ターミナル
$ bundle install

ページネーション実装

まずはページネーションから実装していきます。

ターミナルでkaminariの設定ファイルを自動生成するコマンドを入力します。

ターミナル
$ rails g kaminari:config

これでkaminariの動作を変えるために必要な設定ファイルを生成できます。

次にページネーション用のビューファイルをを自動生成するコマンドを入力します。

ターミナル
$ rails g kaminari:views bootstrap4

これでbootstrap4のページネーションのデザインに合わせたビューファイルが自動で生成されます。

次に1ページあたりのデータ取得数を書き換えます。
デフォルトでは25件のデータを取得する設定になっています。

config/initializers/kaminari_config.rb
# frozen_string_literal: true
Kaminari.configure do |config|
  config.default_per_page = 20 # 25から20に書き換え
  # config.max_per_page = nil
  # config.window = 4
  # config.outer_window = 0
  # config.left = 0
  # config.right = 0
  # config.page_method_name = :page
  # config.param_name = :page
  # config.params_on_first_page = false
end

次はコントローラーにpageメソッドを追加していきます。

board_controller.rb
def index
  @boards = Board.includes(:user).page(params[:page])
end

def bookmarks
  @boards = current_user.bookmark_boards.includes(:user).page(params[:page])                  
end

kaminariをインストールしたことでモデルクラスに対してpageメソッドが使用できるようになります。
先ほど設定した1ページあたりのデータ取得件数を引数のparams[:page]に格納し、ビューで表示するようにします。

この時点で1ページ目には20件のデータが表示されているはずです。
2ページ目以降も見れるようにリンクを貼ります。
リンクを貼りたい箇所に<%= paginate @boards %>を記述すればOKです。
Image from Gyazo
サーバーを再起動して、このようなリンクが表示されていれば実装完了です。

日本語化

実装は完了ですが英語の部分を日本語化しておきます。

デフォルトのロケール(言語)を日本語にします。

config/application.rb
module アプリ名
  class Application < Rails::Application
  # 追記↓
    config.i18n.default_locale = :ja

次に日本語訳を設定するファイルを作成・記述していきます。
config/locales/ja.ymlを作成し任意の日本語を記述していきます。

config/locales/ja.yml
ja:
  views:
    pagination:
      first: 最初
      last: 最後
      previous: 
      next: 
      truncate: ...

これで設定が完了したのでサーバーを再起動したら反映されているはずです。

検索機能実装

controllerを編集

boards_controller.rb
def index
    @q = Board.ransack(params[:q]) # 検索オブジェクト作成
    @boards = @q.result.includes(:user).page(params[:page]) # 検索結果(検索しなければ全件取得)
end

def bookmarks
    @q = current_user.bookmark_boards.ransack(params[:q]) # 検索オブジェクト作成
    @boards = @q.result.includes(:user).page(params[:page]) # 検索結果(検索しなければ全件取得
end

viewを編集(検索フォーム)

boards/index.html.erb
<!-- 検索フォーム --> 
<%= render 'search_form', url: boards_path, q: @q %>
boards/bookmarks.html.erb
<!-- 検索フォーム -->
<%= render 'search_form', url: bookmarks_boards_path, q: @q %>
_search_form.html.erb
<%= search_form_for q, url: url do |f| %>
  <div class="input-group mb-3">
    <%= f.search_field :title_or_body_cont, class: "form-control", placeholder: '検索ワード' %>
    <div class="input-group-append">
      <%= f.submit "検索", class: "btn btn-primary" %>
    </div>
  </div>
<% end %>

<%= search_form_for q, url: url do |f| %>のurlを一覧とブックマーク一覧のurlで指定することでその範囲内から検索をすることができます。

<%= f.search_field :title_or_body_cont,...でtitleカラムとbodyカラムのどちらからでも検索が部分的に一致すれば表示されます。
条件はこちらのサイトを参考にしました。
Ransackで簡単に検索フォームを作る73のレシピ

viewを編集(検索結果一覧)

index.html.erb,bookmarks.html.erb共通
<!-- 掲示板一覧 -->
<% if @boards.any? %>
  <div class="row d-flex">
    <%= render @boards %>
  </div>
<% else %>
  <h1><%= '検索結果がありません。' %></h1>
<% end %>
<%= paginate @boards %>

これでインスタンスにデータが存在すれば一覧表示され、なければ検索結果がありませんと表示されます。

まとめ

わりとすんなり実装できたかなと思います。
詰まったとことしては検索範囲を全boardとbookmark_boardで範囲分けする部分でしたが、urlを検索範囲に指定することで解決しました。

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

enumを導入すると起きるエラー【rails】

概要

数字を文字列に変換してくれる最強助っ人「enum」ちゃんを導入するとエラー発生。

エラー文
'x'ArgumentError in xxx
'x' is not a valid xxx

スクリーンショット 2020-01-09 18.22.23.png

解決方法

Shift_user.model(enumを書いているモデル)での定義ミス。

詳細

Before↓

model.rb
class ShiftUser < ApplicationRecord

enum work_type:{
"午前": 1,
"午後": 2,
"一日": 3,
}
end

After↓

model.rb
class ShiftUser < ApplicationRecord
  enum work_type: {
    am: 1,
    pm: 2,
    all_day: 3
  }
  # 英語から日本語
  WORK_TYPE = {
    am: '午前',
    pm: '午後',
    all_day: '1日'
  }

そもそもenumで日本語でも指定はできるが、
よく使われるのが「アルファベット or _」を使って命名するらしい。

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

【Rails】 DataTables のテーブルに関連付けされたモデルのデータを表示する方法

はじめに

DataTableへデータを渡すときに json 形式に変換する必要があります。
関連付けされたモデルのデータを簡単に json 形式に変換することができます。

関連リンク

関連リンクを下記に載せておくので、必要であれば参考にしてください。。

関連付けされたモデルのデータを表示する方法

as_json の include をすることで user に関連付けされた post のデータを一緒に json のデータとして作成してくれます。

app/datatables/users_datatable.rb
class UsersDatatable
  # *** 省略 ***

  def as_json(options = {})
    {
        recordsTotal: User.count, # 取得件数
        recordsFiltered: users.total_count, # フィルター前の全件数

        # user モデルに関連付けされた post モデルを include して json ファイルを作成する。
        data: users.as_json(include: :post), # 表データ
    }
  end

  # *** 省略 ***
end

まとめ

こちらは突き詰めると、 DataTable 特有の使い方ではなく、 json ファイルの作り方の記事になってしまっています。
使い方が全くわからないという方には少しは参考になるかなーと思い投稿させていただきました。

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

初学者によるプログラミングMemo #18 フィボナッチ数列(メモ化)

はじめに

今回はフィボナッチ数列のお話です
なお、本記述はMacにおいて、Railsでの開発を前提としています
また、まだまだひよっこですので、不備等ございましたらご指摘いただけると幸いです
*追記
コメント欄に簡潔なコードがありますので、そちらもご覧ください
元の記述は自分が考えた軌跡として残しておきます

目次

  • フィボナッチ数列とは
  • プログラムで解いてみる

フィボナッチ数列とは

初項 = 1、第2項 = 1、第3項以降は、前項と前々項の和となる数列です
第3項は初項と第2項の和なので「1+1」で"2"となります
第4項は第2項と第3項の和なので"3"となります
また、フィボナッチ数列は黄金比とも関係してるので、知りたい方は調べてみてください
数列としては、「1,1,2,3,5,8,13,21,34,55,・・・」と続きます

この数列の第n項目は何になるかを求めたいと思います

プログラムで解いてみる

では、プログラムで解いてみましょう
まずは、フィボナッチ数列がどういうものだったかを思い出しましょう

fib(n) = fib(n-2) + fib(n-1)

第n項は前(n-1)項と前々(n-2)項の和でしたよね
しかし、初項と第2項は決まっています
付け加えましょう

fib(n) = fib(n-2) + fib(n-1)
fib(0) = 1
fib(1) = 1

できました
このままではプログラムとしては成り立っていませんので、組み立てましょう

def fib(n)
  if n == 0 || n == 1
    return 1
  else
    fib(n-2) + fib(n-1)
  end
end

できました

しかし、このプログラムには欠点があります
fib(4)で解説しましょう

fib(4) = fib(3) + fib(2)

ですね
でも、fib(3)とfib(2)も求めないといけないですよね
そうすると

fib(4) = fib(3) + fib(2)
fib(3) = fib(2) + fib(1)
fib(2) = fib(1) + fib(0)

こうなりますが、fib(3)の中にもfib(2)が入っていますね
そうするとこうなります

fib(4) = fib(3) + fib(2)
  fib(3) = fib(2) + fib(1)  #fib(4)の中のfib(3)を求める式
    fib(2) = fib(1) + fib(0)  #fib(3)の中のfib(2)を求める式
  fib(2) = fib(1) + fib(0)  #fib(4)の中のfib(2)を求める式

ここで違和感というか、無駄が発生していることにお気づきでしょうか
fib(2)は2回、fib(1)とfib(0)は3回出てきています
nが小さい時はあまり問題がありませんが、n=30くらいになると、答えが返ってくるまでに時間がかかります
n=35で2秒ほどかかります
n=100では計算が終わらないようですので、実行はしないようにしましょう

メモ化を使う

問題が発見されたので、変更しないといけませんね
使えないこともないコードならまだしも、n=100程度でギブアップするなんて使えないコードです
そこで、メモ化というものを使います
配列に入れてしまいましょう
メモ化の特徴としては、記憶領域と引き換えに、時間のコストを削減するというものがあります
いわゆるトレードオフをする必要があるということです

F = []
def fib(n)
  if n == 0 || n == 1
    return 1
  elsif F[n] != nil #ここだけのプログラムであれば不要(関与しない)
    return F[n]
  else
    F[n] = fib(n - 2) + fib(n - 1)
  end
end

できました
変更点は、配列をつくって、それに入れている部分です
しかしこれでは数学的には間違っているので、対話型のプログラムにしつつ正しくしましょう

F = []
def fib(n)
  if n == 0 || n == 1
    return 1
  elsif F[n] != nil 
    return F[n]
  else
    F[n] = fib(n - 2) + fib(n - 1)
  end
end

p "フィボナッチ数列の何項目が知りたいん?うちが教えたるで"

num = gets.to_i

if num >= 1
  p fib(num-1)
else
  p "ちゃんと入力しやん人はいややぁ"
end

できました
数学的な一般項はなかなか複雑ですが、プログラム上での一般項(?)はわかりやすくていいですね

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