20191215のRubyに関する記事は14件です。

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にアクセスし、下の画像のような画面が表示されたらセットアップ完了です。

スクリーンショット 2019-12-14 21.17.12.png

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_attachmentsactive_storage_blobsというテーブルが作成されていれば、インストール成功です。

ActiveStorageは、初期設定ではDisk内(storage以下)にアップロードしたファイルデータを保存するようになっているため、amazon s3のストレージを使用する記述を追加します。

まずは使用するストレージの設定(今回だとamazon s3)を以下のファイルに追加してください。

concig/storage.yml
test:
  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 = :amazon

S3のための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.encdevelopment.keyというファイルを作成してくれます。

以下のように編集してください。

development.yml.enc
aws:
  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の関係を以下の図に表します。
スクリーンショット 2019-12-15 17.16.01.png

Userモデルに1つのファイルを紐づける場合

1ユーザーに1つの画像ファイルしか紐づかない場合、上記の図のNが1になります。まずはこのパターンを実装していきたいと思います。

app/models/user.rb
class User < ApplicationRecord
  has_one_attached :avatar
end

Userモデルにavatarという属性を追加しました。今回はavatarという命名にしましたが、用途に合わせて自由に指定することが出来ます。
ActiveStorageでは、ファイルデータを保存するためにそれぞれのテーブルに個別にカラムを用意しなくても上記の記述を追加するだけでactive_storage_attachmentsactive_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.rb
def 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>

では、早速rails sをして、投稿してみましょう!
てす.gif

Userモデルに複数のファイルを紐づける場合

次は複数投稿投稿出来るようにしてみますz。

app/models/user.rb
class 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.rb
def 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をして試してみましょう!
てす2.gif

image_processingを用いたリサイズ

画像投稿した後は、画面サイズに合わせて画像を取得したくなりますよね。Rails6からimage_processingというgemを使用することが推奨されているため、そちらを使用してリサイズを行っていきます。
Gemfileimage_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 %>

スクリーンショット 2019-12-15 21.08.51.png

簡単にリサイズされましたね。

おまけ

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?
=> false

ActiveStorageでは専用のValidationが用意されていないため、各自で作成しなくてはいけません。
簡単にContent_Typeを確認するValidationを作ってみました。

app/models/user.rb
class 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などを使用する際は少し工夫が必要になるかなと思いました。
次回書けたら書きます・・・。

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

【Ruby】ローカルでKaminari(Gem)をデバッグする環境を作る

環境構築

ソースを取ってくる

  • fork
  • git clone

bundle install

bundle install --path vendor/bundle

Rubyのバージョンを指定する

.ruby-version
2.6.5

Bundler.requireしてGemの処理を呼び出す

tmp/app.rb
require '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コントリビュートしていくぞい?

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

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 install

2.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.rb
class 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
end

Userモデルは以下のようになっています。
デフォルトではdatabase_authenticatable、registerable、recoverable、rememberable、trackable、validatableが使えるようになっています。

app/models/user.rb
class 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.rb
class 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

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

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

というわけで定数でした。

また定数は基本的に変更しないという前提ですが、警告は出るものの変更ができます。
これについて変更できないように定数を定義する方法もありますが、それについてはいずれ書きたいと思います。

最後に

これまでこれまでにローカル変数インスタンス変数クラス変数グローバル変数について書き、そして今回は定数について書いてきました。

正直変数について舐めていました。
まだまだ分かってない部分もありそうです。

しかし、変数一つ一つを学んだことで、メソッドやクラス、モジュールなども一緒に勉強ができました。

やはり種の部分から勉強するこで、幹や葉に繋がっていくので、最初の出てくるような部分こそしっかりやることが近道なのかと改めて気づかされました。

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

Classiエンジニア新人研修での学び方の学び

Classi Advent Calendar 2019の16日目の記事をご覧いただき,ありがとうございます!
2019年新卒エンジニア@willsmileです.自称コーヒー好きですが,コーヒーのことを語ると,教育と学習の方に“脱線”してしまい,「結局,教育のほうが趣味なんじゃないの」と最近自覚した者です.

まえがき

本文では,Classiエンジニア新人研修を通じて,自分自身が「気づいた開発について新たな観点(の一例)」を語り,将来の自分,あるいは他の人にも参考できるように,「どうやって気づいたのか」を一段上の観点から言語化することを試みます.

本題

研修の概要

Classiエンジニア新人研修のカリキュラム担当者が業界で有名なRubyist@igaigaです.Classi入社前に@igaigaさんが著者である「ゼロからわかるRuby超入門」という本を持っています.それを読んでいて,Ruby言語の概念の厳密さと理解しやすさのバランスをよく取れている本という印象を残っています.@igaigaさんとの初対面の自己紹介で,「あの本の著者だ!すごい!」というびっくりから,研修の一日目が始まりました.

研修の題材と進み方について,4日目の記事@yukoonoさんが紹介したように,万葉さんの公開カリキュラムにベースにして,各課題ステップにおいて,実装案の議論・検討,コーディング,コードレビューと修正といった「疑似体験」のサイクルが回します.このプロセスを通じて,わかったこと・気づいたことをメンターと話し合いながら,一緒に整理することで,これからの業務とつながるように,自分自身の理解を構築(再構築)していきます.これからは,実装案の議論・検討について,一個の例を通じて,自分自身の気づきを紹介します.

気づきの経緯

カリキュラムで定義されたタスク管理システムの要件の一つとして,「タスクに優先順位をつけたい」があります(以下の図がシステムの目標状態のイメージ).

Untitled.png
図1.タスクとその優先順位(イメージ)

カリキュラムのステップ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.実装案のメリットとデメリットの比較
Untitled 1.png
注:上の表には,コストという概念を使っているが,この例の場合.プロの人にとって,実装のコストの差があまりなく,適切ではないかもしれないが,ここの意図は「コストというよく見かける判断軸を登場させる」ことである.

それを通じて,「正解って,実はないですね!」ということが初めて気づきました.つまり,それぞれの実装案はメリットとデメリットがあって,どれにするかが具体的な状況に合わせて選択することが重要です.例えば,自分が「最適だ」と思っていた実装案③でも,「何でもかんでもマスターデータにすると,管理するのは大変になっちゃうよ」というデメリットがありますし,最初に却下した実装案①でも,「後からソートしやすいね」というメリットがあります.問題の本質は,コスト,機能の拡張性といったことについてどう考えているのです.もちろん,それについて,ディベロッパーだけではなく,デザイナー,プロダクトマネージャーなどの異なる役割の方を含めて,一緒に議論して考えることは大切だと考えます.
このように,自身の「うまく考えていなかったことで,やっちゃった」経験から,様々な人の知恵を借りて,経験者の補助で振り返ることを通じて,プロのように考える「判断軸」(もちろん,これは氷山の一角にすぎない)を見つけていました.

あとがき

その2ヶ月間の研修を通じて,@igaiga先生,メンターの@nagatashinyaさん,またたくさんのエンジニアの先輩たちのおかけで,今でも「生きている経験」をたくさん得ました.以下の研修卒業の時に,自分のTwitterのメッセージで,本日の話を締めます.
Twitter_message.png
明日の投稿は,メンターの@nagatashinyaさんです.お楽しみに!

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

[Ruby on Rails] 他のテーブルを参照してidを取得し、外部キーとしてデータを挿入する

実現したい事

データを挿入するテーブルとは別のテーブルの情報を参照して、該当するデータのidを外部キーとして、元のテーブルにデータを追加する
スクリーンショット 2019-12-15 17.32.24.png

前提

テーブルは生産量のデータを持つ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.rb
class 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
end

permitでは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

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

Ruby & Rails チートシート

Rubyの論理値の評価

  • A && B
    true(nil, flase以外)が現れたら終わりでそれが論理値になる
  • A || B
    false(nilかfalse)が現れたら終わりでそれが論理値になる

特定のtest methodのみを走らせる

$ rails test test/integration/users_login_test.rb   TESTOPTS="--name test_login_with_valid_information"

本番デプロイ先のURLを確認できる

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

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
end

returnつけたりつけなかったり!!!なんなの!!!

不要なのに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感とか、書いててシックリくることも多いのですが、なんかやっぱ型システム周りの思想の違いは大きいなぁと思ってます。
ダックタイピングって誰得なのかがまだわかってないので、しっくりくる日を待ち望んでます。

かしこ

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

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"
        else

TypeVariableが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]

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

Rubyのdefの基本について

勉強していて分からなくなったからRubyのdefについて、自分の振り返り用。

def はメソッドを定義するためにある。

def メソッド名
 puts "hello"
end

hello

メソッド名を書くだけでhelloが出力される.
簡単に言うとトンカツ定食を注文すると、ご飯、味噌汁、トンカツ、サラダ、小鉢が付いてくる様な感じ。

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

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. おはようございます今日は良い天気です

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

ユーザー認証ってなんだ??(超初学者)

~投稿者のスペック~

・プログラミング歴2ヶ月
・ポンコツです

 

とあるRailsの教材を進め(掲示板作る系)途中で何回か???な事ありましたが、
前進あるのみでやっとのことでユーザー認証の仕組みまでたどり着けました。

:gear:ユーザー認証って??

システムやアプリケーションを正当に使用できるユーザーかどうかの確認などに使われる。たとえば、LAN上のサーバーにログインする場合、ユーザー名とパスワードでユーザー認証が行われる。〜コトバンク引用〜 

:beginner:はい、意味不明ですね。

ではユーザー認証例見てみましょう。


①Aさんが寒くなってきたのでダウンを買おうとECサイトのユルクロをみました。

②このウルトラヘビーダウンいいじゃん!(カートに入れる)

③Aさんが決済前に誤ってサイトを閉じる

④ダウンどこにあったっけ...とサイトを開くとカートのなかにダウンがある!ラッキー♪


これよくありますよね。
特になにも考えてなかったですけどよくよく考えると不思議ですよね。
調べてみると実はこれもユーザー認証なんですね。

ではもう一度


①Aさんが寒くなってきたのでダウンを買おうとECサイトのユルクロをみました。

サーバーにユルクロのページを見せてとリクエスト、
サーバーは対象のウェブページと会員証をAさんのパソコンに返す

②このウルトラヘビーダウンいいじゃん!(カートに入れる)
③Aさんが決済前に誤ってサイトを閉じる
④ダウンどこにあったっけ...とサイトを開くとカートのなかにまだダウンがある!ラッキー♪

Aさんのパソコン上に保存されている会員証をサーバーが読み取り、さっきカートにダウン入れた人だと判断し表示内容を変更してくれる。


この会員証をCookieと呼び。

Cookieには会員ナンバー(user_id)とその他情報が付いている。

例で言うと

ブラウザは
会員ナンバー3000の人は買い物カゴにダウン×1

を覚えてくれている感じですね。

会員証をサーバーに見せると上記の情報も反映してくれる感じです。

とりあえず今回は超大枠のみで細かいところまで理解できたらまた記事にしようと思います。


:beginner:
超初学者目線のエラー解決までの道のりや勉強のアウトプット用にQiitaを使わせてもらおうかなと思っています(^^)

間違っているところやこっちのがわかりやすいよってあれば教えて下さい。(優しめで)


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

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.rb
def 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.rb
def 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

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

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が出ることなく呼び出せる。

結論グローバル変数はどこでも呼び出せる

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