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

Ruby on Railsで新規アプリケーションを立ち上げる自称最速手順(github連携)

目的

新規のアプリケーションを作成する際、
毎回「あれ?次どうするんだっけ?」ってなって調べている時間がムダなので、
最低限のものを最速で作る手順を残しておきます。
細かいことにこだわらず、とにかくサッと作成したいときに参考にしてください。
githubにも連携します。

前提

マークアップはhaml,scssを使用
リセットcssはyui3を使用
ローカルでビューファイルを開くところまでを記載

1.Railsのバージョン確認

ターミナル
% cd ~
% ruby -v

バージョンの変更が必要な場合は以下のコマンドを実行
(--version=以降は適宜指定してください。)

ターミナル
% gem install rails --version="5.0.7.2"
% rbenv rehash

2.アプリケーションの作成

ターミナル
% cd
% cd projects
% rails _5.0.7.2_ new <アプリケーション名> -d mysql
% cd <アプリケーション名>
% rails db:create
% git init

3.GitHubとの連携

GitHub DesktopのCurrent Repositoryタブ
→Add
→Add Existing Repository
→Chooseから ~ projects/<アプリケーション名>を選択
→Add Repository
→コミット名をinitial commitとし、Commit to master
→Publish repository
→Publish repository
→GitHubのリポジトリに反映していればOK

4.ルーティング/コントローラー/ビュー の設定

routes.rb
Rails.application.routes.draw do
  root to: 'posts#index'
end
ターミナル
% rails g controller posts
posts_controller.rb
class PostsController < ApplicationController
  def index    
  end
end
Gemfile
#最下部に追加
gem "haml-rails"
ターミナル
% bundle install

app/assets/stylesheetsフォルダ内で以下を実行

application.cssを削除する
application.scssを新規作成する
posts.scssのファイル名を _posts.scssに変更する
_reset.scssを新規作成する
ダウンロード/yui3-3.18.1/src/cssreset/css/cssreset.cssのコードを_reset.scssに貼り付ける

application.scss
@import "reset";
@import "posts";

app/views/postsフォルダ内で以下を実行

index.html.hamlを新規作成

index.html.haml
hello

確認

ターミナル
% rails s

ローカルでhelloと表示されれば完了

以上です。
最後まで読んでいただきありがとうございました。

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

【Rails】パスワードの2回入力は不要。1回にしてマスキング解除機能を実装【jQuery】

環境

Ruby on Rails 5.2.4(gem 'devise'は不使用)
⇒ gem 'font-awesome-sass'を使用
jquery-rails 4.3.5
haml 5.1.2

前提

Webアプリケーションのユーザ新規登録画面で、確認の為にパスワードを2回入力させる事ってよくあると思うんですよ。
Railsの機能的にもpasswordとpassword_confirmationが合致しないとユーザ登録できない仕様になっていますし。

これに関してはpassword用の入力フォームを1つ用意し、
userをsaveさせる前にpasswordの値をpassword_confirmationにも代入する事で、確認用のpassword_confirmationフォームを省くことが可能です。

users_controller.rb
# 色々省略
  def create
    @user = User.create(user_params)
    @user.password_confirmation = user_params[:password] #ここでpasswordの値を代入する
    if @user.save
      session[:user_id] = @user.id
      redirect_to mypage_path
      flash[:notice] = "新規登録が完了しました"
    else
      flash[:alert] = @user.errors.full_messages
      redirect_to root_path
    end
  end

  private

    def user_params
      params.require(:user).permit(:name, :email, :password, :password_confirmation)
    end

ただ、このままだとユーザが新規登録の時に想定と異なる値を入力してしまった場合、間違いが分からないので再ログインができなくなりますよね?
ましてやその後のパスワードの変更手続きって面倒じゃないですか?

という疑問を感じたので調べてみた所、ちょっと古いですが下記記事でこの件によるユーザビリティの低下が示されてました。
https://uxmilk.jp/18568

じゃあどうするのって話ですが、上の記事内にもこの記事のタイトルにもありますが、マスキング(*マークでパスワードを非表示させる機能)を解除できる機能を作ればいいんですね。

完成形はこんな感じ
パスワードマスキング.mov.gif

解決策

それっぽく見えるものなら何でもいいですが、今回はfontawesomeの目を使います。
こんなフォームがあったとします。

新規登録ビュー
-# 色々省略
.signup
  .signup-top
    新規登録
  .signup__contents
    = form_with(model: @user, url: users_path, local: true) do |f|
      -# ユーザアカウント
      .form-group
        = f.label :アカウント名, class: "form-title"
        %br
        = f.text_field :name, class: 'form-control'
        %br          
      -# メールアドレス
      .form-group
        = f.label :メールアドレス, class: "form-title"
        %br
        = f.email_field :email, class: 'form-control'
        %br
        -# パスワード
      .form-group
        = f.label :パスワード, class: "form-title"
        %br
        = f.password_field :password, class: 'form-control' #ここでマスキングが機能している
        .form-password
          = icon("fas","eye-slash") #目のアイコン。これのクリックで制御する
        %br
      .form-submit
        = f.submit "Signup", class: "btn-submit"

このpassword_field部分がChromeの検証では下記画像のように表記されています。
スクリーンショット 2020-03-22 20.40.52.png
この赤枠で囲われているtype="password"によってマスキングが動いているんですね。という事で、jQueryで目のアイコンをクリックした時にtype="text"に変更する制御を与えましょう。
また分かりやすくする為、ついでに目のアイコンも一緒に切り替えます。

signup.js
  // パスワード入力フォームのスラッシュ目アイコンをクリックするとpassのマスキングを解除
  function eyeslashClick(){
    $(".fa-eye-slash").on("click", function(){
      $(".fa-eye").off("click") // offを設定しないとクリックした回数分だけ重複作動してしまう為
      let input = $(this).parents(".form-group").find("input");
      input.attr("type", "text"); // typeをpasswordに変更しマスキングを解除する
      $(this).removeClass(); // 目を消す
      $(this).addClass("fas fa-eye"); // アイコンをスラッシュのない目に置き換える
      eyeClick(); // 追加したfas fa-eyeにクリックイベントを付与
    });
  }
  // パスワード入力フォームの目アイコンをクリックするとpassをマスキング
  function eyeClick(){
    $(".fa-eye").on("click", function(){
      $(".fa-eye-slash").off("click") // offを設定しないとクリックした回数分だけ重複作動してしまう為
      let input = $(this).parents(".form-group").find("input");
      input.attr("type", "password"); // typeをpasswordに変更しマスキングを有効にする
      $(this).removeClass(); // 目を消す
      $(this).addClass("fas fa-eye-slash"); // アイコンをスラッシュのある目に置き換える
      eyeslashClick(); // 追加したfas fa-eye-slashにクリックイベントを付与
    });
  }
  // 目アイコンのクリックイベント呼び出しをデフォルト化
  eyeslashClick();
  eyeClick();

addClassで追加するだけでは置き換わった目のアイコンのクリックイベントが有効にならないので、忘れず付与しましょう。

総評

マスキングの解除を実装してもユーザが確認せず登録したら意味ないだろって?
そんなの知らぬ( ´_ゝ`)

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

7つのアクションのみのインクリメンタルサーチ

インクリメンタルサーチと7つのアクションのみのインクリメンタルサーチとの相違点

主に編集するファイル場所の違いと、それによるリクエストパスの違いになります。
1.検索に使用するコントローラーとビューの「名前」「場所」が異なる
2.構造が異なるため、ルーティングも異なる
3.ルーティングが異なるため、検索のリクエストパスが異なる
などの違いがあります。

コントローラーとビューの「名前」「場所」が異なる

7つのアクションのみで検索機能を実装した場合、searches_controller.rbの作成にnamespaceを利用します。
そのため、コントローラーが格納されているディレクトリの構造が変わります。
app/controllers/tweets/searches_controller.rb
加えて、作成したsearches_controller.rbのindexアクションに対応するビューは、
app/views/tweets/searches/にindex.html.erbとして格納されます。
これらの「namespaceを利用したことによる構造と名前の違い」があります。

検索のリクエストパスが異なる

ルーティングが変更されるため、検索機能を呼び出すリクエストを送信する処理を記述するJSファイルで、指定するリクエストパスを変更します。

jQueryの導入

jQueryにgem 'jquery-rails'が導入されていなければ導入します。
【例】

gem 'jquery-rails'

ターミナル

bundle install

【例】

app/assets/javascripts/application.js
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
// vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery  #追加
//= require rails-ujs
//= require activestorage
//= require turbolinks
//= require_tree 

textカラムにインデックスを設定します。
ターミナル

$ rails g migration AddIndexToTweets
db/migrate/20XXXXXXXXXXXX_add_index_to_tweets.rb
class AddIndexToTweets < ActiveRecord::Migration
  def change
    add_index :tweets, :text, length: 32
  end
end

ターミナル

$ rails db:migrate

ルーティングなどAPI側の準備

アクションの中でHTMLとJSONなどのフォーマット毎に条件分岐するため、respond_toを使用します。
【例】

app/controllers/tweets/searches_controller.rb
class Tweets::SearchesController < ApplicationController
  def index
    @tweets = Tweet.search(params[:keyword])
    respond_to do |format|
      format.html
      format.json
    end
  end
end

app/views/tweets/searches/のディレクトリにindex.json.jbuilderファイルを作成します。

app/views/tweets/searches/index.json.jbuilder
json.array! @tweets do |tweet|
  json.id tweet.id
  json.text tweet.text
  json.image tweet.image
  json.user_id tweet.user_id
  json.nickname tweet.user.nickname
  json.user_sign_in current_user
end

テキストフィールドの作成

【例】

index.html.erb
<%= form_with(url: search_tweets_path, local: true, method: :get, class: "xxxx") do |form| %>
  <%= form.text_field :keyword, placeholder: "投稿を検索する", class: "yyyy" %>
  <%= form.submit "検索", class: "zzzz" %>
<% end %>
<div class="aaaa">
  <% @tweets.each do |tweet| %>
    <%= render partial: "tweet", locals: { tweet: tweet } %>
  <% end %>
  <%= paginate(@tweets) %>
</div>

テキストフィールドが入力されるたびにイベントが発火させる

app/assets/javascripts/のディレクトリにsearch.jsファイルを作成します。

app/assets/javascripts/search.js
$(function() {
  $(".yyyy").on("keyup", function() {
    var input = $(".yyyy").val();
  });
});

イベント時に非同期通信できるように

【例】

app/assets/javascripts/search.js
$(function() {
  $(".yyyy").on("keyup", function() {
    var input = $(".yyyy").val();
    $.ajax({   #追加〜
      type: 'GET',
      url: '/tweets/searches',
      data: { keyword: input },
      dataType: 'json'
    })   #〜追加
  });
});

非同期通信の結果を得て、HTMLを作成

非同期通信の結果を元にビューを生成します。
【例】

app/assets/javascripts/search.js
$(function() {
  $(".yyyy").on("keyup", function() {
    var input = $(".yyyy").val();
    $.ajax({
      type: 'GET',
      url: '/tweets/searches',
      data: { keyword: input },
      dataType: 'json'
    })
    .done(function(tweets) {   #追加〜
      $(".contents.row").empty();
      if (tweets.length !== 0) {
        tweets.forEach(function(tweet){
          appendTweet(tweet);
        });
      } else {
        appendErrMsgToHTML("一致するツイートがありません");
      }
    })   #〜追加
  });
});

tweetsに投稿の情報が入っている場合のappendTweet関数と、
tweetsに投稿の情報が入っていない場合のappendErrMsgToHTML関数を定義します。
記述内容は同じです。
【例】

_tweethtml.erb
<div class="content_post" style="background-image: url(<%= tweet.image %>);">
  <div class="more">
    <span><%= image_tag 'arrow_top.png' %></span>
    <ul class="more_list">
      <li>
        <%= link_to "詳細", tweet_path(tweet.id), method: :get %>
      </li>
      <% if user_signed_in? && current_user.id == tweet.user_id %>
        <li>
          <%= link_to '編集', "/tweets/#{tweet.id}/edit", method: :get %>
        </li>
        <li>
          <%= link_to '削除', "/tweets/#{tweet.id}", method: :delete %>
        </li>
      <% end %>
    </ul>
  </div>
  <%= simple_format(tweet.text) %>
  <span class="name">
    <a href="/users/<%= tweet.user_id %>">
      <span>投稿者</span><%= tweet.user.nickname %>
    </a>
  </span>
</div>

【例】

app/assets/javascripts/search.js
$(function() {

  var search_list = $(".aaaa");   #追加〜

  function appendTweet(tweet) {
    if(tweet.user_sign_in && tweet.user_sign_in.id == tweet.user_id){
      var current_user = `<li>
                            <a href="/tweets/${tweet.id}/edit" data-method="get" >編集</a>
                          </li>
                          <li>
                            <a href="/tweets/${tweet.id}" data-method="delete" >削除</a>
                          </li>`
    } else {
      var current_user = ""
    }

    var html = `<div class="content_post" style="background-image: url(${tweet.image});">
                  <div class="more">
                    <span><img src="/assets/arrow_top.png"></span>
                    <ul class="more_list">
                      <li>
                        <a href="/tweets/${tweet.id}" data-method="get" >詳細</a>
                      </li>
                      ${current_user}
                    </ul>
                  </div>
                  <p>${tweet.text}</p><br>
                  <span class="name">
                    <a href="/users/${tweet.user_id}">
                      <span>投稿者</span>${tweet.nickname}
                    </a>
                  </span>
                </div>`
    search_list.append(html);
  }

  function appendErrMsgToHTML(msg) {
    var html = `<div class='name'>${ msg }</div>`
    search_list.append(html);
  }   #〜追加

  $(".yyyy").on("keyup", function() {
    var input = $(".yyyy").val();
    $.ajax({
      type: 'GET',
      url: '/tweets/searches',
      data: { keyword: input },
      dataType: 'json'
    })
    .done(function(tweets) {
      search_list.empty();
      if (tweets.length !== 0) {
        tweets.forEach(function(tweet){
          appendTweet(tweet);
        });
      } else {
        appendErrMsgToHTML("一致するツイートがありません");
      }
    })
  });
});

エラー時の処理

app/assets/javascripts/search.js
$(function() {

  var search_list = $(".aaaa");

  function appendTweet(tweet) {
    if(tweet.user_sign_in && tweet.user_sign_in.id == tweet.user_id){
      var current_user = `<li>
                            <a href="/tweets/${tweet.id}/edit" data-method="get" >編集</a>
                          </li>
                          <li>
                            <a href="/tweets/${tweet.id}" data-method="delete" >削除</a>
                          </li>`
    } else {
      var current_user = ""
    }

    var html = `<div class="content_post" style="background-image: url(${tweet.image});">
                  <div class="more">
                    <span><img src="/assets/arrow_top.png"></span>
                    <ul class="more_list">
                      <li>
                        <a href="/tweets/${tweet.id}" data-method="get" >詳細</a>
                      </li>
                      ${current_user}
                    </ul>
                  </div>
                  <p>${tweet.text}</p><br>
                  <span class="name">
                    <a href="/users/${tweet.user_id}">
                      <span>投稿者</span>${tweet.nickname}
                    </a>
                  </span>
                </div>`
    search_list.append(html);
   }

  function appendErrMsgToHTML(msg) {
    var html = `<div class='name'>${ msg }</div>`
    search_list.append(html);
  }

  $(".yyyy").on("keyup", function() {
    var input = $(".yyyy").val();
    $.ajax({
      type: 'GET',
      url: '/tweets/search',
      data: { keyword: input },
      dataType: 'json'
    })
    .done(function(tweets) {
      search_list.empty();
      if (tweets.length !== 0) {
        tweets.forEach(function(tweet){
          appendTweet(tweet);
        });
      } else {
        appendErrMsgToHTML("一致するツイートがありません");
      }
    })
    .fail(function() {   #追加〜
      alert('error');
    });   #〜追加
  });
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

direnv(linux)

はじめまして

みなさんはじめまして。本日やっと問題解決し、雄叫びをあげましたkyonです。
備忘録として初投稿いたします。
どうぞよろしくお願いいたします。

環境

CentOS Linux release 8.1.1911 (Core)
mysql Ver 8.0.17 for Linux on x86_64 (Source distribution)
Visual Studio Code バージョン: 1.43.1

タイトルにもありますとおりlinuxでdirenvを使いたく、以下URLを参考とさせていただき、
参考URL Linuxでdirenvを使う
.envrcファイルを作成し、Railsプロジェクトの /binディレクトリにパスを通すところまですんなりとできましたが、その後ずっとコイツと戦っておりました。

Access denied for user 'ENV['MYSQL_DATABASE']'@'localhost' (using password: YES)

多分一週間くらい戦ったと思います。
調べれば直接パスワードをdatabase.ymlへ記述すればいいことは知っていましたが、先々のことを考えた結果direnvを使うことにしました。
が、しかしどうしても分からず。。

.envrc
export 'DATABASE_USERNAME'="root"
export 'DATABASE_PASSWORD'="password" ←mysqlのパスワード

。。。。。?

database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  username: ENV['DATABASE_USERNAME']
  password: ENV['DATABASE_PASSW0RD']

。。。。。。。。??

ただ調べていくうちに.envrcファイルは合ってそうだな、と思っていてdatabase.ymlのusernameとpasswordの書き方がずっと不明でした。

今日ふとdatabase.yml の管理方法いろいろ
こちらのURLに辿り着き、問題解決したと言うことです。

問題解決

database.yml には <%= ... %> で Ruby コードを埋め込むことができます。

そしてdatabase.ymlを以下のように記述しました。

database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV['DATABASE_USERNAME'] %>
  password: <%= ENV['DATABASE_PASSWORD'] %>

埋め込んでなかったんですね。
お恥ずかしい。

駄文ですが最後まで読んでいただき、ありがとうございました。
これを機に投稿していきたいと思います。
kyonでした。

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

【Rails】いいね機能の実装

現在絶賛作成中のポートフォリオでいいね機能を実装する際、色々躓いてしまったので反省もかねてメモしとこうと思います。

環境

  • Rails 5.2.2
  • mysql 8.0.17
  • macOS

実現したいこと

Twitterのようないいね機能の実装。

そのために必要な処理

いいねボタンを押下した場合、

  • 「いいねしたユーザーのid:user_id」と「いいねされた投稿のid:post_id」が「中間テーブル:likesテーブル」に保存される。
  • いいねボタンが「いいね追加」と「いいね削除」で切り替わる。

必要手順

  1. 中間テーブルの作成
  2. テーブルの紐付け
  3. ルーティングの設定
  4. コントローラー側の処理
  5. ビューでの表示処理

大体こんな感じです。

では順番に見ていきましょう。

中間テーブルの作成

まずは、「いいねしたユーザー」と「いいねされた投稿」を保存するテーブルの作成からです。

$ rails g model Like user:references post:references

referencesを指定することで、外部キー制約が自動で付きます。
非常に便利。

問題なければこのまま

$ rails db:migrate

しちゃいましょう。

テーブルの紐付け

お次はモデルでhas_manyを使ってテーブルを紐付けます。

これに関しては、
【Rails】投稿とユーザーの紐付け(一対多)のメモ
でも似たような記事を書きましたので、復習になります。

post.rb
has_many :likes
has_many :users, through: :likes

def liked_by?(user)
  likes.where(user_id: user.id).exists?
end

いいねされた投稿に対して、いいねしたユーザーは複数いるのでhas_manyを使います。

ここで使われているthroughは、likeテーブルを経由して直接userテーブルと繋げるためのものです。

また、

post.rb
def liked_by?(user)
  likes.where(user_id: user.id).exists?
end

この子は「すでにいいねしたかどうか」を判断するためのメソッドで、後にビュー側で必要となってきます。

一応簡単に説明すると、
likesテーブルの「いいねしたユーザー:user_id」カラムにuser.idが存在するのか探すという処理です。

引数userにはビューでcurrent_userを指定して入れます。

user.rb
has_many :likes, dependent: :destroy
has_many :like_posts, through: :likes, source: :post

ユーザー側でもしてることはほとんど変わりません。
一点気をつけるとすれば、

user.rb
has_many :like_posts, through: :likes, source: :post

の部分です。

多くの関連記事でlike_postsのところがpostsになっていたのですが、もし他に has_many :posts があるなら名前が被らないようにしましょう。

railsが混乱してエラーになってしまうので、like_postsのように名前が被らないように注意することをおすすめします。

ルーティングの設定

お次はルーティングです。

routes.rb
resources :posts do
  post 'add' => 'likes#create'
  delete '/add' => 'likes#destroy'
end

postのidをとってくるためにネストします。
中は普通にルーティング設定してあげればおkです。

コントローラー側の処理

ここまできたらあともう少し。

処理を書くためにコントローラーを作成します。

$ rails generate controller likes

コントローラーができたら以下の処理を記述します。

likes_controller.rb
class LikesController < ApplicationController
    before_action :authenticate_user!
    before_action :set_like

    def create
        user = current_user
        post = Post.find(params[:post_id])
        like = Like.create(user_id: user.id, post_id: post.id)
    end
    def destroy
        user = current_user
        post = Post.find(params[:post_id])
        like = Like.find_by(user_id: user.id, post_id: post.id)
        like.delete
    end

    private
    def set_like
        @post = Post.find(params[:post_id])
    end
end

create/destroyアクションで共通する処理は以下になります。

likes_controller.rb
user = current_user
post = Post.find(params[:post_id])
  • いいねするユーザーであるcurrent_userを変数userに格納
  • いいねされた投稿のidとPostテーブルのidが一致するものをfindで見つけて変数postに格納

createアクション

createアクションでは、いいねされた場合の処理を記述します。

likes_controller.rb
like = Like.create(user_id: user.id, post_id: post.id)

Likeテーブルに、
user_idが、先ほどcurrent_userを格納した変数userのidで、post_idが、いいねされたPostテーブルのidを格納した変数post
のデータをcreateで作成する処理です。

destroyアクション

destroyアクションでは、いいねが取り消された場合の処理を記述します。

likes_controller.rb
like = Like.find_by(user_id: user.id, post_id: post.id)
like.delete

find_byで、user_idがcurrent_userのidと一致するもの且つ、post_idがいいねされたpostのidと一致するものを探して、変数likeに格納します。

そしてdeleteメソッドでlikeを削除。

set_post

地味に一番下のところに、

likes_controller.rb
private
def set_like
    @post = Post.find(params[:post_id])
end

とありますが、こちらはjsのところで必要となってきますのでひとまずおいといてください。

ビュー側の処理

index.html.erbでは投稿一覧を表示しています。
この投稿一つ一つにいいねぼたんを表示させたいわけですが、そのためにはいくつか階層を分ける必要があります。

とりあえず、いいねボタンを表示したいところに以下のように記述しましょう。

index.html.erb
<div id="like-btn-<%= post.id %>">
   <%= render 'likes/like', post: post %>
</div>

renderを使用することで、いいねだけを表示するviewを表示します。

post: post は、いいねだけを表示するviewのpostにpost(ここでは@postsをeachで回してます)の情報を入れるでというくらいの意味あいだと理解して問題ないかと。

例えば、postの情報とは違った情報も渡したいのであれば、それに適応したものを渡してあげたらおk(私の場合はブックマーク一覧のviewでも同じようにいいねできるようにしたかったので、別にviewを作成してブックマークされている投稿の情報を格納したlikeを post: like として設定していました)。

_like.html.erb

「いいねだけを表示するview」である_like.html.erbを作成します。

app/views/likes/_like.html.erb
<% if post.liked_by?(current_user) %>
   <%= link_to(post_add_path(post), method: :delete, remote: true, id: :"like-button-#{post.id}") do %>
       <i class="fa-lg fas fa-heart icon-btn liked"></i>
   <% end %>
<% else %>
   <%= link_to(post_add_path(post), method: :post, remote: true, id: :"like-button-#{post.id}") do %>
       <i class="fa-lg fas fa-heart icon-btn not-like"></i>
   <% end %>
<% end %>

if post.liked_by?(current_user)

まずこのif文は、先ほどmodelで定めたメソッドを使ってすでに現在のユーザーがいいねしてるかどうかを判定します。

  • すでにいいねしてる場合 → link_toはmethod: :delete
  • まだいいねしてない場合 → link_toはmethod: :post

となるわけですね。

remote: true

link_toに記述しているremote: trueですが、この子はコントローラーに値を送信する役割を果たし、ajaxを発火してくれる優秀な子です。
ajaxを利用することで、画面全体をリダイレクトする必要がなく、いいねアイコンの部分のみ更新してくれるので、アプリ全体の負担が減ります。

では最後に、remote: trueで呼び出すためのjsファイルを作ってあげましょう。

create.js.erb/destroy.js.erb

_like.html.erbと同階層に次の二つのファイルを作成します。

create.js.erb
$('#like-btn-<%= @post.id %>').html("<%= escape_javascript(render partial: "likes/like", locals: { post: @post }) %>");
destroy.js.erb
$('#like-btn-<%= @post.id %>').html("<%= escape_javascript(render partial: "likes/like", locals: { post: @post }) %>");

内容はどっちも一緒です。

先ほどコントローラー側で設定したset_likeで、いいねされた投稿のidを取得して、それをいいねアイコンのidとしています。

ここまでできたら、いいねボタンの完成です!

あとはいいねしたときと外した時でスタイルを変更するなど、創意工夫してください。

まとめ

いいね機能はほとんどのアプリで使うと思うので、結構情報があったのですが、いまいち仕組みが理解ができなくて結構苦戦しました。
特に中間テーブルへの保存で時間を取られたと思います。

できる範囲で細かく書いたので、誰かの助けになれば幸いです。

ではでは。

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

bundle install あれこれ、、、、(☝︎ ՞ਊ ՞)☝︎

railsで便利な機能を加えるために使われるコマンド

bundle install

は、今まで何気なく使っていましたが、今回の、デプロイ作業の際に発生したエラーにより深く知る事ができました。。。

簡単にbundle installのあれこれについて簡潔に解説します

bundler

依存関係を持っている複数のものを一括でインストールしてくれるというものだ

gemfile

bundle installをする際の設計図的な機能を持つファイル

Gemfile.lock

gemfile.lockは実際にインストールした結果図

bundle install

gemfile・Gemfile.lock の内容を踏まえて必要な情報をインストールするコマンド

bundle update

gemfileの内容を踏まえて、情報をアップデートするコマンド
また、その更新された情報ををGemfile.lockに記述します

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

Unknown action [初心者備忘録]

Unknown action

railsでアプリを作成していたところ、初めて見るエラーが発生しました。
結果からいうとコードの誤記という恥ずかしいイージーミスでしたので、反省と忘れないために書きたいと思います。

エラー発生

Users::RegistrationsControllerのnew_credit_cardアクションを実行したところ以下のエラーが発生しました。

Unknown action
The action 'new_creditcard' could not be found for Users::RegistrationsController

なるほど、アクションがないのか。
ということで、RegistrationsControllerを確認します。

解決

以下がコントローラーで当該のアクションを呼び出してる箇所とroutes.rbです。

uses/registrations_controller.rb
render :new_credit_card
config/routes.rb
get 'creditcards', to: 'users/registrations#new_creditcard'

しっかりアクション名を書き間違えていました。
ということで、これを修正することでエラーが直りました。

おわり

今回はアクション名がルーティングとコントローラーで不一致だったためエラーが起きましたが、呼び出したいアクションをコントローラーのprivate以下に書いた場合でも、今回と同じエラーが起きるそうです。
最後まで見ていただきありがとうございました。

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

【Rails】バリデーションによるエラーメッセージ表示の手順

はじめに

Railsで、バリデーションによるエラーメッセージの表示をしようとしたら
エラーが出てしまい、調べてみたらとても初歩的なミスを犯していたので
忘れないよう、エラーメッセージ表示の手順を、自分用のメモみたいな感じでまとめました!

手順

「新規投稿」を例にとることにします。

まず、新規投稿が行われる手順から
順を追って、バリデーションによるエラーメッセージの表示方法について説明していきます。


1.まずは、modelフォルダの中のpost.rbファイルの中で、バリデーションを設定します。
これによってバリデーションに弾かれたデータには自動でエラーメッセージが取得されます。

models/post.rb
class Post < ApplicationRecord

  validates :content, presence: true,
                      length: {maximum: 140}

end



2.「new.html.erb」で投稿ボタンを押すと、form_tagで指定したURL("/posts/create")へデータが送信されます

new.html.erb
 <%= form_tag("/posts/create") do %>

   <textarea name="content"> </textarea>
   <input type="submit" value="投稿">

 <% end %>



3.送信されたデータをルーティングで取得し、createアクションへ

routes.rb
post "posts/create" => "posts#create"



4.送信されて来たデータの保存が完了した場合→("/posts/index")画面へ。
バリデーションに弾かれ、保存されなかった場合→再び("/posts/new")画面へ転送する。

posts_controller.rb
 def create
   @post = Post.new(content: params[:content])
     if @post.save
       redirect_to("/posts/index")
     else
       render("posts/new")
     end
 end


この後、データベースに保存された全ての投稿データを取得し、繰り返し処理によりビューで表示することで、投稿が一つずつ表示されるわけですが

私は、バリデーションに弾かれたエラーメッセージの取得が、手順3の段階でされるのだと思っていました。

それゆえ、以下のように3の手順で取得した@postから、エラーメッセージを表示させようとしたら

new.html.erb
 <% @post.errors.full_messages.each do |message| %>

    <%= message %>

 <% end %>

undefined method `errors' for nil:NilClass
といったように、エラーメッセージが定義されてませんと怒られてしまいました…ごめんなさい…。


何故取得できないのかというと、2の手順でバリデーションに弾かれたデータは
newアクションへ転送され、newアクションで取得する必要があるからでした。

つまり、弾かれたデータは直前のアクションに送信されます。
しかし、それ以前に「new.html.erb」で「createアクションで定義した変数」を使えるわけないのですから当然でした……

ですので、以下のように、newアクションで変数を定義してあげることで
ここに、バリデーションで弾かれたデータが作成されるようになります。

posts_controller.rb
  def new
    @post = Post.new
  end



あとは、「new.html.erb」で、エラーメッセージを表示するためのコードを書いてあげれば
ちゃんとエラーメッセージが表示されるようになるはずです。

new.html.erb
 <% @post.errors.full_messages.each do |message| %>
    <%= message %></p>
 <% end %>

参考

【Rails】バリデーションのエラーメッセージを表示する
Progate

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

クイズアプリにおけるデータベース設計のアンチパターン

想定する読者

  • ポートフォリオとしてクイズ系アプリを作成している方

アンチパターン例 3つ

例えば4択クイズを作りたいというとき、以下のようなDB設計を思いつくかもしれません。
※筆者が思いつきました。
これらは明確なアンチパターンです。
何故アンチパターンかというと、仕様変更に弱いからです。

part1 マジックナンバーカラム

id question choice1 choice2 choice3 choice4 answer_number
1 次のうち偶数なのは? 3 7 8 1 3
2 「令和」はどう読む? れいわ へいせい しょうわ たいせい 1

仕様変更さん「○×クイズもできるようにしたい〜〜選択肢を2つに減らすだけだし簡単ですよね?選択肢も4つだけじゃなくて、5つとかもあり得るかも♪」

教訓: カラム名にマジックナンバーが入ってたらアンチパターンの合図

part2 フィールドに配列ぶちこみ

id question choices answer_number
1 次のうち偶数なのは? [3, 7, 8, 1] 3
2 「令和」はどう読む? [れいわ, へいわ, しょうわ, たいせい] 1
3 今は平成か? [○, ×] 2
4 次のうち一番大きい数字はどれか? [5, 10, 9, 8, 4, 1] 2

仕様変更さん1「よーし、検索機能を入れることになりました!検索機能って基本的な機能なので簡単ですよね?選択肢の中の文でも検索がかけられるようにしたいです♪」

仕様変更さん2「特定の選択肢は後で編集ができるようにしたいですね〜〜ユーザーさんって誤字とかしちゃうこともあるじゃないですか♪」

教訓: フィールドに配列を入れたくなったらアンチパターンの合図

part3 テーブルを分けて1対多にする(が、分けきれてない)

questionsテーブル

id content answer_number
1 「令和」はどう読む? 1
2 今は平成か? 2

choicesテーブル

id content question_id
1 れいわ 1
2 へいせい 1
3 しょうわ 1
4 たいせい 1
5 2
6 × 2

仕様変更さん「複数の答えを用意できるようにしたいです! この複雑な世の中で、答えって一つじゃないと思うんです♪」

何がダメだったのか: answer_numberはどちらかというとchoicesテーブルにあるべき情報だと思います。関心の範囲と、テーブルの範囲を合致させるように気をつけると、仕様変更による影響が少なくできると思います。

(おそらく)正しい設計

questionsテーブル

id content
1 「令和」はどう読む?
2 次のうち偶数のものはどれか?

choicesテーブル

id content is_answer question_id
1 れいわ true 1
2 へいせい false 1
3 しょうわ false 1
4 たいせい false 1
5 11 false 2
5 12 true 2
5 15 false 2
5 99 false 2
5 18 true 2

アンチパターンと上から目線で言いながら、これが最適かと言われれば微妙です笑
ただし、現役のエンジニアさんに見せたら「良いんじゃない?」って言われたんで、そこまで悪くない設計だとは思います。
マサカリ頂けると有難いです。

ちなみにRailsだったら、どんなmigrationファイル作ればいいの

$ ruby -v
ruby 2.6.5
$ rails -v
Rails 5.2.4
db/migrate/時間_create_questions.rb
class CreateQuestions < ActiveRecord::Migration[5.2]
  def change
    create_table :questions do |t|
      t.text :content, null: false # 問題文の中身
      t.references :user, foreign_key: true # クイズを作った人を想定

      t.timestamps
    end
  end
end
db/migrate/時間_create_choices.rb
class CreateChoices < ActiveRecord::Migration[5.2]
  def change
    create_table :choices do |t|
      t.text :content, null: false
      t.boolean :is_answer
      t.references :question, foreign_key: true

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

【Ruby on Rails】railsでやってみたいこと。

rails入門参考動画

Ruby on Rails 5入門(ドットインストール)

やりたいこと(目標)

  • 1.基本的なcrud作成 (create,show,index,delete)
  • 2.DBのselect結果を反映したセレクトボックスの作成
  • 3.入力データと登録済みデータの比較(登録済みデータが存在すれば、 エラーメッセージを出して登録させない)
  • 4.恐竜登録ツールのrails版の作成
  • 5.恐竜画像登録ツールのrails版

rails_try1.jpg

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

[ancestry]編集画面でカテゴリー表示、選択

フリマアプリの作成で編集画面に遷移した時、出品時に設定したカテゴリーを親(parent)、子(child)、孫(grandchild)まで表示させたい

※出品機能が完了した程で進めます

完成イメージ

Image from Gyazo

コントローラー

items_controller.rb
  before_action :set_item

~~省略~~

  def edit

    grandchild_category = @item.category
    child_category = grandchild_category.parent


    @category_parent_array = []
    Category.where(ancestry: nil).each do |parent|
      @category_parent_array << parent.name
    end

    @category_children_array = []
    Category.where(ancestry: child_category.ancestry).each do |children|
      @category_children_array << children
    end

    @category_grandchildren_array = []
    Category.where(ancestry: grandchild_category.ancestry).each do |grandchildren|
      @category_grandchildren_array << grandchildren
    end

  end

~~省略~~

  def set_item
    @item = Item.find(params[:id])
  end

edit内
上2行は孫と子のレコードを取得してます。

下3セットは親、子、孫と配列を作り、親はnameを、子と孫はancestryを格納してます。

ビュー

edit.html.haml
  .main-items
    = form_with model: @item, local: true do |f|
~~ 省略 ~~
      .wrapper.category-wrapper
        = f.label :category_id , class: 'wrapper__label category-wrapper-label', id: "wrapper__label--category" do
          カテゴリー:
          %span.required ※必須
          .category-wrapper-box
            .category-wrapper-select
              .category-wrapper-select__box
                = f.select :parent_name, @category_parent_array, {selected:@item.category.parent.parent.name}, { class: 'category-wrapper__category form-control', id: 'parent_category'}
            .category-wrapper-select#children_wrapper
              .category-wrapper-select__box
                = f.select :child_id, options_for_select(@category_children_array.map{|b| [b.name, b.id, {data:{category: b.id}}]}, {prompt: "指定なし", selected: @item.category.parent.id}),{}, {class: 'category-wrapper__category form-control', id: 'child_category'}
            .category-wrapper-select#grandchildren_wrapper
              .category-wrapper-select__box
                = f.select :category_id, options_for_select(@category_grandchildren_array.map{|b| [b.name, b.id, {data:{category: b.id}}]}, {prompt: "指定なし", selected: @item.category.id}),{}, {class: 'category-wrapper__category form-control', id: 'grandchild_category'}

各要素コントローラーで配列格納したインスタンス変数を使用

selectタグのselected:で遷移直後の表示を指定

子と孫はoptions_for_selectを使用してます。
これにより親要素を変更した際、指定なしとなり、カテゴリーを選択できるようにしてます。

孫のidをitemテーブルに紐つけてカテゴリーを判断しているので、他でわかりやすくなるよう:caregory_idと指定してます。

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

docker-composeでrailsをdocker化したらBundlerバージョンで怒られる

最近は railsもdocker化するのが多くみられるのですが、ハマりポイントも多いみたいですね。
特にdocker-composeを使ったときには、キャッシュがかかってうまく修正反映できないことがあります。

エラーで怒られた。

例えば、既存のbundlerバージョンが合わなくて、bundler version 2.x

You must use Bundler 2 or greater with this lockfile.

というエラー。

dockerでrubyのイメージを使うときには、bundlerも一緒に入ってるわけですが、
その時に作った時のGemfileに書かれているBundlerバージョンと合わないことがあります。

解決法

その時には、Gemfile.lockを削除してからgemライブラリ達をビルドし直す必要があります。

$ rm Gemfile.lock
$ sudo docker-compose build
$ sudo docker-compose up

この時に気を付けなければいけないのは、docker-compose buildを使うこと、
どうやら、docker buildとdocker-compose buildはキャッシュが別物みたいです。
なので、いくらDockerでビルドし直しても、docker-compose には反映されなくて詰みます
(私は数時間失いました)。
エラーも出ないが、反映もされないのでキャッシュトラブルはとてもややこしい。

参考
https://qiita.com/azul915/items/5b7063cbc80192343fc0
https://qiita.com/yoshijbbsk1121/items/87250501b32c6433943e

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

_destroyキーの使い方 ~~親(item)の子(image)を削除する~

一次ソースはこちらです。

問題

出品した商品(item)の画像を削除したいが、削除ボタンを押し更新ボタンを押しても削除できない

edit.html.haml
  .main-items
    = form_with model: @item, local: true do |f|
      .wrapper.image-wrapper
        #image-box.image-wrapper__image-box
          = f.fields_for :images do |i|
            - if @item.persisted?
              = i.check_box :_destroy, data:{ index: i.index}, class: 'hidden-destroy'
            .image-wrapper__image-box__js.js-file_group{:id => "item_images_attributes_#{i.index}_id", data:{index: "#{i.index}"}}
              = i.label :content, class: "image-wrapper__image-box__js__label" do
                .image-wrapper__image-box__js__label__image.img_field{id: "img_field--#{i.index}", onClick: "$('#file').click()"}
                  - if @item.images[i.index][:content].present?
                    = image_tag asset_path(@images[i.index].content), class:"preview "
                  - else
                    = image_tag 'icon_camera.png', class: "image-wrapper__image-box__js__label__image__url", id: "default-img"
                = i.file_field :content, class: "image-wrapper__image-box__js__label__file js-file", id: "item_images_attributes_#{i.index}_content"
              .js-remove
                %span.js-remove__text
                  削除

前準備

問題コードの
= i.check_box :_destroy, data:{ index: i.index}, class: 'hidden-destroy'
によって各画像のチェックボックスにチェックが入ったら_destroyに値"1"がついた画像を削除するようにしているよ!(チェックボックスは隠してます)

モデル
item.rb
accepts_nested_attributes_for :images, allow_destroy: true

accepts_nested_attributes_forを使用し、paramsのimages_attributes:キー内で値を取り、送ることで親モデル(item)に紐つく子モデル(image)の削除、更新を行える。

allow_destroy: trueで_destroyキーが使えるようになる。

コントローラー
items_controller.rb
  private
  def item_params
    params.require(:item).permit(
      :name, :description, :price, :brand_id, :area, :condition, :fee, :category_id,
      :shipping_days, images_attributes: [:content, :id, :_destroy]
      ).merge(user_id: current_user.id)
  end

ストロングパラメーターにimages_attributes: [:content, :id, :_destroy]を記述

原因追求

Image from Gyazo

コントローラー
items_controller.rb
  def update
    binding.pry #デバック
    if @item.update(item_params)
      redirect_to item_path(@item)
    else
      flash.now[:alert] = '画像を1枚以上添付してください'
      redirect_to edit_item_path(@item)
    end
  end

binding.pryを使用し、更新ボタンを押した時のparams中身をターミナルで確認する

ターミナル

※横スクロール長くてすみません。
※配列の添字(index)により一枚目は"0"から始まっています。

"item"=>{"images_attributes"=>{"0"=>{"_destroy"=>"0", "id"=>"2"}, "1"=>{"_destroy"=>"1", "id"=>"47"}, "2"=>{"_destroy"=>"1", "id"=>"48"}, "3"=>{"_destroy"=>"1", "id"=>"49"}, "4"=>{"_destroy"=>"0", "id"=>"50"}, "5"=>{"_destroy"=>"0"}},

今回は5枚ある画像から2、3、4枚目を削除した。
しかし、paramsに入る内容は"4"=>{"_destroy"=>"0", "id"=>"50"}までで良いはずだが、不要な"5"=>{"_destroy"=>"0"}}が入ってしまっている。
("5"は一番右の新規画像追加用のカメラマークのこと)

ブラウザ

この不要な"5"をChromeの検証ツールElementsで確認してみると
Image from Gyazo
不要なhtmlタグみっけ

checkboxによりこのタグがある。
nameがつけられparamsに入ってしまっていた。

解決方法

- if @item.images[i.index][:content].present?をfields_forの直下に持ってきて、既存の画像のみにチェックボックスが付与されるように分けよう!!

edit.html.haml
  .main-items
    = form_with model: @item, local: true do |f|
      .wrapper.image-wrapper
        #image-box.image-wrapper__image-box
          = f.fields_for :images do |i|
            - if @item.images[i.index][:content].present?
              - if @item.persisted?
                = i.check_box :_destroy, data:{ index: i.index}, class: 'hidden-destroy'
              .image-wrapper__image-box__js.js-file_group{:id => "item_images_attributes_#{i.index}_id", data:{index: "#{i.index}"}}
                = i.label :content, class: "image-wrapper__image-box__js__label" do
                  .image-wrapper__image-box__js__label__image.img_field{id: "img_field--#{i.index}", onClick: "$('#file').click()"}
                    = image_tag asset_path(@images[i.index].content), class:"preview "
                .js-remove
                  %span.js-remove__text
                    削除
            -else
              .image-wrapper__image-box__js.js-file_group{:id => "item_images_attributes_#{i.index}_id", data:{index: "#{i.index}"}}
                = i.label :content, class: "image-wrapper__image-box__js__label" do
                  .image-wrapper__image-box__js__label__image.img_field{id: "img_field--#{i.index}", onClick: "$('#file').click()"}
                    = image_tag 'icon_camera.png', class: "image-wrapper__image-box__js__label__image__url", id: "default-img"
                  = i.file_field :content, class: "image-wrapper__image-box__js__label__file js-file", id: "item_images_attributes_#{i.index}_content"
                .js-remove
                  %span.js-remove__text
                    削除

これでparamsを確認すると

"item"=>{"images_attributes"=>{"0"=>{"_destroy"=>"0", "id"=>"2"}, "1"=>{"_destroy"=>"1", "id"=>"47"}, "2"=>{"_destroy"=>"1", "id"=>"48"}, "3"=>{"_destroy"=>"1", "id"=>"49"}, "4"=>{"_destroy"=>"0", "id"=>"50"}}

"5"が入ってないOK

binding.pryを外して更新すると削除できました!

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

【Rails】データベース関連の知識、操作方法

はじめに

Railsでアプリを開発するにあたって、データベース関連の設計、操作で色々苦労したので、自分の覚えの為にもまとめてみました。

環境

  • Ruby 2.5.1
  • Rails 5.0.7

そもそもデータベースとは

一定の形式で、「複数で共有、利用すること」と「検索、加工すること」を目的に整理されたデータの集まりの事を指す。プログラミングに限定された用語ではなく、日常生活で使う辞書等もデータベースみたいですね!

データベース種類

  • 階層型
  • ネットワーク型
  • リレーショナル型(RDBと呼ばれ最も主流)
    • Oracle Database
    • MySQL
    • PostgreSQL
    • Microsoft SQL Server など
  • NoSQL

MySQLを使って開発する事が多かったので、ここからMySQL(リレーショナル型)に絞って話を進めていきます。

リレーショナルデータベース(RDB)、SQLとは

RDBは現在主流のデータベースで、エクセルみたいな表で構成されたデータベース。行と列を持ち、表形式でデータの関係性を示す。SQLを用いたデータのアクセスが可能。

SQL・・・Structured Query Languageの略で、リレーショナルデータベース(RDB)の操作を行うための言語。日本語訳は「構造化された問い合わせ言語」みたいな感じですかね!とにかくデータベースから情報を参照する言語。

データベース設計で意識する事

正規化、アソシエーション、制約の話をまとめていきます。

正規化

正規化・・・データベースのデータ構造をより効率的で重複や無駄のないシンプルな構造にするための手順。正規化の話は複雑ですが以下例です。
×予約情報が重複している為望ましくない
UNADJUSTEDNONRAW_mini_1a.jpg
○テーブルを2つに分け予約情報の重複を解消
2020-03-21 11.44のイメージ.jpg

アソシエーション

アソシエーション・・・テーブル間の関係性をモデルの上の関係として操作できるようにする仕組み。
アソシエーションを利用すると複数のテーブルにまたがるデータ操作もより直感的に利用できる。
【モデルへの書き方】

〇〇.rb
      # 1対多
      has_many :モデル名複数形
      # 多対1
      belongs_to :モデル名単数形
    # 1対1 どちらかのモデルにhas_one,もう一方にbelongs_to
      has_one :モデル名単数形
      belongs_to :モデル名単数形
      # 多対多 中間テーブルが必要
      has_many :中間テーブル名複数形
    has_many :モデル名複数形 through: :中間テーブル名複数形

制約による安全性

制約とは・・・特定のデータの保存を許さない事。例えば同じニックネームのユーザーを登録できないようにする、名前のデータが空のユーザーを保存を許さない等。主な制約は以下です。

制約種類

  • NOT NULL制約・・・空(nil)レコードは保存できない。
  • 一意性制約 ・・・同じ値を設定できない。一意性制約をかけるときは、インデックスの作成も必要。 全てのデータを検索しないと、過去のデータと重複しているか判断できない為。
  • 主キー制約 ・・・Railsでは主キーはidカラムとして自動で作成(テーブルの一番左のカラム)。
  • 外部キー制約 ・・・外部キー制約は、外部キーの対応するレコードが必ず存在しなくてはいけないという制約です。外部キーのカラムに値があっても、その値を主キーとして持つ他のテーブルにレコードが存在する必要あり。
create_users.rb
      # NOT NULL制約 nameはカラム名,stringはデータ型
      t.string :name, null: false
      # 一意性制約 インデックスとセット usersはテーブル名、emailはカラム名、stringはデータ型
      add_column :users, :email, :string
      add_index :users, :email, unique: true
      # 外部キー referencesはデータ型 これでuser_idというカラムが生成される
      t.references :user, foreign_key: true

データ型種類

  • string : 文字列
  • text : 長い文字列
  • integer : 整数
  • float : 浮動小数(実数)
  • datetime : 日時
  • time : 時間
  • date : 日付
  • boolean : Boolean

Railsでのコマンド集

ターミナル/マイグレーションファイル
  <データベース全般>
   #データベース作成(色んなテーブルを入れる箱の作成) database.ymlの内容に基づく
   rails db:create
   #データベース削除
   rails db:drop
   #マイグレーションファイルの適用
   rails db:migrate
   #マイグレーションファイルがどこまで適用されているか確認
   rails db:migrate:status
   #マイグレーションのバージョンを下げる デフォルトでは一つずつ
   rails db:rollback
   #マイグレーションのバージョンを複数下げる 例では3段階
   rails db:rollback STEP=3

   <モデル(テーブル)関連>コマンドでマイグレーションが作成されるので、そこで編集等を行う
   #モデル(テーブル作成) モデル名は単数形/頭文字を大文字にする
   rails g model モデル名

   #最初からカラム付きでモデルを作成したい時,2行目は例
   rails g model モデル名 カラム名:型
   rails g model User name:string email:string

   #既存のモデル(テーブル)にカラムを追加/削除,2行目は例
   rails g migration <マイグレーションファイル名> <追加するカラム名:型>
   rails g migration add_email_to_users email:string
   #以下マイグレーションファイル(add付きのファイル名でもカラムの削除は行える)
   # 追加(2行上のコマンドであればマイグレーション作成時からある)
   add_column :users, :email, :string
   # 削除(必要に応じて追加)
   remove_column :users, :gmail, :string
   # まとめて削除(必要に応じて追加)
   remove_columns :users, :column_1, :column_2 [, ...]
   # 追加する場所を指定する場合(必要に応じて追加)
   # 以下、nameカラムの直後にemailカラムを追加する場合
   add_column :users, :email, :string, :after => :name

   #指定のテーブル削除,2行目は例
   rails g migration Dropテーブル名
   rails g migration DropUser

   #既存のテーブル名を変更する,2行目は例
   rails g migration Rename変更前テーブル名To変更後テーブル名
   rails g migration RenameUserToCustomer

   #既存のカラムの内容を変更(例.Userモデルのemailカラムに制約をつける場合)
   rails g migration ChangeColumnToUser
   #以下マイグレーションファイル
  # 変更内容
   def up
     change_column :users, :email, :string, null: false
   end

   # 変更前の状態
   def down
     change_column :users, :email, :string, null: true
   end

その他の便利なメソッド

外部キー制約が要因で特定のレコードを削除できないエラーが起きる事があると思います。その時はdelete, delete_all, destroy, destroy_allメソッドが大変便利です。

こちらの記事が大変参考になりました。
https://qiita.com/kamelo151515/items/0fa7fb15a1d2c1e44db2

参考URL

https://qiita.com/kamelo151515/items/0fa7fb15a1d2c1e44db2
https://qiita.com/ryouzi/items/2682e7e8a86fd2b1ae47

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

[Rails]Swiperで画像スライド作成

完成イメージ

Image from Gyazo

Swiperとは

スライダー(カルーセル)が作れるJavaScriptライブラリです。
しかもPCでもスマホでも使えて、レスポンシブ対応可能!
引用元 https://garigaricode.com/swiper/

使用準備

今回はCDNから読み込む方法で行きます。

application.html.haml
!!!
%html
  %head
    %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
    %title FrimaApp
    %script{src: "https://js.pay.jp/", type: "text/javascript"}
    = csrf_meta_tags
    = csp_meta_tag
    = stylesheet_link_tag    'application', media: 'all'
    %link{:href => "https://unpkg.com/swiper/css/swiper.min.css", :rel => "stylesheet"}/
    = javascript_include_tag 'application'
  %body
    %script{:src => "https://unpkg.com/swiper/js/swiper.min.js"}
    = yield

%link{:href => "https://unpkg.com/swiper/css/swiper.min.css", :rel => "stylesheet"}/
は%haad内に

%script{:src => "https://unpkg.com/swiper/js/swiper.min.js"}
は%body内に記述します。

ビュー

show.html.haml
              / メイン表示部分、各スライド
              .swiper-container.swiper1
                .swiper-wrapper
                  - @images.each do |images|
                    = image_tag asset_path(images.content), class:"swiper-slide"
                / 次へ、前へボタン
                .swiper-button-prev.prev1
                .swiper-button-next.next1
                / ページネーション
                .swiper-pagination

              / 2段目、各スライド
              .swiper-container.swiper2
                .swiper-wrapper
                  - @images.each do |images|
                    = image_tag asset_path(images.content), class:"swiper-slide"

JSファイル

javascripts/item/show.js
$(function(){ 
    var mySwiper1 = new Swiper('.swiper1', {
        loop: true, // ループ
        loopedSlides: (".swiper1").length, // loop: trueの場合必須 総スライド数の半分以上の値を設定
        navigation: {
            nextEl: '.next1', // 次ページボタンのセレクタ名を指定
            prevEl: '.prev1', // 前ページボタンのセレクタ名を指定
        },
        pagination: {
            el: '.swiper-pagination', // ページネーションのセレクタ名を指定
            type: 'bullets', // ●○◯◯形式
            clickable: true, // type: 'bullets'の時のみ有効 ◯クリックで直接そのスライドへ移動
        },
    });
    var mySwiper2 = new Swiper('.swiper2', {
        loop: true,
        loopedSlides: (".swiper2").length, // 最初の前、最後の後に複製される非表示のスライド数を指定
        slideToClickedSlide: true, // スライドクリックでそのスライドに移動する
        centeredSlides: true, // センター表示
        slidesPerView: 3, // 一度に表示するスライド数を指定
        controller: {
            control: mySwiper1, // 連動させるSwiperの定義名を指定
            by: 'slide',  // 連動Swiperをスライド単位で制御
        },
    });
    mySwiper1.controller.control = mySwiper2; 
});

mySwiper1.controller.control = mySwiper2

なぜ1個目と2個目のSwiperでcontollerの指定方法が違っているかというとですね、コードは上から順番に実行されるため、1個目のSwiperを作っている最中は2個目のSwiperは定義前なのでまだ存在しません。
この状態で連携しようとしても「そんなものないですよ」という判断になってしまいます。
2個目のSwiperを作っている時には、1個目のSwiperはすでに存在しているため、var mySwiper2 = … の中で書くことができます。
なので、2個目のSwiperも作り終わってから1個目のcontoller指定をしてやるとうまくいきます。
引用元 https://garigaricode.com/swiper/

SCSS

item.show.scss
              .swiper-container {
                width: 400px;
                font-size: 0;
              }
              /* 2段目のSwiper全体のスタイル */
              .swiper2 {
                margin-top: 8px;
              }
              /* 2段目のSwiperのスライドのスタイル */
              .swiper2 .swiper-slide {
                height: 100px;
                line-height: 100px;
                -webkit-filter: brightness(0.5);
                filter: brightness(0.5); // 画像の明度調節
              }
              /* 2段目のSwiperの現在のスライドのスタイル */
              .swiper2 .swiper-slide-active {
                -webkit-filter: brightness(1);
                filter: brightness(1);
              }

-webkit-

-webkit-とは?
webkitの記述は、ベンダープレフィックスと呼ばれCSS3で実装予定の機能をブラウザ各社が先行して実装した機能を各ブラウザで使えるようにしたものです。
引用元 https://code-schools.com/css-webkit/

完成

引用を多用しました。

ご指摘等あればぜひコメントお願いします!!

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

#Rails の Controller でネストした params を permit / require するのはメソッドチェーンじゃいかんともしがたいからメソッドを何度も実行するしかないのか? ( ActionController::Parameters )

  • require / permit / permit! の引数の受け取り方も、返り値も使い方もなんだか不揃いで、なんとも使いがたい
  • メソッドチェーンも使いづらくパラメータがネストされている時に permit / require しづらい
  • permit! は引数を受け取れず、破壊的に params を変えてしまう
  • require と permit を別々に実行して、頑張って組み立て直す必要があるかもしれない
  • 具体ケースとしては JSON リクエストを受け取った結果を Controller の params で扱っているのだが、Rails のもともとのフレームワークのレールからは外れる部分が多いのか、苦労がある。
# ネストした params
params = ActionController::Parameters.new(
  name: 'Alice',
  age: 22,
  contact: ActionController::Parameters.new(
    tel: 07011112222,
    email: 'user@example.com'
  )
)
# => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: false>} permitted: false>


# require は必須パラメータチェックのためだけに利用して、返り値は使わない
params.require([:name, :age, :contact])
params[:contact].require([:tel, :email])

# permit する時はネストの構造に合わせた書き方をして、返り値を permit された params として利用する
peritted_params = params.permit(:name, :age, contact: [:tel, :email])
# => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true>} permitted: true>

僕はもう疲れたよ

image

公式

  • tap のブロックで頑張る方法が書かれていた
  • 結局 Action Controller Parameters は頼りに出来ない気がした
def person_params
  params.require(:person).permit(:name).tap do |person_params|
    person_params.require(:name) # SAFER
  end
end

ActionController::Parameters

example

# Execute with rails console

# Rails like Nested params with ActionController::Parameters instance
params = ActionController::Parameters.new(
  name: 'Alice',
  age: 22,
  contact: ActionController::Parameters.new(
    tel: 07011112222,
    email: 'user@example.com'
  )
)
# <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: false>} permitted: false>

# Ooops
# Args must be Array
params.require(:name, :age, :contact)
# ArgumentError: wrong number of arguments (given 3, expected 1)

# It works
params.require([:name, :age, :contact])
# => ["Alice", 22, <ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: false>]

# But with method chain
# How to permit multiple params Require and Permit ?

# require method returns values Array
# it is not work for generate permitted params
# because .permit .require both method does not change "params"
params.require([:name, :age, :contact])[2].permit(:tel, :email).require([:tel, :email])
# => [941921426, "user@example.com"]

# require method returns values Array
# Unable to use methods chain
params.require([:name, :age, :contact])[2].require([:tel, :email]).permit(:tel, :email)
# NoMethodError: undefined method `permit' for [941921426, "user@example.com"]:Array

# It is answer?
# User require and permit methods
# Without return values
# Without method chains
# 
# And last execute permit!

params.require([:name, :age, :contact])
params[:contact].require([:tel, :email])

peritted_params = params.permit(:name, :age, contact: [:tel, :email])
# <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true>} permitted: true>

# OR
params.permit(:name, :age, contact: [:tel, :email])
params.permit!
params
# => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true>} permitted: true>

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3045

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

#Rails / permit and require Nested params / Without method chains / ActionController::Parameters

# Execute with rails console

# Rails like Nested params with ActionController::Parameters instance
params = ActionController::Parameters.new(
  name: 'Alice',
  age: 22,
  contact: ActionController::Parameters.new(
    tel: 07011112222,
    email: 'user@example.com'
  )
)
# <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: false>} permitted: false>

# Ooops
# Args must be Array
params.require(:name, :age, :contact)
# ArgumentError: wrong number of arguments (given 3, expected 1)

# It works
params.require([:name, :age, :contact])
# => ["Alice", 22, <ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: false>]

# But with method chain
# How to permit multiple params Require and Permit ?

# require method returns values Array
# it is not work for generate permitted params
# because .permit .require both method does not change "params"
params.require([:name, :age, :contact])[2].permit(:tel, :email).require([:tel, :email])
# => [941921426, "user@example.com"]

# require method returns values Array
# Unable to use methods chain
params.require([:name, :age, :contact])[2].require([:tel, :email]).permit(:tel, :email)
# NoMethodError: undefined method `permit' for [941921426, "user@example.com"]:Array

# It is answer?
# User require and permit methods
# Without return values
# Without method chains
# 
# And last execute permit!

params.require([:name, :age, :contact])
params[:contact].require([:tel, :email])

peritted_params = params.permit(:name, :age, contact: [:tel, :email])
# <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true>} permitted: true>

# OR
params.permit(:name, :age, contact: [:tel, :email])
params.permit!
params
# => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true>} permitted: true>

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3044

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

#Rails permit nested params ( Action Controller Parameters ) example

# Rails like Nested params with ActionController::Parameters instance
params = ActionController::Parameters.new(
  name: 'Alice',
  age: 22,
  contact: ActionController::Parameters.new(
    tel: 07011112222,
    email: 'user@example.com'
  )
)
# <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: false>} permitted: false>


# Permit only flat params
permitted_params = params.permit(:name, :age)

# params not changed
# see "permitted: false"
params
# => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: false>} permitted: false>

# permit return value is permitted only flat params
permitted_params
# => <ActionController::Parameters {"name"=>"Alice", "age"=>22} permitted: true>


# permit all params
params.permit!

# permitted include all nested params
# see flags "permitted: true"
params
# => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true>} permitted: true>

params.permitted?
# => true

params[:contact].permitted?
# => true



# permit only nested params

params2 = ActionController::Parameters.new(
  name: 'Alice',
  age: 22,
  contact: ActionController::Parameters.new(
    tel: 07011112222,
    email: 'user@example.com'
  )
)


contact_permitted_params = params2[:contact].permit(:tel, :email)
# => <ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true>

partly_permitted_params2 = params2.merge(contact: contact_permitted_params)
# <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true>} permitted: false>

partly_permitted_params2.permitted?
# => false

partly_permitted_params2[:contact].permitted?
# => true

# NOTE
# permit! bang does not receive arguments oh no ...

# params2[:contact].permit!(:tel, :email)
# ArgumentError: wrong number of arguments (given 2, expected 0)

# In this way wee need permit nested params with two times execute methods
#
# 1. permit with args 
# 2. permit!

# params2[:contact].permit(:tel, :email)
# => <ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true>
# params2[:contact].permit!
# => <ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true>
# params2
# => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true>} permitted: false>



params_include_nil = ActionController::Parameters.new( name: 'Alice', age: 22, contact: nil )
# => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=>nil} permitted: false>

# flat permit allowed
params_include_nil.permit(:name, :age, :contact)
# => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=>nil} permitted: true>

# nexted permit ignored
params_include_nil.permit(:name, :age, contact: [:tel, :email])
# => <ActionController::Parameters {"name"=>"Alice", "age"=>22} permitted: true>



# When nested params has empty values instance
params_include_empty = ActionController::Parameters.new( name: 'Alice', age: 22, contact: ActionController::Parameters.new )
# => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {} permitted: false>} permitted: false>

# NG : When specified as flatten key
#      nested params deleted
params_include_empty.permit(:name, :age, :contact)
# => <ActionController::Parameters {"name"=>"Alice", "age"=>22} permitted: true>

# NG : When specified Empty hash
#      nested params deleted
params_include_empty.permit(:name, :age, contact: [])
# => <ActionController::Parameters {"name"=>"Alice", "age"=>22} permitted: true>

# NG : When specified some key
#      Nothing in permitted nested params but not deleted
params_include_empty.permit(:name, :age, contact: [:tel])
# => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {} permitted: true>} permitted: true>


Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3043

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

jQueryでFlash, error_messageを閉じる方法(Bootstrap xボタンが反応しない場合)。

はじめに

Bootstrapのflash,error_messagesなどに使われる
<div class= "alert alert-○○">
が閉じない場合の対処法です。
私の環境が悪く閉じるアクションができないのか定かではありませんが
jQueryを使って閉じるアクションを可能にする方法を書いておきます。

私の場合、xマークのボタンは表示できましたがクリックをしても一切反応しませんでした。

開発環境

  • bootstrap-sass (3.3.7)
  • devise (4.7.1)
  • jquery-rails (4.3.1)
  • rails (5.2.4.1)

Flashやerror_messagesにxボタンをつける

こちら Bootstrap4版
こちら Bootstrap3版
上記のサイトのサンプルを確認すると

<div class="alert alert-success alert-dismissible">
  <button type="button" class="close" data-dismiss="alert">&times;</button>
  <strong>Success!</strong> Indicates a successful or positive action.
</div>

サイトでは、↓

To close the alert message, add a .alert-dismissible class to the alert container. Then add class="close" and data-dismiss="alert" to a link or a button element (when you click on this the alert box will disappear).

簡単に要約するとalertなどを閉じる時は
class=alert-dismissiblealertコンテナ
<button>タグにclass="close",data-dismiss="alert"
を記入すればxボタンクリック時に消えますよと。

(私の環境で何か導入し忘れのものがある、もしくは、Javascript,jQueryで閉じるアクションする前提なのかもしれません。わかる方宜しければコメントなどで教えてください。)

jQueryで閉じるイベント

applivation.js
$(function() {
  $(".close").click(function() {
    $(".alert").hide();
  });
});

参考サイト

w3schools.com
(https://www.w3schools.com/bootstrap/bootstrap_alerts.asp)
(https://www.w3schools.com/bootstrap4/bootstrap_alerts.asp)

終わりに

xボタンを押した際にBootstrapを適用したクラスのFlashやエラーメッセージを閉じることができると思います。
jQuery、Javascriptの重要性を再確認できました。

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

jQueryでFlash, Alertsを閉じる方法(Bootstrap xボタンが反応しない場合)。

はじめに

Bootstrapのflash,error_messagesなどに使われる
<div class= "alert alert-○○">
が閉じない場合の対処法です。
私の環境が悪く閉じるアクションができないのか定かではありませんが
jQueryを使って閉じるアクションを可能にする方法を書いておきます。

私の場合、xマークのボタンは表示できましたがクリックをしても一切反応しませんでした。

開発環境

  • bootstrap-sass (3.3.7)
  • devise (4.7.1)
  • jquery-rails (4.3.1)
  • rails (5.2.4.1)

Alertやerror_messagesにxボタンをつける

こちら Bootstrap4版
こちら Bootstrap3版
上記のサイトのサンプルを確認すると

<div class="alert alert-success alert-dismissible">
  <button type="button" class="close" data-dismiss="alert">&times;</button>
  <strong>Success!</strong> Indicates a successful or positive action.
</div>

サイトにでは、↓

To close the alert message, add a .alert-dismissible class to the alert container. Then add class="close" and data-dismiss="alert" to a link or a button element (when you click on this the alert box will disappear).

簡単に要約するとalertなどを閉じる時は
class=alert-dismissiblealertコンテナ
<button>タグにclass="close",data-dismiss="alert"
を記入すればxボタンクリック時に消えますよと。

(私が何か導入し忘れのものがある、もしくは、Javascript,jQueryで閉じるアクションする前提なのかもしれません。わかる方よければコメントなどで教えてください。)

JQueryで閉じるイベント

applivation.js
$(function() {
  $(".close").click(function() {
    $(".alert").hide();
  });
});

参考サイト

w3schools.com
(https://www.w3schools.com/bootstrap/bootstrap_alerts.asp)
(https://www.w3schools.com/bootstrap4/bootstrap_alerts.asp)

終わりに

xボタンを押した際にBootstrapを適用したクラスのFlashやエラーメッセージを閉じることができると思います。
jQuery、Javascriptの重要性を再確認できました。

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

jQueryでFlash, error_messageを閉じる方法(Bootstrap xボタンが反応しない場合)。

はじめに

Bootstrapのflash,error_messagesなどに使われる
<div class= "alert alert-○○">
が閉じない場合の対処法です。
私の環境が悪く閉じるアクションができないのか定かではありませんが
jQueryを使って閉じるアクションを可能にする方法を書いておきます。

私の場合、xマークのボタンは表示できましたがクリックをしても一切反応しませんでした。

開発環境

  • bootstrap-sass (3.3.7)
  • devise (4.7.1)
  • jquery-rails (4.3.1)
  • rails (5.2.4.1)

Alertやerror_messagesにxボタンをつける

こちら Bootstrap4版
こちら Bootstrap3版
上記のサイトのサンプルを確認すると

<div class="alert alert-success alert-dismissible">
  <button type="button" class="close" data-dismiss="alert">&times;</button>
  <strong>Success!</strong> Indicates a successful or positive action.
</div>

サイトでは、↓

To close the alert message, add a .alert-dismissible class to the alert container. Then add class="close" and data-dismiss="alert" to a link or a button element (when you click on this the alert box will disappear).

簡単に要約するとalertなどを閉じる時は
class=alert-dismissiblealertコンテナ
<button>タグにclass="close",data-dismiss="alert"
を記入すればxボタンクリック時に消えますよと。

(私の環境で何か導入し忘れのものがある、もしくは、Javascript,jQueryで閉じるアクションする前提なのかもしれません。わかる方宜しければコメントなどで教えてください。)

JQueryで閉じるイベント

applivation.js
$(function() {
  $(".close").click(function() {
    $(".alert").hide();
  });
});

参考サイト

w3schools.com
(https://www.w3schools.com/bootstrap/bootstrap_alerts.asp)
(https://www.w3schools.com/bootstrap4/bootstrap_alerts.asp)

終わりに

xボタンを押した際にBootstrapを適用したクラスのFlashやエラーメッセージを閉じることができると思います。
jQuery、Javascriptの重要性を再確認できました。

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

Ruby on Rails チュートリアル第9章メモ

メモ

8章でおこなった基本的なログイン機構にremember me機能の追加を永続クッキーを生成。
永続的セッションシステムの構築を目指す。
機能そのものへの理解を深めたいのでテストは飛ばす。

Remember me

ブラウザを閉じた後でも、ユーザーのログイン状態を有効にする機能。この機能を使うと、ユーザーが明示的にログアウトを実行しない限り、ログイン状態を維持。

トークンとは

パスワードの平文と同じような秘匿されるべき情報を指します。パスワードとトークンとの一般的な違いは、パスワードは使用者が自身で作成・管理する情報であるのに対し、トークンはコンピューターなどが生成した情報であるということ。

記憶トークンと暗号化

  1. 記憶トークンにはランダムな文字列を生成して用いる。
  2. ブラウザのcookiesにトークンを保存するときは、有効期限を設定。
  3. トークンはハッシュ値に変換してからDBに保存。
  4. ブラウザのcookiesに保存するユーザーIDは暗号化しておく。
  5. 永続ユーザーIDを含むcookiesを受け取ったら、そのIDでDBを検索し、記憶トークンのcookiesがDB内のハッシュ値と一致することを確認。

記憶トークンをハッシュ化した値を保存する場所remember_digestカラムを追加、マイグレーション。

$ rails generate migration add_remember_digest_to_users remember_digest:string
#トークンはユーザーがいじるものじゃない(かついじられてはいけない)から完成したファイルに特にインデックスを追加するなどはないのでそのまま
$ rails db:migrate
  1. トークン生成メソッドを追加。
app/models/user.rb
 class User < ApplicationRecord
  before_save { self.email = email.downcase }
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }

  # 渡された文字列のハッシュ値を返す
  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end

  # ランダムなトークンを返す
  def User.new_token
    SecureRandom.urlsafe_base64
  end
end

ユーザーを記憶するには、記憶トークンを作成して、そのトークンをダイジェストに変換したものをDBに保存。
モデルにクラスメソッドとして定義。

  1. 記憶トークンをユーザーと関連付け、トークンに対応する記憶ダイジェストをDBに保存。

マイグレーションファイルを作成した際にremember_digestカラムを作成したが、remember_token(記憶トークン)カラムは作成していない。なので、user.remember_tokenメソッドを使ってトークンにアクセスし、かつ、トークンをDBに保存せずに実装する必要がある。

そこで、パスワードの実装と同様の手法でこれを解決。パスワードではpassword属性とDB上にあるセキュアなpassword_digest属性の2つにを使っていた。仮想のpassword属性はhas_secure_passwordメソッドで自動作成されていたが、今回はremember_tokenのコードを自分で書く必要がある。これを書くために、attr_accessorメソッドでremember_tokenを「仮想の」属性として宣言し、Userインスタンスに値を持たせる際には、暗号化し、remember_digestとする。

app/models/user.rb
 class User < ApplicationRecord
  attr_accessor :remember_token <--注目
  before_save { self.email = email.downcase }
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }

  # 渡された文字列のハッシュ値を返す
  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end

  # ランダムなトークンを返す
  def User.new_token
    SecureRandom.urlsafe_base64
  end

  # 永続セッションのためにユーザーをデータベースに記憶する
  def remember
    self.remember_token = User.new_token <--注目
    update_attribute(:remember_digest, User.digest(remember_token)) <--記憶ダイジェストを更新
    #remember_digestカラムにUser.digest(remember_token)を代入
  end
end

ポイント①:マイグレーションを行ってあるので、Userモデルには既にremember_digest属性が追加されているが、remember_token属性はまだ追加されていない。user.remember_tokenでトークンにアクセス出来るように、かつトークンをデータベースに保存せずに実装する必要があるためattr_accessorを使ってアクセス可能な属性を作成している。

ポイント②:selfを付けないとremember_tokenがローカル変数になってしまうただの変数代入になってしまうのがRuby。今欲しいのはローカル変数ではない。selfキーワードを与えると、この代入によってユーザーのremember_token属性が期待どおりに設定される。

ログイン状態の保持

user.rememberメソッドが動作するようになったので、ユーザーの暗号化済みIDと記憶トークンをブラウザの永続cookiesに保存して、永続セッションを作成する準備ができた。

永続セッションを実際に行うにはcookiesメソッドを使用する。
cookiesメソッドは、sessionのときと同様にハッシュとして扱える。
個別のcookiesは、1つのvalue (値) と、オプションのexpires (有効期限) からできている。

cookies[:remember_token] = { value:   remember_token,
                             expires: 20.years.from_now.utc }

ただし20年期限は定番化しているので、permanentメソッドで省略できる。

cookies.permanent[:remember_token] = remember_token

ユーザーIDをcookiesに保存するには、sessionメソッドで使ったのと同じパターンを使う。

cookies[:user_id] = user.id

しかし、このままではIDが生のテキストとしてcookiesに保存されてしまうため、signedメソッド(暗号化させる)を使う。

cookies.signed[:user_id] = user.id

ユーザーIDと記憶トークンはペアで扱う必要があるので、cookiesも永続化しないといけない。そこで、signedとpermanentをメソッドチェーンでつないで

cookies.permanent.signed[:user_id] = user.id

cookiesを設定すると、以後のページのビューでこのようにcookiesからユーザーを取り出せるようになる。

User.find_by(id: cookies.signed[:user_id])

cookies.signed[:user_id]では自動的にユーザーIDのcookiesの暗号が解除され、元に戻る(便利!!)

最後に渡されたトークンがユーザーの記憶ダイジェストと一致することをチェック!

BCrypt::Password.new(remember_digest) == remember_token

このコードは奇妙。bcryptで暗号化されたパスワードをトークンと直接比較している。ということは == 比較する際にダイジェストを復号化しているのか?しかし、bcryptのハッシュは復号化できないはずなので、復号化しているはずがない。そこでbcrypt gemのソースコードを詳しく調べてみると、なんと比較に使っている==演算子が再定義されている。実際の比較をコードで表すと、次のようになっている。

BCrypt::Password.new(remember_digest).is_password?(remember_token)

実際の比較では、== の代わりにis_password?という論理値メソッドが使われている。

これを記憶トークンと記憶ダイジェストを比較するauthenticated?メソッドとして定義して、まとめると

app/models/user.rb
 class User < ApplicationRecord


  # 渡されたトークンがダイジェストと一致したらtrueを返す
  def authenticated?(remember_token)
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
  end
end

*注意*
このメソッドのremember_tokenはローカルメソッドである。
       remember_digestはUsersカラムの属性である。 まちがえないように

セッションヘルパーに
userモデル定義のrememberメソッドを呼びだして記憶トークンを作成→データベースにダイジェストを記録→その語cookiesに引数で値(ID)とオプションの期限を与える、といった流れのrememberメソッドを定義。

このrememberヘルパーメソッドはsessionsコントローラーのヘルパーメソッドであり、先ほどモデルで定義したuser.rememberメソッドとは違う点に注意。実際に、user.rememberメソッドとは違って引数としてuserを取っている。

app/helpers/sessions_helper.rb
 module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end

  # ユーザーのセッションを永続的にする
  def remember(user) <---------これ
    user.remember
    cookies.permanent.signed[:user_id] = user.id
    cookies.permanent[:remember_token] = user.remember_token
  end

  # 現在ログインしているユーザーを返す (いる場合)
  def current_user
    @current_user ||= User.find_by(id: session[:user_id])
  end


ログインのアクション時に組み込む

app/controllers/sessions_controller.rb
 class SessionsController < ApplicationController

  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      remember user
      redirect_to user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

  def destroy
    log_out
    redirect_to root_url
  end
end

しかし、今のままだと

  # 現在ログインしているユーザーを返す (いる場合)
    @current_user ||= User.find_by(id: session[:user_id])

current_userメソッドでは一時セッションしか扱っていないので、このままでは正常に動作しない。
永続セッションの場合は、session[:user_id]が存在すれば一時セッションからユーザーを取り出し、それ以外の場合はcookies[:user_id]からユーザーを取り出して、対応する永続セッションにログインする必要がある。書き換えると

app/helpers/sessions_helper.rb
  # 記憶トークンcookieに対応するユーザーを返す
  def current_user
    if (user_id = session[:user_id]) #<--このコードを言葉で表すと、
                                         #「(ユーザーIDにユーザーIDのセッションを代入した結果) ユーザーIDのセッションが存在すれば」
      @current_user ||= User.find_by(id: user_id)
    elsif (user_id = cookies.signed[:user_id])
      user = User.find_by(id: user_id)
      if user && user.authenticated?(cookies[:remember_token])
        log_in user
        @current_user = user
      end
    end
  end

ユーザーを忘れる

記憶と逆を行う。
user.forgetメソッドによって、user.rememberが取り消される。
具体的には、記憶ダイジェストをnilで更新。

app/models/user.rb
 class User < ApplicationRecord
  
 ・
  # ユーザーのログイン情報を破棄する
  def forget
    update_attribute(:remember_digest, nil)
  end
end

同じ流れ。
Forgetヘルパーメソッドを追加してlog_outヘルパーメソッドから呼び出す。
よく見てみると、forgetヘルパーメソッドではuser.forgetを呼んでからuser_idとremember_tokenのcookiesを削除していることがわかる。

app/helpers/sessions_helper.rb
 module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end
  .
  .
  .
  # 永続的セッションを破棄する
  def forget(user)
    user.forget
    cookies.delete(:user_id)
    cookies.delete(:remember_token)
  end

  # 現在のユーザーをログアウトする
  def log_out
    forget(current_user)
    session.delete(:user_id)
    @current_user = nil
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ユーザー情報にアイコンとプロフィールを追加する方法を丁寧に解説(rails)

はじめに

こんにちは。今回はSNSなどで普段よく目にするアイコンとプロフィールの実装方法を解説していきます。
Line、Twitter、Instagram、YouTubeなどで目にするアイコンは全て丸い形をしているので、そのようなデザインにする方法も一緒に解説します。このCSSのレイアウト調整が意外と苦戦しました(汗)

それにしてもなんでアイコンって正方形じゃなくて丸いんだろう、とふと疑問に思ったので調べてみたのですが、どうやらTwitterなどにある添付画像が四角いのでパッと見でアイコンだと区別がつくように丸くしたようです。

余談はこの辺にして実際の完成イメージからご紹介します。

完成イメージ

今回は編集画面でアイコンを追加、追加したアイコンを詳細画面に表示のみご紹介しますが、
新規登録時にアイコンを追加できるようにしたり、投稿と紐付けてユーザー名の横にアイコンを表示させたりも
今回の実装をベースにすれば容易に実現可能だと思います。

exhoop_200321_icon.gif

容量の関係で画質荒めですが、イメージはこんな感じです。
編集画面では非同期で画像をプレビューし、Updateボタンで詳細画面に遷移させるといった流れになります。

それでは行きましょう!

環境

  • ruby 2.5.1
  • Rails 5.2.3
  • mysql
  • Haml 5.1.2
  • Ruby Sass 3.7.4

前提条件

  • deviseを用いてユーザーの新規登録・ログイン・ログアウト・編集・削除・詳細画面の実装済みであること
  • Jqueryが使えるようにgemやapplication.jsに必要な記載を追加・実装済みであること
  • carrierwave, minimagickをgemで導入済みであること

開発の流れ

  1. Usersテーブルにアイコンとプロフィールのカラムを追加
  2. Usersコントローラーにアイコンとプロフィールのカラムを追加
  3. 画像アップロード用のファイルを作成
  4. 編集画面のビューファイルにアイコンとプロフィールを追加
  5. 編集画面のビューに対応するscssファイルでレイアウトを調整
  6. 非同期で画像プレビューするためにJSファイルを作成
  7. 詳細画面に表示する用のビュー、レイアウトを調整

手順

1. Usersテーブルにアイコンとプロフィールのカラムを追加

下記のようにターミナルからコマンドを打ちカラムを追加します。
アイコンはファイル名で保存されるので文字列のstring型、プロフィールは不定長文字列のtext型で定義します。

ターミナル.
$ rails g migration AddImageAndProfileToUsers image:string profile:text
$ rails db:migrate

2. Usersコントローラーにアイコンとプロフィールのカラムを追加

user_paramsでimageカラムとprofileカラムをpermit内に追加して、データを更新できるようにしておきましょう。
showアクションでアイコンやプロフィールを表示するためにそれぞれ@image, @profileにデータを入れてあげます。
editアクションでも既にimageにデータが入っている場合に表示させるので@imageを追加してあげます。
ここでプロフィールやその他のデータがeditアクション内に無いのは後ほど手順5で解説します。

users_controller.rb
class UsersController < ApplicationController

  def edit
    user = User.find(params[:id])
    @image = user.image
  end

  def update
    if current_user.update(user_params)
      redirect_to user_path(current_user)
    else
      render :edit
    end
  end

  def show
    user = User.find(params[:id])
    @id = user.id
    @name = user.name
    @image = user.image
    @profile = user.profile
    @videos = user.videos.order("created_at DESC")
  end

  private

  def user_params
    params.require(:user).permit(:id, :name, :email, :image, :profile)
  end
end

機能的に無くても動作には影響しませんが、ついでにストロングパラメーターにimageカラムを追加してあげましょう。
ストロングパラメーターは意図しない登録・更新を防ぐ、いわば脆弱性対策です。

application_controller.rb
class ApplicationController < ActionController::Base
  (省略)
  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :image]) 
  end
end

3. 画像アップロード用のファイルを作成

画像をアップロードするためにCarrierWave, MiniMagickの仕組みを使います。
ターミナルで下記を入力することで、uploadersフォルダ下にファイルが作成されます。

ターミナル.
$ rails g uploader Image

作成されたファイルを開き、上から4行目あたりにある下記の1行がコメントアウトされているので外してください。

uploaders/image_uploader.rb
class ImageUploader < CarrierWave::Uploader::Base
  (省略)
  include CarrierWave::MiniMagick
  (省略)
end

ImageUploaderをuserモデルに追加することで先ほど作成したImageUploaderが適用されます。
追加で、これは好みですがプロフィール文の文字数を最大250文字に制限しています。

model/user.rb
class User < ApplicationRecord
  (省略)
  validates :profile, length: { maximum: 250 }
  mount_uploader :image, ImageUploader
end

4. 編集画面のビューファイルにアイコンとプロフィールを追加

ここでかなり苦戦しました。
何故苦戦したかと言うと、アイコンの枠>既に画像が存在する場合の表示>新しく選択した画像>ファイル選択用のボタンといった具合に、アイコンの部分だけでも4つの要素を用意しなければならず、その関係性を崩さないようにCSS、JQueryで調整しなければならなかったからです。他に簡略的に書ける良い方法がある場合は下のコメント欄にてご指摘ください。

users/edit.html.haml
(省略)
    = form_for(current_user) do |f|
      //アイコン追加部分
      .account-page__inner--icon
        .account-page__inner--icon__label
        .account-page__inner--icon__input
          - if @image.present?
            = image_tag @image.url, class: 'account-page__inner--icon__input__image', width: '100%'
          .account-page__inner--icon__input__image2
          = f.file_field :image, class: 'account-page__inner--icon__input__btn', id: 'upload-icon'

      .account-page__inner--user-form
        (省略)
        // プロフィール追加部分
        .field
          .field-label
            = f.label :profile
          .field-input
            = f.text_area :profile, autocomplete: 'off'
        (省略)

アイコンの解説

form_forを用いてfile_fieldでファイル選択用のフォームを用意します。
form_forについて忘れちゃった・詳しく知りたいという方はこちら(https://pikawaka.com/rails/form_for)

要素を整理するとこのようになります。
.account-page__inner--icon__input(アイコンの枠)
.account-page__inner--icon__input__image(既に画像が存在する場合の表示)
.account-page__inner--icon__input__image2(新しく選択した画像)
.account-page__inner--icon__input__btn(ファイル選択用のボタン)

手順2のUsersコントローラーのshowアクションでimageだけ@image=user.imageとしていた理由ですが、既に画像が存在する場合(if @image.present?)という条件をつけ、ある場合はその画像を表示する必要があるためです。プロフィールについてはtext_area部分でカラムに対して直接書き込むだけで、アイコンのような特殊な表示切り替えを行っていないため、わざわざ変数に置き換えてやる必要がありません。

画像の表示にはimage_tagを使用し、@image.urlで画像データを呼び出します。width: '100%'にすることでアイコンの枠(幅:180px, 高さ:180px)に対して幅いっぱいとなるので画像がぴったり真ん中に来るようになります。

JQueryで非同期で画像プレビューさせるのはファイル選択用のボタンを発火源とするので、
ここにid名:upload--iconをつけておきます。Jqueryの実装については手順6で後ほど解説します。

プロフィールの解説

これは簡単ですね。
複数行のテキストを保存したいのでtext_areaを使用して、autocomplete: 'off'で入力候補の表示をオフにするだけです。
ついでにlabelで入力フォームの上にprofileという文字を表示しておきましょう。

5. 編集画面のビューに対応するscssファイルでレイアウトを調整

さて、いよいよレイアウトの調整に入っていくわけですが、非常に複雑ですのでポイント毎に順を追って解説していきます。

アイコン枠を丸くする方法

幅と高さを同じサイズにし、border-radius: 100pxで要素の角が取れて丸くなります。
このinputは.account-page__inner--icon__input(アイコンの枠)です。

user.scss
&__input {
  width: 180px;
  height: 180px;
  border-radius: 100px;
}

親要素からはみ出した部分を非表示にする

下記を追加しないとアイコンの枠は丸くしたものの、追加する画像が四角い場合、親要素の丸からはみ出て表示されてしまいます。

user.scss
&__input {
  overflow: hidden;
}

アイコン枠内に、既に画像が存在する場合の表示、新しく選択した画像、ファイル選択用のボタンを全て入れ込む

少し長いですが内容としては簡単です。親要素であるinputにposition: rerative、小要素全てにposition: absoluteを記述して、あとは配置を調整。btn部分は表示する必要がないのでopacity: 0で透明化し、cursor: pointerでマウスホバー時にカーソルマークが変わるようにしています。btnに対してopacity: 0では無くdisplay: noneするとボタンが無効化されてしまうので注意してください。

btn部分で幅と高さを1000pxにして親要素に対してright: 0;にしているのには理由があります。このやり方は正攻法ではないと思いますが、私なりに調べ模索した方法になります。このような記述にしないといけなくなった問題は下記の通りです。

file_fieldでファイル選択用のボタンが追加されますが、「ファイルを選択」という部分にはcursor: pointerが有効にならない。一方で「選択されていません」という文字部分にはcursor: pointerが適用されるといった謎な仕様になっています。そこでサイズをめちゃくちゃ大きくし親要素に対してright: 0とすることでカーソルが適用される「選択されていません」部分だけでアイコン枠を埋めてしまおうといった魂胆です。良い子はマネしないで下さい。

user.scss
&__input {
  position: relative;
  &__image {
    position: absolute;
    top: 0;
    left: 0;
  }
  &__image2 {
    position: absolute;
    top: 0;
    left: 0;
  }
  &__btn {
    position: absolute;
    top: 0;
    right: 0;
    width: 1000px;
    height: 1000px;
    opacity: 0;
    cursor: pointer;
  }

プロフィールは大したことないですが、一応載せておきます

user.scss
textarea {
  margin: auto;
  width: 400px;
  height: 150px;
  background-color: black;
  color: white;
  border: solid 1px rgb(150, 150, 150);
  border-radius: 5px;
  padding: 10px;
}

6. 非同期で画像プレビューするためにJSファイルを作成

ファイル名は○○.jsなら何でも良いです。
先ほど作成した編集画面のビューファイルの中でファイル選択用のボタンを発火源とするため、id名:upload-iconを追加しました。この発火源を\$fileField = $('#upload-icon')と書きます。画像を格納したい部分.account-page__inner--icon_input_image2を$previewに代入し、emptyで一旦空に、その後appendで画像を追加します。ここでimgタグを用いていますが、動画素材の場合はvideoタグを指定してやれば動画のプレビューも作ることができます。細かいところは私もまだまだ勉強中なので解説できませんが、流れとしてはこのような感じです。

app/assets/javascripts/icon_preview.js
$(function(){
  $fileField = $('#upload-icon')

  $($fileField).on('change', $fileField, function(e) {
    file = e.target.files[0]
    reader = new FileReader(),
    $preview = $(".account-page__inner--icon__input__image2");

    reader.onload = (function(file) {
      return function(e) {
        $preview.empty();
        $preview.append($('<img>').attr({
          src: e.target.result,
          width: "100%",
          class: "preview",
          title: file.name
        }));
      };
    })(file);
    reader.readAsDataURL(file);
  });
});

7. 詳細画面に表示する用のビュー、レイアウトを調整

最後の工程です!ここまででアイコン・プロフィールの追加は完了しているので、追加したこれらをユーザー詳細画面に表示していきましょう。手順2でコントローラーは既に追記済みなのでビューファイルに書くだけです。

アイコンは編集画面のビューファイル同様に画像が存在する場合(-if @image.present?)という条件をつけてあげないと、画像がない状態で@imageを呼び出そうとしてエラーになります。

また、text_areaで追加したプロフィール文の改行を受付けるためにはsimple_formatをつけます。これを付けずに@profileだけ記載すると、編集画面で入力した際の改行が全て無効となってしまいます。

users/show.html.haml
.content
  .userbox
    .userbox__top
      .userbox__top__left
        // アイコン表示部分
        .userbox__top__left__icon
          -if @image.present?
            = image_tag @image.url, width: '100%'
    (省略)
    // プロフィール表示部分
    .userbox__bottom
      = simple_format(@profile)
    (省略)

レイアウトは手順5を参考に実装してください。アイコン枠、現在の画像のみの表示なので難しくないですね。
これで完成イメージのようなアイコン・プロフィールが作れたかと思います!

お疲れ様でした!

参考

https://qiita.com/shlia/items/d4fdd952b38d4140062d
https://qiita.com/akr03xxx/items/82ba45f7ef4fdbd5c702

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

Railsプロジェクト作成手順(Vue版)

環境

  • macOS Mojave 10.14.6
  • rbenv 1.1.2
  • Homebrew 2.2.10
  • ruby 2.7.0
  • Rails 6.0.2.2

方法

1. ディレクトリを作成する。

$ mkdir practice_project

2. 作ったディレクトリに移動する。

$ cd practice_project

3. Gemfileを生成する。

$ bundle init

4. Gemfileを編集する。

Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "rails" ←コメントアウトを外す

5. Gemをインストールする.

bundle install --path vendor/bundle

6. Railsプロジェクトを生成する。

$ bundle exec rails new . -d mysql --skip-test --webpack=vue

Overwrite /Users/koji/practice/vue_practice/Gemfile? (enter "h" for help) [Ynaqdhm] Y

おわりに

『もっと簡単にできる方法あるよーーー!』

『ここわかりにくいよーーー!』

『ここ間違っているよーーー!』

等あればコメントいただけるとめちゃくちゃ嬉しいです!!!

twiiter → https://twitter.com/jiko797torayo

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

Railsプロジェクト作成手順

環境

  • macOS Mojave 10.14.6
  • rbenv 1.1.2
  • Homebrew 2.2.10
  • ruby 2.7.0
  • Rails 6.0.2.2

方法

1. ディレクトリを作成する。

$ mkdir practice_project

2. 作ったディレクトリに移動する。

$ cd practice_project

3. Gemfileを生成する。

$ bundle init

4. Gemfileを編集する。

Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "rails" ←コメントアウトを外す

5. Gemをインストールする.

bundle install --path vendor/bundle

6. Railsプロジェクトを生成する。

$ bundle exec rails new . -d mysql --skip-test

Overwrite /Users/koji/practice/vue_practice/Gemfile? (enter "h" for help) [Ynaqdhm] Y

(Vueを使う場合)

$ bundle exec rails new . -d mysql --skip-test --webpack=vue

Overwrite /Users/koji/practice/vue_practice/Gemfile? (enter "h" for help) [Ynaqdhm] Y

おわりに

『もっと簡単にできる方法あるよーーー!』

『ここわかりにくいよーーー!』

『ここ間違っているよーーー!』

等あればコメントいただけるとめちゃくちゃ嬉しいです!!!

twiiter → https://twitter.com/jiko797torayo

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