- 投稿日:2019-12-15T22:22:08+09:00
Rails 6 ActiveStorageを使用してS3に画像ファイルをアップロードし、取得した画像をリサイズして表示する方法
はじめまして!スタートアップでサーバーサイドエンジニアをやっています。なかのです!
TechTrain Advent Calendar 2019(User ver)の15日目を担当します!
今回はタイトルの通り、Rails 6 ActiveStorageを使用してS3に画像ファイルをアップロードし、取得した画像をリサイズして表示する方法をお話ししたいと思います。はじめに
まず、簡単にActiveStorageについて説明しますと
Active StorageとはAmazon S3、Google Cloud Storage、Microsoft Azure Storageなどの クラウドストレージサービスへのファイルのアップロードや、ファイルをActive Recordオブジェクトにアタッチする機能を提供します。development環境とtest環境向けのローカルディスクベースのサービスを利用できるようになっており、ファイルを下位のサービスにミラーリングしてバックアップや移行に用いることもできます。
アプリケーションでActive Storageを用いることで、ImageMagickで画像のアップロードを変換したり、 PDFやビデオなどの非画像アップロードの画像表現を生成したり、任意のファイルからメタデータを抽出したりできます。(https://railsguides.jp/active_storage_overview.html 参照)
開発環境は以下になります。
Ruby '2.6.5' Rails '6.0.2'準備
今回はUserに紐づく画像ファイルをアップロードしたいと思いますので、scaffoldを用いてさくっと必要な部分を作ります!
$ rails new activerecord-sample
$ cd activerecord-sample
$ rails generate scaffold user name:string
$ rails db:migrate
$ rails s
rails s
でサーバーを立ち上げ、localhost:3000/users
にアクセスし、下の画像のような画面が表示されたらセットアップ完了です。S3にバケットを作成する
Amazon Simple Storage Service(Amazon S3)は、インターネット用のストレージサービスで、データ (写真、動画、ドキュメントなど)を保存しておくために利用されます。使用するためには事前にバケットを作成しておく必要があります。
S3のセットアップは以下の記事を参考にしてみてください。
https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/user-guide/create-configure-bucket.html
今回は以下のような設定でバケットを作成しました。
項目 入力・選択 バケット名 activestorage-sample-bucket リージョン ap-northeast-1 (東京) パブリックアクセス許可を管理する このバケットに読み取り・書き込みアクセス権限をする 上記以外 全部デフォルトのまま S3でアクセスキーを作る。
アクセスキーを作る方法は、こちらを参考にしてみてください。
https://tech-blog.s-yoshiki.com/2019/06/1292/IAMユーザー作成後に表示された「アクセスキー ID」と「シークレットアクセスキー」 は後ほど使用しますので誰にも教えないように保管しておいてください。
ActiveStorageの導入
ここから実際にActiveStorageを導入していきたいと思います。
以下のコマンド実行してActiveStorageをinstallしてください。
$ rails active_storage:install $ rails db:migrate
active_storage_attachments
とactive_storage_blobs
というテーブルが作成されていれば、インストール成功です。ActiveStorageは、初期設定ではDisk内(
storage以下
)にアップロードしたファイルデータを保存するようになっているため、amazon s3のストレージを使用する記述を追加します。まずは使用するストレージの設定(今回だとamazon s3)を以下のファイルに追加してください。
concig/storage.ymltest: service: Disk root: <%= Rails.root.join("tmp/storage") %> local: service: Disk root: <%= Rails.root.join("storage") %> amazon: service: S3 access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> region: <%= Rails.application.credentials.dig(:aws, :s3, :region) %> bucket: <%= Rails.application.credentials.dig(:aws, :s3, :bucket) %>次に利用するサービスをActiveStorageに認識させます。
config/environments/development.rb#ファイルをAmazon S3に保存する config.active_storage.service = :amazonS3のためのgemが必要なので、
Gemfile
に以下の記述を追加してbundle installしてください。gem "aws-sdk-s3", require: false$ bundle install最後にS3の環境設定を追加します。
Rails6 から各環境でcredentialsの管理が出来るようになったので、以下のコマンドを入力し、development環境でのcredentialsファイルを作成して、S3の設定を追加してください。$ ./bin/rails credentials:edit --environment developmentこちらのコマンドを入力した際、もしcredentialsが作成されていなかったら新たに
config/credentials
以下にdevelopment.yml.enc
とdevelopment.key
というファイルを作成してくれます。以下のように編集してください。
development.yml.encaws: access_key_id: #先ほど取得したaccess_key_idをいれてください。 secret_access_key: #先ほど取得したsecret_access_keyをいれてください。 s3: region: ap-northeast-1 bucket: activestorage-sample-bucket編集が完了したら、値が取得出来ているか確認してみましょう。
$ rails c irb(main):001:0> Rails.application.credentials.dig(:aws, :access_key_id) => "設定したaccess_key_id" irb(main):002:0> Rails.application.credentials.dig(:aws, :secret_access_key) => "設定したsecret_access_key" irb(main):003:0> Rails.application.credentials.dig(:aws, :s3, :region) => "ap-northeast-1" irb(main):004:0> Rails.application.credentials.dig(:aws, :s3, :bucket) => "activestorage-sample-bucket"値がちゃんと取れていれば、設定完了です。
ActiveRecordの実装
UserとAttachmentとBlobの関係を以下の図に表します。
Userモデルに1つのファイルを紐づける場合
1ユーザーに1つの画像ファイルしか紐づかない場合、上記の図のNが1になります。まずはこのパターンを実装していきたいと思います。
app/models/user.rbclass User < ApplicationRecord has_one_attached :avatar endUserモデルにavatarという属性を追加しました。今回はavatarという命名にしましたが、用途に合わせて自由に指定することが出来ます。
ActiveStorageでは、ファイルデータを保存するためにそれぞれのテーブルに個別にカラムを用意しなくても上記の記述を追加するだけでactive_storage_attachments
とactive_storage_blobs
が裏側でよしなに処理してくれます。また、今回は1つのファイルを添付するためhas_one_attached
という記述をしています。画像を投稿出来るようにフォームを追加します。
app/views/users/_form.html.erb
のブロック内に以下の記述を追加してください。app/views/users/_form.html.erb<div class="field"> <%= form.label :name %> <%= form.text_field :name %> </div> # 以下の部分を追加 <div class="field"> <%= form.file_field :avatar %> </div>ストロングパラメーターもavatarを許可するようにします。
app/controllers/users_controller.rbdef user_params params.require(:user).permit(:name, :avatar) end最後にユーザー詳細画面で画像が表示されるようにしましょう。
app/views/users/show.html.erb<p> <strong>Name:</strong> <%= @user.name %> # 以下を追記 <%= image_tag url_for(@user.avatar) %> </p>Userモデルに複数のファイルを紐づける場合
次は複数投稿投稿出来るようにしてみますz。
app/models/user.rbclass User < ApplicationRecord has_many_attached :images end今度はUserモデルにimagesという属性を追加しました。
userに複数の画像を紐づけるためにhas_many_attached
を使用しています。画像を複数投稿出来るようにフォームを追加します。
app/views/users/_form.html.erb
のブロック内に以下の記述を追加してください。app/views/users/_form.html.erb<div class="field"> <%= form.label :name %> <%= form.text_field :name %> </div> # 以下の部分を追加 <div class="field"> <%= form.file_field :images, multiple: true %> </div>
multiple: true
にすることで画像を一度に複数選択出来るようになります。ストロングパラメーターもimagesを許可するようにします。
app/controllers/users_controller.rbdef user_params params.require(:user).permit(:name, images: []) end最後に、ユーザー詳細画面で画像が表示されるようにします。
app/views/users/show.html.erb<p> <strong>Name:</strong> <%= @user.name %> # 以下を追記 <% @user.images.each do |image| %> <%= image_tag url_for(image) %> <% end %> </p>これで複数投稿が出来るようになったので、
rails s
をして試してみましょう!
image_processingを用いたリサイズ
画像投稿した後は、画面サイズに合わせて画像を取得したくなりますよね。Rails6から
image_processing
というgemを使用することが推奨されているため、そちらを使用してリサイズを行っていきます。
Gemfile
にimage_processing
がコメントアウトされていると思いますので、アンコメントしてbundle install します。gem 'image_processing', '~> 1.2'また、ImageMagickをinstallする必要がありますので、Mac OSXを使用している方は
brew install imagemagick
でinstallしてください。これで設定は完了です。
リサイズして表示するように設定していきます。variantメソッドを用いることで簡単にリサイズを行うことが出来ます。先ほど、複数投稿時に使用したviewに追加して、表示を見てみます。
app/views/users/show.html.erb<% @user.images.each do |image| %> <%= image_tag image.variant(resize_to_limit: [100, 100]) %> <% end %>簡単にリサイズされましたね。
おまけ
ActiveStorageでよく使用するメソッドやValidationについて、軽く書いておきたいと思います。
まず、よく使用するメソッド
attached?
メソッド
添付ファイルを持っているかどうかを調べます。irb(main):001:0> user = User.last => #<User id: 20, name: "なかの", created_at: "2019-12-15 11:24:56", updated_at: "2019-12-15 11:24:56"> irb(main):002:0> user.images.attached? => true irb(main):007:0> user2 = User.new => #<User id: nil, name: nil, created_at: nil, updated_at: nil> irb(main):008:0> user2.images.attached? => false
purge
メソッド
Attach、Blob、S3から添付ファイルを削除します。irb(main):009:0> user = User.last => #<User id: 20, name: "なかの", created_at: "2019-12-15 11:24:56", updated_at: "2019-12-15 11:24:56"> irb(main):010:0> user.images.attached? => true irb(main):011:0> user.images.purge => #<ActiveRecord::Associations::CollectionProxy []> irb(main):012:0> user.images.attached? => falseこちらのメソッドはRollbackが起きた際にS3とデータの不整合が起こりやすいため、使用時は注意しなくてはいけません。ActiveRecordのトランザクションとActiveStorageについての話は、こちらの記事がとてもわかりやすいのでおすすめです。
https://tech.smarthr.jp/entry/2018/09/14/130139
detach
メソッド
Attachから添付ファイルのレコードを削除します。irb(main):001:0> user = User.last => #<User id: 21, name: "なかの", created_at: "2019-12-15 12:26:06", updated_at: "2019-12-15 12:26:06"> irb(main):002:0> user.images.attached? => true irb(main):003:0> user.images.detach irb(main):004:0> user.images.attached? => falseActiveStorageでは専用のValidationが用意されていないため、各自で作成しなくてはいけません。
簡単にContent_Typeを確認するValidationを作ってみました。app/models/user.rbclass User < ApplicationRecord has_one_attached :avatar validate :validate_avatar def validate_avatar errors.add(:avatar, "画像データではありません。") unless image? end def image? return '' unless avatar.attached? %w[image/jpg image/jpeg image/png image/gif].include?(avatar.blob.content_type) end endおわりに
ActiveStorageを使ってみて、思ったより簡単に導入できるので、単純な画像投稿機能などにはオススメだと思いました。
ただ、デフォルトで署名付きURLを取得してしまうため、CDNなどを使用する際は少し工夫が必要になるかなと思いました。
次回書けたら書きます・・・。
- 投稿日:2019-12-15T22:21:40+09:00
【Ruby】ローカルでKaminari(Gem)をデバッグする環境を作る
環境構築
ソースを取ってくる
- fork
- git clone
bundle install
bundle install --path vendor/bundleRubyのバージョンを指定する
.ruby-version2.6.5Bundler.requireしてGemの処理を呼び出す
tmp/app.rbrequire 'bundler' Bundler.require p Kaminari.paginate_array([1,2,3,4,5], total_count: 6).page(1).per(2)これでローカルでソースを編集すると反映されるようになります。
RubyMineでDebug実行することも可能です。テストを動かす
script: 'bundle exec rake test'
.travis.yml
を見てみるとscript: 'bundle exec rake test'
という記載があるので実行します。
(.travis.yml
はTravis CIの定義ファイルなので、.travis.yml
と同じことをローカルで行えばテストは動くはずという考えです。)bundle exec rake test
sqlite3
をgemfileに追加するsqlite3がないという旨のエラーが表示されるはずなので、追加して
bundle install
します。Gemfile# frozen_string_literal: true source 'https://rubygems.org' # Specify your gem's dependencies in kaminari.gemspec gemspec gem 'sqlite3'再度
bundle exec rake test
するとテストが動くはずです。まとめ
おそらくですが、他のGemも同じような方法で環境は整いそうな気がします?
OSSコントリビュートしていくぞい?
- 投稿日:2019-12-15T22:08:52+09:00
Rails:deviseの利用方法
目的
webアプリで必須の認証機能を自動作成してくれる便利なgem「devise」を使ってみたので使い方を説明したいと思います。
環境
rails 5.1.6
deviseの導入
1.gemのインストール
1.1.Gemfileの編集とインストール
Gemfileにdeviseを追加source 'https://rubygems.org' . . . gem 'devise'gemをインストール
$ bundle install2.deviseの設定
devise関連ファイルを追加
以下のような英文が表示されます。1から4まで順番に見ていきます。$ rails g devise:install create config/initializers/devise.rb create config/locales/devise.en.yml =============================================================================== Some setup you must do manually if you haven't yet: 1. Ensure you have defined default url options in your environments files. Here is an example of default_url_options appropriate for a development environment in config/environments/development.rb: config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } In production, :host should be set to the actual host of your application. 2. Ensure you have defined root_url to *something* in your config/routes.rb. For example: root to: "home#index" 3. Ensure you have flash messages in app/views/layouts/application.html.erb. For example: <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p> 4. You can copy Devise views (for customization) to your app by running: rails g devise:views ===============================================================================2.1.flashメッセージの設定
<p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p>上記を挿入した箇所に「ログインしました」みたいなメッセージが出ます。
タグのすぐ下に挿入します。
以下のファイルのapp/views/layouts/application.html.erb<!DOCTYPE html> <html> <head> <title>DeviseRails5</title> <%= csrf_meta_tags %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> </head> <body> <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p> <%= yield %> </body> </html>2.2.deviseのviewを作成
Deviseの導入で追加されるViewは、以下のコマンドを実行しなければデザインをカスタマイズできないため実行します。
$ rails g devise:view以下の様なファイルが生成されます。
app/views/devise/shared/_links.html.erb (リンク用パーシャル) app/views/devise/confirmations/new.html.erb (認証メールの再送信画面) app/views/devise/passwords/edit.html.erb (パスワード変更画面) app/views/devise/passwords/new.html.erb (パスワードを忘れた際、メールを送る画面) app/views/devise/registrations/edit.html.erb (ユーザー情報変更画面) app/views/devise/registrations/new.html.erb (ユーザー登録画面) app/views/devise/sessions/new.html.erb (ログイン画面) app/views/devise/unlocks/new.html.erb (ロック解除メール再送信画面) app/views/devise/mailer/confirmation_instructions.html.erb (メール用アカウント認証文) app/views/devise/mailer/password_change.html.erb (メール用パスワード変更完了文) app/views/devise/mailer/reset_password_instructions.html.erb (メール用パスワードリセット文) app/views/devise/mailer/unlock_instructions.html.erb (メール用ロック解除文)3.Userモデルの設定
3.1.Userモデル(devise用)を生成
以下を実行。
$ rails g devise Userマイグレーションファイルができます。
デフォルトでは以下のようになってます。db/migrate/20161112121754_devise_create_users.rbclass DeviseCreateUsers < ActiveRecord::Migration[5.1] def change create_table :users do |t| ## Database authenticatable t.string :email, null: false, default: "" t.string :encrypted_password, null: false, default: "" ## Recoverable t.string :reset_password_token t.datetime :reset_password_sent_at ## Rememberable t.datetime :remember_created_at ## Trackable t.integer :sign_in_count, default: 0, null: false t.datetime :current_sign_in_at t.datetime :last_sign_in_at t.string :current_sign_in_ip t.string :last_sign_in_ip ## Confirmable # t.string :confirmation_token # t.datetime :confirmed_at # t.datetime :confirmation_sent_at # t.string :unconfirmed_email # Only if using reconfirmable ## Lockable # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts # t.string :unlock_token # Only if unlock strategy is :email or :both # t.datetime :locked_at t.timestamps null: false end add_index :users, :email, unique: true add_index :users, :reset_password_token, unique: true # add_index :users, :confirmation_token, unique: true # add_index :users, :unlock_token, unique: true end endUserモデルは以下のようになっています。
デフォルトではdatabase_authenticatable、registerable、recoverable、rememberable、trackable、validatableが使えるようになっています。app/models/user.rbclass User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable end
機能 概要 database_authenticatable サインイン時にユーザーの正当性を検証するためにパスワードを暗号化してDBに登録します。認証方法としてはPOSTリクエストかHTTP Basic認証が使えます。 registerable 登録処理を通してユーザーをサインアップします。また、ユーザーに自身のアカウントを編集したり削除することを許可します。 recoverable パスワードをリセットし、それを通知します。 rememberable 保存されたcookieから、ユーザーを記憶するためのトークンを生成・削除します。 trackable サインイン回数や、サインイン時間、IPアドレスを記録します。 validatable Emailやパスワードのバリデーションを提供します。独自に定義したバリデーションを追加することもできます。 confirmable メールに記載されているURLをクリックして本登録を完了する、といったよくある登録方式を提供します。また、サインイン中にアカウントが認証済みかどうかを検証します。 lockable 一定回数サインインを失敗するとアカウントをロックします。ロック解除にはメールによる解除か、一定時間経つと解除するといった方法があります。 timeoutable 一定時間活動していないアカウントのセッションを破棄します。 omniauthable intridea/omniauthをサポートします。TwitterやFacebookなどの認証を追加したい場合はこれを使用します。 3.2.マイグレーションファイルの編集
ユーザープロファイルにニックネームを実装したいので
Userモデルにのname属性を追加します。db/migrate/[timestamp]_devise_create_users.rbclass DeviseCreateUsers < ActiveRecord::Migration[5.1] def change create_table :users do |t| ## Database authenticatable t.string :name, null: false, unique: true t.string :email, null: false, default: "" t.string :encrypted_password, null: false, default: "" ## Recoverable t.string :reset_password_token t.datetime :reset_password_sent_at ## Rememberable t.datetime :remember_created_at ## Trackable t.integer :sign_in_count, default: 0, null: false t.datetime :current_sign_in_at t.datetime :last_sign_in_at t.string :current_sign_in_ip t.string :last_sign_in_ip ## Confirmable # t.string :confirmation_token # t.datetime :confirmed_at # t.datetime :confirmation_sent_at # t.string :unconfirmed_email # Only if using reconfirmable ## Lockable # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts # t.string :unlock_token # Only if unlock strategy is :email or :both # t.datetime :locked_at t.timestamps null: false end add_index :users, :email, unique: true add_index :users, :reset_password_token, unique: true # add_index :users, :confirmation_token, unique: true # add_index :users, :unlock_token, unique: true end endマイグレーションを実行
$ rails db:migrate以上で、メールアドレスでの認証機能が完成しました。
手作りと比べたら非常に簡単です。カスタマイズも簡単にできます。
感想
これから認証機能を作るときは基本的にdeviseを使うことになりそうですw
- 投稿日:2019-12-15T22:01:30+09:00
Rubyの変数の定義(定数)
いよいよRubyの変数の定義のラストです。
これまでにローカル変数とインスタンス変数、クラス変数、グローバル変数について書きました。今回は定数について書いていきます。
命名規則
- 先頭が大文字
- 英数字と_で構成される
STUDENT = 'Taro' => "Taro" Student_2 = 'Jiro' => "Jiro" Student_3 #何も変数に入れないとエラーが発生する NameError: uninitialized constant Student_3スコープ
定数が定義されたクラス・モジュール内、そのクラス・モジュールの内側で定義されたクラス・モジュール内、およびそのクラス・モジュールを継承またはインクルードしているモジュール内クラス名やモジュール名で修飾知れば外部からアクセスが可能
Student = "Taro" puts Student Taro => nil 3.times do |n| puts Student end Taro Taro Taro => 3 def students Student end students #どこからでも参照可能 => "Taro" class Students puts Student end Taro #どこからでも参照可能 => nilただしインスタンスメソッド内では定数を定義することはできない
def students Student = 'Taro' end SyntaxError: (irb):2: dynamic constant assignmentこの理由はメソッドは複数回呼び出すことが前提で、メソッドの中に定数を定義するということは、メソッドを呼び出す際に毎回更新することになるから。
定数は大前提値を変更・更新しないことが目的なのでメソッド内部で定数を定義しようとするとエラーになる。とはいうものの定数に値を再代入して値を変更することはできる。
Student = 'Taro' Student = 'Yamada Taro' (irb):2: warning: already initialized constant Student (irb):1: warning: previous definition of Student was here => "Yamada Taro"ただし定数の値の変更には警告が伴う
また定数はmoduleやclassの中では定義ができる。そしてネストを指定して定数の値を変更できる。
ただし、変更ごとに警告はでる。module Teachers English = 'Eiko' end => "Eiko" #ネストを指定して値を変更する Teachers::English = 'Keiko' warning: already initialized constant Teachers::English warning: previous definition of English was here => "Keiko" class MedicalStudents Student = 'Hanako' end =>"Hanako" MedicalStudents::Student = 'Yoshiko' warning: already initialized constant MedicalStudents::Student warning: previous definition of Student was here => "Yoshiko"同じ定数でも構造がクラスやモジュールの異なる階層や場所で定義すればそれぞれ違う値を持つことができる。
class Students Student = 'Taro' #Studentsクラス内にMedicalStudentクラスを定義 class MedicalStudent Student = 'Hanako' end #Studentsクラス内にStudentCouncil(生徒会)モジュールを定義 module StudentCouncil Student = 'Jiro' #StudentCouncil(生徒会)モジュールにTeamMemberクラスを定義 class TeamMember Student = 'Sabu' end #StudentCouncil(生徒会)モジュールにSecretary(書記)モジュールを定義 module Secretary Student = 'Yoshiko' end end end Students::Student => "Taro" Students::MedicalStudent::Student => "Hanako" Students::StudentCouncil::Student => "Jiro" Students::StudentCouncil::TeamMember::Student => "Sabu" Students::StudentCouncil::Secretary::Student => "Yoshiko"とうかたちで内部の参照ができる
またクラスを継承したりモジュールをインクルードしても参照できるclass University < Students puts Students::Student puts Students::MedicalStudent::Student puts Students::StudentCouncil::Student puts Students::StudentCouncil::TeamMember::Student puts Students::StudentCouncil::Secretary::Student end Taro Hanako Jiro Sabu Yoshiko => nil #Studentsクラスをモジュールに変更してCramSchool(塾)クラスincludeしてみる module Students #⬅︎をモジュールに書き変え、Studentsモジュールを定義 Student = 'Taro' class MedicalStudent Student = 'Hanako' end module StudentCouncil Student = 'Jiro' class TeamMember Student = 'Sabu' end module Secretary Student = 'Yoshiko' end end end class CramSchool include Students #Studentsモジュールをインクルードする puts Students::Student puts Students::MedicalStudent::Student puts Students::StudentCouncil::Student puts Students::StudentCouncil::TeamMember::Student puts Students::StudentCouncil::Secretary::Student end Taro Hanako Jiro Sabu Yoshiko => nilというわけで定数でした。
また定数は基本的に変更しないという前提ですが、警告は出るものの変更ができます。
これについて変更できないように定数を定義する方法もありますが、それについてはいずれ書きたいと思います。最後に
これまでこれまでにローカル変数とインスタンス変数、クラス変数、グローバル変数について書き、そして今回は定数について書いてきました。
正直変数について舐めていました。
まだまだ分かってない部分もありそうです。しかし、変数一つ一つを学んだことで、メソッドやクラス、モジュールなども一緒に勉強ができました。
やはり種の部分から勉強するこで、幹や葉に繋がっていくので、最初の出てくるような部分こそしっかりやることが近道なのかと改めて気づかされました。
- 投稿日:2019-12-15T19:08:12+09:00
Classiエンジニア新人研修での学び方の学び
Classi Advent Calendar 2019の16日目の記事をご覧いただき,ありがとうございます!
2019年新卒エンジニア@willsmileです.自称コーヒー好きですが,コーヒーのことを語ると,教育と学習の方に“脱線”してしまい,「結局,教育のほうが趣味なんじゃないの」と最近自覚した者です.まえがき
本文では,Classiエンジニア新人研修を通じて,自分自身が「気づいた開発について新たな観点(の一例)」を語り,将来の自分,あるいは他の人にも参考できるように,「どうやって気づいたのか」を一段上の観点から言語化することを試みます.
本題
研修の概要
Classiエンジニア新人研修のカリキュラム担当者が業界で有名なRubyist@igaigaです.Classi入社前に@igaigaさんが著者である「ゼロからわかるRuby超入門」という本を持っています.それを読んでいて,Ruby言語の概念の厳密さと理解しやすさのバランスをよく取れている本という印象を残っています.@igaigaさんとの初対面の自己紹介で,「あの本の著者だ!すごい!」というびっくりから,研修の一日目が始まりました.
研修の題材と進み方について,4日目の記事で@yukoonoさんが紹介したように,万葉さんの公開カリキュラムにベースにして,各課題ステップにおいて,実装案の議論・検討,コーディング,コードレビューと修正といった「疑似体験」のサイクルが回します.このプロセスを通じて,わかったこと・気づいたことをメンターと話し合いながら,一緒に整理することで,これからの業務とつながるように,自分自身の理解を構築(再構築)していきます.これからは,実装案の議論・検討について,一個の例を通じて,自分自身の気づきを紹介します.
気づきの経緯
カリキュラムで定義されたタスク管理システムの要件の一つとして,「タスクに優先順位をつけたい」があります(以下の図がシステムの目標状態のイメージ).
カリキュラムのステップ6において,この要件を満たすために,モデル作成の段階では,タスクの優先順位をValueがStringのEnumで定義という実装案を考えていました.そう設計した理由として,現在の優先順位の仕様(例:低,中,高)を将来的に変更(例:低,やや低,中,やや高,高)があった時にも,対応しやすくなるからです.
class Task < ApplicationRecord ... enum priority: {low: "low", normal: "normal", high: "high"} ... endこの実装案を採用して,ステップ17までに進んだ時に,「優先順位を使って,タスクをソートできるようになる」といった仕様に対して,優先順位の順番を表す情報がシステムにはなかったため,ソートの実装ができなくなりました.それを解決するために,以下のようなコードを書いてみました.それは「そう!」,自分が書いたコードなのに,いま改めて読んでも驚きほどの複雑なもので,コードのリーダビリティーがかなり低くなりました.
class Task < ApplicationRecord ... enum priority: {low: "low", normal: "normal", high: "high"} PRIORITY_ORDERS = ['high', 'normal', 'low'] scope :priority, -> (order_of_priority) { if %w[asc desc].include?(order_of_priority) if order_of_priority == "desc" priority_orders = PRIORITY_ORDERS elsif order_of_priority == "asc" priority_orders = PRIORITY_ORDERS.reverse end order_by = ['CASE'] priority_orders.each_with_index do |priority, index| order_by << "WHEN priority='#{priority}' THEN #{index}" end order_by <<'END' order(order_by.join(' ')) else raise ActiveRecord::StatementInvalid end } endこの状況を整理して,最終的に,優先順位を”マスター化”する(タスクのAssociationで定義)という実装案にしたが,「なぜこうなったのか?」,「そもそも,このような仕様って,どう選択すれば良いのか」といった疑問を持ち,先輩たちの意見を聞きました.
学び方の学び
更に,この経験をメンターと一緒に振り返って,以下のようなかたちで,それぞれの実装案について,様々な意見をまとめていました.
表1.実装案のメリットとデメリットの比較
注:上の表には,コストという概念を使っているが,この例の場合.プロの人にとって,実装のコストの差があまりなく,適切ではないかもしれないが,ここの意図は「コストというよく見かける判断軸を登場させる」ことである.それを通じて,「正解って,実はないですね!」ということが初めて気づきました.つまり,それぞれの実装案はメリットとデメリットがあって,どれにするかが具体的な状況に合わせて選択することが重要です.例えば,自分が「最適だ」と思っていた実装案③でも,「何でもかんでもマスターデータにすると,管理するのは大変になっちゃうよ」というデメリットがありますし,最初に却下した実装案①でも,「後からソートしやすいね」というメリットがあります.問題の本質は,コスト,機能の拡張性といったことについてどう考えているのです.もちろん,それについて,ディベロッパーだけではなく,デザイナー,プロダクトマネージャーなどの異なる役割の方を含めて,一緒に議論して考えることは大切だと考えます.
このように,自身の「うまく考えていなかったことで,やっちゃった」経験から,様々な人の知恵を借りて,経験者の補助で振り返ることを通じて,プロのように考える「判断軸」(もちろん,これは氷山の一角にすぎない)を見つけていました.あとがき
その2ヶ月間の研修を通じて,@igaiga先生,メンターの@nagatashinyaさん,またたくさんのエンジニアの先輩たちのおかけで,今でも「生きている経験」をたくさん得ました.以下の研修卒業の時に,自分のTwitterのメッセージで,本日の話を締めます.
明日の投稿は,メンターの@nagatashinyaさんです.お楽しみに!
- 投稿日:2019-12-15T18:03:58+09:00
[Ruby on Rails] 他のテーブルを参照してidを取得し、外部キーとしてデータを挿入する
実現したい事
データを挿入するテーブルとは別のテーブルの情報を参照して、該当するデータのidを外部キーとして、元のテーブルにデータを追加する
前提
テーブルは生産量のデータを持つProductions(生産量)テーブルと製品の情報を持つItems(製品)テーブルがあります。Productionsモデルには、idとamount(生産量)、Itemsテーブルと紐づけるためのItem_idを持っています。Itemsテーブルにはidとcode(製品コード)とname(製品名)のカラムを持ちます。また、Goodsテーブルにはすでに、製品の情報が入っているものとします。
今しようとしている事
Productionsコントローラのcreateアクションで、Prodectionsテーブルに生産量と製品情報を追加します。
フォームではamount(生産量)とItemsテーブルのcodeの値を入力する形になっています(フォームでは、nameをItem_idとしています)。
この時、amount(生産量)はそのままProductionsモデルに追加できますが、item_idは外部キーなので当然そのまま入力できません。なので、入力された値からItemsモデルのcode情報を参照して、ItemsのidをProductionsのitem_idに追加します。方法
まずはフォームは簡単にこのようになっています。
index.html<%= form_for [@production], url: productions_path do |f| %> <%= f.text_field :amount %> <%= f.text_field :good_id %> <%= f.submit '追加' %> <% end %>そして、コントローラのcreateアクションを以下のように書きます。
controller/productions_controller.rbclass ProductionsController < ApplicationController def create item_id = Item.find_by(code: params[:production][:item_id]).id Production.create(production_params.merge(item_id: item_id)) end private def production_params params.require(:production).permit(:amount) end endpermitではamountだけを許可して、createアクション内で、find_byを用いて、item_id = Item.find_by(code: params[:production][:item_id]).idと記述し、Itemテーブルでデータを探して、item_idに入れ直しています。
そして、createアクションをする際に、item_idをmergeする事で外部キーとしてidの値を挿入することができました。注意点
フォームで、form_forを使っているので、値を渡すときはparams[:production][:item_id]と書きましょう。params[:item_id]と書くと、値が渡せず、エラーになります。
補足情報
Rails 5.0.7.2
ruby 2.5.1
- 投稿日:2019-12-15T17:19:49+09:00
Ruby & Rails チートシート
- 投稿日:2019-12-15T17:01:27+09:00
PHPerが久しぶりにRubyを書いて思ったこと
内容
- PHPer(Laravler)が感じたRuby(Rails)への感想
私の背景
- PHP成分が多い人生
- 初期だとFuelPHPとか触ってた
- ここ数年はLaravel使ってた
- Rubyは2011年頃に遊びで触ってた
- メタプログラミングRubyとか読んで興奮した記憶はある
- この当時のRailsを触って「ほーん、なるほど」、みたいに思った記憶もある
- 仕事では結局使わなかったけど、Project Euler をRubyで解いたりしてみてた
といった感じの背景です。
最近Railsで仕事することになったので、PHPer(というかLaraveler)が感じたRailsに対するギャップをいくつか書きたいと思います。
すでにRailsのプロジェクトがあり、そこにJOINした感じです。Autoloadがわからない
PHPのロード
require_once __DIR__ . '/path/to/file.php'Rubyのロード
require "path/to/file"ここまではなんの問題もなくわかるんですが、autoloadがわからない。
PHPの場合
namespace \Foo; use \Foo\Bar\Hoge; class Xxx extends Hoge { }みたいな感じで「利用するクラスのフルパスを書かなければならない」っていう制約があるので、面倒ではあるんだけど「
(appRoot)/Foo/Bar/Hoge.php
にあるんだな」っていうことが分かる。最近はほとんどPSR-4のautoloadが多いと思うので、PSR-4読めばいいと思う
参考: https://qiita.com/inouet/items/0208237629496070bbd4
Ruby(Rails)の場合
module Foo do class Xxx extends Hoge end endなんなん!?!?!?急に
Hoge
どこから出てきたん!?!?!?!?
Hogeって誰なの!?!?!?!ってすごく思った。
https://qiita.com/eggc/items/ae09d32df5d994522ca1
https://qiita.com/tachiba/items/5b293ca8e9430b9bd07e最新のバージョンでどう動くかはわからんですけど「Railsが頑張って探してるんや」って言われて納得。
納得すると同時に「あぁ〜〜〜パスを明示したいんじゃ〜〜〜」という気持ちになる。
カッコがない
PHPのこれが
piyo(bar(foo($x)));Rubyだとこうなる
piyo bar foo xいいんだけど、「どんだけカッコ嫌いなん???」って気持ちで溢れた。
foo
の引数が3つあるときはpiyo bar foo 1,2,3ってなって、なんか、分かるんだけど、なんやねんカッコ書きたいやんって気持ちになる
piyo(bar(foo(1,2,3))シックリくる〜〜!
って思うけどRobocopとか言うやつが「カッコ書くなや」みたいにいってくる。もうやだ。
blockに感じる違和感
[1,2,3].reject {|n| n > 2}.map {|n| n * 2}.map &:to_sこれと
[1,2,3].reject do |n| n > 2 end.map do |n| n * 2 end.map &:to_sこれって同じ感じだけど、なんで構文2つ分けたん。どっちかひとつでいいじゃんね!
end.map
とかにすごくなにか感じる。returnが無い
def hoge(x) if x > 5 return x * 3 end x endreturnつけたりつけなかったり!!!なんなの!!!
不要なのにreturnつけるとRobocopが「てめぇreturnつけてんじゃねぇよ!」とか言ってくるしもう。もう。
RubyDocが無い・・・?
最近のPHPは割と型がしっかり・・・とは言わないけど、少なくとも宣言する際の不便はかなりなくなってきた。
コンパイル用っていうよりは、プログラミングするときの手助けとしての型、という側面でとても便利。
PHP7より前の時代においてもPHPはPHPDoc
で何となく「型が何なのか明示しろや」文化が多少なりとあった。class Foo { /** * @param integer $x * @param integer $y * @return integer */ public function something($x, $y) { return $x * $y; } }みたいな感じで、「俺は型を宣言したいんじゃ!!!」という欲求に応えてくれた。
class Foo { public function something(int $x, int $y): int { return $x * $y; } }最近はこんな感じにスマートにかけてよかったね^^という気持ち
JSにもTypeScript以前にJSDocあったし、当然RubyにもRubyDocがあると思ってた。
そしたら、RubyDocはなんか違うやつだった。ドキュメント作るやつ。
ちゃうねん、そうじゃないねん。一応RDLってやつがあるみたいだけど、なんか違うというか・・・too muchというか・・・。
https://qiita.com/baban/items/0f782691b6e6e7213453
「Rubyの思想・文化的にそういう型宣言とは仲良くない」的なことだってのはわかってるんですが、「引数が何くるのかわからない状況ってのが怖くて、「え・・・string渡してくるバカがいたら死ぬの・・・?」みたいな気持ちになる。「string渡してくるやつがアホい」っていう思想なんだろうけど。
まとめ
なんか他にもいろいろあったと思うんですけど、思い出した範囲で感じた違和感をかきました。
別にRubyをディスってるわけじゃなくて「全然Rubyの世界観に馴染めてない俺」っていう感じです。
ワンライナー書いたりとかRspecのDSL感とか、書いててシックリくることも多いのですが、なんかやっぱ型システム周りの思想の違いは大きいなぁと思ってます。
ダックタイピングって誰得なのかがまだわかってないので、しっくりくる日を待ち望んでます。かしこ
- 投稿日:2019-12-15T16:49:12+09:00
AIでruby-signatureを生成する
はじめに
タイトルはお約束の釣りでただし、AIはAbstract Interpretationですって奴です。
ruby-signatureを抽象実行で生成しようってのがうまくいきそう(うまくいったとは言っていない)のでまとめてみます。
ruby-signatureって
ruby-signatureはRubyのライブラリや組み込みクラス・メソッド等の戻り値の型情報を記述するための記法と型のデータベースです。ここ にあります。
型情報は手で書くことを想定しているわけですが、自動的に生成出来れば色々便利でしょう。mmcについて
mmcは私が作っているRubyからCに変換するコンパイラです。中に抽象実行をおこなって型を推定するエンジンを持っています。抽象実行とかそういうのは書かないのでここを見てください。
Enumerableの型を解析する
おそらくとりあえず一番むつかしいのはEnumerableじゃないかと思われます。これはModuleでそのままではインスタンスを作ることもできません。また、どのクラスにincludeされるかで結果の型が変ってきます。そのため、型変数を含んだ型情報になります。
Enumerableの正解はこちらになります。型変数の導入
これまで実際の値を用意できないと抽象実行出来なかったのですが、型変数(TypeVariable)というクラスを用意します。このクラスは任意のメソッドを呼び出したら、戻り値として別の型変数を用意して、呼び出されたメソッド名とその戻りの型を記録しておきます。また、引数にブロックやProcオブジェクトがあった場合は、引数として別の型変数を渡してProcオブジェクトを抽象実行します。この辺のコードはこちらになります。method_missingとして実装しています。
define_inf_rule_method :missing, TypeVariable do |infer, inst, node, tup| rec = inst.inreg[0].type[tup][0] name = inst.inreg[1].type[tup][0] block = inst.inreg[3].type[tup][0] if block.is_a?(ProcType) then make_intype(infer, inst, node, tup) do |intype, argc| # intype = inst.inreg.map {|reg| reg.flush_type(tup)[tup] || []} intype[0] = [block.slf] ele = rec.sub_type_var[:ele] ? rec.sub_type_var[:ele] : TypeVarType.new(TypeVariable) rec.sub_type_var[:ele] = ele intype[1] = [ele] intype[2] = inst.inreg[3].type[tup] ntup = infer.typetupletab.get_tupple_id(intype, block, tup) irepssa = block.irep infer.inference_block(irepssa, intype, ntup, argc, block) inst.outreg[0].add_same irepssa.retreg inst.outreg[0].flush_type(tup, ntup) end end ret = rec.sub_type_var[:ret] || TypeVarType.new(TypeVariable) rec.using_method[name] = [inst.inreg.map {|r| r.type[tup]}, ret] rec.sub_type_var[:ret] = ret inst.outreg[0].add_type ret, tup nil end入力プログラム
Enumerableモジュールを型解析するための入力プログラムはこんな感じです
class TypeVariable include Enumerable end class Foo<TypeVariable end MTypeInf::inference_main { slf = Foo.new blk = lambda {|a| TypeVariable.new} slf.collect(&blk) blk = lambda {|a| TypeVariable.new} slf.any?(&blk) blk = lambda {|a| TypeVariable.new} slf.detect(TypeVariable.new, &blk) blk = lambda {|a| TypeVariable.new} slf.each_with_index(&blk) blk = lambda {|a| TypeVariable.new} slf.entries(&blk) }さすがにEnumerableは単独でどう頑張ってもインスタンス化できないので、型変数クラスにincludeしています。あとは、引数をすべて型変数クラスのインスタンスを渡してやればOKです。引数の数が分かれば自動生成も可能でしょう。
表示
解析結果を表示する場所です
if cls == TypeVariable then acls = cls.ancestors[1] mblk = clsobj.method.values[0] sreg = mblk.regtab[0] slftype = sreg.type.values[0][0] tvpara = slftype.sub_type_var.map {|name, ty| ty } interface = slftype.using_method.map {|name, ty| name.val } mess << "#{acls.class} #{acls} #{tvpara} #{interface} \n" elseTypeVariableがselfになっているメソッドはEnumerableなどのModuleです。この場合は型変数型でメソッド呼び出しの度に集めたメソッド情報とその戻り値の型情報をダンプしています。
結果
これでこんな感じの出力になります
Module Enumerable [TV1, TV4] [:each] Instance variables methodes collect (TV0, (Object, TV1, NilClass) -> TV2 ) -> Array<TV2> method_missing (TV0, Symbol(:each), (TV0, TV1, Proc<>, Proc<>) -> Array<TV1> ) -> Array<TV1>|TV4 any? (TV0, (Object, TV1, NilClass) -> TV5 ) -> TrueClass|FalseClass detect (TV0, TV1, (Object, TV1, NilClass) -> TV8 ) -> TV1 each_with_index (TV0, (Object, TV1, Fixnum, NilClass) -> TV10 (Object, TV1, Fixnum, NilClass) -> TV12 ) -> TV0 entries (TV0, ) -> Array<TV1>まだまだ不完全ですけど、結構いい線行っているのではいでしょうか? (ひいき目です)
お手本はこんな感じ
module Enumerable[unchecked out Elem, out Return]: _Each[Elem, Return] def `any?`: () -> bool | () { (Elem arg0) -> untyped } -> bool def collect: [U] () { (Elem arg0) -> U } -> ::Array[U] | () -> ::Enumerator[Elem, Return] def detect: (?Proc ifnone) { (Elem arg0) -> untyped } -> Elem? | (?Proc ifnone) -> ::Enumerator[Elem, Return] def each_with_index: () { (Elem arg0, Integer arg1) -> untyped } -> ::Enumerable[Elem, Return] | () -> ::Enumerator[[ Elem, Integer ], Return] def each_with_object: [U] (U arg0) { (Elem arg0, untyped arg1) -> untyped } -> U | [U] (U arg0) -> ::Enumerator[[ Elem, U ], Return]
- 投稿日:2019-12-15T15:34:45+09:00
Rubyのdefの基本について
勉強していて分からなくなったからRubyのdefについて、自分の振り返り用。
def はメソッドを定義するためにある。
def メソッド名 puts "hello" end helloメソッド名を書くだけでhelloが出力される.
簡単に言うとトンカツ定食を注文すると、ご飯、味噌汁、トンカツ、サラダ、小鉢が付いてくる様な感じ。
- 投稿日:2019-12-15T12:05:22+09:00
Rubyの出力メソッド p、puts、printsメソッドの違いについてまとめました。
Rubyの学習中に、p、puts、printsメソッドを学習しましたが、違いが分かりづらかったので備忘録として作成しました。
pメソッドの特徴
pメソッドの特徴は出力する値と共に型情報(文字列や数値型)を一緒に出力することです。
基本的にはデバッグ用に使われます。コード
1. p '1リットルの重さは'
2. p '1000'
3. p 'グラム程です'実行結果
1. "1リットルの重さは"
2. 1000
3. "グラム程です"文字列は""に囲まれて、数字はこのまま出力されます。
putsメソッドの特徴
■putsメソッド特徴は末尾に改行が入る形で、指定した値を出力することです。
コード
1. puts 'おはようございます'
2. puts '今日は'
3. puts '良い天気です'実行結果
puts おはようございます
puts 今日は
puts 良い天気ですprintメソッドの特徴
■printメソッドの特徴は改行を入れずに引数に指定した値を出力することです。
コード
1. print 'おはようございます'
2. print '今日は'
3. print '良い天気です'実行結果
1. おはようございます今日は良い天気です
- 投稿日:2019-12-15T11:20:18+09:00
ユーザー認証ってなんだ??(超初学者)
~投稿者のスペック~
・プログラミング歴2ヶ月
・ポンコツです
とあるRailsの教材を進め(掲示板作る系)途中で何回か???な事ありましたが、
前進あるのみでやっとのことでユーザー認証の仕組みまでたどり着けました。
ユーザー認証って??
システムやアプリケーションを正当に使用できるユーザーかどうかの確認などに使われる。たとえば、LAN上のサーバーにログインする場合、ユーザー名とパスワードでユーザー認証が行われる。〜コトバンク引用〜
はい、意味不明ですね。
ではユーザー認証例見てみましょう。
①Aさんが寒くなってきたのでダウンを買おうとECサイトのユルクロをみました。
②このウルトラヘビーダウンいいじゃん!(カートに入れる)
③Aさんが決済前に誤ってサイトを閉じる
④ダウンどこにあったっけ...とサイトを開くとカートのなかにダウンがある!ラッキー♪
これよくありますよね。
特になにも考えてなかったですけどよくよく考えると不思議ですよね。
調べてみると実はこれもユーザー認証なんですね。ではもう一度
①Aさんが寒くなってきたのでダウンを買おうとECサイトのユルクロをみました。
サーバーにユルクロのページを見せてとリクエスト、
サーバーは対象のウェブページと会員証をAさんのパソコンに返す
②このウルトラヘビーダウンいいじゃん!(カートに入れる)
③Aさんが決済前に誤ってサイトを閉じる
④ダウンどこにあったっけ...とサイトを開くとカートのなかにまだダウンがある!ラッキー♪
Aさんのパソコン上に保存されている会員証をサーバーが読み取り、さっきカートにダウン入れた人だと判断し表示内容を変更してくれる。
この会員証をCookieと呼び。
Cookieには会員ナンバー(user_id)とその他情報が付いている。
例で言うと
ブラウザは
会員ナンバー3000の人は買い物カゴにダウン×1を覚えてくれている感じですね。
会員証をサーバーに見せると上記の情報も反映してくれる感じです。
とりあえず今回は超大枠のみで細かいところまで理解できたらまた記事にしようと思います。
超初学者目線のエラー解決までの道のりや勉強のアウトプット用にQiitaを使わせてもらおうかなと思っています(^^)間違っているところやこっちのがわかりやすいよってあれば教えて下さい。(優しめで)
- 投稿日:2019-12-15T05:42:32+09:00
Deviseのサインイン後のリダイレクト先は5パターン
結論
以下、5パターン。上から順に優先度が高い。
usersというモデルにdeviseをマッピングさせた場合、
- ログイン前にアクセスしようとしたページ
- user_root_path(as: user_root_pathを指定したパターン)
- user_root_path(名前空間 + rootで設定したパターン)
- root_path(deviseマッピングとは関係ないroot_path)
- "/"
詳細(サインインの観点から)
「サインイン後のリダイレクト先を設定したい!!」
となると、after_sign_in_path_forメソッドを使いますが、
きちんと理解しておかないと、「なんかうまくいかないな」という状態になりがちです。
しっかりやっていきましょう。下記は、after_sign_in_path_forのソースです。
Git_Hub:after_sign_in_path_forメソッドdevise/lib/devise/controllers/helpers.rbdef after_sign_in_path_for(resource_or_scope) stored_location_for(resource_or_scope) || signed_in_root_path(resource_or_scope) endさすが有名gemです。メソッド名でなんとなくわかりますね!!
- stored_location_for(resource_or_scope)
未ログイン時にアクセスしようとしたページがあった場合、サインインした後に飛ばす。
- signed_in_root_path(resource_or_scope) いきなりログインページにアクセスした場合は、このメソッドの戻り値の場所に飛ばす。
ログイン後のリダイレクト先の理解には、
signed_in_root_path(resource_or_scope)メソッド
の理解がキーになりそうです。早速見てみましょう。
devise/lib/devise/controllers/helpers.rbdef signed_in_root_path(resource_or_scope) # モデルオブジェクトが渡されると、deviseでマッピングされたスコープ(usersとか)を返す。 # もしdeviseでマッピングしているモデルがadminという名前空間に属している場合、:admin_usersになる。 scope = Devise::Mapping.find_scope!(resource_or_scope) # deviseを使うモデルが複数ある場合、mappings(ハッシュ)のうち[scope]を取得し、そのrouter_nameを代入する。 # router_nameは、Devise::Mappingに対してoptionが渡されないとnilになるから基本nilっぽい。 router_name = Devise.mappings[scope].router_name home_path = "#{scope}_root_path" # router_nameは基本nilっぽいので、実行されない。selfはobjectクラスのmain。 context = router_name ? send(router_name) : self # contextに対してhome_pathを呼べるなら実行。trueはhome_pathがプライベートメソッドでも呼ぶよの意味。 if context.respond_to?(home_path, true) context.send(home_path) # contextに対して、root_pathを呼べるなら実行。 elsif context.respond_to?(:root_path) context.root_path # ただのroot_pathが呼べるなら実行。 elsif respond_to?(:root_path) root_path # トップページにリダイレクト else "/" end endふむふむ。
- context.respond_to?(home_path, true)
- context.respond_to?(:root_path)
の違いだけちょっとあいまいだが、
admin/users#indexとかの名前空間(admin)があるパターンは、
context.respond_to?(home_path, true)で実行される名前空間(admin)がないパターン(usersのみ)は
context.respond_to?(:root_path)で実行されると理解した。
home_path = "#{scope}_root_path"
の#{scope}は、
adminとかの名前空間 + deviseでマッピングしているモデル名となる。最終的にuser_root_pathとなるのは同じだが、内部的には意味が違うのではないだろうか。。。
下記は、user_root_pathで、home_pathと合致する。
devieでマッピングされたモデルに対して、home_pathはあるか?と聞いている。
context.respond_to?(home_path, true)
get to: "users#index", as: user_root_path下記は、user_root_pathだが、home_pathとは合致しない。
deviseでマッピングされたモデルに対して、root_pathはあるか?と聞いている。
context.respond_to?(:root_path)
namespace users do root to: "users#index" end認識違いあれば、ご指摘ください!!
参考URL
- 投稿日:2019-12-15T02:44:47+09:00
Rubyの変数の定義(グローバル変数)
前回までにローカル変数とインスタンス変数、クラス変数について書きました。
そして今回はグルーバル変数です。命名規則
- 先頭は$
- 英数字と_で構成されている
$student = 'Taro' => "Taro" $_STUDENT_2 = 'Jiro' => "Jiro" $3sutudent = 'Sabu' #$の隣に数字は定義できない syntax error, unexpected tIDENTIFIER, expecting end-of-input #また何も値を入れないとnilになる $student_5 => nilスコープ
どこからでも参照可能
$student = "Taro" puts $student Taro => nil 3.times do |n| puts $student end Taro Taro Taro => 3 def students $student end students #どこからでも参照可能 => "Taro" class Students puts $student end Taro #どこからでも参照可能 => nilグローバル変数はどこから参照できるので下記のようにもできる。
class Students $student = 'Taro' def greet puts "Hi,#{$student}" end def replace=(name) $student = name end end class MedicalStudents < Students end a = Students.new a.greet Hi,Taro => nil a.replace = 'Jiro' a.greet Hi,Jiro => nil b = MedicalStudents.new b.greet b.replace = 'Hanako' b.greet Hi,Hanako => nil a.greet Hi,Hanako => nil #さらに$studentはというと $student => "Hanako" #特にwarningが出ることなく呼び出せる。結論グローバル変数はどこでも呼び出せる