20200227のRailsに関する記事は25件です。

Tweet app コントローラの記載ミス

投稿はできるがtext, image_urlが保存されないトラブルについて

確認して言った手順
1.投稿は完了するがビューへ反映されない。
2.ターミナルなどにはエラー文が出てこない。
3.データベースのtweetsテーブルを確認したところ保存されていない事がわかった。
4.tweets_controller.rbのcreate アクション内でbinding.pryを設け、paramsの中身を確認。

 13: def create
 => 14:   binding.pry
    15:   Tweet.create(tweet_params)
    16: end

[1] pry(#<TweetsController>)> params
=> <ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"0OetjLpGY/lhw+49IYsP6z/lYBicOBakjt7zB/n3lXPLq1ShQdX3hu5sZApmOfmY+rCNTiNpym1PH0lREvaCnw==", "tweet"=>{"image"=>"〇〇.jpg", "text"=>"test"}, "commit"=>"SEND", "controller"=>"tweets", "action"=>"create"} permitted: false>

5.image と text の情報は取得できていることがわかる。
6.次にtweet_paramsを確認すると、user_id しか取得できていないことがわかった。

[2] pry(#<TweetsController>)> tweet_params
  User Load (0.4ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 ORDER BY `users`.`id` ASC LIMIT 1
  ↳ app/controllers/tweets_controller.rb:42
=> {:user_id=>1}

7.したがって、ストロングパラメータで指定しているパラメータに不備があることがわかる。
8. ストロングパラメータにおいて、image と text も許可(permit)するように記載することで解決できる。

"tweet"=>{"image"=>"〇〇.jpg", "text"=>"test"},

※tweetの中にimageとtextが入ってるので出してあげる必要がある。
9.最後に下記の内容へ変更したら正常となった。

tweets_controller.rb
Before
def tweet_params
    {user_id: current_user.id}
 end

After
def tweet_params
  params.permit(:image, :text).merge(user_id: current_user.id)
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

複数の配列にeachメソッドを使いたい

rubyのzipメソッドを使えば可能

Ruby 2.7.0 リファレンスマニュアルよりサンプルコードを引用

p [1,2,3].zip([4,5,6], [7,8,9])
    # => [[1, 4, 7], [2, 5, 8], [3, 6, 9]]

p [1,2].zip([:a,:b,:c], [:A,:B,:C,:D])
    # => [[1, :a, :A], [2, :b, :B]]

p [1,2,3,4,5].zip([:a,:b,:c], [:A,:B,:C,:D])
    # => [[1, :a, :A], [2, :b, :B],
    #     [3, :c, :C], [4, nil, :D], [5, nil, nil]]

p [1,2,3].zip([4,5,6], [7,8,9]) {|ary|
  p ary
}
    # => [1, 4, 7]
    #    [2, 5, 8]
    #    [3, 6, 9]
    #    nil

参考

https://docs.ruby-lang.org/ja/latest/method/Array/i/zip.html

https://freesworder.net/ruby-each-multiple/

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

プログラミングスクールなら

スタート

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

bin/rails db:migrateしようとしたらMysql2::Error: Table '*****' doesn't existが出る

経緯

bin/rails g model image

bin/rials g model product

によって作られたマイグレーションファイル
20200227092210_create_images.rb
20200227092647_create_products.rbを,

20200227092210_create_images.rb
(中略)
      t.references :product, foreign_key: true
(中略)

と書いて, bin/rails db:migrate を実行

起きたエラ〜

(中略)
rails aborted!
StandardError: An error has occurred, all later migrations canceled:

Mysql2::Error: Table 'プロジェクト名_development.products' doesn't exist: SHOW FULL FIELDS FROM `products`
(中略)

というエラーが発生した。

解決策

 スペルのミスも、文法ミスも、探したけれど見つからないよ♪...
 なので、googleで検索したところ、参考になる記事を発見。

どうやら、
referencesされるマイグレーションファイル
の方が先にmigrateされるべきなようで、


20200227092647_create_products.rbを、
 →2020022709 2209_create_products.rb
  (20200227092210_create_images.rb)
にすると、images.rbよりも早くmigrateされた!!

参照したwebページ

teratail:rails db:migrateができません(おそらくmysqlのカラムがマッチしていない)

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

rails:DM送信機能を簡単に実装〜model〜

ダイレクトメッセージ機能

検索に苦労したのでDM機能について簡単に実装手順を紹介します。

前提として、DM機能の実装には
①トークルームを作成してそこで話す機能を実装する方法(中間テーブルが2つ)
②トークルームを作成しない方法
があります。

今回は②についての説明です。

ER図

スクリーンショット 2020-02-27 19.21.42.png

roomモデルを作らなくても作成できる理由は、routesで設定できます。

手順

usersモデルがある前提で書きていきます。

$ rails g model Messages content:string user:references receive_user:references

Messages modelとmigrateファイルを作成します。

完成したものがこちら

_create_messages.rb
class CreateMessages < ActiveRecord::Migration[5.2]
  def change
    create_table :messages do |t|
      t.string :content
      t.references :user, foreign_key: true
      t.references :receive_user, foreign_key: { to_table: :users }

      t.timestamps
    end
  end
end
_create_messages.rb
{ to_table: :users }

を追記しています。
これが無いと、receive_usersテーブルを参照してしまうので『そんなテーブルは存在しないよ』と言われてしまいます。
そのため、外部キーとしてusersテーブルを参照するという指定を行っています。

$ rails db:migrate

↑します。

続いて、model

models/message.rb
class Message < ApplicationRecord
  belongs_to :user
  belongs_to :receive_user, class_name: 'User'
end
models/message.rb
class_name: User 

を追加します。
これによって、receive_userがReceive_userという存在しないクラスを参照することを防ぎます。

models/user.rb
has_many :messages
has_many :sent_messages, through: :messages, source: :receive_user
has_many :reverses_of_message, class_name: 'Message', foreign_key: 'receive_user_id'
has_many :received_messages, through: :reverses_of_message, source: :user

def sent_messages(other_user, content) #メッセージを送るためのメソッド
    unless self == other_user
      self.messages.find_or_create_by(receive_user_id: other_user.id, content: content)
    end
end

これで完成です!

しっかり作成できるか $ rails cで確認することは忘れないでください?‍♂️

補足

修正依頼などあれば気軽にお申し付けください。

参考記事
https://aka-shin.com/techacademy-mentaring-09/

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

マイグレーションファイルの修正方法

マイグレーションファイルの修正方法

マイグレーションファイルのカラムをスペルミスしたときにrails db:rollbackするとマイグレーションファイルを差し戻せる
ロールバック=>修正=>マイグレートで修復できる

データベースに反映済みのマイグレーションファイルは修正してはダメ

マイグレーションファイルが既にマイグレーション済みなのかどうかは、rails db:migrate:statusで確認できる
適用されているときはupと表示され、修正するためにはロールバックする必要がある。適用されていないときは、downと表示され、そのまま修正や削除が可能
修正し終わったらrails db:migrateで実行し、修正した内容を反映される

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

ルートパスとは

ルートパス

ルートパスとはアプリケーションのアクセス先でルーティングの設定で、呼び出すアクションを指定する

root to: 'コントローラー名#アクション名'

【例】コントローラー名がpostの場合

config/routes.rb
Rails.application.routes.draw do
  root to: 'posts#index'
end

これでhttp://localhost:3000にアクセスしたらpostsのindexアクションにとぶことができる

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

ストロングパラメーター、プライベートメソッドとは

ストロングパラメーター

ストロングパラメーターとは指定したキーを持つパラメーターのみを受け取るようにする
特定のキーしか受け取れないような仕組みを構築することができる

プライベートメソッド

クラス外から呼び出すことのできないメソッド
ストロングパラメーターを記述したメソッドは、private以下に記載して、プライベートメソッドとして扱う
【例】プライベートメソッド

class PostsController < ApplicationController
   def index
     (処理) 
   end 

   def new
     (処理) 
   end 

   def create 
     (処理) 
   end 

      private 
   def private_method  # プライベートメソッド 
     params.require(`モデル名`).permit(`カラム名`)
   end 

end
def create
    Post.create(post_params)
  end

private
  def post_params
    params.require(:post).permit(:title, :text, :image)
  end

みたいな感じで記述する。privateのpost_paramsはcreateのpost_paramsに代入される

1.Classの外部から呼ばれたら困るメソッドを隔離

メソッドの中には、Classの外部から呼び出されてしまうとエラーを起こすメソッドも存在し、そのような事態を事前に防ぐことができる

2.可読性

classの外部から呼び出されるメソッドを探すときに、private以下の部分は目を通さなくて良くなり、繰り返し使用するメソッドもprivate以下に集約できますので、コードをシンプルにできる

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

【Rails】自分メモ マイグレーション実行時エラー ActiveRecord::StatementInvalid: Mysql2::Error: Table 'users' already exists: CREATE TABLE `users`

$ rails db:drop

$ rails db:create

$ rails db:migrate

こうしてDBをからにして入れ直したら直った!

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

deviseの関連ファイルが作られず、コマンドが適用されなくなった時の対処法

deviseをインストールしているとconfig/initializers/devise.rb
config/locales/devise.en.ymlファイルが生成されておらず、、さらにrails g devise userrails db:migrate:statusしても

NoMethodError: undefined method `devise' for Install (call 'Install.connection' to establish a connection):Class

というエラーが出ました。すでにuserモデル以外のデータべースは作ってあるのでrails db:migrate:statusが出来ないということはコマンドが適用されていないということだと思います。
同じ状態でbundle installからやり直しても解決出来ませんでした。

考えた仮説

このときGemfileにはちゃんとgem 'devise'と記述されていました。ターミナル履歴は以下の通りです。

#ターミナル履歴
bundle install
rails g devise:install
rails g devise user
rails db:migrate

ターミナル履歴におかしいところはない。
しかしconfig/initializers/devise.rb
config/locales/devise.en.ymlファイルがインストールがされていないので、rails g devise installに問題があるのではないか?
gemはローカルサーバーの再起動をしないと反映されないので、rails g devise installの前に再起動をしていなかったのではないか?

解決方法

調べると、rails g devise installが反映されていないとrails srails g devise userrails db:migrate:statusなど他のコマンドが競合してしまい、コマンドが反映されなくなってしまうようです。
参考記事 : https://qiita.com/ryouzi/items/9c5324ba567109ab2a22

具体的な対処法
1. もう一度bundle install
2. routes.rbにあるdevise_for :installもしくはdevise_for :usersというコードを削除する
3. 2でrailsコマンドが適用されるようになったのでrails db:rollbackでマイグレーションファイルをロールバックする。
4. rails db:migrate:statusでステータス確認
5. userテーブルやモデルが作られている場合はrails d model userで削除
6. rails g devise:install
7. rails g devise user
8. rails db:migrate

最後に

プログラミングを始めて4週間の初心者なので、その目線でわかりやすく書くことを心がけました。
わかりにくい記述、間違っているところがあればご指摘ください。
最後まで見ていただきありがとうございました。

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

ヘルパーメソッドとは、また使用する利点は

ヘルパーメソッドとは

主にviewでHTMLタグを出現させたりテキストを加工するために予めメソッドが用意される。HTMLやRubyの記述がセットになった、ビューファイルで使用できるメソッド
|:-----|:-----|
| ヘルパーメソッド | 使用用途 |
| form_for | 投稿ページなどにおけるフォームの実装 |
| link_to | リンクの実装 |
| simple_format | 投稿した文章を自動で見やすく整形する |
などがある
フォームの実装例

<%= form_for('モデルクラスのインスタンス') do |f| %>
    フォームの中身
<% end %>
<%= form_for(@post) do |f| %>
  <%= f.text_field :text %>
  <%= f.submit %>
<% end %>
<%= link_to 'リンクに表示する文字', 'リンク先のURL' %>
<%= link_to '新規投稿', '/posts/new' %>

みたいに使える

ヘルパーメソッドを使用する利点

  1. 複雑なアプリケーションにおいては、ヘルパーメソッドを使用するとコードがシンプルになる
  2. 特にフォームにおいては、ヘルパーメソッドを使用しないとセキュリティ上の問題が発生する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

dependent: :destroyをつけていても削除できなかった原因

長時間調べたが解決方法が見つからないという状態が続きました。
私のミスだったので、同じような状態の人に私の経験を伝えれればと思い書きます。

内容

紐付けしたオブジェクトを削除するために、dependent: :destroyをつけても、削除できない時に気をつけて欲しいことを書きます。

問題点

ある特定のUserのみ削除できない

モデルの関係性

UserCartが親子関係で、
CartProductCart_productsと 1対多 です。
Users ー Carts → Cart_products ← Products

class User < ApplicationRecord
  has_one :cart, dependent: :destroy
end

class Cart < ApplicationRecord
  belongs_to :user
  has_many :cart_products, dependent: :destroy
  has_many :products, through: :cart_products
end

class Product < ApplicationRecord
  has_many :cart_products, dependent: :destroy
end

class CartProduct < ApplicationRecord
  belongs_to :cart
  belongs_to :product
end

行っていたこと

関連付けられたオブジェクトもdestroy削除できるように
has_many,has_onedependent: :destroyをつけていた。

エラー文

Cannot delete or update a parent row: a foreign key constraint fails (`app_name_development`.`cart_products`, CONSTRAINT `fk_rails_a4f3e327f3` FOREIGN KEY (`cart_id`) REFERENCES `carts` (`id`))

原因

User has_one CartUser にはCartがは1つのみ紐づくようにしていたのですが、
削除できないUserには2つのCartが紐つかれていた。

そのため
User has_one CartUser has_many Cartsと変更すると削除できた。

+----+---------------------+---------------------+---------+
| id | created_at          | updated_at          | user_id |
+----+---------------------+---------------------+---------+
|  3 | 2020-02-21 07:07:57 | 2020-02-21 07:07:57 |       4 |
| 12 | 2020-02-21 07:29:37 | 2020-02-21 07:29:37 |       7 |
| 13 | 2020-02-21 07:34:26 | 2020-02-21 07:34:26 |       7 |
| 16 | 2020-02-22 07:51:47 | 2020-02-22 07:51:47 |      11 |
+----+---------------------+---------------------+---------+

今後の対処法

モデルを作成する時に、オプションとしてunique: trueを付け、userの重複をなしにする。

XXXXXXX_create_cart.rb
class CreateCarts < ActiveRecord::Migration[5.2]
  def change
    create_table :carts do |t|
      t.references :user, foreign_key: true, unique: true #ここ

      t.timestamps
    end
  end
end

結論

User has_one Cart 関係の
User に1つのみのCartが紐づくようにしていたのですが、
削除できないUserには2つのCartが紐つかれていた。

そのため、以下のようにUser has_one CartUser has_many Cartsと変更すると削除できた。

class User < ApplicationRecord
  has_one :cart, dependent: :destroy
end

⬇︎

class User < ApplicationRecord
  has_many :cart, dependent: :destroy
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsでのCookieのSameSite, Secureの対応

概要

2020年2月にChromeのバージョンが80にアップデートされました。
これはCSRFを防ぐためChromeのCookieのSameSite属性をデフォルトでSameSite=Laxにしようというものです。アップデート前はSameSite=Noneと同じ挙動をしていました。

つまりアップデート前までSameSite=Noneでないと正常に動作しないアプリケーションについては明示的にNoneを指定する必要があります。

https://developers-jp.googleblog.com/2019/11/cookie-samesitenone-secure.html
から抜粋

2月のChrome 80 以降、SameSite 値が宣言されていない Cookie は SameSite=Lax として扱われます。
外部アクセスは、SameSite=None; Secure 設定のある Cookie のみ可能になります。
ただし、これらが安全な接続からアクセスされることが条件です。

SameSiteの挙動の説明

SameSite 説明
None クロスドメインでCookieの受け渡しが可能
(ただしSecure=Trueの設定は必須のためhttpの環境だと正常に動作しないはずです)
Lax クロスドメインでGETメソッドであればCookieの受け渡しが可能だがPOSTメソッドは不可
Strict クロスドメインでGET、POSTメソッド両方ともCookieの受け渡しは不可

ただし、これはChromeでの挙動になります。

それ以外のブラウザでは違った挙動をするものがあるので注意が必要です。詳しくは下記のリンク先をご覧ください。
https://www.chromium.org/updates/same-site/incompatible-clients

この中で結構問題があると思われるのは
Versions of Safari and embedded browsers on MacOS 10.14 and all browsers on iOS 12. These versions will erroneously treat cookies marked with SameSite=None as if they were marked SameSite=Strict. This bug has been fixed on newer versions of iOS and MacOS
です。
現時点(2020/2/27)でiOS 12、MacOS 10.14はそれほど古いバージョンではなく使用しているユーザーも多いと思われます。このユーザーに対してはSameSite=Noneと設定しているとSameSite=Strictと同じ挙動をするそうです。
対応するとしたらブラウザによってcookieを書き換える必要がありそうです。

Ruby on RailsでSameSite=Noneを設定する対応例

こちらの記事ではgemでの対応が記載されているのでgemで対応したい方はこちらが良いと思います。
(rails_same_site_cookie gemで、RailsアプリにChrome 80向けのSameSite属性を指定する)

以降はRuby on Railsでの実装例を記載します。

最初にrack gemのバージョンを確認します。
rackが2.0系だとSameSite=Noneの対応が入っておらずエラーになるのでrackのバージョンアップをおこなうかRack::Utilsのモンキーパッチ以下のように作成します。
rackのバージョンアップ時に削除漏れがあるといけないので内容にTODOを記載しておきましょう。

# -*- encoding: binary -*-
# TODO: rack2.1.0以降だとsamesite=Noneの設定が入っているのでソースファイルごと削除する
module Rack
  # Rack::Utils contains a grab-bag of useful methods for writing web
  # applications adopted from all kinds of Ruby libraries.

  module Utils
    def add_cookie_to_header(header, key, value)
      case value
      when Hash
        domain  = "; domain=#{value[:domain]}"   if value[:domain]
        path    = "; path=#{value[:path]}"       if value[:path]
        max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
        # There is an RFC mess in the area of date formatting for Cookies. Not
        # only are there contradicting RFCs and examples within RFC text, but
        # there are also numerous conflicting names of fields and partially
        # cross-applicable specifications.
        #
        # These are best described in RFC 2616 3.3.1. This RFC text also
        # specifies that RFC 822 as updated by RFC 1123 is preferred. That is a
        # fixed length format with space-date delimited fields.
        #
        # See also RFC 1123 section 5.2.14.
        #
        # RFC 6265 also specifies "sane-cookie-date" as RFC 1123 date, defined
        # in RFC 2616 3.3.1. RFC 6265 also gives examples that clearly denote
        # the space delimited format. These formats are compliant with RFC 2822.
        #
        # For reference, all involved RFCs are:
        # RFC 822
        # RFC 1123
        # RFC 2109
        # RFC 2616
        # RFC 2822
        # RFC 2965
        # RFC 6265
        expires = "; expires=" +
          rfc2822(value[:expires].clone.gmtime) if value[:expires]
        secure = "; secure"  if value[:secure]
        httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
        same_site =
          case value[:same_site]
          when false, nil
            nil
          when :none, 'None', :None
            '; SameSite=None'.freeze
          when :lax, 'Lax', :Lax
            '; SameSite=Lax'.freeze
          when true, :strict, 'Strict', :Strict
            '; SameSite=Strict'.freeze
          else
            raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
          end
        value = value[:value]
      end
      value = [value] unless Array === value

      cookie = "#{escape(key)}=#{value.map { |v| escape v }.join('&')}#{domain}" \
        "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"

      case header
      when nil, ''
        cookie
      when String
        [header, cookie].join("\n")
      when Array
        (header + [cookie]).join("\n")
      else
        raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
      end
    end
    module_function :add_cookie_to_header
  end
end

修正箇所は↓の部分を追記しただけです。

when :none, 'None', :None
            '; SameSite=None'.freeze

rackが2.1系以降であれば対応されているので上記の対応は不要です。

使い方はこんな感じでいけると思います。

cookies[:hogehoge] = { value: "sample value", expires: 1.hour.from_now, same_site: "None", secure: true }

環境によってSecure=True or Falseを設定したい場合

以下のようなClassを作成します。

# frozen_string_literal: true

class SecureCookieWithSameSiteLax

  def self.secure?
    Rails.env.staging? || Rails.env.production?
  end

end

使い方はこんな感じです。

secure = SecureCookieWithSameSiteLax.secure?

cookies[:hogehoge] = { value: "sample value", expires: 1.hour.from_now, same_site: "Lax", secure: secure }

production環境、staging環境ではSameSite=Lax Secure=True
development環境ではSameSite=Lax Secure=False
としたい場合の実装方法となります。

SameSite=Noneを設定しなくても問題なく動作するアプリケーションであればLaxStrictを明示的にセットしてsecureな設定にしておくのが良いと思います。

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

RSpecを導入する

RSpecをインストール

Gemfile
group :development, :test do
  gem 'rspec-rails'
$ bundle install
$ bin/rails generate rspec:install
You can silence deprecations warning by setting the environment variable THOR_SILENCE_DEPRECATION.
   create  .rspec
   create  spec
   create  spec/spec_helper.rb
   create  spec/rails_helper.rb

.rspec(設定ファイル)、spec/spec_helper.rbspec/rails_helper.rbが生成されます。

.rspec
--require spec_helper
--format documentation #この行を追加して出力の形式を変更

binstub をインストールしてテストスイートの起動時間を速くします。

Gemfile
group :development do
  gem 'spring-commands-rspec'
$ bundle install
$ bundle exec spring binstub rspec

動作確認

$ bin/rspec

テストが走ってNo examples found.的な記述が確認できればOK。
次にrails generateコマンドを叩いた時にテスト用ファイルが自動で生成されるように修正。

config/application.rb
config.generators do |g|
  g.stylesheets false
  g.javascripts false
  g.helper false
  g.test_framework :rspec,
    controller_specs: false,
    view_specs: false,
    helper_specs: false,
    routing_specs: false
end

最後にtestディレクトリを丸ごと削除してセットアップ完了。

と思ったら、コマンドを叩くたびにthorとかいう、入れた覚えのないgemから以下のエラーが吐かれるようになってしまった。さっき追加したspringと依存関係にある、シェルスクリプトを生成するためのgemらしい。

Deprecation warning: Expected string default value for '--test-framework'; got false (boolean).
This will be rejected in the future unless you explicitly pass the options `check_default_type: false` or call `allow_incompatible_default_type!` in your code
You can silence deprecations warning by setting the environment variable THOR_SILENCE_DEPRECATION.

指示通り環境変数をセットしてエラーを非表示に。dotenvのgemを使ってます。

.env
THOR_SILENCE_DEPRECATION = true

FactoryBotをインストール

テストデータ生成用のGemFactoryBotをインストールします。

Gemfile
group :development, :test do
  gem 'factory_bot_rails'
$ bundle install

FactoryBot.create(:user)create(:user)と書けるように設定を追加。

spec/rails_helper.rb
RSpec.configure do |config|
.
.
  config.include FactoryBot::Syntax::Methods
end

Capybaraをインストール

統合テスト用のGemcapybaraと、関連するGemをインストールします。

Gemfile
group :test do
  gem 'capybara'
  gem 'webdrivers'
  # gem 'selenium-webdriver'
  # gem 'chromedriver-helper'
end

selenium-webdriverchromedriver-helperは不要なので、もし書いてあったら削除します。

$ bundle install

rails_helper.rbを開き、以下の行をコメントアウトして有効化します。

spec/rails_helper.rb
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

これでspec/supportにRSpec関連の設定ファイルを配置することができます。
最後にcapybara用の設定ファイルを作成します。

spec/support/capybara.rb
Capybara.javascript_driver = :selenium_chrome_headless

shoulda-matchersをインストール

マッチャーを拡張するgemshoulda-matchersをインストールします。

Gemfile
group :test do
  gem 'shoulda-matchers'
end

rails_helper.rbを開き、以下の記述を追加。

spec/rails_helper.rb
RSpec.configure do |config|
.
.
  Shoulda::Matchers.configure do |config|
    config.integrate do |with|
      with.test_framework :rspec
      with.library :rails
    end
  end
end

参考

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

チーム開発を始める際にチーム全員でgithubを共有する方法(rails)

チーム開発をする際にgithubを全員で共有する方法

某プログラミングスクールにて、チーム開発を始める際に全員でgithubを共有するのに苦戦したので分かりやすく丁寧にまとめたいと思います。

まず用語の説明をします。

ブラウザ上でのgithub = リモート
ブラウザ上でのgithubでのリポジトリ = リモートリポジトリ (リポジトリとは、chat-spaceなどのアプリケーションを指します。)
自分のPC = ローカル
github desktop = ローカルリポジトリ

まず簡単に流れを掴み、それから1つ1つの詳細を説明していきます。

流れ

1)チームの一人(これからオーナーと呼びます)が自分のローカルでアプリケーションを立ち上げる。
2)オーナーがそのアプリケーションを自分のgithubリモートと繋げる。
3)オーナーがチームメンバーを自分が作ったリモートリポジトリに招待する。
4)メンバーたちがそのリモートリポジトリに入り、その雛形のコードを自分のローカルにコピーする。
5)全員が何かしらの変更点を自分のエディターに書き、pushし、全員がちゃんと1つのリモートリポジトリにつながっているか確認する
6)全員がブランチを切って作業を開始する。

それでは早速初めていきます。

1)チームの一人が自分のローカルでアプリケーションを立ち上げる。

チームの一人(基本的に誰でもOK)がローカルで普段何かアプリケーションを立ち上げる時にやっているように
rails newでアプリを立ち上げます。この際、できればバージョンを指定していた方が安全なので今回は5.2.3を指定して作っていく設定で行います。

今回作るアプリの名前はsampleと言う名前で作る設定で行います。

$ rails _5.2.3_ new sample -d mysql

2)オーナーがそのアプリケーションを自分のgithubリモートと繋げる。

次に、このアプリケーションをgithubに繋げます。

$ cd sample
$ git init   //現在のsampleをgit下に置きます
$ git add .   //全てのファイルを追加します
$ git status  //ちゃんと全てのファイルが入った確認できます

すると、このように全てのファイルがgit下に入ったことが確認できます。

Screenshot from Gyazo

次に、このgit下に移動できたファイル達をgithub desktopに追加していきます。

$ git commit -m "initial commit"

そして次にgithub desktopを開きます。
すると左上に
Screenshot from Gyazo

と言う表示があるので
Current Repositoryをクリックしてください。(この時点でsampleと表示されていますが気にしなくて大丈夫です)
その後
Add ボタンをクリックし、

Add Existing Repository...

をクリックすると

Screenshot from Gyazo

このような表示が出てくるかと思います。

ここで
Choose...

をクリックし、自分が作っているsampleファイルを選択し、
Add Repository

を選択すると、

Screenshot from Gyazo

このような画面が出てくると思います。

ここで左の列の

Screenshot from Gyazo

の欄のSummary(required)のところに、initial commit と記載し、
下の青いボタンのCommit to masterが押せるようになりますので、クリックしてください。

すると

Screenshot from Gyazo

このようにローカルに変更点はもうないよ。と言う表示が出ます。

そして、右側にある

Screenshot from Gyazo

で、Pubulish repositoryをクリックしてください。

すると

Screenshot from Gyazo

この表示が出ます。

Nameはそのままで大丈夫です。

Descriptionは空欄で大丈夫です。

その下のチェックボタンはこのコードをプライベートにする。と書いていますが、
今回はチーム開発をするため、このコードを共有しないといけません。

よって、チェックボタンは外してください。

Screenshot from Gyazo
この状態が正解です。

そしてPublish Repositoryをクリックしましょう。

そうすると、github ブラウザ(リモート)に、リモートリポジトリが作成されます。

おめでとうございます。

自分のgithubブラウザに行き、確認してください。
Screenshot from Gyazo

このように自分のアプリケーションが表示できましたでしょうか。

3)オーナーがチームメンバーを自分が作ったリモートリポジトリに招待する。

次に、このリモートリポジトリにメンバーを招待します。

まずこの画面でSetting ボタンをクリックしてください

Screenshot from Gyazo

するとこの画面に飛びますので、次に

Screenshot from Gyazo

Manage accessをクリックしてください。

するとパスワードを要求されますので入力してください。

すると

Screenshot from Gyazo

このような画面があるページに飛びます。

ここでInvite a collaboratorをクリックしましょう。
すると検索画面が出てきますので、メンバー達のgithubでのアカウント名を正しく記入し、追加していきます。

追加されたメンバー達は、githubmに登録した時に使用したメールアドレスにメールが届きますので確認してください。

すると、メールには
View Invitationと書かれているのでクリックします。 (※メールはPCで閲覧しましょう)
すると、githubのページに飛びますので、そこで
Accept Invitationをクリックすると、無事、オーナーが作ったリポジトリに参加できます。

4)メンバーたちがそのリモートリポジトリに入り、その雛形のコードを自分のローカルにコピーする。

お待たせしました。
ここからはやっとメンバー達も作業開始できます。
メンバー達はそのリポジトリに行ったあと、

Screenshot from Gyazo

この
Clone or download
をクリックし、URLをコピーします。

そしてgithub desktopに行きます。

Screenshot from Gyazo
そして左上にCurrent Repsitoryをクリックします。

そして
Add
をクリックし
Clone Repositoryをクリックします。

するとこのような画面が出てきます。

Screenshot from Gyazo

ここではURLを選択してください。

Screenshot from Gyazo

するとこのようなページに変わりますので

上の欄にはgithubブラウザでコピーしたURLをペーストします。

下のLocal Pathは、このコピーしたファイルをどこに入れるかを自分で決めることができます。

これで全員のローカル環境に、オーナーが作ったローカルと同じものがリモートを通じて繋げることができました。

ここからは実際に確認作業をします。

5)全員が1つのリモートリポジトリにつながっているか確認する.

これは簡単です。一人一人がマスターブランチで適当にREAD.Meなどに記載を加え、commit、pushしてください。
すると、一人一人が変えた場所がgithub desktopのhistryに反映されているはずです。

6)全員がブランチを切って作業を開始する。

ここからは実際にコードをバリバリ書いていく作業です。
全員ブランチを切り、各々の作業をしてください。

これで作業が開始できます。

以上になります。頑張ってください!

間違っている点やご指摘もお待ちしております。

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

railsのacts-as-taggable-onで人気のタグを表示する

初心者向けのacts-as-taggable-onで人気のタグを出す方法です。
自分がアプリ作成の際、使ったので健忘録的なものです。

導入は先人の記事などがありますのでそちらをご参照ください

rails 5.2.4.1での使用です。

最も使われているタグを配列で取得する

ActsAsTaggableOn::Tag.most_used

ActsAsTaggableOn::Tag.most_used(3)

デフォルトで20件取得。
引数で指定すると指定した件数まで取得(上記の場合使われている順番に3件)。

コントローラの使いたいアクションに記載する

posts.controller.rb
@popular_tags = ActsAsTaggableOn::Tag.most_used(3)

インスタンス変数へ代入する。

渡されたインスタンス変数をView側で表示

 <span>人気のタグ:</span>
  <% @popular_tags.each do |ptag| %>
     <%= ptag.name %>
  <% end %>

上記の様にインスタンス変数をeachで取り出して表示することが出来ます。

tag.png

タグにリンクをつける場合

config/routes.rb
get 'tags/:tag', to: 'posts#index', as: :tag

ルーティングを設定して

<span>人気のタグ:</span>
<% @popular_tags.each do |ptag| %>
<%= link_to ptag, posts_path(tag_name: ptag.name) %>

上記の様にすると選択したタグのついている記事などを
抽出して表示することが出来ます。

参考

https://qiita.com/take1457a/items/90465189140ec77b14be

https://morizyun.github.io/blog/acts-as-taggable-on-gem-rails/index.html

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

アプリケーション作成で詰まった箇所

執筆中

環境

Ruby 2.6.5
Rails 5.2.4
Docker 19.03.5
bundler 2.1.4

bundler問題

別途記事

フラッシュメッセージ

表示される

user_sessions_controller.rb
redirect_back_or_to root_url, success: 'ログインしました'

表示されない

user_sessions_controller.rb
redirect_to root_path, success: 'ログアウトしました'

解決法

application_controller.rb
# 追加
add_flash_types :danger, :success, :warning

原因

調査中
そもそもadd_flash_typesの指定がないとBootstrapに対応したsuccess info warning danger の4つのキーが使用できないはずなのにredirect_back_ro_toだと表示されるのは4つのキーがデフォルトで備わっているからなのか?

文字コードがlatin1による文字化け

データベース内で文字化けするだけであって、取り出して使用する場合は文字化けしないのでとりあえず放置。

jQueryのclickイベントでdocument使用時のみ発火しない

試したこと

turbolinksが原因か?

Gemfile.lockからturbolinks削除
application.jsから読み込み削除
application.html.slimから読み込み削除
参考資料

docker-compose bundle update
docker-compose build
・コンテナの再起動
→変わらず

原因

調査中
remote: true & application.jsの//= require rails-ujs

remote: trueで送信するとrails-ujsのjQueryがなんかしてるっぽくclickイベントが使用できない。

解決法

remote: trueをやめるかclickイベントをやめる。
今回はclickイベントを使用せず実装する。

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

Rails Active_hashを攻略する

 今日の目標

acitive_hashでハッシュデータを扱う方法を学習する

active_hashとは?

gemの一種。
ハッシュのデータを、ActiveRecordと同じ感覚で使えるようにしてくれる。

どういう時に使うのか

以下の条件2つにあてはまるとき。

  • これから扱うデータがDBにデータとして保存しておくほど重要ではない、かつ、基本的に変更されない。

  • ActiveRecordと同じ感覚でデータを操作したい。

具体的に

例えば都道府県名一覧やカテゴリなど「基本的に変更されないデータ」があったとする。

そういったデータをDBに保存しておけば取り扱いは楽だけど、データの価値としてはDBに保持しておくほど重要ではない。
じゃあ ↓ の例のように定数でやるか?というとそれも微妙。
コード全体の見通しが悪くなるし、Activerecordを通して使えないので扱いづらくなってしまう。

model
TODOHUKEN = [
 1: {name: :北海道, location: [12.323245, 102.3231231]},
 .
 .
 .
]

そういう悩みを解決するのに便利なのがactive_hash。
ハッシュデータをモデルとして定義することで、
ハッシュをDBから引っ張ってきたデータと同じように扱うことができるようになる。

導入方法

Gemfileにactive_hashを記載

Gemfile
gem 'active_hash'
$ bundle install

これでおしまい

モデル作成

次にモデルを作成する。
今回作成するモデルは2つ、AddressモデルとPrefectureモデル。
まずはrails g modelでAddressモデルを作成。

$ rails g model Address prefecture_id:integer city:string

続けてdb:createとdb:migrateを実行。

$ rails db:create
$ rails db:migrate

次にPrefectureモデルを作成。
こちらはrails g modelでの作成ではなく
ActiveHash::Baseを継承したPrefectureモデルを 自作 する。
※ActiveHash::Baseを継承することで初めて、ActiveRecordのメソッド(allなど)が使えるようになる。

都道府県のデータはハッシュで配列に格納する。

app/models/prefecture.rb
class Prefecture < ActiveHash::Base
  self.data = [
      {id: 1, name: '北海道'}, {id: 2, name: '青森県'}, {id: 3, name: '岩手県'},
      {id: 4, name: '宮城県'}, {id: 5, name: '秋田県'}, {id: 6, name: '山形県'},
      {id: 7, name: '福島県'}, {id: 8, name: '茨城県'}, {id: 9, name: '栃木県'},
      {id: 10, name: '群馬県'}, {id: 11, name: '埼玉県'}, {id: 12, name: '千葉県'},
      {id: 13, name: '東京都'}, {id: 14, name: '神奈川県'}, {id: 15, name: '新潟県'},
      {id: 16, name: '富山県'}, {id: 17, name: '石川県'}, {id: 18, name: '福井県'},
      {id: 19, name: '山梨県'}, {id: 20, name: '長野県'}, {id: 21, name: '岐阜県'},
      {id: 22, name: '静岡県'}, {id: 23, name: '愛知県'}, {id: 24, name: '三重県'},
      {id: 25, name: '滋賀県'}, {id: 26, name: '京都府'}, {id: 27, name: '大阪府'},
      {id: 28, name: '兵庫県'}, {id: 29, name: '奈良県'}, {id: 30, name: '和歌山県'},
      {id: 31, name: '鳥取県'}, {id: 32, name: '島根県'}, {id: 33, name: '岡山県'},
      {id: 34, name: '広島県'}, {id: 35, name: '山口県'}, {id: 36, name: '徳島県'},
      {id: 37, name: '香川県'}, {id: 38, name: '愛媛県'}, {id: 39, name: '高知県'},
      {id: 40, name: '福岡県'}, {id: 41, name: '佐賀県'}, {id: 42, name: '長崎県'},
      {id: 43, name: '熊本県'}, {id: 44, name: '大分県'}, {id: 45, name: '宮崎県'},
      {id: 46, name: '鹿児島県'}, {id: 47, name: '沖縄県'}
  ]
end


active_hashにはbelongs_to_active_hashメソッドが用意されているので、
address.rbに このメソッドを記述し、アソシエーションを定義する。

extend ActiveHash::Associations::ActiveRecordExtensions
belongs_to_active_hash :prefecture
この2行を追加。

なお、Prefectureモデルのファイルには、アソシエーションを明示する必要はない。

これで準備は完了。

app/models/address.rb
class Address < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to_active_hash :prefecture
end

 コンソールで試してみる

$ rails c

[1] pry(main)> @address_1 = Address.create(prefecture_id: 1, city: '函館市')                                                                                       
 id: 1,
 prefecture_id: 1,
 city: "函館市",
 created_at: Tue, 08 Jan 2019 11:41:41 UTC +00:00,
 updated_at: Tue, 08 Jan 2019 11:41:41 UTC +00:00>

[2] pry(main)> @address_2 = Address.create(prefecture_id: 13, city: '新宿区新宿')                                                                                        
 id: 2,
 prefecture_id: 13,
 city: "新宿区新宿",
 created_at: Tue, 08 Jan 2019 11:42:05 UTC +00:00,
 updated_at: Tue, 08 Jan 2019 11:42:05 UTC +00:00>

##############################
[3] pry(main)> @address_1.prefecture.name                                                                                                                                
=> "北海道"
[4] pry(main)> @address_2.prefecture.name                                                                                                                                
=> "東京都"

って感じで、prefecuture_idにPrefectureモデルに定義したハッシュのidを渡して、
@address.prefecture.nameで名前を取れる。
もちろんPrefecture.find でハッシュを取ることも可能。

利点

テーブルの数を無駄に増やさない
Activerecordを継承する
扱うデータが最初の2つの条件にあてはまる場合はactive_hashを活用していきましょう。

使えるメソッド

Country.all             # => returns all Country objects
Country.count           # => returns the length of the .data array
Country.first           # => returns the first country object
Country.last            # => returns the last country object
Country.find 1          # => returns the first country object with that id
Country.find [1,2]      # => returns all Country objects with ids in the array
Country.find :all       # => same as .all
Country.find :all, args # => the second argument is totally ignored, but allows it to play nicely with AR
Country.find_by_id 1    # => find the first object that matches the id

Country.find_by_name "foo"                    # => returns the first object matching that name
Country.find_all_by_name "foo"                # => returns an array of the objects with matching names
Country.find_by_id_and_name 1, "Germany"      # => returns the first object matching that id and name
Country.find_all_by_id_and_name 1, "Germany"  # => returns an array of objects matching that name and id

使えるインスタンスメソッド

Country#id          # => returns the id or nil
Country#id=         # => sets the id attribute
Country#quoted_id   # => returns the numeric id
Country#to_param    # => returns the id as a string
Country#new_record? # => returns true if is not part of Country.all, false otherwise
Country#readonly?   # => true
Country#hash        # => the hash of the id (or the hash of nil)
Country#eql?        # => compares type and id, returns false if id is nil

注意点

検索

ActiveRecordのincludesやjoinsを使ってテーブル間の検索をするのは(たぶん)無理なので、
おとなしくmigrationファイルでActiveRecordを使用したほうが良さそう。

多階層化

gem ancestryを使った多階層カテゴリー化には向いていない

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

Before Rails Tutorial4章 ログイン機能

本投稿は講義資料であり、Rubyの基礎は理解しているが、rails tutorialで躓く読者を対象としています。
1章:環境構築
2章:Scaffold を用いた高速なアプリケーション構築 及び MVCの理解
3章:Scaffold を用いない開発方法 及び 応用
4章:ログイン機能

3章で作成した、蔵書管理アプリ bukukore にログイン機能を付与していきます。
ログイン機能は、 Devise という GEM を用いて構築していきます。

Deviseの使い方はこちら

Devise

bukukore ディレクトリ内の、Gemfile を立ち上げて下記のコードを追記してください。

Gemfile(追記)
gem 'devise'

Gemfile に追記したら、 bundle install していきましょう。

terminal
$ bundle install --path vendor/bundle

devise のインストールが完了したら、deviseを適応させていきます。

terminal
$ rails g devise:install
(省略)
===============================================================================

Some setup you must do manually if you haven't yet:

  1. Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:

       config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

     In production, :host should be set to the actual host of your application.

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root to: "home#index"

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>

  4. You can copy Devise views (for customization) to your app by running:

       rails g devise:views

===============================================================================

deviseをインストールすると、上記のようなメッセージが表示されます。
1つ目のメッセージに従い、デフォルトのURL を設定します。今回は指示通りで良いので、
config/environments/development.rb の一番下に下記のコードを追加します。

config/environments/development.rb(追加)
(省略)
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

2つ目のメッセージは、3つ目のメッセージは、のちほど追加します。
4つ目のメッセージでViewを作成するのですが、そのまえにModelをつくっておきます。
devise で使いたいのは User登録機能なので 下記のコマンドを実行します。

config/initializers/devise.rb(235行目あたりの該当箇所を書換)
config.scoped_views = true

config.scoped_views を false から true に書き換えてください。
(devise をどのフォルダにインストールするかの設定です。)
使い方はこちら

terminal
$ rails g devise User
  create   db/migrate/20200216064430_devise_create_users.rb
  create   app/models/user.rb
   route   devise_for :users

これで user.rb の他に、devise に関するマイグレーションファイルと、ルーティングが追加されています。
ルーティングは、 rails routes を実行することで確認できます。
マイグレーションファイルは、 db/migrate の中にあります。
devise では機能毎に ON / OFF 設定可能です。

モジュール名    各機能の説明
database_authenticatable データベースに保存されたパスワードが正しいかどうかの検証とを行ってくれます。またパスワードの暗号化も同時に行ってくれます。
registerable ユーザー自身がアカウント登録、編集、削除することを許可します。
recoverable パスワードをリセットできるようにし、メールで通知します。
rememberable 30日間ログインしたままにするというような永続ログインを可能にします。ログイン画面の下のチェックボックスにチェックすることで永続ログインを有効化できます。
trackable ユーザーのサインイン回数や、サインイン時間、IPアドレスなどを記録できるようにします。
validatable Emailやパスワードのバリデーションを可能にします。独自に定義したバリデーションを追加することもできます。
confirmable メールに記載されているURLをクリックして本登録を完了する、といったよくある登録方式を可能にします。また、サインイン中にアカウントが認証済みかどうかを検証します。
lockable 一定回数ログインを失敗するとアカウントをロックします。ロック解除にはメールによる解除か、一定時間経つと解除するといった方法があります。
timeoutable 一定時間活動していないアカウントのログインを破棄します。
omniauthable TwitterやFacebookなどのSNS認証を追加したい場合に使用します。
db/migrate/2020(省略)_devise_create_users.rb
# frozen_string_literal: true
class DeviseCreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      t.integer  :sign_in_count, default: 0, null: false
      t.datetime :current_sign_in_at
      t.datetime :last_sign_in_at
      t.string   :current_sign_in_ip
      t.string   :last_sign_in_ip

      ## Confirmable
      t.string   :confirmation_token
      t.datetime :confirmed_at
      t.datetime :confirmation_sent_at
      t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      t.string   :unlock_token # Only if unlock strategy is :email or :both
      t.datetime :locked_at


      t.timestamps null: false
    end

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    add_index :users, :confirmation_token,   unique: true
    add_index :users, :unlock_token,         unique: true
  end
end

上記のようにすべての機能をコメントアウトしておきます。(講義終了後に、興味ある学生が拡張して遊べるための配慮)
今回はメール認証を行いたいので Confirmable を使用します。
user.rb もすべての機能が使用できるように変更しておきます。

app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable,
         :confirmable, :lockable, :timeoutable
end

マイグレーションファイル を変更したので適応させましょう。
マイグレーションファイルの実行は下記のコマンドでしたね。

terminal
$ rails db:migrate
== 20200220202020 DeviseCreateUsers: migrating ================================
-- create_table(:users)
   -> 0.0021s
-- add_index(:users, :email, {:unique=>true})
   -> 0.0008s
-- add_index(:users, :reset_password_token, {:unique=>true})
   -> 0.0010s
== 20200220202020 DeviseCreateUsers: migrated (0.0041s) =======================

rails db:migrate した後は、 rails sever を再起動しておいてください。

マイグレーションを実行することでデータベースの中に、usersテーブルが作成されます。
4つ目のメッセージに書いてあるように、Devise が使用するView(アカウント作成関連) も作っていきます。

terminal
$ rails g devise:views user
  app/views/devise/shared/_links.html.erb (リンク用パーシャル)
  app/views/devise/confirmations/new.html.erb (認証メールの再送信画面)
  app/views/devise/passwords/edit.html.erb (パスワード変更画面)
  app/views/devise/passwords/new.html.erb (パスワードを忘れた際、メールを送る画面)
  app/views/devise/registrations/edit.html.erb (ユーザー情報変更画面)
  app/views/devise/registrations/new.html.erb (ユーザー登録画面)
  app/views/devise/sessions/new.html.erb (ログイン画面)
  app/views/devise/unlocks/new.html.erb (ロック解除メール再送信画面)
  app/views/devise/mailer/confirmation_instructions.html.erb (メール用アカウント認証文)
  app/views/devise/mailer/password_change.html.erb (メール用パスワード変更完了文)
  app/views/devise/mailer/reset_password_instructions.html.erb (メール用パスワードリセット文)
  app/views/devise/mailer/unlock_instructions.html.erb (メール用ロック解除文)

上記コマンドを実行することで、 Devise の機能により、ユーザー作成関連の View が自動生成されます。
この時点でアカウント作成画面などにアクセスは可能ですが、メール認証機能は実装していないのでアカウントは作れません。
http://localhost:3000/users/sign_up

いまのうちに、新規登録、ログインへのリンクを用意しておきましょう。

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>Bukukore</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <% if user_signed_in? %>  
      <%= link_to 'ユーザー編集', edit_user_registration_path %> |  
      <%= link_to "ログアウト", destroy_user_session_path, method: :delete %>  
    <% else %>  
      <%= link_to "新規登録", new_user_registration_path %> |  
      <%= link_to "ログイン", new_user_session_path %>  
    <% end %>  
    <%= yield %>
  </body>
</html>

メール認証

Google 二段階認証

config/initializers/devise.rb の21行目あたりにある
config.mailer_sender を自分の gmail アドレスに変更します。

config/initializers/devise.rb
config.mailer_sender = '[自分のアドレスに置き換えます]@gmail.com'  

最後に開発環境の設定ファイルの最後に以下を追加します。

config/environments/development.rb(追記)
(省略)
ActionMailer::Base.smtp_settings = {  
  address: 'smtp.gmail.com',  
  port: 587,  
  user_name: '[自分のアドレスに置き換えます]@gmail.com',  
  password: '先程取得した二段階認証のアプリパスワード',
  authentication: 'plain',  
  enable_starttls_auto: true  
}  

以上でメール認証処理は完了です。

「こんなの自力でわかるわけない!自力開発なんてできない」
と思うかもしれませんが、GEMの使い方はリファレンスにすべて書いてあるのでご安心ください。
今後、リファレンスを読む癖、調べる癖をつけていきましょう。
https://github.com/heartcombo/devise#getting-started
https://qiita.com/gakkie/items/6ef70c0788c3cbff81ee

実際にユーザー登録を行い、メールが届くことを確認できます。
http://localhost:3000/users/sign_up
届いたメールに記載されている URL をクリックすることで、認証完了です。
rails server のログにも URL が書いてあるので、そちらをコピペしても認証可能です。

どういう仕組みで、メールの認証やログイン・ログアウトが行われているのか詳しくは
https://railstutorial.jp/
で学べます。

ユーザーの一覧と詳細表示ページ

Userの一覧をみるページ、詳細情報をみるページを作成していきましょう。
Model はすでに作っているので、 Controller と View を作成します。

terminal
$ rails g controller Users index show

2つ目のメッセージにあったルート設定もここでしておきましょう。
ユーザーの一覧ページ(users#index)をTOPページにしたいので、下記コードを追加します。

config/routes.rb(追加)
(省略)
  root to: 'users#index'

これで、root(最初のページ)にアクセスがあった時に、usersControllerのindexアクションを呼び出します。
http://localhost:3000/

課題 MVCの復習

1, User Model を使い、users テーブルから全データを取り出し
2, user index ページに表示してみましょう。
3, user show ページにユーザー情報を表示してください http://localhost:3000/users/show/1

ヒント: Model は Controller に記述します。
一度何も見ずにやってみてみると理解度チェックになります。
曖昧な場合は、前章の内容を確認してください。

1の解答例はここをクリックすると表示されます
app/controllers/users_controller.rb
class UsersController < ApplicationController
  def index
    @users = User.all
  end

  def show
  end
end

2の解答例はここをクリックすると表示されます
app/views/users/index.html.erb
<h1>ユーザーの一覧</h1>

<% @users.each do |user| %>
  <p><%= user[:id] %></p>
  <p><%= user[:email] %></p>
<% end %>

3の解答例はここをクリックすると表示されます
app/controllers/users_controller.rb
class UsersController < ApplicationController
  def index
    @users = User.all
  end

  def show
    @user = User.find(params[:id])
  end
end
config/routes.rb(書き換え)
#   get 'users/show'(コメントアウト)
  get 'users/show/:id', to: 'users#show'
app/views/users/show.html.erb
<h1>ユーザーの情報</h1>

<p><%= @user.id %></p>
<p><%= @user.email %></p>

新しいカラムの追加方法

現在 id,email と2つのカラムがあります。これだけでは誰か分かりづらいので
名前を登録するために、 name カラムを追加してみましょう。

terminal
$ rails g migration AddNameToUsers name:string

migration コマンドのまとめはこちら

上記のコマンドで、usersテーブルに name カラムを追加するためのマイグレーションファイルが自動生成されます。

db/migrate/2020(省略)_add_name_to_users.rb(自動生成)
class AddNameToUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :name, :string
  end
end

マイグレーションファイルを実行します。

terminal
$ rails db:migrate
== 20200222020202 AddNameToUsers: migrating ===================================
-- add_column(:users, :name, :string)
   -> 0.0017s
== 20200222020202 AddNameToUsers: migrated (0.0018s) ==========================

users テーブルに、nameカラムを追加することができました。

ストロングパラメータに name を許可する

しかし、追加しただけでは保存できないようになっています。
これは rails が自動的行っているセキュリティ対策の為です
ストロングパラメータについて

保存できるようにするためには、ストロングパラメータへの追加が必要です。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :permitted_parameters, if: :devise_controller?

  private

  def permitted_parameters
    devise_parameter_sanitizer.permit :sign_up, keys: [:name]
    devise_parameter_sanitizer.permit :account_update, keys: [:name]
  end

end 

permitted_parameters アクションの中を見てください。
devise_parameter_sanitizer.permit 【許可する場所】, keys: 【許可するカラム名】
というようにして、許可を与えます。

before_action は、このファイルが呼び出されたときに最初に実行されるものです。

これで新たに、名前を保存できるようになりました。

課題

1, 登録画面に、名前を登録するためのフォームを追加してください。
   http://localhost:3000/users/sign_up
2, 実際に名前を登録し、 show ページで名前を表示してください。
   http://localhost:3000/users/show/1 (必要があればidは登録した番号に変更してください)

BookテーブルとUserテーブルの関連付け

続いて、2章で学習したテーブルの関連付けの復習をやっていきます。
詳細ページに、そのユーザーが登録した、本の情報を表示していきましょう。

http://localhost:3000/users/show/1 ←ユーザーの詳細ページ

http://localhost:3000/books/new ←本の情報登録ページ
http://localhost:3000/books/show/1 ←登録内容を確認

課題

ユーザーの詳細ページに、登録した本の情報が表示されるようにしてください。

解答例はここをクリックすると表示されます
app/models/user.rb(1行追加)
class User < ApplicationRecord
   has_many :books

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable,
         :confirmable, :lockable, :timeoutable
end
app/models/book.rb
class Book < ApplicationRecord
  belongs_to :user
end
app/controllers/users_controller.rb
class UsersController < ApplicationController
  def index
    @users = User.all
  end

  def show
    @user = User.find(params[:id])
    @books = @user.books
  end
end
app/views/users/show.html.erb
<p><%= @user.id %></p>
<p><%= @user.email %></p>
<p><%= @user.name %></p>

<% @books.each do |book| %>
  <p>本のタイトル: <%= book.title %></p>
  <p>著者: <%= book.author %></p>
<% end %>

https://bbbbruno.qrunch.io/entries/wcNZqbMwA5SRBqQj

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

バリデーションエラーの表示とredirect_toとrenderの違い

flashメッセージは表示されるけど、バリデーションエラーが表示されなくて困っているのでteratailに質問を投げてみたところ、エラーメッセージを表示させるにはrenderを使うというご指摘をいただいたので、備忘録として記事に致します。

before

questions.controller.rb
class QuestionsController < ApplicationController

  def new
    @questions = Question.all
    @question = Question.new
  end

  def create
    @question = Question.new(question_params)
    begin
    @question.save!
      flash[:notice] = '投稿に成功しました'
      redirect_to new_question_path
    rescue
      flash.now[:alert] = '投稿に失敗しました'
      redirect_to new_question_path
    end
  end

  private
  def question_params
    params.require(:question).permit(:title, :detail)
  end

after

questions.controller.rb
def new
   @questions = Question.all
   @question = Question.new
 end

 def create
    @questions = Question.all
    @question = Question.new(question_params)
    begin
      @question.save!
      flash[:notice] = '投稿に成功しました'
      redirect_to new_question_path
    rescue
      flash.now[:alert] = '投稿に失敗しました'
      render :new
    end
  end

private
  def question_params
    params.require(:question).permit(:title, :detail)
  end

beforeでは例外処理になった場合に

redirect_to new_question_path

によって、newアクションまで遡ってしまっています。遡ってしまった結果、モデルのインスタンスが失われてしまうので、save!で格納されたエラーメッセージも無かったことになるみたいです。 更新ボタンを押した処理に近いです。

afterではリダイレクトしない場合もnewと同じ情報が必要なので、beginの前はnewと同じコードを入れています。

rescue
 flash.now[:alert] = '投稿に失敗しました'
 render :new

ここをrender :newとすることで、リダイレクトせずにnewのviewファイルを表示しています。こうすることでモデルのインスタンスも失われないので、エラーメッセージも無くなりません。

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

【Rails】Slackへの通知をActionMailerのようなerbテンプレートを使う形で実装する

以下のGemを利用してSlackで通知を実装する際ActionMailerのようにerbを使ってviewを分けられないかと検討した。

https://github.com/stevenosloan/slack-notifier

app/views/admin_slack_notifier/receive_message_from_user.text.erb
【<%= @subject %>】

■□–––––––––––––––––––––□■
▼送信者情報
<%= @message.user.name %>

▼送信内容
<%= @message.content %>

■□–––––––––––––––––––––□■

app/libs/slack_notifier.rb
module SlackNotifier
  CONFIG = YAML.load_file(Rails.root.join('config', 'slack.yml'))[Rails.env]

  class << self
    def post(to, text, options = {})
      options = options.symbolize_keys
      dry_run = options.key?(:dry_run) ? options[:dry_run] : CONFIG['dry_run']
      post_options = format_options(to, text, options)
      notifier = Slack::Notifier.new CONFIG['webhook_url']
      notifier.post post_options unless dry_run
    end

    private

    # rubocop:disable Metrics/AbcSize
    def format_options(to, text, options)
      post_options = {
        'channel' => (options[:channel].presence || CONFIG[to.to_s]['channel']),
        'icon_emoji' => (options[:icon_emoji].presence || CONFIG[to.to_s]['icon_emoji']),
        'username' => (options[:username].presence || CONFIG[to.to_s]['username']),
        'text' => text
      }
      post_options[:title] = options[:title] if options.key?(:title)
      post_options
    end
    # rubocop:enable Metrics/AbcSize
  end
end


app/libs/admin_slack_notifier.rb
module AdminSlackNotifier

  class << self

    def receive_message_from_user(message)
      @message = message
      @subject = 'ユーザーより新しいメッセージが送信されました'
      post_with_template 'admin_channel', __method__, binding
    end

    private

    def post_with_template(to, view_name, binding)
      erb = Rails.root.join('app', 'views', name.underscore, "#{view_name}.text.erb").read
      text = ERB.new(erb).result(binding)
      SlackNotifier.post to, text
    end
  end
end

config/slack.yml
development: &default
  dry_run: true
  username: &username Admin
  webhook_url: WEBHOOK_URL
  admin_channel:
    channel: admin_channel
    icon_emoji: ":crystal_ball:"
    username: *username
staging:
  <<: *default
  dry_run: false
production:
  <<: *default
  dry_run: false
test:
  <<: *default

以下のような形でAction名と同様のviewを習得してSlackの通知を送信することができる。

sample.rb
    AdminSlackNotifier.receive_message_from_user(message)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ネストしたものに削除機能導入

gorupの中のmessageに削除機能導入

ネストの中のmessageに削除機能導入するさい躓いたので備忘録として書きます。

routes.rb
  resources :groups, only: [:new, :create, :edit, :update, :show] do
    resources :messages, only: [:index, :create, :destroy]
  end
messages_controller.rb
  before_action :set_group
...
  def destroy
    message = Message.find(params[:id])
    message.destroy
    redirect_to group_messages_path(@groups)
  end
...
  def set_group
    @groups = Group.find(params[:group_id])
  end

rails routes
Image from Gyazo

問題点

躓いた箇所は下記のlink_to後ろの記述
このままではどのグループのどのメッセージなのか指定できてない

messages/index.html.erb
      <%= link_to "/messages/#{message.id}", method: :delete do%>

下記のようにメッセージの前にどのグループかを指定することで解決

messages/index.html.erb
      <%= link_to "/groups/#{@groups.id}/messages/#{message.id}", method: :delete do%>

rails routesをした時にURI Patternを見ておく!!!

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

Ruby on Rails チュートリアル学習記録 第3章

第2章については特に書く内容がなかったため学習記録は付けませんでした

3.1

heroku createを実行するとbash: heroku: command not foundというエラーメッセージ。
https://qiita.com/RyuGotoo/items/7d44f7aa51f7c90ad0b7
↑こちらの記事参考に
nvm install node
npm install -g heroku-cli
を実行後、あらためてheroku createを実行すると上手くいった。

3.2

特になし

3.3

特になし

3.4

特になし

3.5

特になし

3.6

特になし

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

rails g model/migrationの違い

rails g model...

main.rb
rails g model モデル名 カラム名:データ型...

モデル名とテーブルとデータができる

rails g migration...

main.rb
rails g migration add_user_id_to_posts

postsテーブルにuser_idカラムを加える

rails g model
rails g migration
はいずれも
rails db:migrate を実行する必要あり

rails db:migrate

main.rb
rails db:migrate

これがないとデータベースに反映されません

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

chat-spaceを作ろう②

TOPページを作成する

コントローラーを作成し、ルートを設定する

ターミナル
rails g controller messages
messagesコントロラーと、views/messagesディレクトリが作成される
controller/messages_controller.rb
def index
end
topページ用のindexアクションを定義する
routes.rb
root "messages#index"
とりあえず、TOPページを表示する為のルートを設定する

ビューファイルを作成する

views/messagesディレクトリ内に以下のファイルを作成する

  • index.html.haml (indexアクションに対応したビューファイル)
  • _side_bar.html.haml (TOPページの左サイド部分)
  • _main_chat.html.haml (TOPページのメイン部分)
views/messages/index.html.haml
.wrapper
side_barmain_chatの要素を囲むコンテナ(特に意味はない)
  = render "shared/side_bar.html.haml"
  = render "main_chat.html.haml"
  部分テンプレートを呼び出す

今日ここまで
次回はアイコンを使用できる様にします
見本サイト

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