4
5

More than 3 years have passed since last update.

未経験からエンジニアになって初めて知ったRailsの知識(第1週目)

Last updated at Posted at 2021-05-28

未経験からRailsエンジニアになりました。
もともと1年弱railsを学んではいたのですが実務に入ってみると案外知らないことが多いです。
業務中にわからないことがあるたびにメモって、業務終わりにアウトプットしているのでそれをqiitaにも拙い文章ではありますが記事として投稿します。
エンジニアの方は「未経験者はこういうのがわかっていないんだなぁ」という目で御覧ください。技術的に間違ってたらコメントいただけると嬉しいです。

begin ~ rescue ~ else ~ end

簡単に言うとif文のちょっとデカイバージョン。
if文はtrue/falseに分かれるものを扱うのに対し、begin ~ rescueはtrue/falseにわかれない処理(railsのエラー画面が出てくるようなもの)に対して成功/失敗の分岐を行う。

def create
  begin
    @item = Item.create!(item_params)  # 保存に失敗した際にエラーを出力
  rescue => e
    errors.add(:base, e.message)
  end
end

例えばこのコードだと、保存に失敗した際にエラーが発生する可能性がある処理を噛ましているが
これをbegin~rescueなしで書くと

def create
  @item = Item.create!(item_params)  # 保存に失敗した際にエラーを出力
end

このようになる。これだとエラーが発生した段階で処理が止まってしまう。
これを防ぎ、エラーが発生した段階でrescue文を実行させてそのまま処理を続けてくれるのがbegin~rescueの役割。
こういう使い方はしないと思うけどあくまで一例として書いてみた。

transaction

begin~rescueと一緒に使われることが多い。
複数のレコードの処理がすべて成功したかどうかを判定する際に使う。

def save
  Hoge.create(hoge: hoge)
  Fuga.create(fuga: fuga)
end

みたいに複数のレコードを操作するメソッドを定義した際にif文やbegin ~ rescueだけだと例外処理ができない。
具体的にはどの部分でエラーが出ているのかの特定が難しい。よってここにTransactionを使う。

def save
  begin
  ActiveRecord::Base.transaction do
    Hoge.create!(hoge: hoge)
    Fuga.create!(fuga: fuga)
  rescue => e
    return e.message
  end
end

上記のようにすることでcreateのどちらかに失敗した際にエラーを出すことができる。
このとき発生したエラーの情報はeという変数に格納され、e.message でエラー文を出力できる。

banken

ユーザーごとの権限管理を行うgem。bundle install後

rails g banken:install

を実行すると app/loyalities ディレクトリが作成される。権限の設定ファイルは

rails g banken:loyalty books

とすればloyalities以下にファイルが作成される。権限の設定方法は以下のようにLoyaltyクラスに書く。

class BooksLoyalty < ApplicationLoyalty
  def create?
    user.admin? || record.unpublished?
  end
end

このような権限ファイルが存在すると同じ名前のコントローラーにて

class BooksController < ApplicationController

  # 他の処理は省略

  def create
    authorize! @post

    if @post.update(post_params)
      redirect_to @post, notice: 'Post was successfully updated.'
    else
      render :edit
    end
  end

  # 他の処理は省略
end

のようにauthorize! メソッドで同じ名前のコントローラー&&アクションに対応する権限の判定を行うことができる。
権限周りで弾かれた際はloyaltiesディレクトリを見に行くようにする。

scope

whereメソッド等でのモデルの検索処理に名前をつけたもの。例えば

User.where(age: 10..19)

と書かずに

class User < ActiveRecord::Base
  scope :teenager -> { where(age: 10..19) }
end

のようにかけば

User.teenager

で10ageが10~19のユーザーを指定できる。

参考…https://qiita.com/ozin/items/24d1b220a002004a6351

enum

かんたんに言えばActiveHashをモデル内部に埋め込んだもの。

例えばこんなactive_hashがあったとする

class Category < ActiveHash::Base
  self.data = [
      { id: 0, name: 'メンズ'}
      { id: 0, name: 'レディース'}
      { id: 0, name: 'キッズ'}
    ]
end

これをItemモデル内に埋め込んでしまおう、というのがenumを使うとできる。

class Item < ActiveRecord::Base
  enum categoy: { mens: 0, redies: 1, kids: 2}
end

このようにenumを定義しておくと category=1 のItemを保存した際に

   item.mens? # メンズかどうかを判定
   # => true
   item.category # enumに設定された文字列が出力される 
   # => mens 

のような記述が使えるようになる。

gem 'enum_help'

というgemを使うことで日本語化にも対応してja.ymlに

ja:
  enums:
    item:
      category:
        mens: メンズ

のように定義しておけば

 item.category
 # => メンズ

と日本語で出力できる。

参考…https://qiita.com/punkshiraishi/items/799bef63607e03262644

lambda

ブロックをオブジェクト化したもの例えば

lambda2 = lambda { |s| p "Hello, #{s}!" }

のような形でlambdaを定義しておいて

lambda2 = lambda { |s| p "Hello, #{s}!" }
lambda.call("test")

=> "test"

のような形で呼び出す、という使い方をする。
これはここからここまでが一連の処理の流れですよ〜という意味をもたせられることに価値があると思う。

例えば親モデルの一部をコピーした子モデルを作る際に

class Parent < ApplicationRecord
  after_create :child_initialize

  def child_initialize
    self.child.create(fisrt_name: self.first_name, last_name: self.last_name)
  end

end

のようにしてもいいけど、一つのメソッドの処理が長くなってしまうのはあまり好ましくない。これを解決するために
ブロックをオブジェクト化してブロックを引数として渡す。

class Parent < ApplicationRecord
  after_create :child_initialize

  child_params_lambda = lambda {
   first_name: |parent| parent.first_name
   nationality: |parent| parent.nationality
  }

  def child_initialize
    self.child.create(child_params_lambda.call(self))
  end

end

ちょっとテキトーだけどこんな感じでlambdaとしてブロックを事前に作っておく。
lambcaがcallされると「お、なんかブロックでもあるのかな」となるのでちょっとコードが長くても
落ち着くと思う。ここはもう少し深ぼりたいところ。

ActiveSupport::Concern

かんたんにいうとコントローラーで使われる共通の処理をモジュールとしてまとめておく場所。
ビューにおけるhelperのような立ち位置。

class Hoge
  def test
    puts 'test'
  end
end
class Fuga
  def test
    puts 'test'
  end
end

こんな感じで複数のクラスに共通した処理が合ったときにこれを一つにまとめる。

module Test
  extend ActiveSupport::Concern
  def test
    puts 'test'
  end
end

Concernにまとめておくと各クラスでincludeしてあたかもそのクラスで定義したメソッドかのように使える。

class Hoge
  include Test
  def hoge
    test
  end
end

class Fuga
  include Test
  def fuga
    test
  end
end

Concernを使う場面としては、コントローラーに記述するまでもないけど共通してよく使われる処理があるとき。
モジュール化して切り離しておくことで複数のコントローラーでまたいで使うことができる。
なおconcernに書かれた処理は別途concern_specでテストも実装するべし。

trait

FactoryBoyで複数のカラムと値の組み合わせを一つにまとめるために使う。例えば

FactoryBot.define do
  factory :user do
    nickname { Faker::Name.name }
    email { Faker::Internet.free_email }
    password { 'Pass1234' }
    password_confirmation { 'Pass1234' }
    profession { 'student' }
    age { 24 }
  end
end

こんな感じのuserデータが有ったときに、未成年の学生、というFactoryを生成したい。このとき

build(:user, age: ..19, profession: 'student' )

としてもいいけどちょっと記述が長くなってしまう。また意図している内容を記述から読み取る必要がある。これを

FactoryBot.define do
  factory :user do
    nickname { Faker::Name.name }
    email { Faker::Internet.free_email }
    password { 'Pass1234' }
    password_confirmation { 'Pass1234' }

    trait :young_student do
      profession { 'student' }
        age { Faker::Number.number(from: 7, to: 19) }
    end
  end
end

としておけば

build(:user, :young_student)

これだけで未成年の学生、を抽出可能である。

decorator

モデルのデータ整形を専門に扱う。よくあるのはDB上は姓名別で管理しているけどブラウザ上では一緒に扱いたいというようなケース

class User < ApplicationRecord
  # first_name:string last_name:string
end

このようなUserモデルが存在していた際に、姓名をくっつけて表示させることを考える。

module UserDecorator
  def full_name
    "#{first_name} #{last_name}"
  end
end

こんな感じでfull_nameっていうメソッドを定義しておくと

class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
  end
end

とした際に

<%= @user.full_name %>

で姓名を合体した記述が取り出せる。

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5