冗長な検索ロジックはscopeにしちゃおう

こんにちは!kossyです!



さて、今回はRailsで独自にモデルのデータの絞り込みを定義できるscopeについて、
ブログに残してみたいと思います。




環境
Rails 5.1.6
Ruby 2.5.1
MacOS Mojave




実装方法



例えば、「公開が終了した映画の中で原作が漫画でないデータをidが古い順に取りたい」をモデルから絞り込みをするとします。
これをコードに落とし込むと、

@movies = Movie.where(status: :end).where_not(original_work: :comic).order(id)

ってな感じになるかと思うのですが、これをscopeで定義できまして、

class Movie < ApplicationRecord
  scope :not_comic_end_movie, -> { where(status: :end).where_not(original_work: :comic).order(id) } 
end

って感じで定義できます。scopeとして定義すると、

@movies = Movie.not_comic_end_movie

とするだけで、

  @movies = Movie.where(status: :end).where_not(original_work: :comic).order(id)

とした時と同じレコードを取得することができます。

同じような検索を何度も行うような時や、記述が冗長になってしまった時に有効な手段だと思います。

bundle installする時はパスの指定をしよう

こんにちは!kossyです!




さて、今回はbundle installする時の注意事項について、ブログに残してみたいと思います。




bundle installする時はpathを指定しよう

普段Gemfileを編集すると、
bundle installを実行すると思うのですが、

    • path vendor/bundleでパスの指定をしたほうが良いです。

理由は、


gemを任意のディレクトリにインストールし、gemをRailsプロジェクト毎に管理するためです。
ディレクトリを指定しないbundle installはシステム領域にインストールされるため、複数のrailsアプリを同一のマシン上で運用していると、問題が発生することがあります。
vendor/bundleは、アプリ専用の領域のため、他のアプリには影響がありません。

マシン上で、一つしかrailsアプリを動かさないのであれば、システム領域に入れてしまっても問題ないでしょう。

参考:
Ruby - bundle install するときになぜ vendor/bundle に入れるのか|teratail

とのことでした。


bundle install する時は余計なバグを生まないためにも
パスを指定するようにしましょう。

Rspec使う時のgenerator設定

こんにちは!kossyです!




さて、今回はrspec導入の際のgeneratorの設定についてブログに残してみたいと思います。




個人的には以下のような設定にすることが多いです。

config/application.rb


    config.generators do |g|
      g.template_engine  :haml
      g.test_framework   :rspec, fixture: false
      g.view_specs       false
      g.controller_specs false
      g.helper           false
      g.stylesheets      false
      g.javascripts      false

上記の設定ですと、
・viewはerbではなくhaml
・テストフレームワークRspecで、fixtureは生成されない
・viewのspecファイルは生成なし
・controllerもなし
・helperも
・scssファイルなし
・coffeeもなし

みたいな感じですね。

ちなみに、rails newする際に、-Tと入力して実行すると、
デフォルトで生成されるmini-testのスケルトンをスキップすることができます。

active_storageで画像を複数枚保存したい

こんにちは!kossyです!




さて、今回はRails5.2系から使えるようになった、active_storageで画像の複数枚アップロードの実装方法を
ブログに残してみたいと思います。





has_many_attachを使うだけ


active_storageの導入についてはこちらを参考にしてみてください。
開発環境でのActive_Storageの使い方 - web業界未経験からエンジニアになった人のブログ


画像を1枚添付するだけであれば、has_one_attached :image
みたいな感じで設定すれば事足りるのですが、
複数枚アップロードしたい場合は、has_many_attached :images
と記述します。




これで複数枚アップロードを実現できます。

ブラウザから返ってくるenumの値はシンボルじゃなくて文字列だった

こんにちは!kossyです!




さて、今回はenumで定義した値をブラウザからパラメータとして受け取る場合、シンボルの形式ではなく文字列で
返ってくるのに気づかずハマったので、備忘録としてブログに残してみたいと思います。





enumのステータスによって処理を分岐させたいというシチュエーションでハマりました、、、

コードは例としてこんなんです。

  enum status: { draft: 1, published: 2 }

hogehoge_controller.rb

if params[:status] == :draft
~~
else
~~
end


ここでbinding.pryで処理を止めてparamsをみます。
(省略しまくってすみません。エスパーしてください笑)

コンソール

[1] pry(#<DraftsController>)> params                                                                                                                                     
=> <ActionController::Parameters {"utf8"=>"", "authenticity_token"=>"Rl3FrY+0vYyOoCFv2mRhKYs2Gc3ptfCfMAicrzwECs0YpW8ZHOTGWQ6hHVjepT3K1fADkCZ/7nYHmEp6YusqFQ==", "article"=><ActionController::Parameters {"title"=>"test", "description"=>"test", "body"=>"test", "category_id"=>"1", "status"=>"draft"} permitted: false>, "commit"=>"SAVE", "controller"=>"drafts", "action"=>"create"} permitted: false>

statusのハッシュの値が"draft"と文字列で来ています。
文字列とシンボルを比較すれば当たり前ですがfalseが返ります。これで悶々としてました。

解決策は、

if params[:status].to_sym == :draft

とすればtrueを返すことができます。

コンソールで試してみましょう。

[1] pry(main)> string = "string"                                                                                                                                         
=> "string"
[2] pry(main)> symbol = :string                                                                                                                                          
=> :string
[3] pry(main)> string == symbol                                                                                                                                          
=> false
[4] pry(main)> _string = string.to_sym                                                                                                                                   
=> :string
[5] pry(main)> _string == symbol                                                                                                                                         
=> true

こんな感じですね。


ブラウザから送られてくるenumのパラメーターは文字列型で返ると覚えましょう。(自戒を込めて)

特定のテストケースを実行したい時のfocus: true

こんにちは!kossyです!




さて、今回は、特定のテストケースを実行したい時に便利な、
focus: trueオプションの使い方について、ブログに残してみたいと思います。




環境
Rails 5.1.6
Ruby 2.5.1
rspec 3.8.0
MacOS Mojave




まずはspec_helper.rbに設定

以下の記述を追記します。

/spec/spec_helper.rb

RSpec.configure do |config|

省略

  config.filter_run :focus

end


これでfocus: trueを使えるようになります。




後は実行したいブロックに記述するだけ


例えばこんなテストがあったとします。

  describe 'Validation of create User' do
    describe 'blank' do
      it '名前が空白だとエラーになる' do
        user = User.new(name: '', email: 'example@gmail.com', password: 'test1234', password_confirmation: 'test1234')
        user.valid?
        expect(user.errors[:name]).to include('を入力してください')
      end
      it 'メールアドレスが空白だとエラーになる' do
        user = User.new(name: 'テストユーザー', email: '', password: 'test1234', password_confirmation: 'test1234')
        user.valid?
        expect(user.errors[:email]).to include('を入力してください')
      end
      it 'パスワードが空白だとエラーになる' do
        user = User.new(name: 'テストユーザー', email: '')
        user.valid?
        expect(user.errors[:password]).to include('を入力してください')
      end
    end

では、一つ目のitブロックにfocus: trueを設定します。すると、

$ bundle exec rspec spec/models/user_spec.rb


User
  Validation of create User
    blank
      名前が空白だとエラーになる

Finished in 0.24428 seconds (files took 10.93 seconds to load)
1 example, 0 failures


一つ目のテストブロックのテストのみ実行されます。


describeのブロックにも設定することができます。

$ bundle exec rspec spec/models/user_spec.rb



  describe 'Validation of create User', focus: true do
    describe 'blank' do
      it '名前が空白だとエラーになる' do
        user = User.new(name: '', email: 'example@gmail.com', password: 'test1234', password_confirmation: 'test1234')
        user.valid?
        expect(user.errors[:name]).to include('を入力してください')
      end
      it 'メールアドレスが空白だとエラーになる' do
        user = User.new(name: 'テストユーザー', email: '', password: 'test1234', password_confirmation: 'test1234')
        user.valid?
        expect(user.errors[:email]).to include('を入力してください')
      end
      it 'パスワードが空白だとエラーになる' do
        user = User.new(name: 'テストユーザー', email: '')
        user.valid?
        expect(user.errors[:password]).to include('を入力してください')
      end
    end


以下はログ
User
  Validation of create User
    blank
      名前が空白だとエラーになる
      メールアドレスが空白だとエラーになる
      パスワードが空白だとエラーになる

Finished in 0.15704 seconds (files took 8.86 seconds to load)
3 examples, 0 failures

Validation of create Userのdescribeブロックのテストが全て実行されているのがわかります。




テストケースが増えてくると実行して終了するまで待つのが
とても面倒になってきます。

focus: trueはそんなシチュエーションで活躍間違いなしのオプションです。




参考にさせていただいた記事
RSpecで特定のテストケースのみを実行する方法 - TIM Labs
今日から使える! RSpec でテストの実行サンプル it を素早く絞り込み・スキップする方法 - Qiita

Rail5.2系でreferencesカラムを設定しようとした時のエラー

こんにちは!kossyです!





さて、今回は、Rails5.2系で外部キーを設定しようとした時に遭遇したエラーについて、
ブログに残してみたいと思います。



環境
Rails 5.2.2
Ruby 2.5.1






エラーの状況

2019****_create_category.rb

class CreateCategories < ActiveRecord::Migration[5.2]
  def change
    create_table :categories do |t|
      t.string :name, null: false
      t.text :content, null: false
      t.string :image, null: false

      t.timestamps
    end
  end
end


2019****_create_article.rb

class CreateArticles < ActiveRecord::Migration[5.2]
  def change
    create_table :articles do |t|
      t.string :title, null: false, index: true
      t.string :image
      t.text :description, null: false
      t.text :body, null: false
      t.integer :status, null: false
      t.references :user_id, foreign_key: true
      t.references :category_id, foreign_key: true

      t.timestamps
    end
  end
end

この内容でrails db:migrateを実行してみると、
以下のエラーが発生しました。

rails aborted!
StandardError: An error has occurred, all later migrations canceled:

Mysql2::Error: Table 'cms_development.articles' doesn't exist: SHOW FULL FIELDS FROM `articles`

Caused by:
ActiveRecord::StatementInvalid: Mysql2::Error: Table 'cms_development.articles' doesn't exist: SHOW FULL FIELDS FROM `articles`

Caused by:
Mysql2::Error: Table 'cms_development.articles' doesn't exist

Caused by:
Mysql2::Error: Cannot add foreign key constraint




対処(ベストプラクティスかは怪しい)



外部キーを追加できないと怒られていたので、該当部分をコメントアウト

class CreateArticles < ActiveRecord::Migration[5.2]
  def change
    create_table :articles do |t|
      t.string :title, null: false, index: true
      t.string :image
      t.text :description, null: false
      t.text :body, null: false
      t.integer :status, null: false
      # t.references :user_id, foreign_key: true
      # t.references :category_id, foreign_key: true

      t.timestamps
    end
  end
end

そして、rails db:migrateすると、ひとまず成功。

次に、

$ rails g migration AddReferencesToArticle user:references category:references


2019****_add_refereces_to_article.rb

class AddReferenceToArticle < ActiveRecord::Migration[5.2]
  def change
    add_reference :articles, :user, foreign_key: true
    add_reference :articles, :category, foreign_key: true
  end
end

この状態でrails db:migrateしたら、無事にreferenceカラムを追加できました。


これ本番環境でmigrationする時も起きるのでしょうか?
だとすれば、またそのうち対処法をブログに残すことになるかもしれません、、、笑


1/16 追記
t.references :user, foreign_key: true
t.references :category, foreign_key: true
とすればエラーは起きないみたいです、、、
もう同じミスはやらないと心に誓いました。

link_toでページ内ジャンプ機能を設定してみた

こんにちは!kossyです!




さて、Railsのビューヘルパーであるlink_toでページ内リンク機能の設定方法について、
ブログに残してみました。

HTMLではaタグで実現できますが、link_toを使う場合はどのように実装すればいいのか、
少し詰まったので、備忘録として残しておきます。



環境
Rails 5.2.2
Ruby 2.5.1
Haml




anchorオプションで明示的にリンク先を指定する


HTMLではジャンプしたい箇所にidを設定し、aタグのパスにidを指定すれば
ページ内ジャンプ機能を実現できましたが、
link_toの場合はanchorを使えばページ内ジャンプ機能ができます。

app/views/home/index

%header
  = link_to "TOPIC", { anchor: "topic" }



.article#topic
  %h2
    トピック

こんな感じですね。

anchorオプションにidであるtopicを指定しています。

挙動はこんな感じになるかと。
https://gyazo.com/d9a9d15af906e90c9103d45832b27e39




参考にさせていただいたサイト
link_toでページ内リンクとclass指定を共存させる方法 - Qiita

link_toで画像付きのリンクを生成してみた

こんにちは!kossyです!




さて、今回は、Railsのビューヘルパーであるlink_toで、画像付きのリンクを生成する方法を
ブログに残してみたいと思います。




環境
Rails 5.2.2
Ruby 2.5.1
Haml




link_toの引数にimage_tagを使えばOK


同じくRailsのビューヘルパーであるimag_tagを使えば簡単に実現できます。

app/views/samples/index.html.haml


link_to image_tag("画像名", class:"クラス名"), パス


例
link_to image_tag("sample.png", class: "image"), samples_path

これで実現できます。



参考にさせていただいたサイト
https://dotinstall.com/lessons/basic_rails_v3

TECH CAMPの夜間コースを卒業して即戦力エンジニアになれたのか

こんにちは!kossyです!





早いものでWebエンジニアとして働き出してから1ヶ月が経過しました。
スクールで勉強していたRailsと、全く触れたことのなかったAngularを使って、
既存アプリの改修業務を主として行なっています。



さて、私が卒業したプログラミングスクールでは、
「プログラミング未経験者が短期間で即戦力エンジニアへ」
というフレーズが謳い文句になっています。


そこで、実務に携わるようになって1ヶ月経った今、
本当に即戦力エンジニアになれたのか?
について、ブログに残してみたいと思います。









1. 入社して日が浅い段階でタスクを振って頂けた

初日は入社手続きと開発環境の構築に追われ(課題は与えられたが仕様の把握に時間取られる)、
2日目はスプリントレビューだったのでほとんどコードを書かず。
3日目から本格的に開発業務に携わることになりました。


上司に仕様を説明してもらいながら、
まずはコードを記述して、レビューを頂く形のタスクを振ってもらえました。


仕様を把握するのが大変でしたが、サーバーサイドのコードの変更自体は、
見本になるコードがそこかしこに書いてあるので、
なんとか対処できました。(Angularは上司につきっきりで指導してもらいましたが、、、)


「即戦力」の定義によるとは思いますが、
「入社から日が浅い段階でタスクを振られても、ある程度の成果物を提出できる」ことが
即戦力の定義とするならば、該当していると考えてもいいと思っています。









2. 即戦力になれるかどうかは会社で使う言語次第


入社してすぐにタスクを振っていただけたのも、私が経験のある言語と
会社が使っている言語が一致していたから、という理由が非常に大きいと思っています。


私の会社で使う言語は、スクールで学習していたRubyで、
サーバーサイドのフレームワークも同じくRailsです。


自分の経験のある言語と、会社で使用する言語の一致も、
即戦力エンジニアとして活躍するための条件かもしれません。









まとめ

繰り返しますが、即戦力として入社してすぐにチームにジョインしてコードを書くには、
スクールで学んでいた技術と会社で使う技術が一致していないと難しいと思いました。


TECH CAMPの求人はRubyだけでなく、javaPHPpythonなどの言語、
フレームワークcakePHPDjango、Flaskなど、言語・フレームワーク・更にはOSに拘らず紹介されます。
また、フロントエンドエンジニアとしての求人もあったりします。(React, Vue.js, Angular、Nuxt.js等)


全く触れたことのない技術を使う会社で、入社して日が浅い段階で成果を出せと言われても、
正直無理がありますよね、、、笑
算数できないのに数学で満点取って見ろと言われているようなものです、、、


なので、スクールを卒業した全ての方が、
即戦力として活躍するのは難しいのではないかと思いました。


とはいえ、スクールを卒業すればWebアプリ、通信周り、設計の技法の基礎が身につくので、
新しい言語、フレームワークの習得にはさほど時間をかけずに習得できるような素地がある状態で、
業務に入ることができるのではないでしょうか。



TECH CAMPを卒業すれば、フレームワークRailsに限れば、
即戦力エンジニアになれると思います。