20190512のRailsに関する記事は12件です。

Railsのattributeはバリデーションではなくキャスト

どうも、ITのんびりです

モデルに書くattributeの意味がいまいち分かっていなかったので、調べてみました

使用例

attribute :id, :integer

こんな感じでパラメーター名と型を書きますよね
最近まで意味がよく分からず、「idのデータ型をintegerに限定しているのかなあ」と何となく思っていました

バリデーションではなくキャスト

attributeはバリデーションを設定しているのではなく、指定した型以外のデータが送られてきたら指定した型に変換(キャスト)します

attribute(name, cast_type = Type::Value.new, **options)

cast_typeで指定したtypeではない型のデータが飛んできたら指定したtypeにcastします

attribute :id, :integer

integerで指定して10.1がきたら10にcastします

attribute :flag, :bool

boolで指定して"false"という文字列が飛んできたらbooleanのfalseにcastします

バリデーションとの順番

attributeでキャストしてからvalidationが判定されるようです

attribute :id, :integer
validates :id, numericality: { only_integer: true }

idを整数にcastして、整数のバリデーションをかけるとします
idが10.1で送られてきたら、attributeで10にcastされます
idが10で整数なので、only_integer: trueのバリデーションは通ります

整数のバリデーションをかけているのに、10.1が送られてきてバリデーションが通るのはよくないですね(10にcastされているとはいえ)

バリデーションをかけるときは、castの影響を考えてするようにしましょう

参考記事

Rails5: ActiveRecord標準のattributes API(翻訳)

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

[Rails] devise token auth を使う

devise token authのチュートリアルのような記事です。
大部分はこの記事を参考にしています。一部、バージョンの違い(?)によるのか、エラーが出る箇所があったため、この記事を作りました。

セットアップ

はじめにRailsアプリを作ります。

terminal
rails new devise_token_auth_test

GemFileにdevise token auth用のgemを追記します。

Gemfile
# devise関連
gem 'devise'
gem 'devise_token_auth'

# CORS設定
gem 'rack-cors'

gem 'rack-cors'を入れる理由については公式DocsCross Origin Requests (CORS)に載っています。

bundle install した後データベースを作成します。

terminal
bundle install
rake db:create

devise token auth の導入

installします。
通常のdeviseについては、公式Docsにインストールしてくださいとは載っていませんが、rails s時にエラーが出るのでインストールします。

terminal
rails g devise:install
rails g devise_token_auth:install User auth

次に、インストール時に作成されたdb/migrate/~_devise_token_auth_create_users.rbTrackableに関するカラムを追記します。
これがないと、サインイン時にUndefined method current_sign_in_atcurrent_sign_in_atはTrackableに関するカラム)とエラーが出ます。

db/migrate/~_devise_token_auth_create_users.rb
class DeviseTokenAuthCreateUsers < ActiveRecord::Migration[5.2]
  def change

    create_table(:users) do |t|
      ## Required
      t.string :provider, :null => false, :default => "email"
      t.string :uid, :null => false, :default => ""

      ## Database authenticatable
      t.string :encrypted_password, :null => false, :default => ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at
      t.boolean  :allow_password_change, :default => false

      ## 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

      ## User Info
      t.string :name
      t.string :nickname
      t.string :image
      t.string :email

      ## Tokens
      t.text :tokens

      t.timestamps
    end

    add_index :users, :email,                unique: true
    add_index :users, [:uid, :provider],     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

では、devise token auth の設定を追加します。

config/initializers/devise_token_auth.rb
DeviseTokenAuth.setup do |config|
  # リクエストごとにトークンを更新するか
  # 扱いやすいようにFalseにします
  config.change_headers_on_each _request = false

  # トークンの有効期間
  # デフォルトでは2週間です
  config.token_lifespan = 2.weeks

  # ヘッダーの名前対応
  config.headers_names = {:'access-token' => 'access-token',
                          :'client' => 'client',
                          :'expiry' => 'expiry',
                          :'uid' => 'uid',
                          :'token-type' => 'token-type' }
end

また、application_controllerに以下を追記してください。

controllers/application_controller.rb
class ApplicationController < ActionController::Base
  include DeviseTokenAuth::Concerns::SetUserByToken
  skip_before_action :verify_authenticity_token, if: :devise_controller? # APIではCSRFチェックをしない
end

これで準備が出来ました。

試す

サーバーを起動して動かしてみましょう。

terminal
rails s

サインアップ

terminal
curl localhost:3000/api/auth -X POST -d '{"email":"example@example.com", "password":"password", "password_confirmation": "password"}' -H "content-type:application/json"

curlを使ってUserを作成するAPI(localhost:3000/api/auth)を叩いています。
引数の説明は以下の通りです。

-X POST POSTでリクエスト
-d 'foo:bar' Bodyに入れるデータ
-H 'foo:bar' ヘッダーに入れるデータ

今回は、Bodyにemailpasswordを 、ヘッダーに、Bodyの中身がjson形式であることを示すための設定content-type:application/jsonを追加しています。

サインアップに成功すると以下のようなレスポンスが返ってくるはずです。

terminal
{"status":"success","data":{"id":1,"provider":"email","uid":"example@example.com","allow_password_change":false,"name":null,"image":null,"email":"example2@example.com","created_at":"2019-05-12T20:48:24.000+09:00","updated_at":"2019-05-12T20:48:24.000+09:00"}}

サインイン

terminal
curl localhost:3000/api/auth/sign_in -X POST -d '{"email":"example@example.com", "password":"password"}' -H "content-type:application/json" -i

-iオプションを付けてレスポンスのヘッダーも表示されるようにしています。
(ここの情報をこの後使います。)

成功すると以下のようなレスポンスが返ってくるはずです。

terminal
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Content-Type: application/json; charset=utf-8
access-token: xzTWhTURMmPdDjaKB2R9gw
token-type: Bearer
client: S2T6zY0aDB6VBXYVMdpjbg
expiry: 1558871460
uid: example@example.com
Cache-Control: no-store, must-revalidate, private, max-age=0

{"data":{"id":1,"email":"example@example.com","provider":"email","uid":"example@example.com","allow_password_change":false,"name":null,"image":null}}

パスワードの変更

terminal
curl localhost:3000/api/auth/password -X PUT -d '{"password":"password_new", "password_confirmation": "password_new"}' -H "content-type:application/json" -H "access-token: xzTWhTURMmPdDjaKB2R9gw" -H "client: S2T6zY0aDB6VBXYVMdpjbg" -H "uid: example@example.com"

パスワードの変更はPOSTではなくPUTを指定します。
また、ログイン時のレスポンスのヘッダーにあったaccess-token client uidをヘッダーに追加しています。

成功すると以下のようなレスポンスが返ってくるはずです。

terminal
{"success":true,"data":{"id":1,"provider":"email","allow_password_change":false,"email":"example@example.com","uid":"example@example.com","name":null,"image":null,"created_at":"2019-05-12T20:30:50.000+09:00","updated_at":"2019-05-12T21:31:33.000+09:00"},"message":"パスワードの更新に成功しました。"}

deviseのコントローラーを編集したい時

terminal
rails g controller api/auth/registrations
controllers/api/auth/registrations_controller.rb
class Api::Auth::RegistrationsController < DeviseTokenAuth::RegistrationsController
  # hogehoge
end
config/routes.rb
Rails.application.routes.draw do

  namespace :api do
    mount_devise_token_auth_for 'User', at: 'auth', controllers: {
        registrations: 'api/auth/registrations'
    }
  end

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

Railsの「annotate」gemが最高すぎる件

環境

  • Docker version 18.09.2
  • Ruby 2.4.5
  • Rails 5.0.0

なぜ書いたのか

「あれ、テーブルってどんな構造してたっけ❓」
っていう時がかなりあった。

今まで確認する時は、

  1. DBに入る
  2. コマンドぽちぽち入力
  3. DB抜ける

上記3点のプロセスだったのが、

  1. Modelのrbファイル確認

annotateをインストールするだけでここまで短縮できることに感動したから

導入、実装

1.Gemfileに追記

Gemfile
group :development do 
  gem 'annotate' 
end

※基本的に開発環境でのみ使用するので、development内に書く

2.gemをインストール

ターミナル
$ docker-compose build

3.gemの実行

ターミナル
$ docker-compose exec web bundle exec annotate

これでModelを確認すると、下記のような感じでコメントアウトでテーブルの構造が!すごい!
スクリーンショット 2019-05-12 20.54.35.png

ただこれだとテーブルを作成、更新するたびに実行しなければならないので、
migrationをトリガーに実行するように設定ファイルを生成

ターミナル
docker-compose exec web bundle exec rails g annotate:install

これでmigrationの度にannotateが実行されるように?

おまけ

routeにも適用できるらしいのでやってみた

ターミナル
$ docker-compose exec web bundle exec annotate --routes

スクリーンショット 2019-05-12 21.17.53.png

参考

https://qiita.com/tsuchinoko_run/items/9bcc15e8992cb237e08a
https://www.udemy.com/share/1014LIB0sacllTQ3s=/

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

【rails】slickをを使ったスライダーの実装

前置き

今回かなり手こずったrailsでのslickの使用方法について説明します。環境によっては作成方法が違いますので、ご理解頂けますと幸いです。
今回作成したスライダーのGIFが下記URLに添付しております。(課題の成果物となります)
カルーセル・サンプル
かなり初心者向けに作成していますので、「こんにゃの知ってるにゃ!」
と思われる方々向けに作成していません。

slickとは

jQueryのプラグインです。様々なスライダーを実装する事ができるので、カルーセル機能の定番としてよく使用されます。
なので、いろんな開発環境でも使用できるように、設置が簡単かつシンプルで便利なのが特徴だと思いました。
何より素晴らしのが、レスポンシブに対応している事だと思います。

作成前の確認しておく事項

今回作成するスライダーの機能
・ 矢印をクリックする事で次のページに飛べるようにする
・ 下部分の丸をクリックする事でそのページに飛べるようにする
・ 自動スライダーを実装する
・ 横にスライドさせる

とこんな感じになります。

公式サイトとREADMEとこちらのサイトを今回は参考にしています。
slick
slick README
http://uzurea.net/explanation-of-jquery-slider-slick/

スライダーの実装

slick
公式サイトの
image.png

赤枠部分を使用します。まずファイルをダウンロードしてください。
ファイルの中身は(2019-05-12)の時点でこんな感じです
image.png
今回はこの赤枠の[slick]ファイルを使用します。
image.png
その中の赤枠ファイルを使用します。

ファイルの格納場所

slick-theme.scss
slick.scss

は、これから使用する、Slider・Icons・Arrows・Dots のcssの設定が入っています。

app/assets/stylesheets
の中に格納してください。

slick.min.js

には動作の設定が入っているので
app/assets/javascripts
に格納してください。

読み込み準備

app/views/layoutsに先ほどの
image.png
の下部分の赤枠のコードを貼り付けます。

application.html.haml
!!!
%html
  %head
    = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload'
    %link{href: "//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.css", rel: "stylesheet", type: "text/css"}/
    = javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
    %script{src: "//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.min.js", type: "text/javascript"}
  %body
    = yield

次にjqueryを使用する為に
gemを作成します。既に作成している場合は飛ばしてくださいませ〜

gem 'jquery-rails'
application.js
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require_tree .   ←これが一番後ろにこないと、うまく動きません

scssのインポートを忘れずに

application.scss
@import "slick-theme"; 
@import "slick";

*作成する物が常に動的に更新される想定でしたら、CDNを勉強する事をお勧めします。

ビューの編集

スライダー部分の簡略した書き方が下記になります

index.html.haml
%div.slick
  %div
    %h3 1
  %div
    %h3 2
  %div
    %h3 3
  %div
    %h3 4
  %div
    %h3 5
  %div
    %h3 6

jsの編集

jqの描き方は下記のようになります

carousel.js
$(document).on('turbolinks:load', function(){
  $('.slick').slick({
    autoplay:true,  ←自動再生
    dots:true,      ←ドットの描写
  });
});

これだけで、最低限必要な実装が作成されます。
buttonとか作成しなくても勝手に作成してくれます。
「便利にゃ〜」
上記で基本は作成できました。

最終調整

次に、現在の画面では、矢印(今回はpreviousとnextの部分)やドットの表示が画面外にあるので、画面の中に配置する調整を行います。
この時気をつけて頂きたいことがあります。

application.scss
@import "slick-theme"; 
@import "slick";
@import "carousel";

編集するscssはダウンロードファイルより後ろに作成してください。
cssは、上書き方式なので、ダウンロードファイルより前にインポートすると
反映されません。

先ずは・・・

slick-theme.scss
を見てください。

slick-theme.scss
/* Arrows */

.slick-prev,
.slick-next {
    position: absolute;
    display: block;
    height: 20px;
    width: 20px;
    line-height: 0px;
    font-size: 0px;
    cursor: pointer;
    background: transparent;
    color: transparent;
    top: 50%;
    -webkit-transform: translate(0, -50%);
    -ms-transform: translate(0, -50%);
    transform: translate(0, -50%);
    padding: 0;
    border: none;
    outline: none;
    &:hover, &:focus {
        outline: none;
        background: transparent;
        color: transparent;
        &:before {
            opacity: $slick-opacity-on-hover;
        }
    }
    &.slick-disabled:before {
        opacity: $slick-opacity-not-active;
    }
    &:before {
        font-family: $slick-font-family;
        font-size: 20px;
        line-height: 1;
        color: $slick-arrow-color;
        opacity: $slick-opacity-default;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
    }
}

.slick-prev {
    left: -25px;
    [dir="rtl"] & {
        left: auto;
        right: -25px;
    }
    &:before {
        content: $slick-prev-character;
        [dir="rtl"] & {
            content: $slick-next-character;
        }
    }
}

.slick-next {
    right: -25px;
    [dir="rtl"] & {
        left: -25px;
        right: auto;
    }
    &:before {
        content: $slick-next-character;
        [dir="rtl"] & {
            content: $slick-prev-character;
        }
    }
}

長いですが、この部分が矢印のscssになります。
[dir="rtl"]
は属性セレクターです。指定された属性が存在するかどうかや、その値に基づいて要素を選択します。上記は、HTMLのdir属性がrtlの時を選択します。
私は今回矢印を作成せずに実装する事にしています。なので、コンソールからコードを見てもらうとわかるのですが、擬似要素の::beforeは作成されていません。
image.png
矢印を作成したやり方でも問題ないのですが、私の場合はコードを短くしたかった・・・
ちなみに、、、slick.jsファイルのオプションの箇所に下記のように記述すれば好きな画像をarrowsに設定できます。
詳しくはこちらのサイトを参考にしてください。
実証していないので、不確かなのですが、css側でも下記の様に変更できるそうなので。試してみるのも良いかもしれません。詳しくはこちら

slick.js
$(function(){
  $("#slider").slick({
    prevArrow: '<img src="前への矢印画像のパス" class="slide-arrow prev-arrow">',
    nextArrow: '<img src="次への矢印画像のパス" class="slide-arrow next-arrow">'
  });
});
carousel.scss
.slick-prev{
  left: 50px;
  height: 80px;
  width: 80px;
  background-image: image-url("hidari.png");
  z-index: 100;
  background-size: 30px;
  background-repeat: no-repeat;
  background-position: center;
}

私の場合は矢印を背景画像で表示する編集をしました。left: 50px;で画面の中に表示できる様になるので、先ずは先に・・・その後諸所調整した感じです。.slick-next も同様に調整してください。

HTMLを追加してimage_tagで対応する方法もあります。
&:hover, &:focus,&:before は今回は使用しないので削除しました。

上記ができれば、ドット部分も簡単に調整できます。
私の作成したコードを添付します

carousel.scss
.slick-dots {
  bottom: 25px;
  li button{
    &::before{
      height: 15px;
      width: 15px;
      background-color: rgb(116, 116, 116);
      border-radius: 50%;
    }
  }
}

私は、、、こんな感じで作成したので、、
参考までに・・・参考にしないでくださいw

ヒント集

矢印の位置がうまく行かない時は

・position: relative;とposition: absolute;がどうなっているか確認すること。
・display: block;になっているか確認すること
・z-index: 100;が適応されていない可能性があるので、position: relative;から、z-index: 100;に至る間に、position: absolute;が全てに適応されているか確認すること。

既存の「←」を消すには

・content: "";

何か間違っている箇所ございましたらコメントくださると大変助かります。

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

これは便利!1つのコマンドでHaml変換できる【Ruby on Rails】

目次

  • gemを追記する(Hamlを導入する)
  • html.erbファイルを一括で変換する

gemを追記する(Hamlを導入する)

Gemfile
gem 'haml-rails'
gem 'erb2haml'

※(注意点)テスト環境と開発環境以外に追記する

ターミナル
$ bundle install

gemを記述したらこのプロセスを忘れてはいけませんでしたね!

html.erbファイルを一括で変換する

$ rake haml:replace_erbs

この処理が終わり、html.erbファイルが下記のようにHaml変換されていれば成功です。

!!!
%html
  %head
    %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
    %title ChatSpace
    = csrf_meta_tags
    = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload'
    = javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
  %body
    = yield

END

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

Railsでアプリ開発〜1日目〜

どんなアプリ?


名前

  • schedun(スケジュン)

内容

  • 勉強などのスケジュールアプリ。
  • 親と子アカウントを作成し、親が出したスケジュールに対して子供が行動。
  • 親との会話も可。

今日の目標

環境構築

様子

  1. どのように環境を構築していくか
    • Docker
    • CentOS
    • Ubuntu

ubuntu

  • 参考書通りにインストール → できない!エラー発生! → なし

docker

  • dockerのwindows版いれる→ Proじゃないのでできない → なし

centos

  • これしかないので頑張ってインストール

1つ目のエラー

Postgresqlを無事にインストールして、
Rails new schedun -d postgresql
としてみました。
そうすると、
pgがありません
と言われたので書いてある通りインストール → まだエラー
小1時間考えて見て、結局、sqliteにしました。

次にやること

  • それぞれのビュー、コントローラーなどを作成
  • 画面のレイアウトを作成
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

gretelをつかわずにRailsでパンくずリストを作成

パンくずリストとは

こういうやつです。
Image from Gyazo

gretelというgemを使うとスッキリ作れるみたいなのですが、当時このgemを知らなかったたま力技で作りました。

ページを判断するメソッド

ページごとにリストが違うため、現在どのページなのかを判断するメソッドをつくりました。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
 before_action :request_path

 def request_path
    @path = controller_path + '#' + action_name
    def @path.is(*str)
        str.map{|s| self.include?(s)}.include?(true)
    end
  end
end

アプリケーションコントローラーにbefore_actionとして設定しているので、ページが遷移するたびにこのメソッドが発動されます。

コードの説明

ページ遷移する際のコントローラー名とアクション名を取得し、@pathに"コントローラー名#アクション名"という形で格納します。

次に引数を配列で取得し、その配列の中に@pathであるものがあるか確かめます。
配列の中にひとつでもtrueがあれば、tureを返しています。

メソッドの呼び出しは例えばこのような形です。

- unless @path.is('products#index')
    .breadcrumbs
      %ul
        %li

引数に「products#index」を渡しているので、productコントローラーのindexアクションの遷移先であるビューでなければ、breadcrumbsクラスが表示されるようになっています。

完成したパンくずリスト

- unless @path.is('products#index')
    .breadcrumbs
      %ul
        %li
          = link_to "○○", root_path
        - if @path.is('categories#index')
          %li
            = icon 'fas', 'chevron-right'
          %li
            %p.last カテゴリー一覧
        - if @path.is('categories#show') && @category.indirect_ids.present?
          %li
            = icon 'fas', 'chevron-right'
          %li
            = link_to "カテゴリー一覧", categories_path, data: {"turbolinks": false}
          %li
            = icon 'fas', 'chevron-right'
          %li
            %p.last #{@category.name}
        - if @path.is('categories#show') && @category.indirect_ids.empty? && @category.children.present?
          %li
            = icon 'fas', 'chevron-right'
          %li
            = link_to "カテゴリー一覧", categories_path
          %li
            = icon 'fas', 'chevron-right'
          %li
            = link_to "#{@category.parent.name}", category_path(@category.parent)
          %li
            = icon 'fas', 'chevron-right'
          %li
            %p.last #{@category.name}
        - if @path.is('categories#show') && @category.children.empty?
          %li
            = icon 'fas', 'chevron-right'
          %li
            = link_to "カテゴリー一覧", categories_path
          %li
            = icon 'fas', 'chevron-right'
          %li
            = link_to "#{@category.parent.parent.name}", category_path(@category.parent.parent)
          %li
            = icon 'fas', 'chevron-right'
          %li
            = link_to "#{@category.parent.name}", category_path(@category.parent)
          %li
            = icon 'fas', 'chevron-right'
          %li
            %p.last #{@category.name}
        - if @path.is('users#show')
          %li
            = icon 'fas', 'chevron-right'
          %li
            %p.last マイページ
        - if @path.is('users#edit')
          %li
            = icon 'fas', 'chevron-right'
          %li
            = link_to "マイページ", user_path(@user)
          %li
            = icon 'fas', 'chevron-right'
          %li
            %p.last プロフィール
        - if @path.is('addresses#edit')
          %li
            = icon 'fas', 'chevron-right'
          %li
            = link_to "マイページ", user_path(@user)
          %li
            = icon 'fas', 'chevron-right'
          %li
            %p.last 住所変更
        - if @path.is('users#credit')
          %li
            = icon 'fas', 'chevron-right'
          %li
            = link_to "マイページ", user_path(@user)
          %li
            = icon 'fas', 'chevron-right'
          %li
            %p.last 支払い方法
        - if @path.is('users#logout')
          %li
            = icon 'fas', 'chevron-right'
          %li
            = link_to "マイページ", user_path(@user)
          %li
            = icon 'fas', 'chevron-right'
          %li
            %p.last ログアウト
        - if @path.is('searches#index')
          %li
            = icon 'fas', 'chevron-right'
          %li
            %p.last
              = "#{@keyword}"

ページごとにリストを作っているので、やっぱりコードが長くなってしまいました。
また、categories#showが3つあったので、それぞれ条件を足しています。

まとめ

次回からはgemを使おうと思います。

ただ、「コントローラー#アクション」であるかを確かめるメソッドはどこかで役に立つときがきそうです。

参考
https://qiita.com/toduq/items/a9fa48926b060b2d8a5b

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

Ajax通信によってセレクトボックスを出現させる

概要

Ajax通信を利用し、あるアクションによってセレクトボックスを出現させます。

↓完成形
Image from Gyazo

困ったポイントは、選択するものによってselectタグのoptionの数が違うということです。

今回でいうと、
レディース トップス/パンツ の2つ
メンズ   トップス/パンツ/バッグ/靴 の4つ

条件によって、ビューに挿入する要素の長さを変えなければいけません。

完成コード

 まずは完成コードがこちらです。

category
$("#range").change(function(){
    $("#range-second").remove();
    $("#range-third").remove();
    var category = $('option:selected').val();

    function buildHTML(children){
      var option = ``
      children.forEach(function(child){
        option += `<option value="${child.id}">${child.name}</option>`
      });

      var html = `<select name="range-second" id="range-second">
                    <option value label=" "></option>
                    ${option}
                   </select>`

      return html;
    };

    $.ajax({
      url: '/users/1/products/new',
      type: "GET",
      data:{category: category},
      dataType: 'json'
      })
      .done(function(data){
        var html = buildHTML(data);
        $(".products_new_container__content__select__box__category").append(html);
      })
      .fail(function(){
        alert('error');
      })
  });

このなかで、今回のポイントとなるコードはこの部分です。

function buildHTML(children){
      var option = ``
      children.forEach(function(child){
        option += `<option value="${child.id}">${child.name}</option>`
      });

      var html = `<select name="range-second" id="range-second">
                    <option value label=" "></option>
                    ${option}
                   </select>`

      return html;
    };

一行目のbuildHTML関数の引数「children」には、selectタグの選択肢が配列として格納されています。

二行目で、まず空の変数optionを宣言します。

その次でforEachメソッドを使用し、childrenに含まれた要素の数だけ変数optionの中にoptionタグを追加していきます。

あとは、必要なだけのoptionタグの入った変数optionを、ビューに挿入する変数htmlのなかに入れるだけです。

まとめ

まず空の箱を用意して、配列の要素をeachでひたすら詰めていくというのは、Rubyでもよくやりました。

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

Rails・Haml・Ajaxでいいね機能を実装

いいね機能とは

ユーザーが投稿した記事や商品に対して、「いいね」をつけます。
以下が完成形です。

Image from Gyazo

実装方法

今回はRails、Hamlという環境で実装します。
ページのリロードはせず、非同期通信で画面を変化させます。

ポイントは以下の通りです。
・ユーザーはひとつの記事や商品に対し、一度しか「いいね」できない。
・「いいね」するとボタンの色が変わり数字が増え、もう一度押すと取り消され元のボタンに戻る。

実装の準備

では、順をおって実装していきましょう。
今回はフリーマーケットサイトにおいて、ユーザーが出品した商品に対しての「いいね」になります。

各モデルの準備

ユーザー(user)が商品(product)に対し「いいね(like)」するので、
・users
・products
・likes
以上三つのモデル、テーブルが必要です。

ですが、今回は「いいね」の実装ということで、userとproductがすでに存在している前提でのお話になります。

いいね(like)は、それをしたユーザー(user)とそれをされた商品(product)に紐づいているので、それに応じたアソシエーションを組む必要があります。

では、それぞれをつくっていきましょう。

ターミナル
$ rails g model like

Modelの命名規則は単数形なので、ここでも単数形で書くのが良さそうです(複数形で書いても勝手に単数形モデルになりますが・・・)。

アソシエーションを組む

ユーザー(user)と商品(product)は「いいね(like)」をたくさん持っていて、「いいね(like)」はそれらに属しています。

ですので、カラムをつくるときは、likesテーブルに「user_id」と「product_id」を持たせます。

db/migrate
class CreateLikes < ActiveRecord::Migration[5.2]
  def change
    create_table :likes do |t|
      t.references :product
      t.references :user
      t.timestamps
    end
  end
end
ターミナル
$ rake db:migrate

アソシエーションは、

app/models/user.rb
class User < ApplicationRecord
 has_many :likes, dependent: :destroy
end
app/models/product.rb
class Product < ApplicationRecord
 has_many :likes, dependent: :destroy
end
app/models/like.rb
class Like < ApplicationRecord

 belongs_to :product
 belongs_to :user

 validates :user_id, presence: true
 validates :product_id, presence: true
 validates_uniqueness_of :product_id, scope: :user_id

end

「dependent: :destroy」
 userやproductが削除された際は、それに関連するlikeも一緒に消してねという意味です。

「validates :user_id, presence: true」「validates :product_id, presence: true」
 likeを生成するときはuser_idとproduct_idが空ではだめだよというバリデーション。

「validates_uniqueness_of :product_id, scope: :user_id」
 ひとつのproductにひとりのuserが複数回いいねできないよう、product_idとuser_idの組み合わせはひとつだけだよというバリデーションです。

とりあえずの紐づけができました。

Likesコントローラーの作成

コントローラーも作ります。
「いいね(like)」は「生成」と「削除」のふたつが想定されるので、createアクションとdestroyアクションがありますね。

ターミナル
$ rails g controller likes

コントローラーの命名規則は複数形です。

app/controllers/likes_controller.rb
class LikesController < ApplicationController

  def create
  end

  def destroy
  end

end

ルーティング

「いいね(like)」はproductに対してのもので、likesコントローラーで持ち主のproduct_idが必要なので、productにネストさせます。

しかし、今回はすでにproductがuserにネストされているので、禁断の2段階ネストになっています。

config/routes.rb
resources :users, only: [:show, :edit, :update] do
 resources :products, only: [:new, :create, :show] do
  resources :likes, only: [:create, :destroy]
 end
end

ここまでが準備です。

実装する

では、実装していきましょう。

まず、全体のイメージをざっくり説明です。
実装は大きく分けて三つ、
1.ユーザーがすでに「いいね」しているかどうかで、ボタンのスタイルが違う。
2.ボタンを押したら数字が増え、もう一度押すと数字が減る。
3.ボタンが押されるというアクションによって、ボタンの状態を切り替える。

まず、1
これはビューにおいてボタンのスタイルを2つつくり、ifを使って場合分けします。

次に、2
「いいね」の数はテーブルにあるレコードの数なので、「いいね」するとレコードが増え、その分数字が増える。減るのは、レコードが削除されるため。

最後に、3
これは非同期通信によるものです。
具体的には、1で説明したビューが部分テンプレートになっており、ボタンが押されたことにより、この部分テンプレートに送る値や条件を変化させています。

この説明ではわかりづらすぎるので、実際に手順を追っていきます。

カウント機能の作成

likesテーブルにあるproduct_idごとのレコードを集計します。
まずは、likeモデルに追加。

app/models/like.rb
class Like < ApplicationRecord
#counter_cache: :likes_count を追加
 belongs_to :product, counter_cache: :likes_count
 belongs_to :user

end

「counter_cache: :likes_count」
 リレーションされたlikeの数を数え、productのlikes_countカラムに入れるという意味。likeがcreateされるとlike_countが+1、destroyされると-1します。

よって、productsテーブルにlikes_countカラムを追加しなければなりません。

ターミナル
$ rails g migration AddClomunToLikes
db/migrate
class AddClomunToLikes < ActiveRecord::Migration[5.2]
  def change
    add_column :products, :likes_count, :integer
  end
end
ターミナル
$ rake db:migrate

こんな感じのものができあがります。
Image from Gyazo

実際に「いいね」していくと、このように数字がカウントされます。

ビューの作成

ビューの作成ですが、まずはユーザーがすでに「いいね」しているのかどうかのメソッドをつくります。

app/models/product.rb
class Product < ApplicationRecord
 ...

#以下を追加
 def like_user(user_id)
  likes.find_by(user_id: user_id)
 end
end

引数として渡されたuser_idが、likesテーブルのuser_idカラムにすでに存在しているかを確かめ、true、falseを返します。

app/views/products/show.html.haml
...

.item-button-container
  .item-button-container__left
    = render partial: 'likes/like', locals: { product: @product, products: @products, likes: @likes, like: @like}

...
app/views/likes/_like.rb
#ユーザーがサインインしているかどうか
- if user_signed_in?
 #ログインしているユーザーがすでに「いいね」しているかどうか
  - if product.like_user(current_user.id)
    .item-button-container__left__dislike
      =link_to user_product_like_path(current_user, product, like), method: "DELETE", remote: true do
        %i.fas.fa-heart
        %span いいね!
        %span 
          = product.likes_count
  - else
    .item-button-container__left__like
      =link_to user_product_likes_path(current_user, product), method: "POST", remote: true do
        %i.fas.fa-heart
        %span いいね!
        %span
          = product.likes_count
-else
  .item-button-container__left__like
    %i.fas.fa-heart
      %span いいね!
      %span = product.like_count

「= product.likes_count」で、さきほどのいいねのカウント数が表示されます。

Ajax通信

ボタンを押すというアクションによってAjax通信をします。

といっても、link_toメソッドにremote: trueを記述しているので、jsファイルに通信のあれこれを定義することはありません。すでにこのlink(今回のボタン)はAjaxになっています。

「いいね」を押した先のコントローラー

app/controllers/likes_controller.rb
class LikesController < ApplicationController

  def create
    @like = Like.create(user_id: current_user.id, product_id: params[:product_id])
    @likes = Like.where(product_id: params[:product_id])
    get_product
  end

  def destroy
    @like = Like.find_by(user_id: current_user.id, product_id: params[:product_id])
    @like.destroy
    @likes = Like.where(product_id: params[:product_id])
    get_product
  end

  def get_product
    @product = Product.find(params[:product_id])
  end
end

ボタンによってcreate、destroyをするとlikes_countの数値が変わるので、数値の変わったlikesとproductを改めて取得してビューに渡します。

最後に、これらの値が渡ったjsファイルをつくります。

app/views/likes/create.js.haml
$(".item-button-container__left").html("#{escape_javascript(render partial: 'like', locals: { product: @product, products: @products, likes: @likes, like: @like})}")
app/views/likes/destroy.js.haml
$(".item-button-container__left").html("#{escape_javascript(render partial: 'like', locals: { product: @product, products: @products, likes: @likes, like: @like})}")

ここでは部分テンプレートのみを書き換えています。

つまり、部分テンプレートに渡す値を「いいね」が追加、削除された後のものに置き換えているということです。
これにより、現在のユーザーの「いいね」も追加されたり削除されるので、条件分岐によりボタンのスタイルが変わります。

まとめ

Ajax通信で部分テンプレートを差し替えるというテクニックは他にも応用ができそうです。

仕組みが複雑で説明がとても難しく散漫な記事となってしまいましたので、参考記事とともにご覧いただければ幸いです。

https://qiita.com/YuitoSato/items/94913d6a349a530b2ea2
https://www.prime-architect.co.jp/myblog/ruby-on-rails-1559

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

OSSで政治系のことについて議論できるChatスペースを作りたい

課題

日本ではSNSで政治のことは呟かれない

日本人は周りの人に合わせる癖があるので、SNSの投稿は基本的にキラキラしたものが多いです。自分がキラキラした生活を送っていることを投稿すればするほど、みんなからのいいねがたくさん来たり、みんなと同じような投稿をしたりすると共感が得られるシステム、それがSNSです。だからいつもSNSでフォロワーの多い人や友達のツイートを見て、同じような投稿をし、共感をえて、いいねをもらう。一つのゲームみたいなもので、ポケモンGOとかとあまり変わりありません。
キラキラしたものをinstagramに載せるのが悪いと言ってる訳ではなく、社会にはまだまだ問題がたくさんあり、不幸な人もたくさんいるのでその人たちの意見も届いたり、解決策を出せる場所が必要。

解決策

政治系のSNSを作っちゃお!!!

やること

オープンソースのSpectrumを公開サーバーに移す

https://github.com/withspectrum/spectrum/blob/alpha/docs/deployments.md

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

ポートフォリオ『CodeZine』使用解説

CodeZine

これまでに読んだ技術本を

・ タイトル ・ おすすめポイント ・ 画像

の3点で簡潔にまとめてユーザー間でシェアするアプリです。

使い方の説明

・テストユーザーでログイン

・記事を投稿するために「新規登録」ボタンをクリック

→新規記事投稿ページの入力フォームに、

おすすめの読んだ本のタイトル、おすすめポイント、画像の3点を書き込む。

・投稿者一覧ページに新規記事が追加される。

ezgif.com-video-to-gif.gif

・記事を編集・削除するには

→記事一覧ページの編集ボタンをクリック

ezgif.com-video-to-gif (1).gif

・記事の検索するには
→名称もしくは登録日時を入力し検索ボタンをクリック。
ezgif.com-video-to-gif (2).gif

主な機能

■ユーザー管理機能

・新規ユーザー登録機能

・ユーザー詳細表示機能

・ユーザー一覧表示機能

・ユーザーアカウント編集・削除機能

・ユーザーログイン、ログアウト機能

■投稿記事管理機能

・新規記事投稿機能

・記事詳細表示機能

・記事への画像アップロード機能

・記事編集・削除機能

・記事一覧表示機能

・記事検索機能

■ユーザー・記事・検索結果一覧のページネーション機能

使用技術

・開発環境

ローカル for Mac

・テスト環境

RSpec(システムテスト、結合テスト・機能テスト)

・本番環境

Heroku

・DB

Postgresql

・フロント開発

Bootstrap

Slim

・画像アップロード機能

Active Storange

・ページネーション機能

kaminari

・検索機能

ransack

・CSV形式のファイルのインポート、エクスポート

・Ajax

投稿記事削除

依存関係

Rails: 5.2.3

Ruby: 2.5.1

Bootstrap: 4.3.1

RSpec 3.8

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

ポートフォリオ『CodeJin_BOOK123』使用解説

*アプリ名が登録商法に引っかかる可能性があるようなので変更しました。
 ご指摘ありがとうございます!
 

Codejin_BOOK123

これまでに読んだ技術本を

・ タイトル ・ おすすめポイント ・ 画像

の3点で簡潔にまとめてユーザー間でシェアするアプリです。

使い方の説明

・テストユーザーでログイン

・記事を投稿するために「新規登録」ボタンをクリック

→新規記事投稿ページの入力フォームに、

おすすめの読んだ本のタイトル、おすすめポイント、画像の3点を書き込む。

・投稿者一覧ページに新規記事が追加される。

ezgif.com-video-to-gif (3).gif

・記事を編集・削除するには

→記事一覧ページの編集ボタンをクリック

ezgif.com-video-to-gif (4).gif

・記事の検索するには
→名称もしくは登録日時を入力し検索ボタンをクリック。

ezgif.com-video-to-gif (5).gif

主な機能

■ユーザー管理機能

・新規ユーザー登録機能

・ユーザー詳細表示機能

・ユーザー一覧表示機能

・ユーザーアカウント編集・削除機能

・ユーザーログイン、ログアウト機能

■投稿記事管理機能

・新規記事投稿機能

・記事詳細表示機能

・記事への画像アップロード機能

・記事編集・削除機能

・記事一覧表示機能

・記事検索機能

■ユーザー・記事・検索結果一覧のページネーション機能

使用技術

・開発環境

ローカル for Mac

・テスト環境

RSpec(システムテスト、結合テスト・機能テスト)

・本番環境

Heroku

・DB

Postgresql

・フロント開発

Bootstrap

Slim

・画像アップロード機能

Active Storange

・ページネーション機能

kaminari

・検索機能

ransack

・CSV形式のファイルのインポート、エクスポート

・Ajax

投稿記事削除

依存関係

Rails: 5.2.3

Ruby: 2.5.1

Bootstrap: 4.3.1

RSpec 3.8

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