未経験から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 %>
で姓名を合体した記述が取り出せる。