20210606のRailsに関する記事は23件です。

[Ruby on Rails] deviseを使ってログイン機能

1.Gemを入れる ターミナル gem 'devise' gem 'devise-i18n' gem 'devise-i18n-views' //上記を追加 $ bundle 2.deviseの設定 ターミナル $ rails g devise:install $ rails g devise User //deviseを用いてUserモデル作成 db/migrate/xxxxx_devise_create_users.rb def change create_table :users do |t| ## Database authenticatable t.string :email, null: false, default: "" t.string :encrypted_password, null: false, default: "" // ==== 省略 ===== // // ==== 追加したいカラム名(私の場合) ==== // t.string :last_name t.string :first_name t.integer :history_status, default: 0 t.integer :prefecture_code t.string :work t.string :profile_image_id t.text :introduction t.boolean :is_deleted, default: false // ==== ここまで ==== // t.timestamps null: false end // 必要な記述を終えたら... $ rails db:migrate デフォルトが英語表記なので、そこを変更していく。 3.ja.ymlを追加 まずは、デフォルトの:enを変更する。 config/application.rb module アプリケーション名 class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 5.2 //以下の部分を:en => :ja に変更する config.i18n.default_locale = :ja end end 次に日本語翻訳ファイルの作成を行う。 ターミナル $ rails g devise:views:locale ja このコマンドによってconfig/locales/devise.views.ja.ymlが作成される。 ファイルへの記述はこちらのgithubが良いと思われます。 これで日本語表記が完了するはずです。 4.おまけ 自分で作ったモデルも対応させたい!って時はこんな感じに作ってみましょう。 私は手動でconfig/locales/models.ja.ymlを作成。 config/locales/models.ja.yml ja: activerecord: attributes: user: last_name: 苗字 first_name: 名前 history_status: 教員歴 prefecture_code: 学校所在地 参考サイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【エラー】管理者画面(Active Admin)に新たに管理するモデルを追加する際に経験したエラー

はじめに 新たに管理するモデルを追加しようとした際に、エラーが出てしまった時の備忘録記事となります。 インストールの詳細は省きます。 同じ現象であれ?となった人の解決きっかけになれればと思います。 * ActiveAdmin 特有のエラーではありません。その他エラーの際にも参考になれば幸いです。    環境 macOS Bigsur Rails 6.1 Ruby 2.7 インストール gemfile に追記 gem 'activeadmin' gem 'devise' gem 'rails-i18n', '~> 6.0' gem 'devise-i18n' ターミナルで実行 bundle install エラー 【今回追加するモデル】 Movie uninitialized constant モデル名 (NameError) Did you mean? 初期化されていない定数があるときにエラーが発生 管理者画面で管理するデータを登録するテーブルがないときにエラーが発生 (例) uninitialized constant Movie(NameError) Did you mean? 下記を実行しテーブルを作成することで解決 rails g model User rails db:migrate conflict config/initializers/devise.rb Overwrite /Users/ユーザー名/アプリ名/config/initializers/devise.rb? (enter "h" for help) [Ynaqdhm] rails g devise:install を インストールしてから rails g active_admin:installをインストールしないとdevise.rbファイルがコンフリクトする [Ynaqdhm]の後に入力を求められるので、問題がなければ y を入力することで上書きできる。 PG::DuplicateTable: ERROR: テーブルが重複しているときに発生 エラー詳細 (例)movies が重複 rails db:migrate == 20210605042245 CreateMovies: migrating ===================================== -- create_table(:movies) rails aborted! StandardError: An error has occurred, this and all later migrations canceled: PG::DuplicateTable: ERROR: relation "movies" already exists データベースの重複しているテーブルを削除することで解決 drop table テーブル名; rails db:migrate (例) movies テーブルを削除 drop table movies; rails db:migrate
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails 6.0.3 のインストール時に出た、minemagicとRails is not currently installed on this system.の解決法

railsチュートリアルの第6版をゲットして、Railsをインストールしようとしたら、2回ほど詰まったので、どう解決したかを書いていきます。 1. 開発環境 OS:Mac BigSur ver11.4 rbenv:1.1.2 ruby:3.0.1 shell:zsh 2. minemagic エラーの内容 railsチュートリアルに記載のある、rails 6.0.3を入れるために gem install rails -v 6.0.3 をしたところ、以下のエラーが出ました ERROR: Error installing rails: ERROR: Failed to build gem native extension. current directory: /Users/shiorin/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/mimemagic-0.3.10/ext/mimemagic /Users/shiorin/.rbenv/versions/3.0.1/bin/ruby -I/Users/shiorin/.rbenv/versions/3.0.1/lib/ruby/3.0.0 -rrubygems /Users/shiorin/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/rake-13.0.3/exe/rake RUBYARCHDIR\=/Users/shiorin/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/extensions/x86_64-darwin-20/3.0.0/mimemagic-0.3.10 RUBYLIBDIR\=/Users/shiorin/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/extensions/x86_64-darwin-20/3.0.0/mimemagic-0.3.10 rake aborted! Could not find MIME type database in the following locations: ["/usr/local/share/mime/packages/freedesktop.org.xml", "/opt/homebrew/share/mime/packages/freedesktop.org.xml", "/opt/local/share/mime/packages/freedesktop.org.xml", "/usr/share/mime/packages/freedesktop.org.xml"] minemagic-0.3.10で何か問題が起きているっぽい... 詳しく調べてみたところ、ライセンス関連の問題が起こっていることによる失敗のようでした。 Railsの一部であるactivestorageが依存しているmimemagic gemが、ライセンス関連の問題でrubygems.orgから取り下げられました。これにより、mimemagic <= 0.3.5に依存しているRailsアプリがbundle installに失敗するようになりました。 ( mimemagicの最新動向 - HackMD より引用) 解決した方法 HackMDやエラーメッセージをみたところ、shared-mime-infoを入れたら解消されるとのことだったので、インストール $ brew install shared-mime-info ただその時に、xcode-select --installをしなくてはいけないというエラーが出たので、実行したところサーバから入手できないとの表示が... この現象はmacのバージョンが古いのが原因だったので、Big Surにアップデートしたところ、無事にshared-mime-info を入れることができました 3. Rails is not currently installed on this system. エラーの内容 shared-mime-infoが無事にインストールできたので、改めてrails 6.0.3を入れてみたところ $ gem install rails -v 6.0.3 ... Successfully installed nio4r-2.5.7 Successfully installed actioncable-6.0.3 Successfully installed rails-6.0.3 17 gems installed うまくいっていそうにも関わらず、sudoをつけて再インストールしてみても、railsコマンドがないというエラーが出続けました... $ rails -v Rails is not currently installed on this system. To get the latest version, simply type: $ sudo gem install rails You can then rerun your "rails" command. 解決した方法 sudoをつけてインストールしても、変わらずにrailsコマンドがないというエラーが出続ける... ということで、railsを参照している元がのPATHを確認してみました。 $ which -a rails /Users/shiorin/.rbenv/shims/rails /usr/bin/rails $ which rails /usr/bin/rails 私は、rbenvからrubyを入れていたので、やっぱりrailsの参照元が違うことが原因そう なので、railsがrbenvの方から参照するように、zsh_profileに以下を追加しました。 ~/.zsh_profile export PATH="$HOME/.rbenv/bin:$PATH" eval "$(rbenv init -)" するとrailsの参照元が、rbenvになったので 晴れて、railsがインストールされたことを確認できました $ rails -v Rails 6.0.3 これで、無事にRailsチュートリアル_第6版のスタートがきれそうです 参考記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails6】ER図とテーブル設計からmodelとmigrateを素早く作成する手順

はじめに Qiita初投稿です。 2021年3月から実際の開発案件に参加させて頂き、案件を通じて学んだ内容を投稿したいと思います。 modelやmigrateを作成する情報を多く目にしますが、実際にコードを書いて実装した際、作成手順で迷ったり、migrateの作成し直しを何度も行ったりした点を備忘録的にまとめました。 RubyonRails初学者の為、間違っている情報を記載している場合はご指摘いただけますと幸いです。 開発環境 macOS Catalina バージョン 10.15.7 Ruby 2.7.2 Rails 6.1.1 mysql Ver 8.0.23 modelとmigrateを作成するまでの流れ 開発において、要件定義やER図・テーブル設計は、より高度な知識や理解を必要とする作業だと思いますが、私の場合は基本設計が既にできあがった状態から参加させて頂きました。 そのため、ここではER図とテーブル設計が完成した状態から、素早く(約半日くらいで)modelとmigrateを作成し、seedsで動作確認を行うまでの流れをまとめたいと思います。 下記のER図とテーブル設計は簡略化したものですが、基本的な項目は網羅するよう記載したつもりです。 作業の流れ ER図 Userテーブル   ①テーブル情報整理   ②migrateとmodelを作成 Productテーブル   ①テーブル情報整理   ②migrateとmodelを作成 Contractテーブル   ①テーブル情報整理   ②migrateとmodelを作成 seedsでDBテストデータ作成 バリデーションが正しく機能するかテストする 正しく関連付け(アソシエーション)ができているかをテストする 補足 1. ER図 ※IE記法 ER図から、モデルの関係性とKeyの指定を把握 UserモデルとContractモデルは、1対0以上(1対多)の関係 ProductモデルとContractモデルは、1対0以上(1対多)の関係 PKやFKについては、テーブル設計と比較して一致確認 PK(PRIMARY KEY/主キー) UK(UNIQUE KEY/ユニーク制約キー) FK(FOREIGN KEY/外部キー) 2. Userテーブル カラム カラムの説明 キー index not null カラムの型 default 備考 id id PK ● ● bigint name ユーザー名   ⚪ ⚪ string     email Eメール UK ● ⚪ string 文字数を255文字までに制限するEmailアドレスのフォーマットを検証 password パスワード ⚪ string      ※ PK = Primary Key UK = Unique Key ※ ● 暗黙的に設定 ⚪ 明示的に設定する ①上記Userテーブルの情報整理(テーブル情報をコードに置き換えて整理) migrate model キー【PK】 暗黙的にindex,not null設定が入る キー【UK】 index: { unique: true } または add_index :users, :email, unique: true を書く  uniqueness: true index index: true を設定 または add_index を書く (PKは自動設定) not null null: false (PKとUKは自動設定) presence: true カラムの型 name:string  email:string password:string (PKはbigint型が自動で入る) 備考                  length: { maximum: 255 } format: { with: VALID_EMAIL_REGEX } VALID_EMAIL_REGEX = /\A[\w+-.]+@[a-z\d-]+(.[a-z\d-]+)*.[a-z]+\z/i.freeze 一意制約は、アプリ側(model)だけでよいのか、DB側(migrate)もかけるのか迷いましたが、両方にかけるのが正解 参考:Railsチュートリアル6章:「Active Recordはデータベースのレベルでは一意性を保証していないという問題」を参照 参考:Rails 一意性制約のかけ方 ②Userテーブルのmodelとmigrate作成 上記で整理した内容を実際にターミナルに入力 $ rails g model モデル名 カラム名:型 各カラムが作成されるところまではターミナルに入力し、追加設定はmodel、migrateに直接入力、と分けて作業するのがわかりやすいと思います $ rails g model User name:string email:string password:string Running via Spring preloader in process 78395 invoke active_record create db/migrate/2021xxxxxxxxxx_create_users.rb create app/models/user.rb invoke test_unit create test/models/user_test.rb create test/fixtures/users.yml modelとmigrateが作成される(テストはRSpecで書くのであれば、削除) model/user.rb class User < ApplicationRecord end migrate/2021xxxxxxxxxx_create_users.rb class CreateUsers < ActiveRecord::Migration[6.1] def change create_table :users do |t| t.string :name t.string :email t.string :password t.timestamps end end end テーブル条件をDB側(migrate)に追加する null: false add_index :users, :email, unique: true ※一意制約はindex: { unique: true }でもOK あとでschema.rbと見比べたりするのであれば、add_index:にunique: trueとしたほうが見やすい migrate/2021xxxxxxxxxx_create_users.rb class CreateUsers < ActiveRecord::Migration[6.1] def change create_table :users do |t| t.string :name, null: false t.string :email, null: false t.string :password, null: false t.timestamps end add_index :users, :name add_index :users, :email, unique: true end end 設定し忘れがないかschema.rbを確認 $ rails db:migrate db/schema.rb ActiveRecord::Schema.define(version: 2021_05_xx_xxxxxx) do create_table "users", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "name", null: false t.string "email", null: false t.string "password", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.index ["email"], name: "index_users_on_email", unique: true t.index ["name"], name: "index_users_on_name" end end テーブル条件をmodelに追加する presence: true uniqueness: true model/user.rb class User < ApplicationRecord validates :name, presence: true validates :email, presence: true, uniqueness: true validates :password, presence: true end Emailの255文字制限を追加 length: { maximum: 255 } 正規表現を定数に代入 VALID_EMAIL_REGEX = /\A[\w+-.]+@[a-z\d-]+(.[a-z\d-]+)*.[a-z]+\z/i.freeze フォーマット検証を追加 format: { with: VALID_EMAIL_REGEX } models/user.rb class User < ApplicationRecord VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i.freeze validates :name, presence: true validates :email, presence: true, uniqueness: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX } validates :password, presence: true end 関連付け(アソシエーション)を追加 UserとContractは1:0以上(1:多)なので、has_many :contract, dependent: :destroyを追加しておく ※dependent: :destroyはUserを削除した時、関連付けしている契約(Contract)も同時に削除する models/user.rb class User < ApplicationRecord has_many :contracts, dependent: :destroy VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i.freeze validates :name, presence: true validates :email, presence: true, uniqueness: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX } validates :password, presence: true end ここで、Userのmigrate、modelをすぐにテストする場合は、5. seedsでDBテストデータ作成を参考にテストデータを作成してください(この状態ではアソシエイトは動きません) 3. Productテーブル カラム カラムの説明 キー index not null カラムの型 default 備考 id id PK ● ● bigint product_name 商品名       ⚪ ⚪ string     product_price 商品の価格    ⚪ integer 0 ※ PK = Primary Key ※ ● 暗黙的に設定 ⚪ 明示的に設定する ①上記Productテーブルの情報整理(テーブル情報をコードに置き換えて整理) migrate model キー【PK】 暗黙的にindex,not null設定が入る index add_index を設定 または index: true を書く (PKは自動設定) not null null: false (PKは自動設定) presence: true カラムの型 product_name:string  product_price:integer (PKはbigint型が自動で入る) default default: 0 ②Productテーブルのmodelとmigrate作成 上記で整理した内容を実際にターミナルに入力 Productテーブル情報を$ rails g model モデル名 カラム名:型にあてはめる $ rails g model Product product_name:string product_price:integer Running via Spring preloader in process 85812 invoke active_record create db/migrate/2021xxxxxxxxxx_create_products.rb create app/models/product.rb invoke test_unit create test/models/product_test.rb create test/fixtures/products.yml models/product.rb class Product < ApplicationRecord end db/migrate/2021xxxxxxxxxx_create_products.rb class CreateProducts < ActiveRecord::Migration[6.1] def change create_table :products do |t| t.string :product_name t.integer :product_price t.timestamps end end end テーブル条件をDB側(migrate)に追加する null: false default: 0 add_index :products, :product_name migrate/2021xxxxxxxxxx_create_products.rb class CreateProducts < ActiveRecord::Migration[6.1] def change create_table :products do |t| t.string :product_name, null: false t.integer :product_price, null: false, default: 0 t.timestamps end add_index :products, :product_name end end 設定し忘れがないかschema.rbを確認 $ rails db:migrate db/schema.rb ActiveRecord::Schema.define(version: 2021_05_xx_xxxxxx) do create_table "products", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "product_name", null: false t.integer "product_price", default: 0, null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.index ["product_name"], name: "index_products_on_product_name" end create_table "users", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "name", null: false t.string "email", null: false t.string "password", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.index ["email"], name: "index_users_on_email", unique: true t.index ["name"], name: "index_users_on_name" end end テーブル条件をmodelに追加する presence: true models/product.rb class Product < ApplicationRecord validates :product_name, presence: true validates :product_price, presence: true end 関連付け(アソシエーション)を追加 ProductとContractは1:0以上(1:多)なので、has_many :contractを追加しておく 商品(Product)を削除した時に契約(Contract)を同時に削除する必要はないので、dependent: :destroyは不要 models/product.rb class Product < ApplicationRecord has_many :contracts validates :product_name, presence: true validates :product_price, presence: true end ここで、Productのmigrate、modelをすぐにテストする場合は、5. seedsでDBテストデータ作成を参考にテストデータを作成してください(この状態ではアソシエイトは動きません) 4. Contractテーブル カラム カラムの説明 キー index not null カラムの型 default 備考 id id PK ● ● bigint user_id ユーザーID FK ● ● bigint 外部キー(Userテーブルのidと紐付ける) product_id 商品ID FK ● ● bigint 外部キー(Productテーブルのidと紐付ける) product_name 商品名 ⚪ ⚪ string *Contract作成時、product_nameから転記 contract_money 契約金 ⚪ integer 0 contract_status 契約ステータス integer enum(reservation:0, confirm:1) ※ PK = Primary Key FK = Foreign Key ※ ● 暗黙的に設定 ⚪ 明示的に設定する ①上記Contractテーブルの情報整理(テーブル情報をコードに置き換えて整理) migrate model キー【PK】 暗黙的にindex,not null設定が入る キー【FK】 user:references product:references (暗黙的にindex,not null設定が入る) 詳細には、references とすると自動的に null: false と foreign_key: true が migrate に追記される  index: true は記載されないが、index設定が自動的に db/schema.rb に入る index add_index :contracts, :product_name を設定 または index: true を書く (PK,FKは自動設定) not null references で null: false 設定 presence: true を設定 カラムの型 product_name:string  contract_money:integer contract_status:integer (PKはbigint型が自動で入る) default default: 0 備考 enum contract_status: { reservation: 0, confirm: 1, } ②Contractテーブルのmodelとmigrate作成 上記で整理した内容を実際にターミナルに入力 Contractテーブル情報を$ rails g model モデル名 カラム名:型にあてはめる $ rails g model Contract user:references product:references product_name:string contract_money:integer contract_status:integer Running via Spring preloader in process 79699 invoke active_record create db/migrate/2021xxxxxxxxxx_create_contracts.rb create app/models/contract.rb invoke test_unit create test/models/contract_test.rb create test/fixtures/contracts.yml modelとmigrateが作成される(テストはRSpecで書くなら、削除) model/contract.rb class Contract < ApplicationRecord belongs_to :user belongs_to :product end migrate/2021xxxxxxxxxx_create_contracts.rb class CreateContracts < ActiveRecord::Migration[6.1] def change create_table :contracts do |t| t.references :user, null: false, foreign_key: true t.references :product, null: false, foreign_key: true t.string :product_name t.integer :contract_money t.integer :contract_status t.timestamps end end end テーブル条件をDB側(migrate)に追加 index add_index :contracts, :product_name not null設定 null: false default設定 default: 0 ついでに行も揃えると見やすい migrate/2021xxxxxxxxxx_create_contracts.rb class CreateContracts < ActiveRecord::Migration[6.1] def change create_table :contracts do |t| t.references :user, null: false, foreign_key: true t.references :product, null: false, foreign_key: true t.string :product_name, null: false t.integer :contract_money, null: false, default: 0 t.integer :contract_status t.timestamps end add_index :contracts, :product_name end end 設定漏れがないかschema.rbを確認 $ rails db:migrate db/schema.rb ActiveRecord::Schema.define(version: 2021_05_26_081531) do create_table "contracts", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.bigint "user_id", null: false t.bigint "product_id", null: false t.string "product_name", null: false t.integer "contract_money", default: 0, null: false t.integer "contract_status" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.index ["product_id"], name: "index_contracts_on_product_id" t.index ["product_name"], name: "index_contracts_on_product_name" t.index ["user_id"], name: "index_contracts_on_user_id" end create_table "products", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "product_name", null: false t.integer "product_price", default: 0, null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.index ["product_name"], name: "index_products_on_product_name" end create_table "users", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "name", null: false t.string "email", null: false t.string "password", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.index ["email"], name: "index_users_on_email", unique: true t.index ["name"], name: "index_users_on_name" end add_foreign_key "contracts", "products" add_foreign_key "contracts", "users" end 今回のテーブル設計にはありませんが、以下はよくある?設定  t.references :userの参照先テーブル名がカラム名とは違う場合 foreign_key: { to_table: :customers }と参照先を指定する migrate/2021xxxxxxxxxx_create_contracts.rb t.references :user, null: false, foreign_key: { to_table: :customers } テーブル条件をmodelに追加 presence: true 追加 enum追加 model/contract.rb class Contract < ApplicationRecord belongs_to :user belongs_to :product validates :user, presence: true validates :product, presence: true validates :product_name, presence: true validates :contract_money, presence: true validates :contract_status, presence: true enum contract_status: { reservation: 0, confirm: 1, } end 関連付け(アソシエーション)を追加 UserモデルとContractモデルの「1対0以上(1対多)」の関係と、ProductモデルとContractモデルの、「1対0以上(1対多)」の関係をmodelに反映 model/contract.rb class Contract < ApplicationRecord belongs_to :user belongs_to :product validates :user, presence: true validates :product, presence: true validates :product_name, presence: true validates :contract_money, presence: true validates :contract_status, presence: true enum contract_status: { reservation: 0, confirm: 1, } end models/user.rb class User < ApplicationRecord has_many :contracts, dependent: :destroy VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i.freeze validates :name, presence: true validates :email, presence: true, uniqueness: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX } validates :password, presence: true end model/product.rb class Product < ApplicationRecord has_many :contracts validates :product_name, presence: true validates :product_price, presence: true end これで、関連付けされた別テーブルのデータが使用できるようになるので、次項以下で確認します migrateとmodelの作成作業はひとまず完了です 5. seedsでDBテストデータ作成 db/seeds.rbでテストデータを作成する目的は、以下二点です ①DBが正常に保存できるかを確認する ②テーブル設計どおりにモデルのバリデーション等が機能するか確認する db/seeds.rb user1 = User.create!(name: "akagi", email: "akagi@example.com", password: "password") user2 = User.create!(name: "uozumi", email: "uozumi@example.com", password: "password") user3 = User.create!(name: "anzai", email: "anzai@example.com", password: "password") puts "userデータの投入に成功しました!" product1 = Product.create!(product_name: "サッカーボール", product_price: 10000 ) product2 = Product.create!(product_name: "バスケットボール", product_price: 15000) puts "productデータの投入に成功しました!" Contract.create!(user_id: user1.id, product_id: product1.id, product_name: product1.product_name, contract_money: 500000, contract_status: 0) Contract.create!(user_id: user2.id, product_id: product2.id, product_name: product2.product_name, contract_money: 1000000, contract_status: 1) Contract.create!(user_id: user1.id, product_id: product2.id, product_name: product2.product_name, contract_money: 500000, contract_status: 0) puts "Contractデータの投入に成功しました!" テストデータを作成するコマンドを入力 $ rails db:seed ※一度、seedデータを作成し終えている場合は、$ rails db:resetを実行 うまく保存できない(エラーが出る)場合は、binding.pryをseeds.rbに入れてどこがうまく行かないのか確認しながら修正する 6.バリデーションが正しく機能するかテストする 全てのバリデーションテストはRSpecのモデルスペックで実装することを想定し、ここでは、主な項目だけをテストしていきます。 ターミナル $ rails console # user4を正常なデータとして作成することができる(seeds.rbが動作する時点で確認済ですが…) > user4 = User.create(name: "mitui", email: "mitui@example.com", password: "password") # Emailの正規表現が機能しているか確認 > user4.update(email: "mitui@..example.com") => false # エラーメッセージも確認する > user4.errors.full_messages => ["Email is invalid"] # 文字制限が機能している確認 > user4.update(email:" #{"m" * 255}@example.com") => false > user4.errors.full_messages => ["Email is too long (maximum is 255 characters)", "Email is invalid"] # uniqueが機能しているか確認 > user4.update(email: "akagi@example.com") => false > user4.errors.full_messages => ["Email has already been taken"] 7.正しく関連付け(association)ができているかをテストする ターミナル $ rails console # Userが正常に保存されているか確認 > user1, user2, user3 = User.all > user1.name => "akagi" # Userモデルの has_many :contracts, dependent: :destroy が関連付けできているか確認 > user1.contracts # user1のcontractsを全て取得する > user1.contracts.all # user1のcontractsの1つ目のproduct_nameを取得する > user1.contracts[0].product_name # productモデルの has_many :contracts が関連付けできているか確認 > product2 = Product.second # productオブジェクトから、contractと関連付けされたuserを取得する > product2.contracts[1].user # user3.contracts.create!でuser3とproduct2のContractのインスタンスを作成して保存 > user3.contracts.create!(product_id: product2.id, product_name: product2.product_name, contract_money: 750000, contract_status: 0) # 作成保存できているか、Contractを確認 > Contract.last これで、DB周辺のmodelとmigrateの確認も完了 あとは必要な機能要件に合わせて、viewsとcontrollersの作成に入る 参考:Railsガイド Active Record 8.補足 途中でカラムを追加したくなった時の手順で迷ったので補足 例えば、db/migrate/2021xxxxxxxxxx_create_contracts.rbに contract_titleをうっかり作成し忘れていた事に途中で気づいたような時、AddColumnを多用するのはよくない(自論)と思いますので、githubのmasterにマージする前後で作業を分けました githubにブランチをpushしてmasterにマージする前 ターミナル # どこまで戻すか確認する $ rails db:migrate:status database: qiita_posting Status Migration ID Migration Name -------------------------------------------------- up 20210526055004 Create products up 20210526065824 Create users up 20210526081531 Create contracts # 必要なところまでdbを戻す(以下は一つだけ戻す書き方) $ rails db:rollback # もし、2つ戻すなら、rails db:rollback STEP=2 と書く db/migrateにカラム(contract_title)を追加する db/migrate/2021xxxxxxxxxx_create_contracts.rb class CreateContracts < ActiveRecord::Migration[6.1] def change create_table :contracts do |t| t.references :user, null: false, foreign_key: true t.references :product, null: false, foreign_key: true t.string :product_name, null: false t.string :contract_title, null: false t.integer :contract_money, null: false, default: 0 t.integer :contract_status t.timestamps end add_index :contracts, :product_name end end rails db:migrateを再度実行する githubにブランチをpushしてmasterにマージした後のカラム追加 $ rails g migration AddColumnToContracts contract_title:string # 変更内容 def change add_column :contracts, :contract_title, :string, null: false end end 参考:【Rails】 マイグレーションファイルを徹底解説! 数ある追加・修正パターンの一部ではありますが、備忘録的にまとめてみました。 また、記載している内容の間違いやよりよい手順等ありましたら、ご指摘頂けますと幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsチュートリアル 第6版 振り返り 第14章

はじめに このまとめ記事はRailsチュートリアルを1周終えた私が1周目で分からなかった所や記憶に残したい箇所のみをピックアップして記述しています。完全解説記事ではないので注意して下さい。 私と同じく2周目の方、たまに復習したいなと振り返りを行う方等におすすめです。 とは言いつつも、9章あたりから難しくなったのでこの章もほぼ全て書き残しています。 ユーザーをフォローする(14章) この章では、他のユーザーをフォロー(およびフォロー解除)できるソーシャルな仕組みの追加と、フォローしているユーザーの投稿をステータスフィードに表示する機能を追加する。 まずは、ユーザー間の関係性をどうモデリングするかについて学び、その後にモデリング結果に対応するWebインターフェースを実装していく。 このとき、Webインターフェースの例としてAjaxについても紹介する。 この最終章では、本書の中で最も難易度の高い手法をいくつか使っいるそう。 その中には、ステータスフィード作成のためにRuby/SQLを「だます」テクニックも含まれいる。 この章の例全体にわたって、これまでよりも複雑なデータモデルを使う。 Relationshipモデル(14.1) ユーザーをフォローする機能を実装する第一歩は、データモデルを構成すること。 ただし、これは見た目ほど単純ではない。 素朴に考えれば、has_many(1対多)の関連付けを用いて「1人のユーザーが複数のユーザーをhas_manyとしてフォローし、1人のユーザーに複数のフォロワーがいることをhas_manyで表す」といった方法で実装が可能に思える。 しかし後ほど説明があるようだが、この方法ではたちまち壁に突き当たってしまう。 これを解決するためのhas_many throughについてもこの後で説明する。 データモデルの問題(および解決策)(14.1.1) ユーザーをフォローするデータモデル構成のための第一歩として、典型的な場合を検討してみる。 あるユーザーが、別のユーザーをフォローしているところを考える。 具体例を挙げると、CalvinはHobbesをフォローしている。 これを逆から見れば、HobbesはCalvinからフォローされていることになる。 CalvinはHobbesから見ればフォロワー(follower)であり、CalvinがHobbesをフォローした(followed)ことになる。 Railsにおけるデフォルトの複数形の慣習に従えば、あるユーザーをフォローしているすべてのユーザーの集合はfollowersとなり、user.followersはそれらのユーザーの配列を表すことになる。 だが残念なことに、この名前付けは逆向きではうまくいかない。 あるユーザーがフォローしているすべてのユーザーの集合は、このままではfollowedsとなってしまい、英語の文法からも外れるうえに非常に見苦しいものになってしまう。 そこで、Twitterの慣習にならい、本チュートリアルではfollowingという呼称を採用。 したがって、あるユーザーがフォローしているすべてのユーザーの集合はcalvin.followingとなる。 これにより、followingテーブルと has_many関連付けを使って、フォローしているユーザーのモデリングが可能。 user.followingはユーザーの集合でなければならないため、followingテーブルのそれぞれの行は、followed_idで識別可能なユーザーでなければならない。 さらに、それぞれの行はユーザーなため、これらのユーザーに名前やパスワードなどの属性も追加する必要がある。 引用:Railsチュートリアル14.1.1図14.6 フォローしているユーザーの素朴な実装例 上記の実装例では、非常に無駄が多い。 各行には、フォローしているユーザーのidのみならず、名前やメールアドレスまで存在している。 これらはいずれもusersテーブルに既にあるものばかり。 さらによくないことに、followersの方をモデリングするときにも、同じぐらい無駄の多いfollowersテーブルを別に作成しなければならなくなってしまう。 結論としては、このデータモデルはメンテナンスの観点から見て悪夢。 ユーザー名を変更するたびに、usersテーブルのそのレコードだけでなく、followingテーブルとfollowersテーブルの両方について、そのユーザーを含むすべての行を更新しなければならなくなるから。 この問題の根本は、必要な抽象化を行なっていないこと。 正しいモデルを見つけ出す方法の1つは、Webアプリケーションにおけるfollowingの動作をどのように実装するかをじっくり考えること。 7章では、RESTアーキテクチャは、作成されたり削除されたりするリソースに関連していると学んだ。 ここから、2つの疑問が生まれる。 ①あるユーザーが別のユーザーをフォローするとき、何が作成されるのか。 ②あるユーザーが別のユーザーをフォロー解除するとき、何が削除されるのか。 この点を踏まえて考えると、この場合アプリケーションによって作成または削除されるのは、つまるところ2人のユーザーの「関係(リレーションシップ)」であることがわかる。 つまり、1人のユーザーは1対多の関係を持つことができ、さらにユーザーはリレーションシップを経由して多くのfollowing(またはfollowers)と関係を持つことができるということ。 このデータモデルには他にも解決しなくてはいけない問題がある。 Facebookのような友好関係(Friendships)では本質的に左右対称のデータモデルが成り立つが、Twitterのようなフォロー関係では左右非対称の性質がある。 すなわち、CalvinはHobbesをフォローしていても、HobbesはCalvinをフォローしていないといった関係性が成り立つ。 このような左右非対称な関係性を見分けるために、それぞれを能動的関係(Active Relationship)と受動的関係(Passive Relationship)1と呼ぶことにする。 例えば先ほどの事例のような、CalvinがHobbesをフォローしているが、HobbesはCalvinをフォローしていない場合では、CalvinはHobbesに対して「能動的関係」を持っている。 逆に、HobbesはCalvinに対して「受動的関係」を持っている。 まずは、フォローしているユーザーを生成するために、能動的関係に焦点を当てていく。 先ほどの図 14.6は実装のヒントになった。 フォローしているユーザーはfollowed_idがあれば識別することができるので、先ほどのfollowingテーブルをactive_relationshipsテーブルと見立ててみる。 ただしユーザー情報は無駄なので、ユーザーid以外の情報は削除。 そして、followed_idを通して、usersテーブルのフォローされているユーザーを見つけるようにする。 このデータモデルを模式図にすると、以下のようになる。 引用:Railsチュートリアル14.1.1図14.7 能動的関係をとおしてフォローしているユーザーを取得する模式図 能動的関係も受動的関係も、最終的にはデータベースの同じテーブルを使うことになる。 したがって、テーブル名にはこの「関係」を表す「relationships」を使用する。 モデル名はRailsの慣習にならって、Relationshipとする。 また、1つのrelationshipsテーブルを使って2つのモデル(能動的関係と受動的関係)をシミュレートする方法については、後ほどの解説となる。 作成したRelationshipデータモデルは以下の通り。 引用:Railsチュートリアル14.1.1図14.8 このデータモデルを実装するために、まずは次のようにマイグレーションを生成。 $ rails generate model Relationship follower_id:integer followed_id:integer このリレーションシップは今後follower_idとfollowed_idで頻繁に検索することになるため、それぞれのカラムにインデックスを追加。 db/migrate/[timestamp]_create_relationships.rb class CreateRelationships < ActiveRecord::Migration[6.0] def change create_table :relationships do |t| t.integer :follower_id t.integer :followed_id t.timestamps end add_index :relationships, :follower_id add_index :relationships, :followed_id add_index :relationships, [:follower_id, :followed_id], unique: true end end 上記のコードでは、複合キーインデックスという行もあることに注目。 これは、follower_idとfollowed_idの組み合わせが必ずユニークであることを保証する仕組み。 これにより、あるユーザーが同じユーザーを2回以上フォローすることを防ぐ。 もちろん、このような重複(2回以上フォローすること)が起きないよう、インターフェイス側の実装でも注意を払う。 しかし、ユーザーが何らかの方法で(例えばcurlなどのコマンドラインツールを使って)Relationshipのデータを操作するようなことも起こり得る。 そのような場合でも、一意なインデックスを追加していれば、エラーを発生させて重複を防ぐことが可能。 最後にマイグレーションしてこの項は終了。 rails db:migrate User/Relationshipの関連付け(14.1.2) フォローしているユーザーとフォロワーを実装する前に、UserとRelationshipの関連付けを行う。 1人のユーザーにはhas_many(1対多)のリレーションシップがあり、このリレーションシップは2人のユーザーの間の関係なので、フォローしているユーザーとフォロワーの両方に属す(belongs_to)。 マイクロポストのときと同様、次のようなユーザー関連付けのコードを使って新しいリレーションシップを作成。 user.active_relationships.build(followed_id: ...) この時点で、アプリケーションコードは、マイクロポストの時のようになるのではないかと予測したかもしれないが、2つの大きな違いがある。 まずは1つ目の違いについて。 以前、ユーザーとマイクロポストの関連付けをしたときは、次のように書いた。 class User < ApplicationRecord has_many :microposts . . . end 引数の:micropostsシンボルから、Railsはこれに対応するMicropostモデルを探し出し、見つけることができた。 しかし今回のケースで同じように書くと、 has_many :active_relationships となってしまい、(ActiveRelationshipモデルを探してしまい)Relationshipモデルを見つけることができない。 このため、今回のケースでは、Railsに探して欲しいモデルのクラス名を明示的に伝える必要がある。 2つ目の違いは、先ほどの逆のケースについて。 以前はMicropostモデルで、 class Micropost < ApplicationRecord belongs_to :user . . . end このように書いた。 micropostsテーブルにはuser_id属性があるため、これを辿って対応する所有者(ユーザー)を特定することが可能だった。 データベースの2つのテーブルを繋ぐとき、このようなidは外部キー(foreign key)と呼ばれている。 すなわち、Userモデルに繋げる外部キーが、Micropostモデルのuser_id属性ということ。 の外部キーの名前を使って、Railsは関連付けの推測をしていしている。 具体的には、Railsはデフォルトでは外部キーの名前を<class>_idといったパターンとして理解し、 <class>に当たる部分からクラス名(正確には小文字に変換されたクラス名)を推測する。] ただし、先ほどはユーザーを例として扱ったが、今回のケースではフォローしているユーザーをfollower_idという外部キーを使って特定しなくてはいけない。 また、followerというクラス名は存在しないので、ここでもRailsに正しいクラス名を伝える必要が発生する。 先ほどの説明をコードにまとめると、UserとRelationshipの関連付けは以下の通りとなる。 app/models/user.rb class User < ApplicationRecord has_many :microposts, dependent: :destroy has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy . . . end 上記では、ユーザーを削除したら、ユーザーのリレーションシップも同時に削除される必要があるため、関連付けにdependent: :destroyも追加している。 app/models/relationship.rb class Relationship < ApplicationRecord belongs_to :follower, class_name: "User" belongs_to :followed, class_name: "User" end この2つを定義すると、関連付けによって多くのメソッドが使えるようになった。 今回使えるようになったメソッドは以下の通り。 メソッド 用途 active_relationship.follower フォロワーを返します active_relationship.followed フォローしているユーザーを返します user.active_relationships.create(followed_id: other_user.id) userと紐付けて能動的関係を作成/登録する user.active_relationships.create!(followed_id: other_user.id) userを紐付けて能動的関係を作成/登録する(失敗時にエラーを出力) user.active_relationships.build(followed_id: other_user.id) userと紐付けた新しいRelationshipオブジェクトを返す Railsチュートリアル14.1.2表14.1 Relationshipのバリデーション(14.1.3) 先に進む前に、Relationshipモデルの検証を追加して完全なものにしておく。 テストコードとアプリケーションコードは素直な作りになっている。 ただし、User用のfixtureファイルと同じように、生成されたRelationship用のfixtureでは、マイグレーションで制約させた一意性を満たすことができない。 ということで、ユーザーのときと同じで今の時点では生成されたRelationship用のfixtureファイルも空にしておく。 test/models/relationship_test.rb require 'test_helper' class RelationshipTest < ActiveSupport::TestCase def setup @relationship = Relationship.new(follower_id: users(:michael).id, followed_id: users(:archer).id) end test "should be valid" do assert @relationship.valid? end test "should require a follower_id" do @relationship.follower_id = nil assert_not @relationship.valid? end test "should require a followed_id" do @relationship.followed_id = nil assert_not @relationship.valid? end end app/models/relationship.rb class Relationship < ApplicationRecord belongs_to :follower, class_name: "User" belongs_to :followed, class_name: "User" validates :follower_id, presence: true validates :followed_id, presence: true end test/fixtures/relationships.yml # 空にする フォローしているユーザー(14.1.4) いよいよRelationshipの関連付けの核心、followingとfollowersに取りかかる。 今回はhas_many throughを使用。 1人のユーザーにはいくつもの「フォローする」「フォローされる」といった関係性がある。 こういった関係性を「多対多」と呼ぶ。 デフォルトのhas_many throughという関連付けでは、Railsはモデル名(単数形)に対応する外部キーを探す。 has_many :followeds, through: :active_relationships つまり上記のコードの場合、 Railsは「followeds」というシンボル名を見て、これを「followed」という単数形に変え、 relationshipsテーブルのfollowed_idを使って対象のユーザーを取得してくる。 しかし、user.followedsという名前は英語として不適切で、代わりに、user.followingという名前を使用したい。 そのためには、Railsのデフォルトを上書きする必要がある。 ここでは:sourceパラメーターを使用して following配列の元はfollowed idの集合である」ということを明示的にRailsに伝える。 app/models/user.rb class User < ApplicationRecord has_many :microposts, dependent: :destroy has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy has_many :following, through: :active_relationships, source: :followed . . . end 今回設定した関連付けによってフォローしているユーザーを配列の様に扱えるようになった。 例えば、include?メソッドを使ってフォローしているユーザーの集合を調べてみたり、関連付けを通してオブジェクトを探しだせたりする。 user.following.include?(other_user) user.following.find(other_user) また、followingで取得したオブジェクトは、配列のように要素を追加したり削除したりすることも可能。 user.following << other_user user.following.delete(other_user) followingメソッドで配列のように扱えるだけでも便利だが、Railsは単純な配列ではなく、もっと賢くこの集合を扱っている。 例えば次のようなコードでは、 following.include?(other_user) フォローしている全てのユーザーをデータベースから取得し、その集合に対してinclude?メソッドを実行しているように見えますが、しかし実際にはデータベースの中で直接比較をするように配慮している。 次に、followingで取得した集合をより簡単に取り扱うために、followやunfollowといった便利メソッドを追加していく。 これらのメソッドは、例えばuser.follow(other_user)といった具合に使用する。 さらに、これに関連するfollowing?論理値メソッドも追加し、あるユーザーが誰かをフォローしているかどうかを確認できるようにする。 今回は、こういったメソッドはテストから先に書いていく。 というのも、Webインターフェイスなどで便利メソッドを使うのはまだ先なので、すぐに使える場面がなく、実装した手応えを得にくいから。 一方で、Userモデルに対するテストを書くのは簡単かつ今すぐできる。 そのテストの中で、これらのメソッドを使っていく。 手順は以下の通り。 ①following?メソッドであるユーザーをまだフォローしていないことを確認。 ②followメソッドを使ってそのユーザーをフォローする。 ③following?メソッドを使ってフォロー中になったことを確認。 ④最後にunfollowメソッドでフォロー解除できたことを確認。 test/models/user_test.rb test "should follow and unfollow a user" do michael = users(:michael) archer = users(:archer) assert_not michael.following?(archer) michael.follow(archer) assert michael.following?(archer) michael.unfollow(archer) assert_not michael.following?(archer) end 上記を参考にしながら、followingによる関連付けを使ってfollow、unfollow、following?メソッドを実装する。 このとき、可能な限りself(user自身を表すオブジェクト)を省略している点に注目。 app/models/user.rb # ユーザーをフォローする def follow(other_user) following << other_user end # ユーザーをフォロー解除する def unfollow(other_user) active_relationships.find_by(followed_id: other_user.id).destroy end # 現在のユーザーがフォローしてたらtrueを返す def following?(other_user) following.include?(other_user) end これでテストが成功すればこの項は終了。 フォロワー(14.1.5) リレーションシップというパズルの最後の一片は、user.followersメソッドを追加すること。 これはuser.followingメソッドと対となる。 フォロワーの配列を展開するために必要な情報は、relationshipsテーブルに既に存在している。 つまり、active_relationshipsのテーブルを再利用することが可能。 実際、follower_idとfollowed_idを入れ替えるだけで、フォロワーについてもフォローする場合と全く同じ方法が活用できる。 引用:Railsチュートリアル14.1.5図:14.9 上の図を参考にしたデータモデルの実装を行う。 app/models/user.rb class User < ApplicationRecord has_many :microposts, dependent: :destroy has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy has_many :passive_relationships, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy has_many :following, through: :active_relationships, source: :followed has_many :followers, through: :passive_relationships, source: :follower . . . end このリストで注意すべき箇所は、次のように参照先(followers)を指定するための:sourceキーを省略してもよかったという点。 has_many :followers, through: :passive_relationships これは:followers属性の場合、Railsが「followers」を単数形にして自動的に外部キーfollower_idを探してくれるから。 followingと違って必要のない:sourceキーをそのまま残しているのは、has_many :followingとの類似性を強調させるため。 次に、followers.include?メソッドを使って先ほどのデータモデルをテストしていく。 test/models/user_test.rb test "should follow and unfollow a user" do michael = users(:michael) archer = users(:archer) assert_not michael.following?(archer) michael.follow(archer) assert michael.following?(archer) assert archer.followers.include?(michael #追加 michael.unfollow(archer) assert_not michael.following?(archer) end end テストコードは上記の通り。 following?と対照的なfollowed_by?メソッドを定義してもよかったが、サンプルアプリケーションで実際に使う場面がなかったのでこのメソッドは省略したとのこと。 1行だけしか追加を行っていないが、実際には多くの処理が正しく動いていなければ成功しない。 ここでテストが成功すればこの項は終了。 [Follow]のWebインターフェイス(14.2) この章の最初に、フォローしているユーザーのページ表示の流れについて説明した。 この節では、フォロー/フォロー解除の基本的なインターフェイスを実装する。 また、フォローしているユーザーと、フォロワーにそれぞれ表示用のページを作成。 フォローのサンプルデータ(14.2.1) 1つ前の章のときと同じように、サンプルデータを自動作成するrails db:seedを使って、データベースにサンプルデータを登録できると便利。 先にサンプルデータを自動作成できるようにしておけば、Webページの見た目のデザインから先にとりかかることができ、バックエンド機能の実装を後に回すことができる。 db/seeds.rb # 以下のリレーションシップを作成する users = User.all user = users.first following = users[2..50] followers = users[3..40] following.each { |followed| user.follow(followed) } followers.each { |follower| follower.follow(user) } 上記は、リレーションシップのサンプルデータを生成するためのコード。 最初のユーザーにユーザー3からユーザー51までをフォローさせ、それから逆にユーザー4からユーザー41に最初のユーザーをフォローさせる。 こうしてリレーションシップを作成しておけば、アプリケーションのインターフェイスを開発するには十分。 最後にサンプルデータを更新してこの項は終了。 $ rails db:migrate:reset $ rails db:seed 統計と[follow]フォーム(14.2.2) これでサンプルユーザーに、フォローしているユーザーとフォロワーができた。 プロフィールページとHomeページを更新して、これを反映させる。 最初に、プロフィールページとHomeページに、フォローしているユーザーとフォロワーの統計情報を表示するためのパーシャルを作成。 次に、フォロー用とフォロー解除用のフォームを作成する。 それから、フォローしているユーザーの一覧("following")とフォロワーの一覧("followers")を表示する専用のページを作成。 Twitterの慣習に従ってフォロー数の単位には「following」を使い、例えば「50 following」といった具合に表示する。 引用:Railsチュートリアル14.2.2図14.10 上記の統計情報には、現在のユーザーがフォローしている人数と、現在のフォロワーの人数が表示されている。 それぞれの表示はリンクになっており、専用の表示ページに移動できる。 5章では、これらのリンクはダミーテキスト'#'を使って無効にしていた。 しかしルーティングについての知識もだいぶ増えてきたので、今回は実装することにする。 実際のページ作成はまだ行わないが、ルーティングは今実装する。 このコードでは、resourcesブロックの内側で:memberメソッドを使っている。 config/routes.rb resources :users do member do get :following, :followers end end この場合のURLは/users/1/following や /users/1/followersのようになると推測する人が多いそう。 まさにそれを行っている解釈でOKらしい。 また、どちらもデータを表示するページなため、適切なHTTPメソッドはGETリクエスト。 したがって、getメソッドを使って適切なレスポンスを返すようにする。 ちなみに、memberメソッドを使うとユーザーidが含まれているURLを扱うようになるが、 idを指定せずにすべてのメンバーを表示するには、次のようにcollectionメソッドを使用する。 resources :users do collection do get :tigers end end このコードは /users/tigersというURLに応答する。 追記:Railsガイドの説明がすごく分かりやすかったので参考にしました。 Rails のルーティング - Railsガイド get :following, :followersを定義したことによって生成されるルーティングテーブルがこれ。 HTTPリクエスト URL アクション 名前付きルート GET /users/1/following following following_user_path(1) GET /users/1/followers followers followers_user_path(1) 引用:Railsチュートリアル14.2.2表14.2 ルーティングを定義したので、統計情報のパーシャルを実装する準備が整った。 このパーシャルでは、divタグの中に2つのリンクを含めるようにする。 app/views/shared/_stats.html.erb <% @user ||= current_user %> <div class="stats"> <a href="<%= following_user_path(@user) %>"> <strong id="following" class="stat"> <%= @user.following.count %> </strong> following </a> <a href="<%= followers_user_path(@user) %>"> <strong id="followers" class="stat"> <%= @user.followers.count %> </strong> followers </a> </div> このパーシャルはプロフィールページとHomeページの両方に表示されるため、最初の行では、次のコードで現在のユーザーを取得する。 <% @user ||= current_user %> これは、@userがnilでない場合(つまりプロフィールページの場合)は何もせず、nilの場合(つまりHomeページの場合)には@userにcurrent_userを代入するコード。 その後、フォローしているユーザーの人数を、次のように関連付けを使って計算。 @user.following.count これはフォロワーについても同様。 @user.followers.count 上のコードは、13章でマイクロポストの投稿数を表示した方法と同じ。 あのときは次のように書いた。 @user.microposts.count 今回も以前と同様に、Railsは高速化のためにデータベース内で合計を計算している点に注意。 また、一部の要素で、次のようにCSSidを指定していることにも注目。 <strong id="following" class="stat"> ... </strong> こうしておくことによって後にAjaxを実装するときに便利となる。 そこでは、一意のidを指定してページ要素にアクセスしているから。 これで統計情報パーシャルができあがり。 Homeページにこの統計情報を表示するには、以下のようにする。 app/views/static_pages/home.html.erb <% if logged_in? %> <div class="row"> <aside class="col-md-4"> <section class="user_info"> <%= render 'shared/user_info' %> </section> <section class="stats"> <%= render 'shared/stats' %> </section> <section class="micropost_form"> <%= render 'shared/micropost_form' %> </section> </aside> <div class="col-md-8"> <h3>Micropost Feed</h3> <%= render 'shared/feed' %> </div> </div> <% else %> . . . <% end %> 統計情報にスタイルを与えるために、SCSSを追加。 (なお、このSCSSにはこの章で使うすべてのスタイルが含まれている。) app/assets/stylesheets/custom.scss .gravatar { float: left; margin-right: 10px; } .gravatar_edit { margin-top: 15px; } .stats { overflow: auto; margin-top: 0; padding: 0; a { float: left; padding: 0 10px; border-left: 1px solid $gray-lighter; color: gray; &:first-child { padding-left: 0; border: 0; } &:hover { text-decoration: none; color: blue; } } strong { display: block; } } .user_avatars { overflow: auto; margin-top: 10px; .gravatar { margin: 1px 1px; } a { padding: 0; } } .users.follow { padding: 0; } この後すぐ、プロフィールにも統計情報パーシャルを表示するが、今のうちに[Follow]/[Unfollow]ボタン用のパーシャルも作成しておく。 app/views/users/_follow_form.html.erb <% unless current_user?(@user) %> <div id="follow_form"> <% if current_user.following?(@user) %> <%= render 'unfollow' %> <% else %> <%= render 'follow' %> <% end %> </div> <% end %> このコードは、followとunfollowのパーシャルに作業を振っているだけ。 パーシャルでは、Relationshipsリソース用の新しいルーティングが必要となる。 config/routes.rb resources :relationships, only: [:create, :destroy] フォロー/フォロー解除用のパーシャル自体は、これ。 app/views/users/_follow.html.erb <%= form_with(model: current_user.active_relationships.build, local: true) do |f| %> <div><%= hidden_field_tag :followed_id, @user.id %></div> <%= f.submit "Follow", class: "btn btn-primary" %> <% end %> app/views/users/_unfollow.html.erb <%= form_with(model: current_user.active_relationships.find_by(followed_id: @user.id), html: { method: :delete }, local: true) do |f| %> <%= f.submit "Unfollow", class: "btn" %> <% end %> これらの2つのフォームでは、いずれもform_withを使ってRelationshipモデルオブジェクトを操作している。 主な違いは、_follow.html.erbは新しいリレーションシップを作成するのに対し、_unfollow.html.erbは既存のリレーションシップを見つけ出すという点。 すなわち、前者はPOSTリクエストを Relationshipsコントローラに送信してリレーションシップをcreate(作成)し、後者はDELETEリクエストを送信してリレーションシップをdestroy(削除)するということ。 最終的に、このフォロー/フォロー解除フォームにはボタンしかない。 しかし、それでもこのフォームはfollowed_idをコントローラに送信する必要がある。 これを行うために、hidden_field_tagメソッドを使用。 このメソッドは、次のフォーム用HTMLを生成する。 <input id="followed_id" name="followed_id" type="hidden" value="3" /> 隠しフィールドのinputタグを使うことで、ブラウザ上に表示させずに適切な情報を含めることができる。 このテクニックを使ってフォロー用フォームをパーシャルとしてプロフィール画面に表示した結果が以下の通り。 app/views/users/show.html.erb <% provide(:title, @user.name) %> <div class="row"> <aside class="col-md-4"> <section class="user_info"> <h1> <%= gravatar_for @user %> <%= @user.name %> </h1> </section> <section class="stats"> <%= render 'shared/stats' %> </section> </aside> <div class="col-md-8"> <%= render 'follow_form' if logged_in? %> <% if @user.microposts.any? %> <h3>Microposts (<%= @user.microposts.count %>)</h3> <ol class="microposts"> <%= render @microposts %> </ol> <%= will_paginate @microposts %> <% end %> </div> </div> これで上手くいけば2つのボタンが表示される。 しかしまだ動作はしない。 実はこのボタンの実装には2通りの方法があるらしい。 1つは標準的な方法でもう1つはAjaxを使う方法。 でもその前に、フォローしているユーザーとフォロワーを表示するページをそれぞれ作成してHTMLインターフェイスを完成させる。 演習 1 省略 2 省略 3 プロフィールページ、Homeページに表示されている統計情報に対してテストを書いてみる。 users_profile_test.rb test "profile display" do get user_path(@user) assert_template 'users/show' assert_select 'title', full_title(@user.name) assert_select 'h1', text: @user.name assert_select 'h1>img.gravatar' assert_match @user.microposts.count.to_s, response.body assert_select 'div.pagination',count: 1 @user.microposts.paginate(page: 1).each do |micropost| assert_match micropost.content, response.body end assert_match @user.active_relationships.count.to_s, response.body assert_match @user.passive_relationships.count.to_s, response.body end site_layout_test.rb log_in_as(@user) get root_path assert_template 'static_pages/home' assert_template 'static_pages/home' assert_select "a[href=?]", root_path, count: 2 assert_select "a[href=?]", help_path assert_select "a[href=?]", about_path assert_select "a[href=?]", contact_path #assert_select "a[href=?]", signup_path assert_select "a[href=?]", users_path assert_select "a[href=?]", user_path(@user) assert_select "a[href=?]", edit_user_path(@user) assert_select "a[href=?]", logout_path assert_match @user.active_relationships.count.to_s, response.body assert_match @user.passive_relationships.count.to_s, response.body end [Following]と[Followers]ページ(14.2.3) フォローしているユーザーを表示するページと、フォロワーを表示するページは、いずれもプロフィールページとユーザー一覧ページを合わせたような作りになるという点で似ている。 どちらにもフォローの統計情報などのユーザー情報を表示するサイドバーと、ユーザーのリストがある。 さらに、サイドバーには小さめのユーザープロフィール画像のリンクを格子状に並べて表示する予定。 この要求に合うモックアップがこれらとなる。 引用:Railsチュートリアル14.2.3図14.14 フォローしているユーザー用ページのモックアップ 引用:Railsチュートリアル14.2.3 ユーザーのフォロワー用ページのモックアップ ここでの最初の作業は、フォローしているユーザーのリンクとフォロワーのリンクを動くようにすること。 Twitterに倣って、どちらのページでもユーザーのログインを要求するようにする。 そこで前回のアクセス制御と同様に、まずはテストから書いていく。 test/controllers/users_controller_test.rb require 'test_helper' class UsersControllerTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) @other_user = users(:archer) end . . . test "should redirect following when not logged in" do get following_user_path(@user) assert_redirected_to login_url end test "should redirect followers when not logged in" do get followers_user_path(@user) assert_redirected_to login_url end end この実装には1つだけトリッキーな部分がある。 それはUsersコントローラに2つの新しいアクションを追加する必要があるということ。 これは、前節で定義した2つのルーティングにもとづいており、これらはそれぞれfollowingおよびfollowersと呼ぶ必要がある。 それぞれのアクションでは、タイトルを設定し、ユーザーを検索し、@user.followingまたは@user.followersからデータを取り出し、ページネーションを行なって、ページを出力する必要がある。 app/controllers/users_controller.rb before_action :logged_in_user, only: [:index, :edit, :update, :destroy, :following, :followers] . . . def following @title = "Following" @user = User.find(params[:id]) @users = @user.following.paginate(page: params[:page]) render 'show_follow' end def followers @title = "Followers" @user = User.find(params[:id]) @users = @user.followers.paginate(page: params[:page]) render 'show_follow' end private . . . end 本チュートリアルのいたるところで見てきたように、Railsは慣習に従って、アクションに対応するビューを暗黙的に呼び出す。 例えば、showアクションの最後でshow.html.erbを呼び出す、といった具合。 一方で、上記のアクションは、renderを明示的に呼び出し、show_followという同じビューを出力している。 したがって、作成が必要なビューは1つとなる。 renderで呼び出しているビューが同じである理由は、このERbはどちらの場合でもほぼ同じであり、1つで両方の場合をカバーできるから。 app/views/users/show_follow.html.erb <% provide(:title, @title) %> <div class="row"> <aside class="col-md-4"> <section class="user_info"> <%= gravatar_for @user %> <h1><%= @user.name %></h1> <span><%= link_to "view my profile", @user %></span> <span><b>Microposts:</b> <%= @user.microposts.count %></span> </section> <section class="stats"> <%= render 'shared/stats' %> <% if @users.any? %> <div class="user_avatars"> <% @users.each do |user| %> <%= link_to gravatar_for(user, size: 30), user %> <% end %> </div> <% end %> </section> </aside> <div class="col-md-8"> <h3><%= @title %></h3> <% if @users.any? %> <ul class="users follow"> <%= render @users %> </ul> <%= will_paginate %> <% end %> </div> </div> ここまでくればモックアップ図のようにページが表示されるようになる。 また、この段階で上手くいっていればテストが成功となる。 次に、show_followの描画結果を確認するため、統合テストを書いていく。 ただし今回の統合テストは現実性のテストだけに留めており、網羅的なテストにはしない。 理由は、HTML構造を網羅的にチェックするテストは壊れやすく、生産性を逆に落としかねないから。 したがって今回は、正しい数が表示されているかどうかと、正しいURLが表示されているかどうかの2つのテストを書く。 まずは結合テストの生成から。 $ rails generate integration_test following invoke test_unit create test/integration/following_test.rb 今度はテストデータをいくつか揃える。 リレーションシップ用のfixtureにデータを追加。 orange: content: "I just ate an orange!" created_at: <%= 10.minutes.ago %> user: michael ユーザーとマイクロポストを関連付けできたことを思い出す。 上のコードではユーザー名を次のように書いているが、 user: michael これは内部的には次のようなコードに自動的に変換される。 user_id: 1 この例を参考にしてRelationship用のfixtureにテストデータを追加する。 one: follower: michael followed: lana two: follower: michael followed: malory three: follower: lana followed: michael four: follower: archer followed: michael このfixtureでは、前半の2つでMichaelがLanaとMaloryをフォローし、後半の2つでLanaとArcherがMichaelをフォローしている。 あとは、正しい数かどうかを確認するために、assert_matchメソッドを使ってプロフィール画面のマイクロポスト数をテストをし、正しいURLかどうかもをテストを行う。 test/integration/following_test.rb require 'test_helper' class FollowingTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) log_in_as(@user) end test "following page" do get following_user_path(@user) assert_not @user.following.empty? assert_match @user.following.count.to_s, response.body @user.following.each do |user| assert_select "a[href=?]", user_path(user) end end test "followers page" do get followers_user_path(@user) assert_not @user.followers.empty? assert_match @user.followers.count.to_s, response.body @user.followers.each do |user| assert_select "a[href=?]", user_path(user) end end end なお、上記ではこのようなコードを加えているが、 assert_not @user.following.empty? このコードは次のコードを確かめるためのテスト。 @user.following.each do |user| assert_select "a[href=?]", user_path(user) end つまり、もし@user.following.empty?の結果がtrueであれば、assert_select内のブロックが実行されなくなるため、その場合においてテストが適切なセキュリティモデルを確認できなくなることを防いでいる。 (followersについても同様。) ここでテスト行って成功していればこの項は終了となる。 [Follow]ボタン(基本編)(14.2.4) この項では、いよいよ[Follow]/[Unfollow]ボタンを動作させる。 フォローとフォロー解除はそれぞれリレーションシップの作成と削除に対応しているため、まずはRelationshipsコントローラが必要。 $ rails generate controller Relationships Relationshipsコントローラのアクションでアクセス制御することはそこまで難しくはない。 しかし、前回のアクセス制御のときと同様に最初にテストを書き、それをパスするように実装することでセキュリティモデルを確立させていく。 今回はまず、コントローラのアクションにアクセスするとき、ログイン済みのユーザーであるかどうかをチェック。 もしログインしていなければログインページにリダイレクトされるので、Relationshipのカウントが変わっていないことを確認。 test/controllers/relationships_controller_test.rb require 'test_helper' class RelationshipsControllerTest < ActionDispatch::IntegrationTest test "create should require logged-in user" do assert_no_difference 'Relationship.count' do post relationships_path end assert_redirected_to login_url end test "destroy should require logged-in user" do assert_no_difference 'Relationship.count' do delete relationship_path(relationships(:one)) end assert_redirected_to login_url end end 次に、上記のテストをパスさせるために、logged_in_userフィルターをRelationshipsコントローラのアクションに対して追加。 app/controllers/relationships_controller.rb class RelationshipsController < ApplicationController before_action :logged_in_user def create end def destroy end end [Follow]/[Unfollow]ボタンを動作させるためには、フォームから送信されたパラメータを使って、followed_idに対応するユーザーを見つけてくる必要がある。 その後、見つけてきたユーザーに対して適切にfollow/unfollowメソッドを使用する。 app/controllers/relationships_controller.rb class RelationshipsController < ApplicationController before_action :logged_in_user def create user = User.find(params[:followed_id]) current_user.follow(user) redirect_to user end def destroy user = Relationship.find(params[:id]).followed current_user.unfollow(user) redirect_to user end end 上記を見てみると、先ほどのセキュリティ問題が実はそれほど重大なものではないことが分かる。 もしログインしていないユーザーがcurlなどのコマンドラインツールなどを使ってこれらのアクションに直接アクセスするようなことがあれば、current_userはnilになり、どちらのメソッドでも2行目で例外が発生する。 なおエラーは、アプリケーションやデータに影響は及ぼさない。 このままでも支障はありませんが、やはりこのような例外には頼らない方がよい。 上ではひと手間かけてセキュリティのためのレイヤーを追加した。 これで、フォロー/フォロー解除の機能が完成。 どのユーザーも、他のユーザーをフォローしたりフォロー解除したりできる。 これで実際にボタンが機能しているのを確認してこの項は終了となる。 [Follow]ボタン(Ajax編)(14.2.5) フォロー関連の機能の実装はこのとおり完了したが、ステータスフィードに取りかかる前にもう一つだけ機能を洗練させてみる。 前の項では、Relationshipsコントローラのcreateアクションとdestroyアクションを単に元のプロフィールにリダイレクトしていた。 つまり、ユーザーはプロフィールページを最初に表示し、それからユーザーをフォローし、その後すぐ元のページにリダイレクトされるという流れ。 ユーザーをフォローした後、本当にそのページから離れて元のページに戻らないといけないのか? この点を考えなおしてみる。 これはAjaxを使うことで解決可能。 Ajaxを使えば、Webページからサーバーに「非同期」で、ページを移動することなくリクエストを送信することができる。 WebフォームにAjaxを採用するのは今や当たり前になりつつあるため、RailsでもAjaxを簡単に実装できるようになっている。 フォロー用とフォロー解除用のパーシャルをこれに沿って更新するのは簡単。 たとえば次のコード form_with(model: ..., local: true) を form_with(model: ..., remote: true) このように置き換えるだけで更新できる。 たったこれだけで、Railsは自動的にAjaxを使うようになる。 具体的な更新の結果は以下の通り。 app/views/users/_follow.html.erb <%= form_with(model: current_user.active_relationships.build, remote: true) do |f| %> <div><%= hidden_field_tag :followed_id, @user.id %></div> <%= f.submit "Follow", class: "btn btn-primary" %> <% end %> app/views/users/_unfollow.html.erb <%= form_with(model: current_user.active_relationships.find_by(followed_id: @user.id), html: { method: :delete }, remote: true) do |f| %> <%= f.submit "Unfollow", class: "btn" %> <% end %> ERbによって実際に生成されるHTMLはそれほど重要ではないが一応掲載。 <form action="/relationships/117" class="edit_relationship" data-remote="true" id="edit_relationship_117" method="post"> . . . </form> ここでは、formタグの内部でdata-remote="true"を設定している。 これは、JavaScriptによるフォーム操作を許可することをRailsに知らせるためのもの。 Rails 2以前では、完全なJavaScriptのコードを挿入する必要があった。 しかし先ほどの例で見たように、現在のRailsではHTMLプロパティを使って簡単にAjaxが扱えるようになっている。 フォームの更新が終了したら、今度はこれに対応するRelationshipsコントローラを改造して、Ajaxリクエストに応答できるようにする。 こういったリクエストの種類によって応答を場合分けするときは、respond_toメソッドというメソッドを使用する。 respond_to do |format| format.html { redirect_to user } format.js end この文法は少々変わっていて混乱を招く可能性があるが、上の(ブロック内の)コードのうち、いずれかの1行が実行されるという点が重要。 このためrespond_toメソッドは、上から順に実行する逐次処理というより、if文を使った分岐処理に近いイメージ`となる。 RelationshipsコントローラでAjaxに対応させるために、respond_toメソッドをcreateアクションとdestroyアクションに追加する。 app/controllers/relationships_controller.rb class RelationshipsController < ApplicationController before_action :logged_in_user def create @user = User.find(params[:followed_id]) current_user.follow(@user) respond_to do |format| format.html { redirect_to @user } format.js end end def destroy @user = Relationship.find(params[:id]).followed current_user.unfollow(@user) respond_to do |format| format.html { redirect_to @user } format.js end end end 上記では、ユーザーのローカル変数(user)をインスタンス変数(@user)に変更した点に注目。 これは、Ajaxを使用する前はインスタンス変数の必要がなかったが、使用後はインスタンスヘンスが必要になったため。 しかしこれでコントローラがAjaxリクエストに対応した。 今度はブラウザ側でJavaScriptが無効になっていた場合(Ajaxリクエストが送れない場合)でもうまく動くようにする。 config/application.rb require_relative 'boot' . . . module SampleApp class Application < Rails::Application . . . # 認証トークンをremoteフォームに埋め込む config.action_view.embed_authenticity_token_in_remote_forms = true end end 一方で、JavaScriptが有効になっていても、まだ十分に対応できていない部分が存在している。 というのも、Ajaxリクエストを受信した場合は、Railsが自動的にアクションと同じ名前を持つJavaScript用の埋め込みRuby(.js.erb)ファイル(create.js.erbやdestroy.js.erbなど)を呼び出すから。 これらのファイルではJavaScriptと埋め込みRuby(ERb)をミックスして現在のページに対するアクションを実行することができる。 ユーザーをフォローしたときやフォロー解除したときにプロフィールページを更新するために、これらのファイルを編集しなければならない。 JS-ERbファイルの内部では、DOM(Document Object Model)を使ってページを操作するため、RailsがjQuery JavaScriptヘルパーを自動的に提供している。 これによりjQueryライブラリの膨大なDOM操作用メソッドが使えるようになるが、13章で使った通り今回は2つしか使わない。 1つずつ確認する。 まずはドル記号($)とCSS idを使って、DOM要素にアクセスする文法について知る必要がある。 例えばfollow_formの要素をjQueryで操作するには、次のようにアクセス。 $("#follow_form") 本来は、これはフォームを囲むdivタグであり、フォームそのものではなかった。 jQueryの文法はCSSの記法から影響を受けていて、#シンボルを使ってCSSのidを指定する。 また、jQueryはCSSと同様、ドット.を使ってCSSクラスを操作できる。 そして次に必要なメソッドはHTML。 これは、引数の中で指定された要素の内側にあるHTMLを更新する。 例えばフォロー用フォーム全体を"foobar"という文字列で置き換えたい場合は、次のようなコード。 $("#follow_form").html("foobar") 純粋なJavaScriptと異なり、JS-ERbファイルでは組み込みRuby(ERb)が使える。 create.js.erbファイルでは、フォロー用のフォームをunfollowパーシャルで更新し、フォロワーのカウントを更新するのにERbを使っている。 app/views/relationships/create.js.erb $("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>"); $("#followers").html('<%= @user.followers.count %>'); このコードではescape_javascriptメソッドを使っている点に注目。 このメソッドは、JavaScriptファイル内にHTMLを挿入するときに実行結果をエスケープするために必要。 app/views/relationships/destroy.js.erb $("#follow_form").html("<%= escape_javascript(render('users/follow')) %>"); $("#followers").html('<%= @user.followers.count %>'); destroy.js.erbファイルの方も同様。 これで実際に動作確認が行えればこの項は終了。 フォローをテストする(14.2.6) フォローボタンが動くようになったため、バグを検知するためのシンプルなテストを書いていく。 ユーザーのフォローに対するテストでは、 /relationshipsに対してPOSTリクエストを送り、フォローされたユーザーが1人増えたことをチェック。 assert_difference '@user.following.count', 1 do post relationships_path, params: { followed_id: @other.id } end 上記は標準的なフォローに対するテストだがAjax版もやり方は大体同じ。 Ajaxのテストでは、xhr :trueオプションを使うようにするだけ。 assert_difference '@user.following.count', 1 do post relationships_path, params: { followed_id: @other.id }, xhr: true end ここで使っているxhr(XmlHttpRequest)というオプションをtrueに設定すると、Ajaxでリクエストを発行するように変更する。 したがって、Relationshipsコントローラのrespond_toでは、JavaScriptに対応した行が実行されるようになる。 また、ユーザーをフォロー解除するときも構造はほとんど同じで、postメソッドをdeleteメソッドに置き換えてテスト。 つまり、そのユーザーのidとリレーションシップのidを使ってDELETEリクエストを送信し、フォローしている数が1つ減ることを確認。 assert_difference '@user.following.count', -1 do delete relationship_path(relationship) end 上記が従来のテストで assert_difference '@user.following.count', -1 do delete relationship_path(relationship), xhr: true end こっちがAjax用のテスト。 これらをまとめたテストがこちら。 test/integration/following_test.rb require 'test_helper' class FollowingTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) @other = users(:archer) log_in_as(@user) end . . . test "should follow a user the standard way" do assert_difference '@user.following.count', 1 do post relationships_path, params: { followed_id: @other.id } end end test "should follow a user with Ajax" do assert_difference '@user.following.count', 1 do post relationships_path, xhr: true, params: { followed_id: @other.id } end end test "should unfollow a user the standard way" do @user.follow(@other) relationship = @user.active_relationships.find_by(followed_id: @other.id) assert_difference '@user.following.count', -1 do delete relationship_path(relationship) end end test "should unfollow a user with Ajax" do @user.follow(@other) relationship = @user.active_relationships.find_by(followed_id: @other.id) assert_difference '@user.following.count', -1 do delete relationship_path(relationship), xhr: true end end end これでテストが成功すればこの項は終了。 そしてここでparams: { followed_id: @other.id }の意味が調べても全く分かりませんでした。 ユーザーフォローフォームの <div><%= hidden_field_tag :followed_id, @user.id %></div> をテストで行うとこうなると勝手に解釈しました。 ステータスフィード(14.3) 次は、最後の難関のステータスフィードの実装を行う。 この節で扱われている内容は、チュートリアルの中で一番高度なものらしい。 現在のユーザーにフォローされているユーザーのマイクロポストの配列を作成し、現在のユーザー自身のマイクロポストと合わせて表示。 このセクションを通して、複雑さを増したフィードの実装に進んでいく。 これを実現するためには、RailsとRubyの高度な機能の他に、SQLプログラミングの技術も必要。 手強い課題に挑むため、ここで実装すべき内容を慎重に見直すことが重要。 モックアップがこれ。 引用:Railtチュートリアル14.3図14.21 動機と計画(14.3.1) ステータスフィードの基本的なアイデアはシンプル。 引用:Railtチュートリアル14.3.1図14.22 上の図はmicropostsのサンプルデータ付きのデータモデルとその結果を示している。 図の矢印で示されているように、この目的は、現在のユーザーによってフォローされているユーザーに対応するユーザーidを持つマイクロポストを取り出し、同時に現在のユーザー自身のマイクロポストも一緒に取り出すこと。 どのようにフィードを実装するのかはまだ明確ではないが、テストについてはやや明確そうなため、まずはテストから書いていく。 このテストで重要なことは、フィードに必要な3つの条件を満たすこと。 ①フォローしているユーザーのマイクロポストがフィードに含まれていること。 ②自分自身のマイクロポストもフィードに含まれていること。 ③フォローしていないユーザーのマイクロポストがフィードに含まれていないこと。 そのため、まずはMichaelがLanaをフォローしていて、Archerをフォローしていないという状況を作ってみる。 この状況のMichaelのフィードでは、Lanaと自分自身の投稿が見えていて、Archerの投稿は見えないことになる。 先ほどの3つの条件をアサーションに変換して、Userモデルにfeedメソッドがあることに注意しながら、更新したUserモデルに対するテストを書いてみる。 test/models/user_test.rb require 'test_helper' class UserTest < ActiveSupport::TestCase . . . test "feed should have the right posts" do michael = users(:michael) archer = users(:archer) lana = users(:lana) # フォローしているユーザーの投稿を確認 lana.microposts.each do |post_following| assert michael.feed.include?(post_following) end # 自分自身の投稿を確認 michael.microposts.each do |post_self| assert michael.feed.include?(post_self) end # フォローしていないユーザーの投稿を確認 archer.microposts.each do |post_unfollowed| assert_not michael.feed.include?(post_unfollowed) end end end 先にテストを書いているため、ここのテストは失敗する。 ここでこの項は終了。 フィードを初めて実装する(14.3.2) ステータスフィードに対する要件定義はテストで明確となった。 (つまり先程のテストが成功すればOK。) 早速フィードの実装に着手する。 最終的なフィードの実装はやや込み入っているため、細かい部品を1つずつ確かめながら導入していく。 最初に、このフィードで必要なクエリについて考える。 ここで必要なのは、micropostsテーブルから、あるユーザー(つまり自分自身)がフォローしているユーザーに対応するidを持つマイクロポストをすべて選択(select)すること。 このクエリを模式的に書くと次のようになる。 SELECT * FROM microposts WHERE user_id IN (<list of ids>) OR user_id = <user id> 上のコードを書く際に、SQLがINというキーワードをサポートしていることを前提としている。 (Railsでは実際にサポートされている。) このキーワードを使うことで、idの集合の内包(set inclusion)に対してテストを行える。 プロトフィードでは、上のような選択を行うためにActive Recordのwhereメソッドを使っていた。 このときに選択すべき対象はシンプルで、現在のユーザーに対応するユーザーidを持つマイクロポストを選択すればよかった。 Micropost.where("user_id = ?", id) 今回必要になる選択は、上よりも少し複雑。 例えばこんな感じ。 Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id) これらの条件から、フォローされているユーザーに対応するidの配列が必要であることがわかる。 これを行う方法の1つは、Rubyのmapメソッドを使うこと。 このメソッドはすべての「列挙可能(enumerable)」なオブジェクト(配列やハッシュなど、要素の集合で構成されるあらゆるオブジェクト)で使える。 例題として、mapメソッドを使って配列を文字列に変換すると、次のようになる。 $ rails console >> [1, 2, 3, 4].map { |i| i.to_s } => ["1", "2", "3", "4"] 上に示したような状況では、各要素に対して同じメソッドが実行される。 これは非常によく使われる方法であり、次のようにアンパサンド(Ampersand)& と、メソッドに対応するシンボルを使った短縮表記が可能。 この短縮表記であれば、変数iを使わずに済む。 >> [1, 2, 3, 4].map(&:to_s) => ["1", "2", "3", "4"] この結果に対してjoinメソッドを使うとidの集合をカンマ区切りの文字列として繋げることが可能。 >> [1, 2, 3, 4].map(&:to_s).join(', ') => "1, 2, 3, 4" 上のコードを使えば、user.followingにある各要素のidを呼び出し、フォローしているユーザーのidを配列として扱うことが可能。 例えばデータベースの最初のユーザーに対して実行すると、次のような結果になる。 >> User.first.following.map(&:id) => [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51] 実際、この手法は実に便利なため、Active Recordでは次のようなメソッドも用意されている。 >> User.first.following_ids => [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51] このfollowing_idsメソッドは、has_many :followingの関連付けをしたときにActive Recordが自動生成したもの。 これにより、user.followingコレクションに対応するidを得るためには、関連付けの名前の末尾に_idsを付け足すだけで済む。 結果として、フォローしているユーザーidの文字列は、次のようにして取得することができる。 >> User.first.following_ids.join(', ') => "3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 なお、以上は説明のためのコードであり、実際にSQL文字列に挿入するときは、このように記述する必要はない。 実は、?を内挿すると自動的にこの辺りの面倒を見てくれる。 さらに、データベースに依存する一部の非互換性まで解消してくれるらしい。 つまり、ここではfollowing_idsメソッドをそのまま使えばよいだけとなる。 Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id) そしてこのコードを実際に実装する。 app/models/user.rb class User < ApplicationRecord . . . # パスワード再設定の期限が切れている場合はtrueを返す def password_reset_expired? reset_sent_at < 2.hours.ago end # ユーザーのステータスフィードを返す def feed Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id) end # ユーザーをフォローする def follow(other_user) following << other_user end . . . end これでテストが成功するようになる。 いくつかのアプリケーションにおいては、この初期実装だけで目的が達成され、十分に思える。 しかし、まだ足りないものがuserモデルにはあるらしい。 とりあえずこの項はこれで終了となる。 サブセレクト(14.3.3) 前の項で出てきたたりないものは、投稿されたマイクロポストの数が膨大になった場合の処理。 現在のままではうまくスケールしない。 つまり、フォローしているユーザーが5,000人程度になるとWebサービス全体が遅くなる可能性がある。 この節では、フォローしているユーザー数に応じてスケールできるように、ステータスフィードを改善していく。 前の項の問題点は、following_idsでフォローしているすべてのユーザーをデータベースに問い合わせし、さらに、フォローしているユーザーの完全な配列を作るために再度データベースに問い合わせしている点。 Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)では、集合に内包されているかどうかだけしかチェックされていないため、この部分はもっと効率的なコードに置き換えられる また、SQLは本来このような集合の操作に最適化されています。 実際、このような問題は、SQLのサブセレクト(subselect)を使うと解決できる。 まずはコードを若干修正し、フィードをリファクタリングすることから始める。 app/models/user.rb class User < ApplicationRecord . . . # ユーザーのステータスフィードを返す def feed Micropost.where("user_id IN (:following_ids) OR user_id = :user_id", following_ids: following_ids, user_id: id) end . . . end 上の実装では、これまでのコード Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id) を次のように置き換えた。 Micropost.where("user_id IN (:following_ids) OR user_id = :user_id", following_ids: following_ids, user_id: id) 前者の疑問符を使った文法も便利だが、同じ変数を複数の場所に挿入したい場合は、後者の置き換え後の文法を使う方がより便利。 上の説明が暗に示すように、これからSQLクエリにもう1つのuser_idを追加する。 特に、次のRubyコード following_ids このようなSQLに置き換えることができる。 following_ids = "SELECT followed_id FROM relationships WHERE follower_id = :user_id" このコードをSQLのサブセレクトとして使う。 つまり、「ユーザー1がフォローしているユーザーすべてを選択する」というSQLを既存のSQLに内包させる形になり、結果としてSQLは次のようになる。 SELECT * FROM microposts WHERE user_id IN (SELECT followed_id FROM relationships WHERE follower_id = 1) OR user_id = 1 このサブセレクトは集合のロジックを(Railsではなく)データベース内に保存するため、より効率的にデータを取得することが可能。 これで基礎を固めることができたので、もっと効率的なフィードを実装する準備ができた。 (ここに記述されているコードは生のSQLを表す文字列であり、following_idsという文字列はエスケープされているのではなく、見やすさのために式展開しているだけ) app/models/user.rb class User < ApplicationRecord . . . # ユーザーのステータスフィードを返す def feed following_ids = "SELECT followed_id FROM relationships WHERE follower_id = :user_id" Micropost.where("user_id IN (#{following_ids}) OR user_id = :user_id", user_id: id) end . . . end このコードはRailsとRubyとSQLのコードが複雑に絡み合っていて厄介ではあるが、ちゃんと動作が行える。 そしてここでテストが成功すれば問題なし。 もちろん、サブセレクトを使えばいくらでもスケールできるなどということはない。 大規模なWebサービスでは、バックグラウンド処理を使ってフィードを非同期で生成するなどのさらなる改善が必要。 ただし、Webサービスをスケールさせる技術は非常に高度かつデリケートな問題らしく、チュートリアルではここまでの改善で止めておくそう。 これでステータスフィードの実装は完了。 実際のフィードを確認するとしっかり動作している。 演習 1 Homeページで表示される1ページ目のフィードに対して、統合テストを書く。 test/integration/following_test.rb require 'test_helper' class FollowingTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) log_in_as(@user) end . . . test "feed on Home page" do get root_path @user.feed.paginate(page: 1).each do |micropost| assert_match CGI.escapeHTML(micropost.content), response.body end end end 2 上記のコードでは、期待されるHTMLをCGI.escapeHTMLメソッドでエスケープしている。 このコードでは、なぜHTMLをエスケープさせる必要があったのか。 "本当にわかりませんでした。調べても自分の中で消化できていない状態です。" 3 app/models/user.rbのfeedのコードは`joinメソッドを使えばRailsで直接表現が可能。 def feed part_of_feed = "relationships.follower_id = :id or microposts.user_id = :id" Micropost.joins(user: :followers).where(part_of_feed, { id: id }) end ↑のコードでも正しいフィードを返すことを確認する。 確認はできたけど自分の中では全く理解できてないのが現状です。 SQLとjsはもう少ししっかり勉強しないといけないと感じます。 終了 長い時間をかけてしまいましたが、これにて2週目まとめ投稿終了です。 こうやって記事に残しておくことによって、1週目より理解度が格段と上がりました。 しかし、人間すぐ忘れるもので今振り返ってみると多分全体の6割ぐらいしか定着してないかと感じます。 隙間時間に自分の投稿を読んで知識の定着を図りたいと思います。 そして今後のプランとしては ①Webアプリ(ポートフォリオ)を作りながら足りていな知識を平行で勉強する。 ②先に足りていない知識を勉強してからwebアプリを作る。 ③Rubyの定着をより図るため教本をやってから①のプランに入る。 が自分の頭の中にあります。 自分がまだ学生であれば③一択なのですが、社会人なのであまり時間をかけていられない現状もあります。 実は、一回途中で読むのを辞めてしまった「パーフェクトRuby」があるので本当はやりたい!! また、自分の甘えもありますが社会人になってから毎日1時間程度しか勉強できていません。 それを考えると1~2年で今の会社を退職してプログラミングスクールに通ってrubyに向き合うのもありかなと最近感じています。 どの選択をするにせよ、自分の中でしっかりと調べながら回答を出したいと思います。 ということでしばらくは毎日の勉強時間を今後の方針を立てる時間に当てて、スピーディかつ丁寧に今後について考えたいと思います。 能動的と受動的「能動的」は「自ら他へ働きかける様子」という意味。「受動的」は「他から働きかけを受ける様子」という意味。「能動的」の意味と使い方・「主体的」「受動的」との違い - 言葉の意味を知るならtap-biz ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Railsの環境構築【初めの一歩】

はじめに 初投稿になります。(どうかお手柔らかに・・・) スキルアップを目的とした活動の記録、そして誰かの役に立てればなぁと思い作成しました。 1.Rails実行の必要条件(今回やること) Railsガイドを見るとRubyだけではRailsは動かないようです。 以下のものがPCに入っているのが条件なんだとか。 ・Ruby ・SQLite3 ・Node.js ・Yarn 条件を満たすために上から順にやっていきましょう。 2.環境・PCスペック等 必要ないと思いますが一応・・・ OS Windows10 CPU core i5-4570 メモリ 12GB 3.Ruby本体のインストール Rubyの公式サイトにインストール方法がいくつか紹介されていた。 (https://www.ruby-lang.org/ja/downloads/) 一般的に使用されることの多いOS(LinuxやMacOS,Windows等)はインストール方法が提供されているらしい。 今回はWindows10向けに提供されている「RubyInstaller」( https://rubyinstaller.org/ )を利用する事にした。 ダウンロードボタンをクリックするとダウンロードページに遷移できる。 公式のダウンロードページに2021/06/05時点で「安定版は 3.0.1です。」との記述があるためこの言葉を信用してダウンロード開始。 ダウンロードが完了したら実行して以下の手順で進める。 ①I accept the License にチェックしてNextボタンを押下。 ②ファイル場所を指定しない場合はInstallを押下。 ③何かよく分からないけどとりあえずNext(気になる人は調べてからNext) ④終わるまで待つ ⑤FINISH!!! 4.MSYS2のインストール 「2.Ruby本体のインストール」を手順通りに終わらせると、MSYS2というやつのインストールが始まる。 こいつはWindowsでUNIX等のシェルを使えるようにするためのものみたい。 よく分からない。使ってから覚えよう。 以下の手順でインストール(多分あってる) ①Enterターン ②Success!!! なんか警告とかいっぱい出てくるけど成功してるのでよさそう? 知識は後からついてくると信じて次に行きます。 5.初めてのRuby インストール時に環境変数が設定されているためコマンドプロンプトで「ruby」が使えるようになっている。 とりあえずインストールが出来ているか確認するため 次のバージョン確認のコマンドを実行する $ruby -v 次のような結果を出力すれば正常にインストール出来ている ruby 3.0.1p64 (2021-04-05 revision 0fb782ee38) [x64-mingw32] 次はハローワールド 任意の場所にファイルを作成して次のコードを入力する helloworld.rb print "Hello World" 作成したファイルをコマンドプロンプトで指定して実行 今回はCドライブ直下とします。 $ruby C:\helloworld.rb 以下のような出力が出れば上手く出来ています Hello World 6.SQLite3のインストール RailsでSQLite3を使用するには、SQLite3本体とDLLファイルが必要なようです。 本体については次のgemコマンドを使ってインストールします。 $gem install sqlite3 1 gem installed という表示が出れば正常に完了。 次にDLLファイルのダウンロード SQLite3のダウンロードサイト(https://www.sqlite.org/download.html )から「sqlite-dll-win65-x64-3350500.zip」をダウンロード ダウンロードした物を展開して、中身に入っているdllファイルをRubyのbinフォルダの中に移動します。 ↓↓↓上のファイルを下のフォルダに移動 なんだかよく分かってないけど、これでヨシ! 7.Node.jsインストール 次はNode.jsをインストール nodejsのサイト( https://nodejs.org/ja/ )からダウンロード 今回は推奨版を使う。 ダウンロードしたインストーラーを実行してとりあえず全部YESマン!FINISH!!!(適当だけど大丈夫) ダウンロードが完了したら次のコマンドでバージョンを確認する $node -v バージョン情報が出てきたらOK! 8.Yarnのインストール 次はYarnのインストール YarnはNode.jsのパッケージ管理らしい。 npmとの違いがよく分からないが、とりあえず必要らしいのでインストールします。 node.jsをインストールしたときにnpmコマンドが使用できるようになっているので以下のコマンドを実行 $npm install --global yarn インストール後に以下コマンドで恒例のバージョン確認しよう。 $yarn -v 結果 1.22.10 良い感じ! 9.Ruby on Railsのインストール Ruby on Railsのインストールにはgemコマンドを使用します。 このgemコマンドとは、Rubyのパッケージ管理をするためのものです。 バージョン云々がありますが、とりあえず動けばよいので最新版をダウンロードします。 以下のコマンドを実行します。 $gem install rails インストールが始まります。 完了後に正常にインストール出来ているか確認するためにバージョン確認コマンドを実行する。 $rails -v 以下のような結果になればOK Rails 6.1.3.2 10.Railsサーバー起動 これでいよいよ最後です。 まずはサーバー起動のために任意の場所にアプリケーションの作成をしましょう。 今回はCドライブ直下にします。 $cd / $rails new sample 意外と長かった。 successfully installedという表示が出ていれば正常に作れています。 次にサーバーの起動です。作成したアプリケーションまで移動して、以下のコマンドを実行 $cd C:\sample $rails s しばらくすると、 Listening on http://127.0.0.1:3000 という表示が出るのでこのアドレスにブラウザでアクセスします。 (127.0.0.1というのは自身を指すアドレスで3000はポート番号になります!) この画面が出たら正常にサーバーが起動できています! やったね!! 今後 今後も週1程度で更新できていければな~と思っています。 お気づきの点がございましたらコメント等で教えていただければ幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails バリデーション validates

バリデーションとは DBにデータが保存される前に、データが正しいか、入力規則に則った形かを確認する仕組みのこと。 create create! save save! update update! 上記メソッドの時バリデーションがトリガされ、オブジェクトが有効な場合にのみデータベースに保存されます。 バリデーションのスキップ decrement! decrement_counter increment! increment_counter toggle! touch update_all update_attribute update_column update_columns update_counters 上記メソッドの時うはバリデーションをが行われずに、スキップされる。 オブジェクトの保存は実行される。 valid?とinvalid? valid?とメソッドを組み合わせて使用することでバリデーションが実行され、オブジェクトにエラーがない場合はtrueが返され、そうでなければfalseが返されます invalid?はvalid?と逆の働きをする。 エラーが発生した場合はtrueが返され、そうでなければfalseが返されます よく使いそうなバリデーションヘルパー errors[] class Person < ApplicationRecord validates :name, presence: true end >> Person.new.errors[:name].any? # => false >> Person.create.errors[:name].any? # => true 特定のオブジェクトの属性が有効かどうかを確認 confirmation class Person < ApplicationRecord validates :email, confirmation: true end 2つのテキストフィールドで受け取る内容が完全に一致する必要がある場合に使用 format class Product < ApplicationRecord validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/, message: "英文字のみが使えます" } end 正規表現と属性の値など指定した条件と合っているかテストする際に使用 length class Person < ApplicationRecord validates :name, length: { minimum: 2 } validates :bio, length: { maximum: 500 } end 属性の長さを検証。 :minimum: 最小値 :maximum: 最大値 :inまたは:within: 値を範囲で指定 属性の長さは与えられた値と等しくないとならない presence class Person < ApplicationRecord validates :name, :login, :email, presence: true end 指定した属性の値が空でないことを確認。 uniqueness  class Account < ApplicationRecord validates :email, uniqueness: true end 属性の値が一意性、重複していないか検証。 参考サイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【rails】docker + rails + heroku メールでユーザー認証をしようとした時にhttp://localhost:3000/~からメールが届く

1.起きている問題やエラーメッセージ 備忘録として残しておきます。 チームメンバーと開発したポートフォリオの一つをherokuでデプロイしました。 アプリへサインインした際、ユーザー認証メールが届き、URLをクリックするとアプリのドメイン名に遷移させたいという話です。 docker+rails環境でアプリを作成し、本番環境にデプロイしました。 サインアップにgem deviseを使用しているため、サインアップすると ユーザー認証メールが届きますが、記載のURLをクリックすると、localhost形式のURLからメールが届いてしまい、ユーザー認証ができませんでした。 2. 関連していそうなソースコードの見直し config/environments/production.rb Rails.application.configure do # 本番環境用 config.file_watcher = ActiveSupport::EventedFileUpdateChecker config.action_mailer.default_url_options = { host: 'サブドメイン名(アプリ名).herokuapp.com', protocol: 'https' } config.action_mailer.raise_delivery_errors = true config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { address: 'smtp.gmail.com', port: 587, user_name: ENV['USER_EMAIL'], password: ENV['EMAIL_PASSWORD'], authentication: 'plain', enable_starttls_auto: true } end production.rbの設定に間違いはなさそうなのに、なぜかlocalhostへ遷移される状態になってしまっておりました。 3. 問題にぶつかる前に、アプリをどうやってデプロイしたのか heroku container:login heroku container:push web -a アプリ名 heroku addons:attach postgresql名 -a アプリ名 heroku run rake db:migrate -a アプリ名 heroku open -a アプリ名 https://qiita.com/NA_simple/items/57ad10717568fea2160b こちらの記事を参考にデプロイを実行。 サインインをトライして到着したメールに記載のURLをクリックすると、 http://localhost:3000/users/confirmation?confirmation_token=.... に遷移されてユーザー認証ができませんでした。 実際に「メールアドレス確認」をクリックして認証を完了させようとすると、 http://localhost:3000/users/confirmation?confirmation_token=.... というURLに飛ばされてしまいました。 しかしそれでは実際にアプリが使えないので、 http://アプリドメイン名/users/confirmation?confirmation_token=.... になることを望んでいました。 4.解決方法 そもそもdockerfileの設定に間違いの原因がありました。 FROM ruby:2.6.5 RUN apt-get update -qq && apt-get install -y nodejs postgresql-client RUN mkdir /myapp WORKDIR /myapp COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install COPY . /myapp # Add a script to be executed every time the container starts. COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 ENV LANG C.UTF-8 # Start the main process. CMD ["rails", "server", "-b", "0.0.0.0"] 最後のCMD ["rails", "server", "-b", "0.0.0.0"]が原因でした。 デプロイする際、このコマンドは開発環境を実行するという意味になっており、開発環境をデプロイしてしまっている。ということがわかりました。何を意味するのかというと、production.rbをいくら編集しても、本番環境をデプロイしていないため何の意味もなさなかったということになります。 今回の場合はdevelopments.rbを編集してやり過ごすことにしました。 deveopment.rb Rails.application.configure do # 本番環境用 config.file_watcher = ActiveSupport::EventedFileUpdateChecker #編集前 #config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } #編集後 config.action_mailer.default_url_options = { host: 'サブドメイン名(アプリ名).herokuapp.com', protocol: 'https' } config.action_mailer.raise_delivery_errors = true config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { address: 'smtp.gmail.com', port: 587, user_name: ENV['USER_EMAIL'], password: ENV['EMAIL_PASSWORD'], authentication: 'plain', enable_starttls_auto: true } end 上記development.rbの config.action_mailer.default_url_optionsを本番用のものに編集し直しました。 その場凌ぎにはなりますが、メイラーを送信するデフォルトのドメインをlocalhostから本番環境のドメイン名に変更しました。 このように修正して再度デプロイをし直し、ユーザー認証メールのURLをクリックしたら無事に本番環境のドメインに遷移されてユーザー認証を成功させることができました。 デプロイはエラーが付きまとうので大変と感じる人も多いと思います。 私も久しぶりにデプロイし、今回のエラーにぶつかってしまいました。実際skypeを繋ぎながら記事を参考に試しましたがなかなかうまくいかず、現場のエンジニアの知り合いに知恵を貸していただいて解決しました。正直dockerもherokuもrailsも知識が足りておらず、まだまだ勉強不足だなと痛感しました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rspecを使う方法

Rspecのセットアップ Gemfileの中のgroup :development, :test do ~ endとある「do ~ endの間」に記述 Gemfile group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] gem 'rspec-rails' gem 'factory_bot_rails' gem 'faker' end Gemを記述できたらbundle install % bundle install サーバー再起動 % rails s 導入したRspecを使用できるようにする % rails g rspec:install テストコードの結果をターミナル上で可視化するため、 .rspecに以下の記述をしましょう。 .rspec --require spec_helper #以下を記述 --format documentation この記述をすることによってテスト実行時のログがより詳細に表示される。 エラーメッセージを英語に設定 spec/rails_helper.rb # 中略 I18n.locale = "en" # RSpec.configure do |config| 〜 end の外に記載 RSpec.configure do |config| # 中略 end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails6】DockerでRails6の開発環境構築【ゼロからデプロイまで】

概要 Dockerでrails6の開発環境を作成し、簡単なCRUD機能を実装してからHerokuへとデプロイするまでの手順を紹介します。 前提条件 Dockerの各コマンドを利用できる状態になっている。 Herokuのユーザー登録が完了し、ブラウザでログインすることができる。 HerokuCLIを利用できる状態になっている。 【1】 環境構築編 必要ファイルのセットアップ 本記事ではdocker-qiita-onikiという名前でRailsアプリケーションを設定しますが、この部分はお好きなアプリケーションの名前に書き換えてください。 まずは下記のようにアプリケーションの名前でディレクトリを用意し、各種ファイルを作成します。 $ mkdir docker-qiita-oniki $ cd docker-qiita-oniki $ touch Dockerfile Gemfile Gemfile.lock docker-compose.yml entrypoint.sh 各種ファイルをテキストエディタで開いて下記のように記載してください。 ※ docker-qiita-onikiはお好きなアプリケーション名に書き換えです! 1. Dockerfile Dockerfile FROM ruby:2.7.3 ENV LANG C.UTF-8 ENV DEBCONF_NOWARNINGS yes ENV APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE yes EXPOSE 3000 RUN apt-get update -qq && apt-get install -y build-essential postgresql-client libpq-dev RUN apt-get update && apt-get install -y curl apt-transport-https wget && \ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ apt-get update && apt-get install -y yarn RUN mkdir /docker-qiita-oniki WORKDIR /docker-qiita-oniki COPY Gemfile /docker-qiita-oniki/Gemfile COPY Gemfile.lock /docker-qiita-oniki/Gemfile.lock RUN bundle install COPY . /docker-qiita-oniki COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 CMD ["rails", "server", "-b", "0.0.0.0"] 2. docker-compose.yml docker-compose.yml version: '3' services: db: image: postgres volumes: - ./tmp/db:/var/lib/postgresql/data environment: POSTGRES_HOST_AUTH_METHOD: 'trust' web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/docker-qiita-oniki ports: - "3000:3000" depends_on: - db 3. entrypoint.sh entrypoint.sh #!/bin/bash set -e rm -f /docker-qiita-oniki/tmp/pids/server.pid exec "$@" 4. Gemfile Gemfile source "https://rubygems.org" gem "rails", "~>6.1.3" ※ Gemfile.lockは記載の必要なし(空のまま) 5. 確認 これでDockerにRails環境を構築するために必要な設定は揃いました。下記のようなディレクトリ構成になったことを確認してください。 docker-qiita-oniki ├── Dockerfile ├── docker-compose.yml ├── entrypoint.sh ├── Gemfile └── Gemfile.lock コンテナのビルド 必要ファイルを用意したディレクトリでコンテナの起動準備に必要なコマンドを実行していきます。 まずはRailsの新規作成をします。 $ docker-compose run web rails new . --force --no-deps --database=postgresql --skip-bundle お馴染みのRailsファイル達が作成されたことを確認してください。 docker-qiita-oniki ├── app ├── bin ├── config ├── db ├── lib ├── log ├── public ├── storage ├── test ├── tmp ├── vendor ├── .gitattributes ├── .gitignore ├── .ruby-version ├── config.ru ├── docker-compose.yml ├── Dockerfile ├── entrypoint.sh ├── Gemfile ├── Gemfile.lock ├── package.json ├── Rakefile └── README.md 必要なファイルが揃ったことを確認したらビルドします。(時間がかかります) $ docker-compose build DBの作成 config/database.ymlにPostgreSQLに接続するための設定を追記します。 config/database.yml ~ ~ default: &default adapter: postgresql encoding: unicode # For details on connection pooling, see Rails configuration guide # https://guides.rubyonrails.org/configuring.html#database-pooling pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> ### 下記を追記 host: db username: postgres passowrd: ### 上記を追記 development: <<: *default database: docker_qiita_oniki_development ~ ~ 以下のコマンドでDBを作成します。 $ docker-compose run web rake db:create Webpackerインストール Rails6から標準装備となったWebpackerを下記のコマンドによりインストールします(時間がかかります)。 $ docker-compose run web rails webpacker:install コンテナの起動 必要な準備が揃ったので いよいよコンテナを起動してRailsのスタートページを表示していきましょう。 docker-compose up ブラウザからhttp://localhost:3000/にアクセスしてみましょう、無事にRailsのスタートページ「Yay! You’re on Rails!」が表示されましたでしょうか?確認後、起動したコンテナを停止するときは[Ctrl] + [C]を押してください。 ここまでで【1】環境構築編は終了です。お疲れ様でした。 【2】 CRUDアプリケーション作成編 せっかくRailsの環境構築をしたので簡単なCRUDアプリケーションを作成しましょう。 と言っても、本記事はあくまでDockerの環境構築とHeorkuへのデプロイがメインなのでここはRailsの便利機能scaffoldを使ってサクッと進めてしまいます。 停止している場合は、再度コンテナを起動します。 docker-compose up Rails開発を進めるためにDocker環境でRailsコマンドを入力する方法はいくつかあるのですが、今回はDocker起動用のターミナル1つ残しておき、もう一つターミナルを立ち上げるスタイルで進めたいと思います。 ターミナル上で[command] + [T]を押して2つ目のターミナルを開いてください。 この状態で、コマンド入力可能な方のターミナルで下記の2つのコマンドを入力すると、タイトル(titile)と本文(body)の要素を持つタスク(Task)を投稿できるRailsアプリケーションが出来上がります。 $ docker-compose exec web rails g scaffold Task title:string body:text $ docker-compose exec web rails db:migrate DBのマイグレーションを行なったため、一度コンテナを再起動したいと思います。 Docker起動用のターミナルの方で[Command]+[C]で停止、docker-compose upで起動、という手順で再起動をかけたあと、http://localhost:3000/tasksにアクセスしましょう。Taskを作成/編集/削除/閲覧できる機能が実装されていることが確認できるはずです。 ただし、このままだとRailsのroot(http://localhost:3000で表示されるページ)が「Yay! You’re on Rails!」のままですので、これもタスクの一覧ページにしてしましょう。 config/routes.rbを開くとscaffoldによりresources :tasksの記載があるはずなので、その上にroot 'tasks#index'を追加してください。 config/routes.rb Rails.application.routes.draw do root 'tasks#index' resources :tasks end これにより、http://localhost:3000にアクセスしてもタスク一覧のページが表示されるようになります。 ここまでで【2】CRUDアプリケーション作成編は終了です。次はいよいよHerokuへのデプロイを行っていきます。ここから先の内容に入る前にコンテナを起動している方は停止を忘れずにお願いします。 【3】 Herokuデプロイ編 それではいよいよここまで作成してきたRailsアプリケーションをHerokuへデプロイしていきます。本記事での内容は全て無料枠でできることになりますので、課金はされることはありません。 1. HerokuコンソールでCreate new app https://jp.heroku.com/にアクセスし、ご自身のHerokuコンソールへとログインしてください。画面右上の[New]というタブで[Create new app]を選択します。 お好きな名前(https://XXXXXXXXXX.herokuapp.com/←XXXXXXXXXXの部分に反映されます!)を入力し、RegionはUnited Statesを選択したまま[Create App]ボタンをクリックしてください。これでアプリケーションのデプロイ先が作成されました。 2. Postgreコンテナのアドオンを追加 作成したアプリのページでConfigure Add-onsをクリックします heroku postgresで検索し、 無料オプション(Hobby Dev - Free)でPostgreコンテナを作成します。[Submit Order Form]をクリックすると即時に作成されます。 アプリケーションのページに戻るとpostgresql-abcdef-12345(postgresql-#{数文字のアルファベット}-#{数文字の数字}の組み合わせ)というようなPostgreコンテナの識別番号が付与されているはずです。以降の作業でコマンドで使用するタイミングがありますのでどこかにメモをしておいてください。 3. HerokuにデプロイできるようにRailsの設定を編集 Heroku上でRailsが作動するようにconfig/environments/development.rbにconfig.hosts << "[Herokuに作成したAppName].herokuapp.com"という一行を加えます。 ※ hogeはご自身が作成したHerokuの名称に書き換えてください config/environments/development.rb require "active_support/core_ext/integer/time" Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. ~ ~ ~ ### 下記の一文を追加 config.hosts << "hoge.herokuapp.com" end 4. ターミナルからHerokuログイン ターミナル経由でHeroku環境に操作をできるように下記のコマンドを入力します。 $ heroku login $ heroku container:login 5. Heroku上にコンテナをPUSHする $ heroku container:push web -a hoge[※ご自身の設定したHerokuアプリ名] $ heroku container:release web -a hoge[※ご自身の設定したHerokuアプリ名] 6. Heroku上のDBを設定しマイグレーションを実行 $ heroku addons:attach postgresql-abcdef-12345 -a hoge[※Herokuアプリ名] $ heroku run rake db:migrate -a hoge[※Herokuアプリ名] ※ postgresql-abcdef-12345には「2. Postgreコンテナのアドオンを追加」でメモしたPostgreコンテナの識別番号を入れてください 7. ブラウザでデプロイしたHerokuアプリケーションを開く heroku open -a hoge[※Herokuアプリ名] ここまで問題なく作業ができていれば、開発環境と同様にタスクの閲覧・追加ができるアプリケーションがHeroku上にデプロイされているはずです、お疲れ様でした! 注意事項 無事にDockerでの開発環境作成からHerokuへのデプロイまで一気に駆け抜けることはできたでしょうか?2点だけ注意事項を書かせてください。 今回のデプロイでは、一応Herokuの無料枠に収まる設定しかしていないはずですが不安な方はデプロイの確認後、削除をお願いします。 今回のデプロイに関してはセキュリティ的な観点での考慮は一切行えておりません。実サービスとして運用される場合はよりセキュアな構成となるように設定面での見直しが必要になると思いますので、ご認識よろしくお願い致します。 最後に いかがでしたでしょうか?私自身、Webエンジニア歴が1年に満たない新参者なので技術的に至らぬ点もあるかもしれません。気になった点・ご指摘事項・ご質問など、お気軽にコメントしていただければ幸いです! 参考記事一覧 https://qiita.com/NA_simple/items/57ad10717568fea2160b https://qiita.com/kodai_0122/items/67c6d390f18698950440
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails マイグレーションでテーブル作成とデータ移行を同時に行う

概要 本番運用中プロダクトへの機能追加対応により 1つのテーブルを2つにわける必要がありました。 当然ながら既存データの移行も考慮しないといけない。 作業手順としては Railsマイグレーション機能で2つの新しいテーブル作成, SQLによるデータ移行が 考えられましたが、 今回はデータ移行もRailsマイグレーション機能で同時に行ってみました。 開発環境 Ruby 2.6.5 Rails 5.2.4 PostgreSQL 9.6 既存テーブル構成と分割後の構成 既存テーブル構成 一つのテーブルに数学スコアと英語スコアが混在 分割後の構成 数学スコアと英語スコアを別々のテーブルに分割 作業詳細 新しいテーブル作成 -> データ移行順でマイグレーションが実行されるよう、マイグレーションファイルを作成する 1. 新しいテーブル作成マイグレーションファイル 数学スコア用のRailsマイグレーション作成 class CreateResultMathScores < ActiveRecord::Migration[5.2] def change t.references :user, foreign_key: true, null: false t.float :score, null: false, comment: "数学スコア" t.timestamps end end 英語スコア用のRailsマイグレーション作成 class CreateResultEnglishScores < ActiveRecord::Migration[5.2] def change t.references :user, foreign_key: true, null: false t.float :score, null: false, comment: "英語スコア" t.timestamps end end 2. データ移行マイグレーションファイル データ移行用のマイグレーションはrails db:rollbackを考慮し def up def down で作成する ActiveRecord Modelを使ったデータ移行も可能だが、このマイグレーションの責務はデータ移行なので Modelに依存しない形にしました class DataMigrationResultScoresToResultMathScoresAndResultEnglishScores < ActiveRecord::Migration[5.2] def up ActiveRecord::Base.transaction do execute <<-SQL insert into result_math_scores ( user_id, score create_at update_at ) select user_id, math_score, now(), now() from result_scores SQL execute <<-SQL insert into result_english_scores ( user_id, score create_at update_at ) select user_id, english_score, now(), now() from result_scores SQL end end def down ActiveRecord::Base.transaction do execute <<-SQL delete from result_math_scores SQL execute <<-SQL delete from result_english_scores SQL end end end 3. 既存テーブルの削除 既存テーブル(result_scores)のDropは 今のところ削除せず、様子見の予定。 結論 Railsマイグレーションでテーブル作成とデータ移行を一緒にやらない方がいいという 意見もあるようですが、rails db:rollback を考慮したマイグレーションであれば 問題ないかと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】単一テーブル継承(STI)でDRYな設計をしよう!

単一テーブル継承とは 同じカラム設計のテーブルを、一つのテーブルにまとめて、継承することで余計なテーブルを増やさず、DRYなテーブル設計にするというもの。考え方はクラスの継承と同じです。 不使用時と使用時の比較 住所テーブルを作成し、同じ内容でユーザーの住所テーブルと、勤め先の住所テーブルを用意する。 不使用時 それぞれのテーブルとモデルを用意している。 テーブル Addressesテーブル、WorkAddressesテーブル、UserAddressesテーブル モデル Addressモデル、WorkAddressモデル、UserAddressモデル 使用時 Addressesのみ用意して、それを継承したモデルを用意している。 テーブル Addressesテーブル モデル Addressモデル、WorkAddressモデル、UserAddressモデル app/models/address.rb class Address < ApplicationRecord end app/models/work_address.rb class WorkAddress < Address end app/models/user_address.rb class UserAddress < Address end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】投稿をいいねされた順で表示する

目的 投稿をいいねされた順番で表示する。 開発環境 macOS: Big Sur Rubyバージョン: 2.6.5 Railsバージョン: 6.0.0 前提 いいね機能が導入されている。 【Rails】いいね機能の実装(非同期) 方法 方法は単純で、全投稿をいいねされた数が多い順で並びかえることが出来ればOKです! 今回はsortを使用しました! app/controllers/posts_controller.rb def index #省略 like_posts = Post.includes(:liked_users).sort {|a,b| b.liked_users.length <=> a.liked_users.length} @like_posts = like_posts.first(3) end 今回は、いいねされた数が多いTOP3投稿までを表示したかったため、@like_posts = like_posts.first(3)と記述し、 並び替えた後の最初の3投稿を取得しています。 全投稿表示する場合は、@like_posts = like_posts.first(3)の記述はいりません。 最後に 並び替えまではスムーズにできましたが、その後の3投稿のみを取得する方法に手間取りました。 探したところ、同じような記事が無かったため誰かの参考になれば幸いです。 では。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】Serviceオブジェクトとの付き合い方。

Serviceオブジェクトとは 肥大化したActiveRecordモデルを分割し、コントローラをスリムかつ読みやすくするうえで非常に有用な、Ruby on Rails開発における一種の開発パターンです。 どんな時に利用する? 複数のモデルに対するコールバックなどを含み、多くの手順で構成された複雑なアクションで、適切な定義箇所が見つからない場合 アクションから外部サービスとやりとりする場合 メリット ModelやControllerがシンプルになり、可読性が向上する 処理がservice層に切り出されている為、Modelのテストが書きやすい Webのインタフェースからビジネスロジックを切り離すことで、責任範囲が明確になる デメリット 明確なルールが存在しない チームでの認識を合わせるのが難しく、運用が難しい 本質的にServiceオブジェクトパターンそのものには、コードベースを読みやすくする力も、メンテしやすくする能力も、concernをうまく分割する手腕もない 実装例 Serviceオブジェクト class Staff::Authenticator def initialize(staff_member) @staff_member = staff_member end def authenticate(raw_password) @staff_member && @staff_member.hashed_password && @staff_member.start_date <= Date.today && (@staff_member.end_date.nil? || @staff_member.end_date > Date.today) && BCrypt::Password.new(@staff_member.hashed_password) == raw_password end end コントローラー app/controllers/staff/sessions_controller.rb class Staff::SessionsController < Staff::StaffBaseController . . . def create @form = Staff::LoginForm.new(login_form_params) if @form.email.present? staff_member = StaffMember.find_by("LOWER(email) = ?", @form.email.downcase) end if Staff::Authenticator.new(staff_member).authenticate(@form.password) if staff_member.suspended? staff_member.events.create!(type: 'rejected') flash.now.alert = 'アカウントが停止されています。' render :new else session[:staff_member_id] = staff_member.id session[:last_access_time] = Time.current staff_member.events.create!(type: 'logged_in') flash.notice = 'ログインしました' redirect_to :staff_root end else flash.now.alert = 'メールアドレスまたはパスワードが正しくありません' render action: 'new' end end . . . end 利用する上での注意点 1. 命名規則を1つに定める Serviceオブジェクトの場合、UserCreatorやUserAuthenticatorなどのように「〜or」で終わる名前を付ける方法が広く採用されています。この命名規則に従おうとすると、少し無理やり命名しなければいけない場合があります。 そのため、CreateUserやAuthenticateUserのように、コマンドやアクションを先に書く命名方法が責務が明確になりおすすめです。 どんな方法にしろ、命名規則は守りましょう。 2.Serviceオブジェクトを直接インスタンス化しない Serviceオブジェクトをインスタンス化しても、単にcallメソッドを実行する以外に実はあまり使い道がありません。 callメソッドを実行するのであれば、次のように抽象化してみましょう。 module Service extend ActiveSupport::Concern class_methods do def call(*args) new(*args).call end end end 3. Serviceオブジェクトの呼び出し方法を1つに統一する 個人的にはcallメソッドを使うのが好みですが、呼び出し方法を統一しておけば、新しいServiceオブジェクトを実装するたびに名前を考える面倒がなくなりますし、他のプログラマーは実装の詳細をチェックしなくてもServiceオブジェクトの使い方をすぐに理解できるという効用もあります。 4. Serviceオブジェクトの責務は1つとする Serviceオブジェクトにさまざまなアクションをまとめることもできますが、アクションのセットは1種類に限定することで、コードも読みやすくなり、より自然になります。 5. callメソッドの引数はシンプルに Serviceオブジェクトに2つ以上の引数が与えられる場合、引数をわかりやすくするためにキーワード引数の導入を検討するとよいでしょう。引数が1つの場合であっても、キーワード引数にしておくことで読みやすさが向上するでしょう。 UpdateUser.call(attributes: params[:user], send_notification: false) 6. Serviceオブジェクトが増えたら名前空間でグループ化する コードをうまく編成するために、名前空間で共通のServiceオブジェクトをグループ化することをおすすめします。グループ化する名前空間は、「外部サービス」や「高レベルの機能」など考えられるどんな基準で決めてもかまいません。ただし、Service Objectの命名規則や配置を読みやすい素直なものにするのが名前空間の主要な目的であることをお忘れなく。規則を1つにしていれば、適切な配置は自然に定まります。不要な選択肢を増やさないようにするのがコツです。 Serviceオブジェクトはアンチパターンなのか? Serviceオブジェクトをググってみると、「利用しない方がいい」などといった意見が意外と多かったりします。 そういった人たちの言い分は、デメリットに書いた通りです。 Serviceオブジェクトを使わないようにするには? ServiceオブジェクトではなくconcernとPOROを使ってみましょう。 インターフェイスが改善され、concernが正しく分離され、OOP原則が健全に使われるようになり、コードを把握しやすくなります。 まとめ 自由度が高く、便利な開発パターンであると同時に、デメリットも併せ持っていることで批判的な意見もありました。 実際に採用している現場もあるので、一概に「使っちゃダメ!」とは言えませんが、チーム内で相談しながら運用していけ場いいのかなと思います。 参考 Railsで重要なパターンpart 1: Service Object(翻訳) Rails:Service層を運用して良かったところ、悪かったところ Service Objectがアンチパターンである理由とよりよい代替手段(翻訳)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】いいね機能の実装(非同期)

目的 Railsで作成したアプリに非同期いいね機能を実装する。 開発環境 macOS: Big Sur Rubyバージョン: 2.6.5 Railsバージョン: 6.0.0 前提 アプリtest-appが作成されている。 【Rails】簡単な投稿アプリの作成 deviseが導入されている。 【Rails】ユーザー管理機能(devise)の導入 jQueryが導入されている。 【Rails】iQueryの導入 Font Awesomeが導入されている。 【Rails】FontAwesomeの導入 手順 はじめに ルーティングの設定 Likeモデルの作成 アソシエーションの設定 バリデーションの設定 メソッドの定義 likesコントローラーの作成 アクションの定義 いいねアイコンの表示 jsファイルの作成 はじめに 今回は非同期でのいいね機能を実装していきます。 工程は多いですが、やっていることは複雑ではないため、初学者さんでもしっかり読めばできる内容かと思います。 ルーティングの設定 それでは早速始めていきます! まずルーティングをネストさせ、設定します! routes.rb Rails.application.routes.draw do #省略 resources :posts, do resources :likes, only: [:create, :destroy] end end Likeモデルの作成 次に下記コマンドでLikeモデルを作成します。 ターミナル % rails g model like 今回はどの投稿に誰がいいねしたかを記録したいので、user_idとpost_idをカラムに追加します。 db/migrate/20XXXXXXXXXXXX_create_likes.rb class CreateLikes < ActiveRecord::Migration[6.0] def change create_table :likes do |t| t.references :user, null: false, foreign_key: true t.references :post, null: false, foreign_key: true t.timestamps end end end 記述できたらマイグレートします。 ターミナル % rails db:migrate 以上でモデルは完成です! アソシエーションの設定 続いてアソシエーションの設定です。 app/models/user.rb class User < ApplicationRecord #省略 has_many :likes, dependent: :destroy has_many :liked_posts, through: :likes, source: :post end app/models/post.rb class Post < ApplicationRecord #省略 has_many :likes, dependent: :destroy has_many :liked_users, through: :likes, source: :user end app/models/like.rb class Post < ApplicationRecord #省略 belongs_to :user belongs_to :post end これでアソシエーションの設定ができました! バリデーションの設定 次はバリデーションの設定です。 1人が1つの投稿に対して、1つしかいいねをつけられないようにします。 app/models/like.rb class Like < ApplicationRecord #省略 validates_uniqueness_of :post_id, scope: :user_id end validates_uniqueness_ofによって、post_idとuser_id の組は1組しかできないようになりました。 メソッドの定義 いいねしているか否かという条件分岐の際に使用するメソッドを定義します。 app/models/user.rb # 省略 def liked_by?(post_id) likes.where(post_id: post_id).exists? end end whereメソッドを使用し、likesテーブルにpost_idが存在しているかどうか検索をかけています。 exists?メソッドは、該当の値があればtrue、なければfalseを返すメソッドです。 likesコントローラーの作成 コントローラーを作成します。 ターミナル % rails g controller likes アクションの定義 次にアクションの定義です。 likes_controller.rb class LikesController < ApplicationController before_action :authenticate_user!, only: [:create, :destroy] before_action :post_params, only: [:create, :destroy] def create Like.create(user_id: current_user.id, post_id: @post.id) end def destroy like = Like.find_by(user_id: current_user.id, post_id: @post.id) like.destroy end private def post_params @post = Post.find(params[:post_id]) end end createとdestroyアクションを定義していきます。 find_byメソッドは、複数の検索条件を指定することができるメソッドです。 いいねアイコンの表示 次に部分テンプレートを切り替える部分にidを付与します! 具体的には「id = "like_<%= @post.id %>"」と記述することで、投稿それぞれ異なるidを付与します。 app/views/posts/show.html.erb <div id = "like_<%= @post.id %>"> <%= render partial: "likes/like", locals: { post: @post } %> </div> 部分テンプレートです。 app/views/likes/_like.html.erb <% if !user_signed_in? %> <i class="fa fa-heart like-btn"></i> <%= post.likes.length %> <% elsif current_user.liked_by?(post.id) %> <%= link_to post_like_path(post, post.likes), class: "like-link", method: :delete, remote: true do %> <i class="fa fa-heart unlike-btn"></i> <% end %> <%= post.likes.length %> <% else %> <%= link_to post_likes_path(post), class: "like-link", method: :post, remote: true do %> <i class="fa fa-heart like-btn"></i> <% end %> <%= post.likes.length %> <% end %> remote: trueを付与することでパラメーターをJS形式で送られ、createアクション後は、create.js.erbが呼び出されます。 app/assets/stylesheets/likes.scss .like-link { text-decoration: none; } .like-btn { font-size: 1em; color: #808080; } .unlike-btn { font-size: 1em; color: #e54747; } いいねした時といいねしていない時で色を変えまてます。 jsファイルの作成 いいね機能を非同期で行うためのjsファイルを作成します。 app/views/likes/creste.js.erb $('#like_<%= @post.id %>').html("<%= j(render partial: 'likes/like', locals: { post: @post }) %>"); app/views/likes/destroy.js.erb $('#like_<%= @post.id %>').html("<%= j(render partial: 'likes/like', locals: { post: @post }) %>"); これで非同期でのいいね機能が実装できたと思います。 確認してみてください! 最後に 以上で、いいね機能の実装は完了です。 長くなってしまいましたが、一つ一つ理解していけば実装できると思いますので、ぜひ試してみてください。 では。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsからHerokuにデプロイしようとしたらerror: failed to push some refs to 'https://git.heroku.com/xxx.git'というエラーが出た

開発環境 macOS Catalina Rails 6.1.3.2 Ruby 2.6.7 Herokuにデプロイしようとするとエラーが出る RailsからHerokuにデプロイした際に以下のようなエラーが... error: failed to push some refs to 'https://git.heroku.com/xxx.git' プッシュに失敗しましたと出ています。 解決方法 ブラウザからHerokuにログインし、作成したアプリケーション画面のOverviewタブの 右側にある「Latest activity」の「Build failed」となっているアクティビティの「 View build log」をクリック。 するとこのような記載がありました。 -----> Building on the Heroku-20 stack -----> Determining which buildpack to use for this app ! Warning: Multiple default buildpacks reported the ability to handle this app. The first buildpack in the list below will be used. Detected buildpacks: Ruby,Node.js See https://devcenter.heroku.com/articles/buildpacks#buildpack-detect-order -----> Ruby app detected -----> Installing bundler 2.2.16 -----> Removing BUNDLED WITH version in the Gemfile.lock -----> Compiling Ruby/Rails -----> Using Ruby version: ruby-2.6.7 -----> Installing dependencies using bundler 2.2.16 Running: BUNDLE_WITHOUT='development:test' BUNDLE_PATH=vendor/bundle BUNDLE_BIN=vendor/bundle/bin BUNDLE_DEPLOYMENT=1 bundle install -j4 Your bundle only supports platforms ["x86_64-darwin-19"] but your local platform is x86_64-linux. Add the current platform to the lockfile with `bundle lock --add-platform x86_64-linux` and try again. Bundler Output: Your bundle only supports platforms ["x86_64-darwin-19"] but your local platform is x86_64-linux. Add the current platform to the lockfile with `bundle lock --add-platform x86_64-linux` and try again. ! ! Failed to install gems via Bundler. ! ! Push rejected, failed to compile Ruby app. ! Push failed エラー分を日本語訳して抜粋すると バンドルはプラットフォーム["x86_64-darwin-19"]しかサポートしていませんが、あなたのローカルプラットフォームはx86_64-linux です。 現在のプラットフォームをロックファイルに追加するには、bundle lock--add-platform x86_64-linuxでロックファイルに現在のプラットフォームを追加して、もう一度試してみてください。 となっており、その通りに実行。 % bundle lock--add-platform x86_64-linux そして変更を忘れずにコミットする。 % git add . % git commit -m 'Add platform' 再度Herokuにデプロイ。 % git push heroku master 無事にデプロイできたー!!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】ルーティングの個人的よく使う小技集

はじめに ルーティングが無いとアプリケーションは動かないですよね。それくらい重要な要素ですが、意外と知られていない機能があるのです。それを使いこなすことで、より便利で分かりやすいルーティングを定義できると思うので、小技を習得していってください! 小技集 リソースフルなルーティングを一括定義する resources :テーブル名のような定義方法をすることで、リソースフルなルーティングを一括定義することが可能です。 resources :users ※定義されたルーティングは以下の通りです。 HTTP動詞 パス コントローラ#アクション GET /users users#index GET /users/new users#new POST /users users#create GET /users/:id users#show GET /users/:id/edit users#edit PATCH /users/:id users#update DELETE /users/:id users#destroy 単数形リソースの定義 idを必要としない(1つしかない)リソースには単数形リソースで定義することができます。 ※単数形リソースは複数形のコントローラに割り当てられます。 resource :user 名前空間を利用して用途を明確化する コントローラを名前空間によってグループ化することもできます。 namespace :admin do resources :users end ※定義されたルーティングは以下の通りです。 HTTP動詞 パス コントローラ#アクション GET /admin/users admin/users#index GET /admin/users/new admin/users#new POST /admin/users admin/users#create GET /admin/users/:id admin/users#show GET /admin/users/:id/edit admin/users#edit PATCH /admin/users/:id admin/users#update DELETE /admin/users/:id admin/users#destroy ルーティングの「concern」機能 concernを使うことで、他のリソースやルーティング内で使いまわせる共通のルーティングを宣言できます。 ※concernはルーティング内のどの場所にでも配置できます。 # concernの定義 concern :commentable do resources :comments end # 下記ルーティングと同義 resources :messages, concerns: :commentable # 上記ルーティングと同義 resources :messages do resources :comments end 名前付きルーティングを使用する :asオプションを使うと、どんなルーティングにも名前を指定できます。 指定した名前_pathで、名前付きヘルパーメソッドを呼び出せます。 get 'exit', to: 'sessions#destroy', as: :logout URLフォーマットを特定の形式に制限する :constraintsオプションを使って、動的セグメントのURLフォーマットを特定の形式に制限できます。 # 「/photos/A12345」にマッチするもののみ許可 get 'photos/:id', to: 'photos#show', constraints: { id: /[A-Z]\d{5}/ } ルーティンググロブ(ワイルドカードセ)を使う *を用いることでワイルドカードを利用できます。 get 'photos/*other', to: 'photos#unknown' コントローラを指定する :controllerオプションは、リソースで使うコントローラを明示的に指定します。 resources :users, controller: 'staffs' ※定義されたルーティングは以下の通りです。 HTTP動詞 パス コントローラ#アクション GET /users staffs#index GET /users/new staffs#new POST /users staffs#create GET /users/:id staffs#show GET /users/:id/edit staffs#edit PATCH /users/:id staffs#update DELETE /users/:id staffs#destroy 終わりに 現場でもよく使われるルーティングの技術だと思うので、「知らなかった。。」と恥をかかないように、今のうちに知っておきましょう! 参考 Rails のルーティング
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

デプロイ後、トップページは表示できるのにユーザー登録画面に行けない

環境 Ruby 2.6.5 RailsRails 6.0.3.6 状況 ユーザー登録機能をdeviseで実装したオリジナルアプリを、Herokuにてデプロイ。 アクセスすると、トップページは表示されますが、トップページから「新規登録」や「ログイン」をクリックすると、エラーが出るという状況です。 結論 Heroku上でのマイグレーションがうまくいっていないことが原因でした。 解決までの過程 ① heroku logs --tail --app <<アプリケーション名>> でログを確認 ログの最下部にstatus=500とあります。500番台は、サーバーがブラウザからのリクエストの処理に失敗した状態です。 調べてみると、DBに関わる場合が多いとわかり、heroku run rails db:migrateでもう一度マイグレーションを試みます。 ② マイグレーション中のログ内にエラーを確認 「No such column: posts.genre」 postsテーブルにgenreというカラムが存在しないことにより、 「all later migrations canceled:」 マイグレーションがキャンセルされた ということがわかりました。 ③ 問題解決のための仮定 postsテーブルを確認すると、genreカラムではなくgenre_idカラムが存在していました。 初めはgenreカラムを作成しましたが、設計を変更し、genre_idカラムに書き換えました。 そのためのファイルが以下です。 このファイルがある状態でHerokuにマイグレーションを行おうとすると、genreカラムの存在が確認できず、エラーが出ると考えました。 ④ 仮定の検証 genreカラムをgenre_idカラムに変更したファイルを削除し、再度heroku run rails db:migrateを実行しました。 ⑤ 問題の解決 マイグレーションが完了し、アプリも正常に作動しました。 まとめと疑問点 エラーは、ログからエラー内容の確認、原因の特定、修正の流れが大切だと再認識しました。 ただ今回の場合、そもそも「新規登録」や「ログイン」のボタンをクリックしても情報入力ページに行くだけなので、DBはまだ関係ないのではと疑問が残りました。 情報入力後、「登録する」や「ログインする」のボタンをクリックするとエラーになるのなら納得できるのですが・・・ この点も含め、より理解が深まるよう、今後も学習を継続したいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RSpecでログイン状態にする方法(sign_inヘルパー)

今回のテストを行うアプリの流れ ログイン ⬇️ 目標を決める ⬇️ 正常に目標が保存されたら、トップページへ遷移する RSpecでnewアクション機能の一連の流れをテスト(総合テスト)するにあたり、 ログイン状態にさせてから目的の実装を行うための方法をまとめました。 私自身が初学者のため、同じような方向けの内容となっています。 環境 Ruby 2.6.5 Rails 6.1.3.1 以下インストール済み Devise 4.8.0 RSpec 3.10.0 FactoryBot Faker 前提 devise導入後、authenticate_userを使って、 applicationコントローラーのlpアクション以外に非ログイン状態でアクセスすると、 ログイン画面にリダレクトされるようにしています。 application_controller.rb class ApplicationController < ActionController::Base #ログインしていない場合はログイン画面へ強制的に遷移する before_action :authenticate_user!, except: [:lp] before_action :configure_permitted_parameters, if: :devise_controller? ~中略~ end テストコード書き方 RSpecでDeviseを使えるようにする spec/rails_helper.rb RSpec.configure do |config| ~中略~ #下のコードを追記 config.include Devise::Test::IntegrationHelpers, type: :system end こちらをrails_helper.rbに記述することで、systemテスト時にdeviseのヘルパーを呼び出すことができます。 ※system以外のrequestなどでも同様に使用できます。 テスト用データを用意 FactoryBotを使ってユーザーのテスト用データを用意します。 今回はユーザーデータと目標データの2つが必要なので2つのデータを作りました。 spec/factories/users.rb FactoryBot.define do factory :user do nickname { 'Taro' } email { Faker::Internet.free_email } password { 'aaBB1234' } password_confirmation { password } family_name { '田中' } given_name { '太郎' } birth_day { '1999-01-01' } end end spec/factories/goals.rb FactoryBot.define do factory :goal do saving_goal { '500000' } purpose { '海外旅行を行くため' } period { '2040-01-02' } association :user end end ユーザーモデルにfamily_nameカラムなどを追加している場合は追加したカラムのデータも記述します。 emailカラムはFakerを使ってダミーデータを生成してみました。 総合テスト用ファイルを作る spec/system/goal_spec.rb require 'rails_helper' RSpec.describe 'ゴール新規登録', type: :system do before do   #テスト用データを読み込む @goal = FactoryBot.build(:goal) @user = FactoryBot.create(:user) end context '新規登録できること' do it '正しい情報を入力すればゴール新規登録ができてトップページに移動する' do # 【sign_in @user】 によって、ログイン状態にする sign_in @user visit new_goal_path fill_in 'goal_saving_goal', with: @goal.saving_goal select '2022', from: 'goal_period_1i' select '12', from: 'goal_period_2i' select '12', from: 'goal_period_3i' fill_in 'goal_purpose', with: @goal.purpose expect do find('input[name="commit"]').click end.to change { Goal.count }.by(1) expect(current_path).to eq(root_path) end  end 先ほどrails_helperにDeviseをincludeしたのでsign_inというヘルパーが利用できます! sign_inによってログイン状態を作ることができるので、ログインしている場合の総合テストが簡単に実装できるはずです。 最後に ご覧いただき、ありがとうございます。 私はこの方法で実装できましたが、うまくいかないこともあるかと思います。 sign_inヘルパーを使用しなくとも、before do~end内で行う実装方法も見つけたため、 こちらも残しておきます。参考になれば、嬉しいです。 参考 Rails RSpec コントローラーテストでログイン状態にする
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者】resources, resource, member, collection, namespaceの使い分け

先に結論 ・resources リソースのCRUDをRestfulに実現するためにRailsが用意しているメソッド [使いどころ] RailsでCRUDを実現するためのメソッド(index, show, new, create, edit, update)を使いたい時にまとめて設定できる。 ・resource recourcesと同じ。違うところは、リソースが複数できることがない時。 つまり、indexアクションがない時。 [使いどころ] リソースが複数存在する可能性がない時。 dashboardとかもある意味リソースがひとつか? ・member 個別のリソースに対して基本の7つ以外のアクションを作成できる。 [使いどころ] リソースとして切り分けるほどでもない小さい概念を扱いたい。 その時、どうしても基本アクション以外が欲しい時。 collectionとの違いは、ネストの親リソースの個別にURLが欲しい時。 ex) Tweetsのreviewなど。個別のツイートに対してレビューが存在する。 ・collection 個別のリソースに対して基本の7つ以外のアクションを作成できる。 [使いどころ] リソースとして切り分けるほどでもない小さい概念を扱いたい。 その時、どうしても基本アクション以外が欲しい時。 memberとの違いは、ネストの親リソースに対して個別のURLが必要ない時。 ex) Tweetsのsearchなど。ツイート全体に対して「探す」という機能が存在する。 ・namespace 全部やっちゃう子 Prefix, Verb, URI, Pattern, Controller#Action が全てネストされる。 scope Prefix, Controller#Actionはそのままに、URI Patternだけネストしちゃう。 scope module: Prefix, URIPatternはそのままに、Controller#Actionだけネストしちゃう。 これだけではわからないと思うので、以下で解説。 ResourcesとResourceの違い resources... 基本的なRestの機能を実現するためのアクションを自動生成 resource ... ひとつしかできる余地のないリソースに対して使用。URLにidの記述がなくなる routes.rb resource :hoge ターミナル $rails routes new_hoge GET /hoge/new(.:format) hoges#new edit_hoge GET /hoge/edit(.:format) hoges#edit hoge GET /hoge(.:format) hoges#show PATCH /hoge(.:format) hoges#update PUT /hoge(.:format) hoges#update DELETE /hoge(.:format) hoges#destroy POST /hoge(.:format) hoges#create ご覧の通り、indexがない。ひとつのリソースしか存在しない前提なので、indexは作られない。 注意ポイントは、URLはhogesではなく、hoge(単数系ルーティング)になっていること。 コントローラーはhogesのままだから間違えやすいポイント。 (基本的に、resourcesとネストさせずに単体で使うことはないかも?) member, collection member memberは、個別のリソースに対して基本の7つ以外のアクションを作成できる。 ※基本の7つとは、index, show, new, create, edit, update, delete routes.rb resources :tweets do member do get 'review' end end ターミナル Prefix Verb URI Pattern Controller#Action review_tweet GET /tweets/:id/review(.:format) tweets#review collection collectionはリソース全体に対する基本の7つ以外のアクションを作成できる。 routes.rb resources :tweets do collection do get 'search' end end ターミナル Prefix Verb URI Pattern Controller#Action search_tweets GET /tweets/search(.:format) tweets#search memberやcollectionで独自のアクションをリソースを指定して作成できますが、できるだけRestfulな設計思想に乗っかるように、7つのアクションを使うようにしましょう。 ちょっと待て、Restとは? Restとは、アプリケーションを構成するコンポーネント(Usersなど)を ・RDBMSのCRUD(Create/Read/Update/Delete) ・HTTPRequestの各メソッド(GET/POST/PUT/DELETE) に対応させて、自由に**作成、読み出し、更新、削除ができるリソースとして扱うアーキテクチャ。 RailsはRestをサポートしており、その最たる例がresourcesやresource。 以上4つのメソッドを実現するアクションとURLを自動で結びつけてくれる。 ネストしたルーティング resourcesをネストさせるとURLをネストさせた形で指定できる。 例えば、アニメはたくさんのレビューを持っていると思うので、以下のようにルーティングをネストさせるべきだろう。 route.rb Rails.application.routes.draw do  resources :anime do   resources :review  end end こうすることで、 /animes -> anime#index /animes/:id anime#show などはもちろん、 /anime/anime_id/review(GET) -> review#index /anime/anime_id/review/id(GET) -> review#show などのように、animeにネストさせた形でreviewのURLを構成することができる。 ちなみにanime_idが絶対に入っていることからmemberを使った場合と同じURLの割り振り方であると、理解できる。 namespace 基本的に、Railsのルーティングはresourcesのネストまでを使えば表現できる。 しかし、ルーティングをグルーピングしたいときは以下のnamespaceを用いる。 routes.rb resources :posts namespace :admin do resources :posts end end こうすることで、Postリソースの操作内容をadminユーザーの場合と、一般ユーザーの場合で分けることができます。これは、member, collectionでは実現できないことです。 /posts /posts/:id /admin/posts /admin/posts/:id と2種類のグルーピングができる そして、コントローラーは、ネストした形にしたものを新しく作成する。 admin/posts_controller.rb class Admin::PostsController < ApplicationController def #メソッド(CRUD) end namespace以外のグルーピング方法 rails routes コマンドで出てくる情報として、Prefix、URI Pattern 、Controller#Action がある。namespaceは全部をネストした形にしてくれる。以下のメソッドはどれかだけをネストしたい場合に使われる。 ・scope ・sope module: namespace -> Prefix、URI Pattern 、Controller#Action scope -> URI Pattern のみ scope module: -> Controller#Actionのみ(ディレクトリ構造) この対応によって使い分ければオッケーです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

valid_email2というgemについて

valid_email2とは メールの正規表現を提供するgemのことです。ドメインに MX レコードがあることをさらに検証します。 インストール方法 Gemfileに以下を記載し、budle installを実行する。 gem "valid_email2" 基本的な使い方 有効なメールアドレスであることを確認する validates :email, presence: true, 'valid_email_2/email': true 用途別使い方 ドメインに MXレコードまたはAレコードがあることを確認したい場合 validates :email, 'valid_email_2/email': { mx: true } # MXレコードがあることを厳密に検証する場合 validates :email, 'valid_email_2/email': { strict_mx: true } ドメインが使い捨て電子メールではないことを確認したい場合 validates :email, 'valid_email_2/email': { disposable_domain: true } 電子メールがサブアドレス指定されていないことを確認したい場合 validates :email, 'valid_email_2/email': { disallow_subaddressing : true } 電子メールの @ の前にドットが含まれていないことを確認したい場合 validates :email, 'valid_email_2/email': { disallow_dotted : true } 独自のカスタムメッセージの作成をしたい場合 validates :email, 'valid_email_2/email': { message : "有効なメールではありません" } 複数のアドレスをコンマで区切って許可したい場合 validates :email, 'valid_email_2/email': { multiple: true } モデル以外での使用例 address = ValidEmail2::Address.new("sample@gmail.com") address.valid? => true address.disposable? => false address.valid_mx? => true address.valid_strict_mx? => true address.subaddressed? => false 参考 公式ドキュメント
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初めてのローカル環境構築(MacでHomebrewを経由しRailsを導入する)

はじめに railsチュートリアルを1周した後に、オリジナルアプリ作成のためにローカル環境を構築しました。 チュートリアル学習時は、cloud9を使用していた為、初めての作業でした。 初学者のため、必須ではないコマンドが含まれているかもしれませんが、どうかご了承ください。 なかなか苦戦しながら環境設定をしたので、同じ境遇の方に少しでも参考になると幸いです。 もし、修正箇所がございましたら、ぜひ、お知らせください。 作業内容 railsに新規アプリ作成 MySQLの設定および導入 Git・GitHubの設定 herokuサーバにアプリをデプロイ 前提 PC仕様 OS:macOS(Catalina) 選定技術 バックエンド:ruby(バージョン:2.7.3) フレームワーク:rails(バージョン:6.1.3.2) データベース:MySQL(バージョン:8.0.25) インフラ:heroku 1.homebrewの導入 はじめに、ターミナル(私の場合はVScodeのターミナル)に入力します。 上手くいけば、homebrewが使用可能になります。 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" homebrewはAppleで提供してないツールをインストールする時に使う便利なソフトのようです。 詳しくは、下記URLを参考にしてください。 ■Homebrew公式ページ 念のため、homebrewがインストールできているか確認します。 brew -v 〜〜実行結果〜〜 Homebrew <バージョン> ここで、homebrewのバージョンが表示されれば、インストールされています。 2.Ruby,Railsのインストール まず、homebrew経由で必要なgem一式をインストールします。 (もしかすると不要かも:ここはrailsチュートリアルを参考にしました) brew install libtool libxslt libksba openssl libyaml rubyのバージョン管理ツールrbenvとrubyをインストールします。 これでrubyを土台にしてRubyGemを使って、railsが構成できる準備が整います。 brew install rbenv ruby-build rubyバージョンを指定して、インストール&コンパイルします。 rbenv install <バージョン> ここで、ターミナルで使用しているシェルを調べておきます。 echo $SHELL 〜〜実行結果〜〜 /bin/zsh 私の場合、上記通り、zshが動作しているようでした。 先程に調べたシェルに対して、rbenv初期化コードを書き込みます。 (おそらく、MacOSがCatalinaの方は、zshかと思います) echo 'eval "$(rbenv init -)"' >> ~/bin/zsh 初期化した内容をすぐに反映させます。 (ターミナルを再起動しても同じ動作となるはずです。) source .bash_profile 土台となるrubyをバージョン指定してインストールします。 (globalは全範囲で使われる設定で、localとするとディレクトリ毎で制御できるようです。) ※railsインストールする前に行います。 rbenv global <バージョン> 一度、rubyがインストールできているか確認します。 ruby -v railsのバージョンを指定して、インストールします。 (RubyGemsをもって、railsをインストールしている) gem install rails -v <バージョン> ちゃんと、railsがインストールできているか確認します。 rails -v これで、rubyとrailsがインストールできました!良かった良かった! 3.MySQLのインストールから設定まで 続いて、データベースの設定していきます。 まず、homebrewを使ってmysqlをインストールします。 brew install mysql mysqlがインストールできたか確認します。 brew info mysql さっそく、mysqlサーバーを起動させてみます。 (停止する時はstartではなくstopをコマンドとします。) mysql.server start root権限でログインします。 mysql -uroot ログインできた所で、セキュリティ設定を行います。 (現状はパスワード無しで、ログイン可能な為) mysql_secure_installation 上記コマンドを行うと、下記質問をMySQLから聞かれるので、それぞれ回答していきます。 (基本は全てy(YES)の回答で良いのかなと。) 質問内容 パスワードの設定を行うか                 → 回答:y パスワードの強度はどうするか               → 回答:0 ~ 2 (Low:0, Medium:1, Strong:2) 登録パスワードは入力してください             → 設定するパスワードを入力 入力されたパスワードで設定して良いか           → 回答:y 匿名ユーザーを削除するか                 → 回答:y リモートサーバーからrootユーザーへのアクセスを遮断するか → 回答:y テスト用のデータベースを削除するか            → 回答:y (最後)入力内容をすぐに反映するか            → 回答:y ここまでで、ちゃんとパスワード設定できているか確認します。 mysql -uroot -p 上記コマンドで「Enter password:」とプロンプト表示されれば、設定が上手くできています。 この後に、railsアプリケーションを作成するので、念のため、 homebrewでインストールされたものを一覧で確認しておきます。 brew list 開発する作業ディレクトリ(environment)を作成して、そこに移動します。 mkdir environment cd environment railsバージョンとデータベースをMySQLに指定して、アプリケーションを作成します。 rails _<バージョン>_ new <アプリケーション名> -d mysql 作成できたアプリケーションのディレクトリに移動します。 cd <アプリ名> アプリを作成すると、gemfileとdatabase.ymlが自動で編集されます。 Gemfileのdatabase欄にあるgemが「sqlite(デフォルト)」ではなく「mysql2」となっています。 アプリ名/Gemfile 〜省略〜 # Use mysql as the database for Active Record gem 'mysql2', '~> 0.5' 〜省略〜 config/database.ymlでは、usernameとpasswordが空白になっています。 そこで、先程MySQLで設定したものを記入していきます。 config/database.yml default: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: <設定したユーザー名> password: <設定したパスワード> socket: /tmp/mysql.sock development: <<: *default database: アプリ名_development test: <<: *default database: アプリ名_test production: <<: *default database: アプリ名_production username: アプリ名 password: <%= ENV['アプリ名_DATABASE_PASSWORD'] %> 設定するusernameとpasswordですが、 セキュリティ上、root権限で開発作業を行うのは望ましくないので、 後には、新規ユーザーを作った方が良いかもしれません。 MySQLの新規ユーザー設定は、下記記事を参考にさせて頂きました。 @reeenapi さん、ありがとうございました。 ■RailsのDBを(初めから| |後から)MySQLに変更する ここで、railsにDBを作成しておきます。 rails db:create 【下記は、必要に応じて導入してみてください。】 ・rubyのコード解析ツール(rubocop) rubocop導入には、下記記事が参考にさせて頂きました。 @d0ne1s さん、ありがとうございました。 ■VSCodeでRubocopを使う もし、新しくコード解析ツールなどのgemを書いていればバンドルを起動しておきます。 bundle install これでMySQLのインストールと設定が完了しました! 4.Git/GitHubの設定 ここからは、変更履歴を残すGit/GitHubの設定をしていきます。 Gitがインストールされているか、念のため、確認します。 (railsでは標準で搭載されていたはずです) git --version まず、Gitの設定(名前)をします。 git config --global user.name "名前" 続いて、Gitの設定(メールアドレス)をします。 git config --global user.email Eメールアドレス 任意で、Gitのパスワードを1日(86,400秒)保持する設定します。 git config --global credential.helper "cache --timeout=86400" 一旦、Gitの設定状況を確認します。 git config -l railsアプリケーションのディレクトリに移動しておきます。 cd <アプリ名> ここで、Gitの初期化を行う。 (.gitファイルが生成されて記録が残る) git init .gitファイルが存在することを、念のため、確認します。 (ls:現ディレクトリ内のファイル一覧を表示, -a:隠しファイルも含める) ls -a gitの現況を確認します。 (未add・未commitの状態を把握) git status 新規作成および変更を加えた全ファイルをgitのステージングエリアへ追加します。 git add -A ステージングエリアの変更分に問題がなければ、コミットします。 (-mでメッセージ付ける) git commit -m "メッセージ" これまでのcommit履歴を表示します。 (オプションの--onelineは1行表示) git log (--oneline) ■GitHub公式ページ(まず、こちらでリポジトリを作成する) 上記URLからリポジトリを作成後、GitHubと現在のGitのリポジトリを紐付けます。 git remote add origin https://github.com/GitHubアカウント名/アプリ名.git Git commitで変更したファイルをGitHubへpushします。 git push -u origin master これで、Git/GitHubの設定が完了です! 5.Herokuの設定からデプロイまで herokuに関しては、下記記事の一部を実行しています。 @murakami-mm さん、ありがとうございました。 ■Herokuへのデプロイ方法【Heroku+Rails+MySQL】 まず、herokuのインストールします。 ■heroku公式ページ(参考) brew tap heroku/brew && brew install heroku いつものように、herokuがインストールできているかの確認します。 heroku -v インストールできていれば、herokuサーバにアプリケーションを作成します。 heroku apps:create アプリ名 作成したアプリに、MySQLベースのClearDBというデータベースを適用させます。 無料ですが、容量が5MBです。詳細は下記にあります。 ■heroku(ClearDB概要) heroku addons:create cleardb:ignite 次は、設定のためにClearDBのURLを確認します。 heroku config 〜〜実行結果〜〜 === アプリ名 Config Vars CLEARDB_DATABASE_URL: mysql://<ユーザー名>:<パスワード>@<ホスト名>/<データベース名>?reconnect=true そして、上記で取得したURLを参考にして、各種設定をしていきます。 heroku config:add DB_NAME='<データベース名>' heroku config:add DB_USERNAME='<ユーザー名>' heroku config:add DB_PASSWORD='<パスワード>' heroku config:add DB_HOSTNAME='<ホスト名>' heroku config:add DB_PORT='3306' heroku config:add DATABASE_URL='mysql2://<ユーザー名>:<パスワード>@<ホスト名>/<データベース名>?reconnect=true' 再度、設定確認のコマンドを打って、ちゃんと設定できているか確認します。 heroku config 最後に、ローカルリポジトリの内容をherokuへpushします。 git push heroku master git push Heroku masterコマンドを打つと、返答メッセージの文末あたりに 自分が作ったWEBアプリのURLが参照されます。 そのURLをクリックして、本番環境(heroku)でアプリが起動できているか確認してみてください!! 終わりに Qiita記事を中心に情報収集しながら、はじめて環境構築をした内容をまとめました。 現在、railsで新規アプリを実装していますが、毎日エラーと戦ってます。笑 今後は、学習内容のアウトプットのために、投稿記事を増やせればと思っています。 拙い文章でしたが、最後までお読みいただき、ありがとうございました!!!!! それでは、また。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]field_for 配下の各オブジェクトを取得する方法

課題 fields_for配下の各オブジェクトを取得する方法を知らなかったのでメモ。 <%= form_with model: @user, local: true do |f| %> <%= f.text_field :name %> <%= f.fields_for :sales do |ff| %> <%= ff.text_field :amount, class: 'form-control' %> <% end %> <%= submit_tag 'submit' %> <% end %> 上記のようにネストされたフォームで、各子レコードを取得したい。 結論 ff.object で取れる コンソールで検証 irb(main):001:0> ff.object => #<Sale id: 1, amount: 1000 > 参考情報 fields_forの上手な使い方 https://qiita.com/kouuuki/items/5daf2b5f34273d8457f7
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む