20200212のRailsに関する記事は30件です。

【Rails】flashメッセージをフェードアウトで消す方法【JavaScript】

はじめに

この記事では、flashメッセージを表示したあと一定時間後にフェードアウトさせる方法を解説します。

flashメッセージ系の記事はたくさんあるのですが、
どれもbootstrapを使用してたり
何やら複雑な方法だったり(Hamlハムル??とかrenderとかややこしい!)。。

もっと簡単な記事はないのか!とモヤモヤしたので、自分まとめます。
自分と同じような人の為に!!(自分用のメモですすみませんw)

flashメッセージの表示方法

それではレッツ実装!!

まずはコントローラーから。

コントローラー

def destroy
   @review = Review.find(params[:id])
   @review.destroy
   if review.destroy
      redirect_to root_path, notice: "︎レビューを削除しました!"
   end
end

メッセージを表示させる記述は4行目のif文からです。

レビューが削除された後、root_path(ホーム画面)に戻って
「レビューを削除しました」というメッセージが表示される。という流れです。

続いてビュー画面へ!

ビュー

<% if flash[:notice] %>
   <div class="flash"><%= flash[:notice] %></div>
<% end %>

<%= yield %>

flashメッセージはいろんな場面で共通で使う事が多いので、
views/layouts/application.html.erb
<%= yield %>より上の部分に記述します。

コントローラーとビューはひとまず完成!!

JavaScriptの下準備

まずはGemfile以下のコードを記述します。

gem 'jquery-rails'

からのターミナルで$ bundle install

続いて
app/assets/javascripts/application.js
に以下のコードを加えましょう。

//= require jquery

これでjQueryの下準備完了。

flashメッセージをフェードアウトさせる方法

いよいよ実装していきます。

①jsファイルに記述する場合(推奨)

以下のコードを
app/assets/javascripts/application.js(上と同じ場所)
に加えたら完成!!

$(function(){
  setTimeout("$('.flash').fadeOut('slow')", 2000);
});

②ビューファイルに直接記述する場合

以下のコードを
flashメッセージを表示させたいビューファイルに加えたら完成!!

<script>
  $(function() {
    setTimeout("$('.flash').fadeOut('slow')", 2000);
  });
</script>

※「.flash」はapplicaton.html.erbのdivに付けたクラス名です。各自自由に命名しましょう。
※数字部分は好みに合わせて変えましょう!ちなみに1000で1秒です。

さいごに

今回はレビューの削除destroy後の実装でしたが、もちろん編集edit
お気に入りfavoriteなんかも実装可能です。

あとはお好みでCSSをいじれば、
よく見るflashメッセージの完成です!!

<参考>
https://qiita.com/dir_sh0606/items/b2165459deda97ae8468

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

ローカル環境にて、nokogiriが原因(?)でbundle installできないときの解決策

ローカル環境にrailsの開発環境を作ろうと以下の記事Ruby初学者のRuby On Rails 環境構築【Mac】
を参考に進めていると、以下のエラーに遭遇した。

$bundle install

#以下が実行結果

Fetching gem metadata from https://rubygems.org/.............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Using rake 13.0.1
Using concurrent-ruby 1.1.6
Using i18n 1.8.2
Using minitest 5.14.0
Using thread_safe 0.3.6
Using tzinfo 1.2.6
Using zeitwerk 2.2.2
Using activesupport 6.0.2.1
Using builder 3.2.4
Using erubi 1.9.0
Using mini_portile2 2.4.0
Fetching nokogiri 1.10.8
Installing nokogiri 1.10.8 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /Users/(user)/Desktop/Rails/vendor/bundle/ruby/2.5.0/gems/nokogiri-1.10.8/ext/nokogiri
/Users/(user)/.rbenv/versions/2.5.0/bin/ruby -I /Users/(user)/.rbenv/versions/2.5.0/lib/ruby/site_ruby/2.5.0 -r
./siteconf20200212-25664-dl2tcu.rb extconf.rb --use-system-libraries
checking if the C compiler accepts  -I
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2... *** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
        --with-opt-dir
        --without-opt-dir
        --with-opt-include
        --without-opt-include=${opt-dir}/include
        --with-opt-lib
        --without-opt-lib=${opt-dir}/lib
        --with-make-prog
        --without-make-prog
        --srcdir=.
        --curdir
        --ruby=/Users/(user)/.rbenv/versions/2.5.0/bin/$(RUBY_BASE_NAME)
        --help
        --clean
/Users/(user)/.rbenv/versions/2.5.0/lib/ruby/2.5.0/mkmf.rb:456:in `try_do': The compiler failed to generate an
executable file. (RuntimeError)
You have to install development tools first.
        from /Users/(user)/.rbenv/versions/2.5.0/lib/ruby/2.5.0/mkmf.rb:574:in `block in try_compile'
        from /Users/(user)/.rbenv/versions/2.5.0/lib/ruby/2.5.0/mkmf.rb:521:in `with_werror'
        from /Users/(user)/.rbenv/versions/2.5.0/lib/ruby/2.5.0/mkmf.rb:574:in `try_compile'
        from extconf.rb:138:in `nokogiri_try_compile'
        from extconf.rb:162:in `block in add_cflags'
        from /Users/(user)/.rbenv/versions/2.5.0/lib/ruby/2.5.0/mkmf.rb:632:in `with_cflags'
        from extconf.rb:161:in `add_cflags'
        from extconf.rb:416:in `<main>'

To see why this extension failed to compile, please check the mkmf.log which can be found here:

/Users/(user)/Desktop/Rails/vendor/bundle/ruby/2.5.0/extensions/x86_64-darwin-18/2.5.0/nokogiri-1.10.8/mkmf.log

extconf failed, exit code 1

Gem files will remain installed in /Users/(user)/Desktop/Rails/vendor/bundle/ruby/2.5.0/gems/nokogiri-1.10.8
for inspection.
Results logged to
/Users/(user)/Desktop/Rails/vendor/bundle/ruby/2.5.0/extensions/x86_64-darwin-18/2.5.0/nokogiri-1.10.8/gem_make.out

An error occurred while installing nokogiri (1.10.8), and Bundler cannot continue.
Make sure that `gem install nokogiri -v '1.10.8' --source 'https://rubygems.org/'` succeeds before
bundling.

In Gemfile:
  rails was resolved to 6.0.2.1, which depends on
    actioncable was resolved to 6.0.2.1, which depends on
      actionpack was resolved to 6.0.2.1, which depends on
        actionview was resolved to 6.0.2.1, which depends on
          rails-dom-testing was resolved to 2.0.3, which depends on
            nokogiri

どうやらnokogiriがインストールできてないみたい..

まず、解決策

以下の記事macOSアップデート後の『xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools)...』の対処法
を参考にした。

以下を実行し、xcodeのツール(?)のインストールをした後、再度bundle installをしたら解決した。

$ xcode-select --install

どのように解決に至ったか

エラーメッセージに


Check the mkmf.log file for more details.  You may
need configuration options.

と出ていたので、mkmk.logを開いた。
そこに

mkmk.log
xcrun: error: invalid active developer path

というエラーが出ていたので、ググったところ上記の解決につながった記事がヒットした。

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

Railsで検索機能を実装する場合の4つのパターン

パターン

  • Ransackを使う
  • コントローラーに書く
  • Concernに書く
  • Formオブジェクトを使う

Ransackを使う

Ransack使うとソートも楽に書けそうだけど、
追々カスタマイズが面倒そうなのもあり最初から自分で書くことが多い。
あまり使ったこと無いので、ケースによっては触ってみたい。

コントローラーに書く

comments = Comment.where(post_id: params[:post_id]) if params[:post_id].present?

こんな感じでコントローラーに直接where文を書くケース。
何も考えずに書くとこうなることが多いと思う。
条件式が増えてくると複雑度上がってrubocopに怒られる。

Concernに書く

module Comments
  module SearchModule
    extend ActiveSupport::Concern

    def self.do_search(params)
     comments = Comment.all
     search_by_post_id(comments, params[:post_id]) if params[:post_id].present?
    end

    def self.search_by_post_id(comments, post_id)
      comments.where(post_id: params[:post_id])
    end
  end
end
Comments::SearchModule.do_search(params)

検索用のConcernモジュールに切り分ける方法。FatControllerはこれで解消できるので良さげ。
ただConcern特有のお作法があるので、自由に書きづらく、単純なクラスが使いたくなった。

Formオブジェクトを使う

class CommentSearchForm < SearchForm
    attribute :post_id, Types::Maybe::Coercible::Integer

    def do_search
     comments = Comment.all
     search_by_post_id(comments, post_id.value_or) if post_id.value_or.present?
    end

    private

    def search_by_post_id(comments, post_id)
      comments.where(post_id: post_id)
    end
end
Dry::Types.load_extensions(:maybe)

module Types
  include Dry::Types.module
end

class SearchForm < Dry::Struct
end
CommentSearchForm.new(params.to_hash.symbolize_keys).do_search

モデルに紐付かないパラメータを処理する時にはVirtusが便利で良く使われているそう。

ただ公式リポジトリをみると作者が後継ライブラリであるdry-rbを推奨していたので、dry-rbを元に実装した。
https://github.com/solnic/virtus

厳密には型指定のみなので以下の3つ。
dry-validationでバリデーションも出来るようなので、後々使ってみたい。

gem 'dry-monads'
gem 'dry-struct'
gem 'dry-types'

普通にgemをインストールすると一番古い0.5系になるので、最新の使い方とは違うので注意。

Formオブジェクトを使ったことで、次のようなメリットがある気がする。
まだ余り使いこなせてないので詳しい方の意見聞きたい。

  • フォーム関連の処理が書かれていることがひと目でわかる
  • FatController対策になる
  • シンプルなクラスでテスト書きやすい
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】Pinterest APIで自分のアカウントのピン一覧を取得し、JSONを返すサンプルコード

はじめに

Pinterest APIを使ってみたので自分用に備忘録を残します。

今回対象とする機能は、「自分のアカウントのピン一覧を取得する」です。
公式ドキュメントはこちら

※利用制限等についてはご注意下さい。

(著作権所有者の許可を得ている場合を除いて)画像を保存または改変しないこと、また Pinterest にある画像をユーザーが印刷できるようにしないこと

ピン以外のデータを保存しないこと

などなど、制限があります。
デベロッパー向けガイドライン | Pinterest Policy

環境

OS: macOS Catalina 10.15.3
Ruby: 2.6.5
Rails: 6.0.2.1

前提

  • Access Tokenの取得方法
  • rails newなど最低限の下準備

などは細かく触れていません。

Access Tokenの取得はこちらから

1. net_http_module.rb

HTTPリクエストを投げる部分は分割しておきます。
今回はGETリクエストを投げるだけなので、超シンプルです。

app/controllers/concerns/net_http_module.rb
require 'active_support'

module NetHttpModule
  extend ActiveSupport::Concern

  require 'net/http'

  def api_get(set_URL)
    uri = URI.parse(set_URL)
    response = Net::HTTP.get_response(uri)
  end
end

2. pins_controller.rb

※事前に以下のようなPinモデル、コントローラーが作成済とします。

$ rails g model Pin pin_id:integer pin_url:string image_url:string width:integer height: integer
$ rails g controller api/v1/pins index
app/controllers/api/v1/pins_controller.rb
class Api::V1::PinsController < ApplicationController
  include NetHttpModule

  def index
    # Pinterest APIのAccess Tokenはcredentialsで管理
    url = "https://api.pinterest.com/v1/me/pins/?access_token=#{Rails.application.credentials.api_key[:pinterest]}&fields=id%2Cnote%2Curl%2Cimage"
    # NetHttpModuleで定義したapi_getメソッドを使用
    response = api_get(url)
    # 429(リクエスト多すぎ)ならエラーメッセージを返す
    if response.code == "429"
      render json: { error: response.message }, status: response.code
    elsif response.code == "200"
      #privateで定義したメソッドを使用してDBにpin情報を保存
      save_pins_and_cursor(response)
      # Vue.jsに返すJSONを作成する(この辺は必要に応じて変更)
      res = {
        message: response.message,
        pins: Pin.all.as_json,
      }
      render json: res, status: response.code
    else
      render json: { error: response.message }, status: response.code
    end
  end

  private

  def save_pins_and_cursor(response)
    pins = JSON.parse(response.body)['data']
    # 返ってきたデータの数だけpin情報をDBに保存する
    pins.each do |pin|
      Pin.create(
                  pin_id: pin['id'], # Pinterestが各ピンにつけている一意なID
                  pin_url: pin['url'], # Pinterestの該当ピンに飛ぶURL
                  image_url: pin['image']['original']['url'], # 画像が保存されているURL
                  width: pin['image']['original']['width'], # 画像の幅
                  height: pin['image']['original']['height'] # 画像の高さ
                )
    end
  end
end

あとはこれで返したJSONをフロントエンド側でゴニョゴニョすればOKです!

【便利】APIを試せるツールも公式に用意されている

こちらのリンクから、APIの各種機能を試せるAPI Explorerにアクセスできます。

GUIで使えるのは頭が疲れてるときにも優しいですね。便利。

スクリーンショット 2020-02-12 18.29.21.png

以上です!

おわりに

最後まで読んで頂きありがとうございました:bow_tone1:

どなたかの参考になれば幸いです:relaxed:

参考にさせて頂いたサイト(いつもありがとうございます)

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

docker-composeで開発/本番環境を切り替える

docker-composeで簡単に環境を切り替えたい

webアプリの開発でサーバーやDBを同時に立ち上げるためにdocker-composeを使用していて
テストや動作確認もコンテナ上で起動してやる場合、webアプリを動かすコンテナとして

  • 「production環境」 最小限のパッケージとモジュールを入れて余分なキャッシュ等も消し、できるだけイメージサイズを小さくしたもの
  • 「development環境」 テスト関連やgit、分析ツール等の開発に必要なものが入ったやや大きいサイズのイメージのもの

というふうに2種類以上の環境を使い分けたくなりますが、これをどう切り替えたらいいかという話

色々やり方は考えられますがdoker-compose内で展開できる環境ファイル.envファイルを使うのがよさそうです。

この記事ではRailsアプリでnginxとMySQLを使用しdocker-composeはversion:3以上を使うのを想定してますが、他のケースでも応用できると思います。

.envをdocker-composeに展開する

.envファイルをdocker-compose.ymlと同じ階層に用意するとdocker-compose内で変数として展開できます。
これを利用するとbuildするDockerfileのファイル名も変数で切り替えることができます。

docker.compose.yml

version: '3.7'

services:
  nginx:
    build: containers/nginx
    volumes: 
      - public:/myapp/public
      - sockets:/myapp/tmp/sockets/
    ports:
      - '8080:80'
    depends_on:
      - rails

  rails:
    build:
      context: .
      dockerfile: Dockerfile_${DOCKER_RAILS_ENV}
    image: webapp_${DOCKER_RAILS_ENV}
    volumes:
      - .:/myapp
      - public:/myapp/public
      - sockets:/myapp/tmp/sockets/
    ports:
      - "3000:3000"
    links:
      - db
    environment: 
      RAILS_ENV: $DOCKER_RAILS_ENV
      DB_HOST: db
      DB_USERNAME: $DB_USERNAME
      DB_PASSWORD: $DB_PASSWORD
      RAILS_SERVE_STATIC_FILES: 

  db:
    image: mysql:5.7
    volumes:
      - mysql-data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: $DB_PASSWORD
      MYSQL_DATABASE: root
    ports:
      - "3306:3306"

volumes:
  mysql-data:
  public:
  sockets:

.env

DOCKER_RAILS_ENV=production
DB_USERNAME=root
DB_PASSWORD=password

docker-compose.ymlのdockerfile: "Dockerfile_${DOCKER_RAILS_ENV}"の部分で
production環境用に作ったDocker_productionというファイルとDocker_developmentというファイルを.envで定義した環境変数で指定し切り替えています。1
またbuild:の下のimage:webapp_${DOCKER_RAILS_ENV}でビルドされるイメージ名を指定しています。2
ちなみに${DOCKER_RAILS_ENV:-development}とすればデファルト値でdevelopmentになります
イメージ名にも同じことができるので必要であればimage: mysql:${DB_VER-5.7}としてバージョンの指定なんかもできます。
参考:案外知られてないdocker-composeの環境変数定義の記法

注意点

ホストマシンのLinux等に.envで指定した変数と同名の環境変数が設定されてる場合、ホストマシンの環境変数が優先されるので、意図的に使いたい場合を除いて名前がバッティングしないようにする必要があります。
今回の例だとRAILS_ENVは使われやすいので、DOCKER_RAILS_ENVとしてかぶらないようにしてます。

またデフォルトで.gitignoreや.dockerignoreに.envは入ってないので間違ってもAWSへの接続情報などのクレデンシャルを.envに記述してgithubやdockerhub等にアップロードしないように注意しましょう。

Dockerfileは複数必要か

結局この方法だとdocker-composeは1つで済みますがDockerfileの方は2種類以上必要になります。
これも一つにできないかと考えたのですが、複雑になるのでやめました。3
アプリのルートをどうしてもDockerfile一つにしたい場合は、片方のDockerfileを配下に別のディレクトリを作って入れてdocker-composeのcontext:の部分を切り替えるようにすればいいと思います。


  1. MySQLやRailアプリに指定するDBパスワード等も一緒に記述してます。 

  2. image: app_name:tag_nameのようにタグも指定できるのでそっちに環境名つけてもいいです 

  3. やろうと思えばdocker-composeのbuild:arg:オプションを使ったりenv_file:を指定して、パッケージのリストを切り替えるみたいなこともできなくもないんですが、if文等が使えないのもあって無駄に見づらく複雑になるだけでした。 

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

docker-composeで.envで開発/本番環境を切り替える

docker-composeで簡単に環境を切り替えたい

webアプリの開発でサーバーやDBを同時に立ち上げるためにdocker-composeを使用していて
テストや動作確認もコンテナ上で起動してやる場合、webアプリを動かすコンテナとして

  • 「production環境」 alpineをベースに最小限のパッケージとモジュールを入れて余分なキャッシュ等も消し、できるだけイメージサイズを小さくしたもの
  • 「development環境」 テスト関連やgit、分析ツール等の開発に必要なものが入ったやや大きいサイズのイメージのもの

のように2種類以上の環境を使い分けたくなりますが、これをどう切り替えたらいいかという話

単純にdocker-compose.ymlを複数作って -f でファイル名を指定する方法や
公式ドキュメントにあるように拡張用のcomposeファイルを作る方法もありますが、doker-compose内で展開できる環境ファイル.envファイルを使用して切り替えるのが色々と応用がきくのでお薦めです。

作りたい環境に合わせたDockerfileを用意する

まず環境に合わせたDockerfileを用意します。
例えば

  • 「Docker_production」
  • 「Docker_development」

という感じに分けます。

.envを用意しdocker-composeに変数を指定する

.envファイルをdocker-compose.ymlと同じ階層に用意するとdocker-compose内で変数のように展開できます。
これを利用するとbuildするDockerfileのファイル名も変数で切り替えることができます。
以下はRailsを想定してますが、他のケースでも応用できると思います。

docker.compose.yml

version: '3.7'
~ 省略 ~

  rails:
    build:
      context: .
      dockerfile: Dockerfile_${DOCKER_RAILS_ENV}
    image: webapp_${DOCKER_RAILS_ENV}
    volumes:
      - .:/myapp
      - public:/myapp/public
      - sockets:/myapp/tmp/sockets/
    ports:
      - "3000:3000"
    links:
      - db
    environment: 
      RAILS_ENV: $DOCKER_RAILS_ENV
      DB_HOST: db
      DB_USERNAME: $DB_USERNAME
      DB_PASSWORD: $DB_PASSWORD
      RAILS_SERVE_STATIC_FILES: 

~ 省略~

.env

DOCKER_RAILS_ENV=production
DB_USERNAME=root
DB_PASSWORD=password

buildするDockerfile名に.envに書かれた環境変数を利用する

docker-compose.ymlのdockerfile: "Dockerfile_${DOCKER_RAILS_ENV}"の部分で
production環境用に作ったDocker_productionというファイルとDocker_developmentというファイルを.envで定義した環境変数で指定し切り替えています。1
.envをDOCKER_RAILS_ENV=developmentとすればdevelopment環境でupができます。

作成されるイメージ名を指定する

デフォルトでは"ディレクトリの名前"_"指定したコンテナ名(rails)"になるので環境を変えても同じイメージ名がつきます。
build:の下にimage:を指定するとイメージ名を指定してビルドできます。
->https://docs.docker.com/compose/compose-file/#build
image:webapp_${DOCKER_RAILS_ENV}でビルドされるイメージ名も環境で変わるようにしています。2

ちなみに${DOCKER_RAILS_ENV:-development}とすれば変数が指定されていない場合のデファルト値がdevelopmentになります
必要であれば使用するDBのイメージ名でimage: mysql:${DB_VER-5.7}として公式イメージのバージョンの指定にも応用できます。
参考:案外知られてないdocker-composeの環境変数定義の記法

注意点

ホストマシンのLinux等に.envで指定した変数と同名の環境変数が設定されてる場合、ホストマシンの環境変数が優先されるので、意図的に使いたい場合を除いて名前がバッティングしないようにする必要があります。
今回の例だとRAILS_ENVは使われやすいので、DOCKER_RAILS_ENVとしてかぶらないようにしてます。

またデフォルトで.gitignoreや.dockerignoreに.envは入ってないので間違ってもAWSのキーなどのクレデンシャルを.envに記述してgithubやdockerhub等にアップロードしないように注意しましょう。

Dockerfileは複数必要か

結局この方法だとdocker-composeは1つで済みますがDockerfileの方は2種類以上必要になります。
これも一つにできないかと考えたのですが、複雑になるのでやめました。3

アプリのルートをどうしてもDockerfile一つにしたい場合は、片方のDockerfileを配下に別のディレクトリを作って入れてdocker-composeのcontext:の部分を切り替えるようにすればいいと思います。


  1. MySQLやRailアプリに指定するDBパスワード等も一緒に記述してます。 

  2. image: app_name:tag_nameの形式でタグも指定できるのでそちらに環境の名前をつけてもいいです。 

  3. やろうと思えばdocker-composeのbuild:arg:オプションを使ったりenv_file:を指定して、パッケージのリストを切り替えるみたいなことはできます。環境によってDockerfileが殆ど変わらない場合はそれでいいのですが、自分の場合はかなり違うこともあって無駄に見づらく複雑になるだけでした。 

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

Railsの検索まとめ(find、find_by、where、結合)

find

user = User.find(97)

主キー(基本的にid)に対応するレコードを取り出すことができる。
見つからない場合、例外(ActiveRecord::RecordNotFound)が発生する。

find_by

user = User.find_by(email: 'hogehoge@example.com')
user2 = User.find_by(age: 24, name: 'Jack')

与えられた条件に合致するレコードのうち、最初の1件を取り出す。
複数条件で絞り込むこともできる。
見つからない場合nilを返す。

where

users = User.where(age: 24) #24才のユーザー
users2 = User.where.not(age: 24) #24才以外のユーザー
users3 = User.where(age: 24).where(region: 'Asia') #24才でアジアに住むユーザー
users4 = User.where(age: 24).or(User.where(name: 'Jack')) #24才、またはJackという名前のユーザー
users5 = User.where(age: 24).where(region: 'Asia').or(User.where(name: 'Jack')) #24才でアジアに住むユーザー、またはJackという名前のユーザー

与えられた条件に合致するレコードの集合を取り出す。
見つからない場合ActiveRecord_Relationクラスを返す。空の配列っぽいけど、配列ではないらしい。

結合

パターンが多すぎるので、使用したものを適宜追加するスタイルでいきます。

1対多

使用するデータ
class User < ApplicationRecord
  has_many :items
end

class Item < ApplicationRecord
  belongs_to :user
end
「user>email」,「item>price」の複数条件で検索し、全てのカラムのデータを取得
users = User.joins(:items).select("users.*, items.*").where(users: {email: 'hogehoge@example.com'}).where(items: {price: 300})

selectで取得するカラム名を指定
where(テーブル名{検索条件})で検索条件を指定

1対1

使用するデータ
class User < ApplicationRecord
  has_one :user_datum
end

class UserDatum < ApplicationRecord
  belongs_to :user
end
user_data>email受け取りフラグが立っているユーザーを取得
users = User.joins(:user_datum).select('users.*, user_data.*').where(user_data: {receive_email: 1})

joinsの引数が単数形になる。
※dataの単数形がdatum

参考

【rails】find・find_by・whereについてまとめてみた

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

Postgresqlに接続できない時の対処法

開発環境

  • ubuntu(WSL)
  • Postgresql

解決策

サーバを起動させるために下記のコマンドを実行します。

sudo /etc/init.d/postgresql start

エラー内容

rails sしたら、以下のようになった。

PG::ConnectionBad (could not connect to server: No such file or directory
        Is the server running locally and accepting
        connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"?

pg_lsclustersで現状確認。
やっぱり、downしてる。

10  main    5432 down   postgres /var/lib/postgresql/10/main /var/log/postgresql/postgresql-10-main.log

下記のコマンドでクラスターを起動させる。
pg_ctlcluster 10 main start

install: cannot change owner and permissions of ‘/var/run/postgresql’: No such file or directory
Error: Could not create /var/run/postgresql/10-main.pg_stat_tmp: No such file or directory

サーバが起動していないことが原因だあるらしいので、下記のコマンドを実行させる。

sudo /etc/init.d/postgresql start

これで動きました。

参考文献

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

固まったRailsのローカルアプリケーションサーバ(Puma)を強制終了する

Railsを使用して開発していると、Pumaが固まってCtrl+CでもPumaを終了できないことがあります。そんな時は以下のコマンドで解決!

pkill -9 -f 'puma 4.3'

上記のコマンドを叩いた後、ターミナルにて以下のように表示されていれば強制終了成功です。

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

固まったRailsのローカルWebサーバ(Puma)を強制終了する方法

Railsを使用して開発していると、Pumaが固まってCtrl+CでもPumaを終了できないことがあります。そんな時は以下のコマンドで解決!

pkill -9 -f 'puma 4.3'

上記のコマンドを叩いた後、ターミナルにて以下のように表示されていれば強制終了成功です。

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

固まったRailsのローカルアプリケーションサーバ(Puma)を強制終了する方法

Railsを使用して開発していると、Pumaが固まってCtrl+CでもPumaを終了できないことがあります。そんな時は以下のコマンドで解決!

pkill -9 -f 'puma 4.3'

上記のコマンドを叩いた後、ターミナルにて以下のように表示されていれば強制終了成功です。

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

dependent: :destroy 削除が出来ない

今回のテーマは、destroyメソッドが機能しない。

という事で、今回陥った点からまずお話致します。

トークルームにてチャットが出来るアプリにて、マイページでトークルームを削除できない。
という問題点でかなり悩まされる事に。

テーブル

memo_roomsに関連付けられているのは、
usersテーブル  
memo_room_postsテーブル
categoriesテーブル
favoritesテーブル
memo_room_commentsテーブルの5つです。

解決前までの流れ。

最初にルームが削除出来ないと気付いた時には
def destroy
@memo_room.destroy
flash[:success] = 'メモルームを削除しました。'
redirect_to user_path(current_user)
end

コントローラにてこんな感じで書いていました。これにより、失敗しても成功しても削除しました。としか出ず、成功はしてるが、
何故か削除出来ていない。こう勘違いしました。どうしても解けず、質問掲示板にて質問をしました。

def destroy
if @memo_room.destroy
flash[:success] = 'メモルームを削除しました。'
else
flash[:danger] = '削除に失敗しました。'
redirect_to user_path(current_user)
end
end

すると・・・成否がどちらでも削除しました。となっているので失敗してるかわからないと、このようにアドバイスを頂き、
アドバイス通りにelseを追加したところ、削除に失敗しました。と表示されました。

成功してると思っていたが、実は失敗していたんですね。それに気づかず成功してる前提で何故消えないかを探していたので
見つかる訳がありません。

rbファイル dependent: :destroy 間違い編

class MemoRoom < ApplicationRecord
  belongs_to :user
  belongs_to :category, dependent: :destroy

  has_many :memo_room_posts, dependent: :destroy
  has_many :favorites, dependent: :destroy
  has_many :memo_room_comments, dependent: :destroy


class MemoRoomPost < ApplicationRecord
  belongs_to :user
  belongs_to :memo_room, dependent: :destroy

has_many :memo_room_posts, dependent: :destroy
  has_many :memo_rooms, dependent: :destroy
  has_many :categories, dependent: :destroy
  has_many :favorites, dependent: :destroy

dependent: :destroyをとりあえず付けておけば削除した際に関連されたデータも消えるから便利程度の認識でした。
ここがまず間違いでこれのせいで、関連付けが多すぎて網目のようになり、ぐちゃぐちゃになってがんじがらめになっていると教えて頂きました。

そして、アドバイスとして一番わかりやすかったのが、

こいつを消したら意味が無くなるもの に対してのみその方向でのdependent: :destroyをつけるようにしてください

この時、初めて使い方をきちんと知りました。とてもわかりやすい一言でした。

解決編

class MemoRoom < ApplicationRecord
  belongs_to :user
  belongs_to :category

  has_many :memo_room_posts, dependent: :destroy
  has_many :favorites, dependent: :destroy
  has_many :memo_room_comments, dependent: :destroy

memo_roomを消す事により、トークルーム内の投稿であるmemo_room_postsとmemo_roomにいいねされたfavoritesそしてトークルームが消えればコメントも無意味になるのでmemo_room_commentsこの3つのみにdependent: :destroyを付けてみました。

すると、がんじがらめから抜けて綺麗になったのか、削除する事が出来ました。必要のない項目にdependentがダメ!なんて初心者は知らない人もいると思うので、役に立つメモとなればと思います。
知ったかぶり ←しっかりと知識をつけることの大切さを知りました。

https://teratail.com/questions/240800

実際に質問した際のURL。も載せておきます。

以上上手く削除が出来なくなってしまった際のメモでした。参考になればと思います。

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

自身が sidekiq のプロセスかどうか判定する

問題

Rails Server や Rake Task のプロセスで実行するパッチを Sidekiq のプロセスでは実行したくないが Sidekiq では、オフィシャルな場合分け方法を提供していない。

解決策

Sidekiq プロセスは起動時に config/sidekiq.yml を読み込み、 Sidekiq.options に格納する。したがって自身が Sidekiq プロセスかどうかを見るには config/sidekiq.yml にしか書かれていない情報がすでに読み込まれているかを確かめればよい。たとえば下のような分岐をする。

if Sidekiq.options[:queues].present?
  puts "Sidekiq プロセス"
else
  puts "Rails Server プロセスをはじめとした Sidekiq 以外のプロセス"
end

もし config/sidekiq.yml 以外で queues を定義している場合はその限りでない。

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

HerokuでUglifier::Error: Unexpected token: operator (>).が出たときの解決策

今回僕がHerokuでデプロイしようと試みようとし立ちはだかった「Uglifier::Error: Unexpected token: operator (>).」のエラについての記事です。

僕がデプロイしたときのバージョン等は以下。

[box class="box17"]
・rubyのバージョン:2.5.1
・Railsのバージョン:5.2.3[/box]

バージョン
rubyのバージョン 2.5.1
Railsのバージョン 5.2.3

やったことはただ1つです。

それはconfigのproduction.rbに記述されてある

config.assets.js_compressor = :uglifier

config.assets.js_compressor = Uglifier.new(harmony: true)

config.assets.js_compressor = :uglifierがconfig.assets.js_compressor = Uglifier.new(harmony: trueに変わりましたね。

こうすることで僕は無事デプロイすることができ、世界中にアプリを公開することができました。

めでたしです。Heriokuは簡単にデプロイできることで重宝されてますが、たまにやっかいなエラーが起きます。

そんなときは焦らずエラー文を読んで、慎重に解決の糸口を探ってみましょう。

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

Rails】An error occurred while installing unf_ext (0.0.7.6), and Bundler cannot continue.【解決策】

AWSでデプロイ作業をしていたら以下のようなエラーが出ました。

An error occurred while installing unf_ext (0.0.7.6), and Bundler cannot continue.

完全に詰んだかと思いましたが、1時間かけて解決することができたので共有しようと思います。

rubyのバージョン 2.5.1
Railsのバージョン 5.2.3
bundlerのバージョン 2.0.2

では早速見ていきましょう。

僕は、Pay.jpのgemをbundle installしたかったのですが、An error occurred while installing unf_ext (0.0.7.6), and Bundler cannot continueというエラーが出ました。

結論、インスタンスを再起動しましょう。

そして、停止することができたら「開始ボタン」を押しましょう。これでひとまずはOKです。

さらに、NginxとMysqldを再起動します。

sudo service nginx restart
sudo service mysqld restart

コマンドは上記です。

これで準備は整いました。これで本番環境でもbundle installすることができると思います。

エラーが無事解決されましたら幸いです。

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

【Rails】deviseユーザーの登録日を表示する【めちゃ簡単】

Deviseのユーザー登録日を表示することに成功しました。めちゃ簡単なので、ぜひ実装してみてください。

rubyのバージョン 2.5.1
Railsのバージョン 5.2.3

手順は以下のような感じになります。

・❶:コントローラーにTime_zoneの記述
・❷:ビューにcreated_atの記述

手順1:コントローラーにTime_zoneの記述

まずは表示したいビューのコントローラーに以下の記述を行います。

class UsersController < ApplicationController

before_action :set_zone

〜〜〜〜省略〜〜〜〜
private
def set_zone
Time.zone='Tokyo' 
end

こんな感じで、日本語表記を綺麗なものに変更します。

僕の場合だと、usersコントローラーのmember.html.erbにユーザー登録日を表示したかったので、usersコントローラーにTime_zoneを表記しました。

別にapplication_controllerでもOKだと思いますが、変なエラーが起きても嫌なので記述する場所は限定しておくのが無難。

手順2:ビューにcreated_atの記述

以下のように記述します(自分が定義した変数に合わせて記述してください)。

<div class="member-information"><%=@user.created_at.strftime("%Y年%m月%d日 ")%></div>

これで実装は終わりです。

ちなみに、created_atというカラムはデフォルトでついているものですが、もしマイグレーションファイルに記述がない場合は以下を記述してくださいね。

t.timestamps null: false

そして、null: falseだとやっかいごとが起こるそうですが、僕の開発アプリだと大丈夫そうなのでnull: falseはつけています。

参考記事

お疲れ様でした!!

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

#Rails + dotenv-rails / overload env files / not linux or OS or shell or docker container env

dotenv-rails does not prepare smart configuration for overload env ?

i write in

config/environments/development.rb

or

config/application.rb

( after Rails app configuration )

Dotenv.overload(*[
  Rails.root.join(".env.#{Rails.env}.local"),
  (Rails.root.join(".env.local") unless Rails.env.test?),
  Rails.root.join(".env.#{Rails.env}"),
  Rails.root.join(".env"),
].compact)

see
https://github.com/bkeepers/dotenv/blob/v2.7.5/lib/dotenv/rails.rb#L57

Original by Github issue

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

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

【AWS&Rails】 listen "#{app_path}/tmp/sockets/unicorn.sock"がないみたいなエラー【解決策】

AWSでデプロイしようと試みてたら以下のようなエラー出ました。

listen "#{app_path}/tmp/sockets/unicorn.sock"

Unicorn関係はほんとに嫌いです?

しかし解決できたので共有します!

rubyのバージョン Railsのバージョン bundlerのバージョン
    2.5.1 5.2.3        2.0.2      

では早速見ていきましょう

手順1 本番環境にいく

ec2ユーザー ElasticIP アプリ名 $

まずはここまでいきましょう。

手順2 ps aux | grep unicornコマンドを叩く

ps aux | grep unicorn

そして1番上の左の数字をkillしてあげてください。

kill 1番上の左の数字

手順3 RAILS_SERVE_STATIC_FILES=1 unicorn_rails -c config/unicorn.rb -E production -Dコマンドを叩く

まだこれだけではエラーは治りません。

最後に以下のコマンドを叩く必要があります。

ec2ユーザー ElasticIP アプリ名 $ RAILS_SERVE_STATIC_FILES=1 unicorn_rails -c config/unicorn.rb -E production -D

これでエラーがでなければ、無事成功です。

デプロイ環境でローカルの内容をみれると思います。

お疲れ様でした!

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

【Rails】uninitialized constant Message::ImageUploaderエラー【解決策】

Railsでアプリ開発を行なっていたところ

uninitialized constant Message::ImageUploader

こんなエラーが出ました。

Rubyのバージョン 2.5.1
Railsのバージョン 5.2.3

しかし、無事解決することができたので、共有したいと思います。

結論、「rails s」をし直しましょう。

$ rails s

僕は今まで何個もアプリを作ってきたんですが、「uninitialized constant Message::ImageUploaderエラー」はrails sし直すことで解決できています。

見たことないエラーはほんとにヒヤヒヤしますね。。。

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

【Rails】See Crash Report log file under the one of following:とかいう脅迫文を解決した

どうも、謎のエラーに苦しめられたノロノロです。

いや〜ひさびさに冷や汗止まらないエラーに出くわしました。!

See Crash Report log file under the one of following

これを解決するのに1時間半はかかりました。

結論、mysqlのバージョンを1つ下げましょう。

僕の場合だと

gem ‘mysql2’, ‘>= 0.4.4’, ‘< 0.6.0’

mysqlのバージョンはこんな感じでした。これを

gem ‘mysql2’, ‘0.4.10’

これに変えて「bundle install」すればOKです。僕とエラー内容が一致しているなら治ると思います。

ただ、なぜこんなエラーが出て、なぜバージョンを下げたらエラーが治るかの「原因」はわからずじまいなんですよね。

ちょっと気持ち悪いですが、無事Railsで開発もできるようになりましたし、よしとしましょう。

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

SEしてるけど実はあんまりコード書いたことないんだよねって人に捧ぐ、Rails on Dockerハンズオン vol.7 - Secure password -

はじめに

第7回目となる今回は、モデルにセキュアなパスワードをもたらしてくれるhas_secure_passwordメソッドの使い方を紹介していきます。
パスワードは他人に知られてはまずいものです。万が一、データを抜かれたり画面に表示されちゃったりしても一目でわからないようになっていることが望ましいですね。そんなことを実現してくれるメソッドがhas_secure_passwordです。

has_secure_password

Railsでは、モデルにセキュアなパスワード属性を実装するメソッドとして、has_secure_passwordが用意されています。モデルに対してhas_secure_passwordを適用することでモデルは以下の恩恵を受けることができます。

  • 仮想属性としてpasswordpassword_confirmationを利用可能
  • passwordpassword_confirmationの同一性について勝手に検証してくれる
  • DBにはハッシュ化したpassword_digestを保存するようになる
  • ハッシュ値でパスワード検証をするauthenticateメソッドを利用可能

ハッシュ化についてちょっとお話ししておきます。ハッシュ化はある文字列を不可逆な別の文字列に変換してくれます。『不可逆』とは元に戻せないという意味です。
『暗号化』の場合は『復号化』することで元の文字列に変換しなおせるんです。これは『可逆』といいますね。

では、早速モデルにhas_secure_passwordメソッドを適用していきます。

has_secure_passwordではハッシュ化を行うためにbcrypt gemを利用します。まずは、bcryptのインストールからやっていきます。Gemfileの中身をみるとわかりますが、bcryptは最初からGemfileの中に書かれておりコメントアウトされているだけです。なのでコメントアウトをとってdocker-compose buildをするだけでOKです。

Gemfile
...
gem 'bcrypt', '~> 3.1.7'
...
$ docker-compose build
$ docker-compose up -d

コンテナも立ち上げておきましょう。

次に、has_secure_passwordメソッドを使うようになるとpasswordをハッシュ化した値をpassword_digestカラムにDB保存するようになります。マイグレーションファイルを作成しDBにpassword_digestカラムを追加しておきます。

# rails g migration add_password_digest_column_to_user password_digest:string
Running via Spring preloader in process 251
      invoke  active_record
      create    db/migrate/20200130075100_add_password_digest_column_to_user.rb

# rails db:migrate
== 20200130075100 AddPasswordDigestColumnToUser: migrating ====================
-- add_column(:users, :password_digest, :string)
   -> 0.0522s
== 20200130075100 AddPasswordDigestColumnToUser: migrated (0.0533s) ===========

そして、Userモデルにhas_secure_passwordメソッドを適用します。

app/models/user.rb
class User < ApplicationRecord
  ...
  has_secure_password
  ...
end

これで完了です!簡単ですね!
では恩恵がちゃんと受けられているか確認してみましょう。

passwordpassword_confirmation

まずはpassword属性とpassword_confirmation属性の二つの仮想属性が利用できることを確認してみます。

> user = User.new(name: "hanako", email: "hanako@sample.com", password: "password", password_confirmation: "password")
=> #<User:0x0000563d32829928
 id: nil,
 name: "hanako",
 email: "hanako@sample.com",
 created_at: nil,
 updated_at: nil,
 password_digest: [FILTERED]>

> user.password
=> "password"

> user.password_confirmation
=> "password"

> user.password_digest
=> "$2a$12$OfB2RwhC2b2hHW.8bIpNqeEoqM9xS3OdLubSUS4Gew0Ece1dnTzDG"

DBカラム的にpassword_digestしかないはずですが、passwordpassword_confirmationが使えていることがわかります。さらにその結果がpassword_digestとして利用可能なのもわかりますね。

passwordpassword_confirmationの一致性確認

ここで一度valid?メソッドを使ってみましょう。これは今現在のモデルでvalidationを突破できるのかを確認できるメソッドです。先ほどまではsaveでvalidationできるかを確認していましたが、実はvalidationを調べるためだけであればこれでOK。

> user.valid?
  User Exists? (3.6ms)  SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER($1) LIMIT $2  [["email", "hanako@sample.com"], ["LIMIT", 1]]
=> true

今はpasswordpassword_confirmationがマッチしているのでvalidationは通っているようです。では、password_confirmationを別の文字列に変更した場合どうでしょうか?

> user.password = "password1"
=> "password1"

> user.valid?
  User Exists? (35.3ms)  SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER($1) LIMIT $2  [["email", "hanako@sample.com"], ["LIMIT", 1]]
=> false

> user.errors.full_messages
=> ["Password confirmationとPasswordの入力が一致しません"]

validationでエラーになっていることがわかりましたね...あ、日本語化していない。
日本語化しましょう!

config/locales/ja.yml
ja:
  activerecord:
    attributes:
      user:
        name: "お名前"
        email: "メールアドレス"
        password: "パスワード"
        password_confirmation: "確認用パスワード"
...
> reload!
Reloading...
=> true

> user.valid?
  User Exists? (4.7ms)  SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER($1) LIMIT $2  [["email", "hanako@sample.com"], ["LIMIT", 1]]
=> false

> user.errors.full_messages
=> ["確認用パスワードとパスワードの入力が一致しません"]

passwordpassword_confirmationが一致していない場合はエラーが起きていますね。
ただ、password_confirmationのようなものは必要なのか?という論争もあると思います。
登録フォームにおけるパスワード確認用の入力欄は必要か | UX MILK
例えばこちらの記事では、確認用パスワードを用意するのではなく、パスワードの欄のマスクを外せるようにした方がコンバージョンが上がると述べられていたりします。
has_secure_passwordでは、password_confirmationnilの場合、passwordpassword_confirmationの一致を確認しません。

> user.password_confirmation = nil
=> nil

> user.valid?
  User Exists? (2.4ms)  SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER($1) LIMIT $2  [["email", "hanako@sample.com"], ["LIMIT", 1]]
=> true

なので確認用パスワードを使う使わないによらず、has_secure_passwordメソッドは強力なのです。

DBではハッシュ化されたpassword_digestが使われる

最初にもお話しした通り、ハッシュ化は不可逆なデータ変換です。
has_secure_passwordではpassword_digestにハッシュ化されたpasswordを保存します。それを確認してみましょう!

実際にUserを作成してみましょう。

> user.save
   (0.4ms)  BEGIN
  User Exists? (2.3ms)  SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER($1) LIMIT $2  [["email", "hanako@sample.com"], ["LIMIT", 1]]
  User Create (69.4ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["name", "hanako"], ["email", "hanako@sample.com"], ["created_at", "2020-01-31 20:35:57.284982"], ["updated_at", "2020-01-31 20:35:57.284982"], ["password_digest", "$2a$12$OfB2RwhC2b2hHW.8bIpNqeEoqM9xS3OdLubSUS4Gew0Ece1dnTzDG"]]
   (5.6ms)  COMMIT
=> true

> user.find(1)
=> #<User:0x0000563d32823898
 id: 1,
 name: "hanako",
 email: "hanako@sample.com",
 created_at: Fri, 31 Jan 2020 20:35:57 JST +09:00,
 updated_at: Fri, 31 Jan 2020 20:35:57 JST +09:00,
 password_digest: [FILTERED]>

> user.password
=> nil

> user.password_digest
=> "$2a$12$OfB2RwhC2b2hHW.8bIpNqeEoqM9xS3OdLubSUS4Gew0Ece1dnTzDG"

user.password_digestをみてもなんのことやらわかりませんね。ちょっと安心です。

パスワード認証するauthenticateメソッド

さて、ハッシュ化されてセキュアになったのはいいのですが、不可逆なのでこのままではパスワードで認証ができません。has_secure_passwordではハッシュ化されたパスワードの認証をするためのauthenticateメソッドが用意されています。これは、password_digestの値と入力したpasswordを同じハッシュ関数でハッシュ化した値の一致をチェックしてくれるものです。

> User.find_by(email: "hanako@sample.com").authenticate("a")
  User Load (4.5ms)  SELECT "users".* FROM "users" WHERE "users"."email" = $1 LIMIT $2  [["email", "hanako@sample.com"], ["LIMIT", 1]]
=> false

> User.find_by(email: "hanako@sample.com").authenticate("password")
  User Load (2.5ms)  SELECT "users".* FROM "users" WHERE "users"."email" = $1 LIMIT $2  [["email", "hanako@sample.com"], ["LIMIT", 1]]
=> #<User:0x0000563d32d80688
 id: 1,
 name: "hanako",
 email: "hanako@sample.com",
 created_at: Fri, 31 Jan 2020 20:35:57 JST +09:00,
 updated_at: Fri, 31 Jan 2020 20:35:57 JST +09:00,
 password_digest: [FILTERED]>

このようにfind_byと組み合わせて使えばメールアドレスとパスワードの2key認証を実装できます。authenticateは不一致の場合はfalseを、一致の場合はモデルオブジェクトを返却します。

passwordにvalidationを設ける

仮想属性であるpasswordに対してもvalidationをつけることができます。
一方で、has_secure_passwordで作られたpasswordにはデフォルトでpresenceのvalidationが設定されています。これに加えて6文字以上でないといけない文字数制限をつけてみましょう。
デフォルトでpresenceがついてはいるのですが、入力がない場合にpresencelengthの両方に引っかかってしまうのでpresenceについては適用しない方がわかりやすいでしょう。
has_secure_passwordvalidations: falseオプションをつけてデフォルトのpresence validationを向こうにした後に、他の属性と同様にlengthのvalidationをつけてみましょう。

app/models/user.rb
class User < ApplicationRecord
  ...
  has_secure_password validations: false
  ...
  validates :password,
    length: { minimum: 6 }
  ...
end

passwordのvalidationについて試してみましょう。

> user = User.new(name: "john", email: "john@sample.com")
=> #<User:0x0000563d32287768
 id: nil,
 name: "john",
 email: "john@sample.com",
 created_at: nil,
 updated_at: nil,
 password_digest: nil>

> user.valid?
  User Exists? (4.9ms)  SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER($1) LIMIT $2  [["email", "john@sample.com"], ["LIMIT", 1]]
=> false

> user.errors.full_messages
=> ["パスワードは6文字以上で入力してください"]

> user.password = "a" * 5
=> "aaaaa"

> user.valid?
  User Exists? (4.8ms)  SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER($1) LIMIT $2  [["email", "john@sample.com"], ["LIMIT", 1]]
=> false

> user.errors.full_messages
=> ["パスワードは6文字以上で入力してください"]

> user.password = "a" * 6
=> "aaaaaa"

> user.valid?
  User Exists? (3.5ms)  SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER($1) LIMIT $2  [["email", "john@sample.com"], ["LIMIT", 1]]
=> true

ということで、passwordlength validationが正しく挙動していることがわかりました。

Userを作成

最後にこのバリデーションの中でちゃんとUserを作成できることを確認しておきましょう!

> User.create(name: "John Smith", email: "john@sample.com", password: "password")  
(1.9ms)  BEGIN
  User Exists? (7.4ms)  SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER($1) LIMIT $2  [["email", "john@sample.com"], ["LIMIT", 1]]
  User Create (30.2ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["name", "John Smith"], ["email", "john@sample.com"], ["created_at", "2020-02-09 16:40:36.252829"], ["updated_at", "2020-02-09 16:40:36.252829"], ["password_digest", "$2a$12$JHC/AEBGXyffzL9..2ThOuxDRlRCSP2RxtFxZxDzeOfsQIX6BGqym"]]
   (3.0ms)  COMMIT
=> #<User:0x000055f6f7e390b0
 id: 2,
 name: "John Smith",
 email: "john@sample.com",
 created_at: Sun, 09 Feb 2020 16:40:36 JST +09:00,
 updated_at: Sun, 09 Feb 2020 16:40:36 JST +09:00,
 password_digest: [FILTERED]>

validationにひっかからないUserであればちゃんと作成できることが確認できましたね!

ユーザー情報を確認するViewを作ってみる

ここまででUserモデルがほぼ完成しました。この情報をUIで見れるようにViewを作ってみましょう!

Scaffoldを思い出してください。ユーザーの詳細情報を見るためのページは/users/:idのURLにアクセスして閲覧することができ、showアクションにルーティングされていました。

今回もそれにそってユーザーのページを作っていきます。

ユーザー詳細ページを作成する

ユーザー詳細ページは、/users/:idのパスにアクセスした時に、そのidのユーザーの詳細情報が表示されるページにしたいと思います。
この通りになるように、ファイルの編集や作成を行っていきます。いままでrails g ~コマンドでファイルを作成してきましたが、Railsの規則に則っていれば普通にファイルを作成しても動きます。

Routing

まずは、/users/:idのルーティングを生成します。以前、Scaffoldの時にresourcesメソッドを使ってUsersコントローラーに対するルーティングを定義しました。/users/:idはその時のshowアクションへのルーティングと同じです。
resourcesメソッドはonlyオプションをつけることで特定のアクションへのルーティングのみを定義してくれます。今回はこのオプションを使ってルーティングを定義します。

config/routes.rb
Rails.application.routes.draw do
  root 'static_pages#home'

  resources :users, only: [:show]
end

これでルーティングの設定は完了です。試しにrails routesコマンドでルーティングを確認してみてもいいかもしれません。

# rails routes
Prefix  Verb  URI Pattern  Controller#Action
root    GET   /            static_pages#home
user    GET   /users/:id   users#show
...

確かにGET /users/:idusers#showにルーティングされています。

Controller

次にUsersコントローラーを作成していきます。コントローラーは複数形の名称にするのがルールです。

# touch app/controllers/users_controller.rb
app/controllers/users_controller.rb
class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
  end
end

usersコントローラにshowアクションがある、というところまでは今までの内容からわかりますね。
showアクションの中身をみてみます。

params[:id]/users/:idのパスパラメータの:idを取得しています。例えば/users/1にアクセスした場合はparams[:id]=1だし、users/2にアクセスした場合はparams[:id]=2です。
User.find()はModel CRUDの回で話した通り、idでUserのモデルオブジェクトを取得するメソッドなので、パスのidのユーザーがこれで取得できるわけです。
あとはViewに引き渡すためにこれをインスタンス変数@userに代入しています。

ではこれを受け取るViewを作っていきます。

View

まず、Viewファイルを格納するディレクトリを作成して、その中にshow.html.erbファイルを作ります。
UsersコントローラのためのViewなのでapp/views/users/ディレクトリ内にファイルを作成していきます。

# mkdir app/views/users
# touch app/views/users/show.html.erb

最初は情報が表示されていればOKとしましょう。特にUIは拘らない。

app/views/users/show.html.erb
<div class="container my-5">
  <%= @user.name %>
  <br>
  <%= @user.email %>
</div>

nameemailを表示。

http://localhost:3000/users/2にアクセスしてみましょう。
image.png
こんなページが表示されていれば成功です!
idは実際にDBに格納されているデータ次第ですので、Rails consoleでUser.allを打つなどして存在するUserのidを確認してみてくださいね。)

後片付け

次回に向けてデータを消します。

$ docker-compose down
$ docker-compose run --rm web rails db:migrate:reset

DBコンテナが立ち上がった状態だと思うのでdownさせます。

$ docker-compose down

まとめ

今回は、has_secure_passwordメソッドを使ってセキュアなパスワードが利用できるようになりました。
コード的にいえばたった1行has_secure_passwordを付け加えるだけでパスワードをハッシュ化してセキュアに扱うことができるようになる。これって強力なメソッドですよね。

さらにユーザーの情報を表示するページまで作ることができました。
次回はSign up(ユーザー登録)ページを作っていきましょう!

では、次回も乞うご期待!ここまでお読みいただきありがとうございました!

Reference

Links

Vol.1 - Introduction -
Vol.2 - Hello, Rails on Docker -
Vol.3 - Scaffold, RESTful, MVC -
Vol.4 - Static pages -
Vol.5 - Model and CRUD -
Vol.6 - Model validation -
・ Vol.7 - Secure password - ?この記事

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

railsメモ

  • ORMとは

wikiによると

O/Rマッピングとは、オブジェクト指向言語におけるオブジェクトと、リレーショナルデータベースにおける>レコードとを対照させることである。ここでの「マッピング」とは「対応付ける」というほどの意味である。

O/Rマッピングによって、リレーショナルデータベースのレコードがオブジェクトとして直感的に扱えるよう?になり、リレーショナルデータベースにアクセスするプログラムを記述する処理を容易にすることが可能となる。オブジェクトへのデータ取得などの処理を透過的に行えるようになるので、煩雑になりがちなデータベースに関する処理の記述がスマートになり、また柔軟なアプリケーションの構築が可能となる。

なお、O/Rマッピング用のフレームワークやライブラリはO/Rマッパーなどと呼ばれる。代表的なものとしては、Java言語向けのHibernateやRuby言語向けのActiveRecordなどがある。

との事である。
つまり、ActiveRecordが入ってるおかげでスクリプト言語と同様に、翻訳しなくても自動翻訳して
データベースとやり取りすることを可能にしているというようなことだと思われる。

  • database.ymlファイルの主な役割とは

db:createのコマンドを実行した時に作成されるデータベースの名称を指定する。

RailsアプリケーションがSQLサーバーにアクセスするときのソケットファイルの位置を指定する。

とのことであるがあまりよくわかっていない

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

Rails AWS  EC2デプロイフロー ①EC2インスタンス作成編

はじめに

自分でアプリケーションは作ったものの、なかなかAWSにデプロイできなったので記録として残しておく!
わかりやすく....

1.EC2インスタンスの作成

①サービスの中から 「EC2」 を選択します

aws インスタンス1.png

②左の一覧からインスタンスをクリック

スクリーンショット 2020-02-12 13.07.43.png

インスタンス作成をクリック

スクリーンショット 2020-02-12 13.08.03.png

AMI(Amazon Machine Image)の選択

AMIとは
Amazon マシンイメージ (AMI) は、ソフトウェア構成 (オペレーティングシステム、アプリケーションサーバー、アプリケーションなど) を記録したテンプレートです。AMI から、クラウドで仮想サーバーとして実行される AMI のコピーであるインスタンスを起動します。

インスタンスとは
インスタンスとは、クラウドの仮想サーバーです。起動時の設定は、インスタンスを起動した際に指定した AMI のコピーです。

1 つの AMI から、複数の異なるタイプのインスタンスを起動することもできます。インスタンスタイプとは本質的に、インスタンスに使用されるホストコンピュータのハードウェアを決定するものです。インスタンスタイプごとに異なる処理内容やメモリの機能が提供されます。インスタンスタイプは、インスタンス上で実行するアプリケーションやソフトウェアに必要なメモリの量と処理能力に応じて選択します。
スクリーンショット 2020-02-12 13.19.54.png

⑤タイプの選択

無料枠で利用できる「t2.micro」を選択しましょう。
スクリーンショット 2020-02-12 13.34.17.png
起動をクリックします。
スクリーンショット 2020-02-12 13.37.05.png

⑥キーペアのダウンロード

1,modalが表示されるので、【新しいキーペアの作成】を選択
2,【キーペア名】を任意で入力
3,【キーペアのダウンロード】を行う。
こちらはインスタンスにSSHでログインする際に必要となる「秘密鍵」です。これがないとEC2インスタンスにログインできないので、必ずダウンロードしてパソコンに保存しておきましょう。
4,【インスタンスの作成】をクリック
キーペアのダウンロードが完了すると、クリック出来ない状態になっていた「インスタンスの作成」が、クリックできるように変更されます。そちらをクリックして、EC2インスタンスを作成しましょう。
スクリーンショット 2020-02-12 13.39.59.png

2.Elastic IPの作成と紐付け

Elastic IPとは

Elastic IPとは、AWSから割り振られた固定のパブリックIPアドレスのことを言います。このパブリックIPアドレスをEC2インスタンスに紐付けることで、インスタンスの起動、停止に関わらず常に同じIPアドレスで通信をすることが可能になります。
Elastic IPアドレスは、AWSに登録したアカウントに紐つけされるIPアドレスです。IPアドレスは基本的にパブリックIPアドレスとプライベートIPアドレスの2つに分けることができ、パブリックIPアドレスはインターネットを通じて機器を利用する際に割り当てられるアドレスで、最もポピュラーなIPアドレスと言えます。
一方のプライベートIPアドレスはインターネットではなくローカルのネットワークでのみ割り当てられるIPアドレスで、インターネットからは遮断されたIPです。

Elastic IPの作成

1,Elastic IPをクリックスクリーンショット 2020-02-12 13.54.23.png

2,Elastic IP アドレスの割り当てをクリック
スクリーンショット 2020-02-12 13.56.42.png
3,割り当てをクリック,その後、閉じるをクリック
スクリーンショット 2020-02-12 13.58.52.png

Elastic IPの紐付け

スクリーンショット 2020-02-12 14.01.43.png
1,上図の【アクション】から【アドレスの関連付け】を選択
2,【アドレスの関連付け】ページにあるインスタンスIDを入力、【プライベートID】には入力しない、【関連付け】をクリック
スクリーンショット 2020-02-12 14.02.39.png

3,インスタンス画面からElastic IPが紐づけられたことを確認する

3.ポートを開く

インスタンス画面
1,セキュリティグループのリンクをクリック
スクリーンショット 2020-02-12 14.11.20.png

2,「インバウンド」タブの中の「編集」をクリック
スクリーンショット 2020-02-12 14.13.30.png

3,開かれたモーダルで、ルールの追加をクリック、タイプを「HTTP」、プロトコルを「TCP」、ポート範囲を「80」、送信元を「カスタム / 0.0.0.0/0, ::/0」に設定
66dca7590aaf906a435cf3e62cfc737d.png

4.EC2インスタンスへのログイン

$ cd ~

$ mv Downloads/鍵の名前.pem .ssh/

$ cd .ssh/

$ chmod 600 鍵の名前.pem

$ ssh -i 鍵の名前.pem ec2-user@作成したEC2インスタンスと紐付けたElastic IP

以上で、AWSのEC2インスタンスの作成手順となります!

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

【Rails】Passwordを無視してUserUpdate(更新)する

実装すること

・User情報Update(更新)時に、password、password_confirmationの値が空の場合でも、Updateを成功させる。

前提条件

・bcryptの導入が完了していること。

実装

例として、このようなフォームを作成して解説します。

edit.html.erb
<%= form_with model: @user, local: true do |f| %>

  <%= f.label :content %>
  <%= f.text_area :content, id: "spec-content", class: "user_edit_content", value: @current_user.content %>

 <%= f.label :password %>
 <%= f.password_field :password, placeholder: "パスワード" %>

 <%= f.label :password_confirmation %>
 <%= f.password_field :password_confirmation, placeholder: "パスワード(確認)" %>

<% end %>
users_controller.rb
  def edit
    @user = User.find_by(id: params[:id])
  end

  def update
    @user = User.find_by(id: params[:id])
    if @user.update(user_edit_params)
      redirect_to user_path(@user), notice: '編集されました'
    else
      render :edit
    end
  end

  private
    def user_edit_params
      params.require(:user).permit(:content,:password,:password_confirmation)
    end
user.rb
has_secure_password validations: false
validates :password, presence: true, confirmation: true, length: { in: 6..50, message: 'は6〜50文字で記入してください' }

このままでは、passwordを更新したくない場合でも、password、password_confirmationに値を入力しなければ、validationに引っかかってしまう。

※has_secure_password validations: false で、独自のvalidationを設定できるようにする。

ifオプションを使用する

validationにifオプションを使用する。
ifオプションは、特定の条件の場合にのみバリデーションを有効にすることが可能。

user.rb
has_secure_password validations: false
validates :password, presence: true, ・・・ if: :password_was_entered?

  def password_was_entered?
    password.present? || password_confirmation.present?
  end

・password_was_entered? の解説
パスワード、またはパスワード(確認)のどちらかが入力されたらなら検証。
どちらとも入力されなかったら検証をパスする。
このようにして実装することができる。

しかし、このままではUserのcreate時にもpasswordが空でもパスしてしまう。

そこで、onオプションを使用し、validatesの実行のタイミングを設定することで、パスさせないことができる。

user.rb
has_secure_password validations: false
validates :password, presence: true, confirmation: true, length: { in: 6..50, message: 'は6〜50文字で記入してください' }, on: :create
validates :password, presence: true, ・・・ if: :password_was_entered?, on: :update

  def password_was_entered?
    password.present? || password_confirmation.present?
  end

最後に

初学者のためわかりづらい、適切ではない部分があるかと思われます。
お気付きの点がございましたら、お気軽にコメントいただけると幸いです。

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

Rails6 のちょい足しな新機能を試す 120(Rails.logger in Fiber編)

はじめに

Rails 6 に追加された新機能を試す第120段。 今回は、Rails.logger in Fiber 編です。
Rails 6 (と Rails 5.2.4.1) では、Fiber の中で Rails.logger.local_level でログレベルを変更しても、それが親(Fiber の親)に影響を与えないようになっています。

Ruby 2.6.5, Rails 6.0.2.1, Rails 5.2.4.1, Rails 5.2.3 で確認しました。 (Rails 6.0.0 でこの修正が入っています。)

$ rails --version
Rails 6.0.2.1

今回は、適切な利用例を思いつきませんでした。controller を1つ作って、 index の中で、 Fiber を使って確認します。

Rails プロジェクトを作る

Rails プロジェクトを新たに作成します。

$ rails new rails_sandbox
$ cd rails_sandbox

Home コントローラを作る

index ビューを持つ Home コントローラを作成する。

$ bin/rails g controller Home index

Controller を修正する

Controller を修正し、 index の中で、Fiber を使います。 Fiber#resume を呼ぶ前後で、Rails.logger

app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    Rails.logger.level = 1
    Rails.logger.info("[Before] Rails.logger.debug? #{Rails.logger.debug?}")

    Fiber.new do
      Rails.logger.local_level = 0
      Rails.logger.info("[Fiber] Rails.logger.debug? #{Rails.logger.debug?}")
    end.resume

    Rails.logger.info("[After] Rails.logger.debug? #{Rails.logger.debug?}")
  end
end

rails server を実行して確認する

rails server を実行し、 http://localhost:3000/home/index にアクセスすると以下のように出力されます。

[Before] Rails.logger.debug? false
[Fiber] Rails.logger.debug? true
[After] Rails.logger.debug? false

Rails 5 では

5.2.4 では、Rails 6 と同様の動作をしますが、 Rails 5.2.3 では、以下のように Fiber#resume 実行後、元に戻りません。

[Before] Rails.logger.debug? false
[Fiber] Rails.logger.debug? true
[After] Rails.logger.debug? true

その他

どうもよくわからない事象が1つ。
以下のように、 Fiber.new のブロックの中を Rails.logger.info から Rails.logger.debug に修正します。

/app/
...
    Fiber.new do
      Rails.logger.local_level = 0
      Rails.logger.debug("[Fiber] Rails.logger.debug? #{Rails.logger.debug?}")
    end.resume
...

そうするとなぜか以下のようにFiberの中のログ出力が無視されてしまいました。

Rails.logger.debug?true になるので、 Rails.logger.debug でも出力されるかと思ったのですが...

[Before] Rails.logger.debug? false
[After] Rails.logger.debug? false

試したソース

https://github.com/suketa/rails_sandbox/tree/try120_logger_in_fiber

参考情報

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

railsアプリをHerokuにデプロイ

Cloud9から作成したrailsアプリをHerokuにデプロイする際に
色々と躓いたので成功した手順を書き留めておきます。

1.Herokuアカウント作成

以下のURLからHerokuアカウントを作成します。
https://jp.heroku.com/

無料プランでも月550時間の稼働時間が利用できます。
利用が想定されるユーザの規模が非常に小さい場合は無料プランで十分だと思います。

2.Herokuにアプリを作成

Herokuにログイン

$ Heroku login

プロジェクトフォルダに移動し、Herokuにrailsアプリを作成

$ Heroku create アプリ名

3.Heroku用の設定

以下のようにGemfileに追加

group :production do
  gem 'pg', '0.20.0'
end

以下のようにproduction:内の記述を変更

config/database.yml
production:
  adapter: postgresql
  encoding: unicode
  pool: 5
  database: フォルダ名_production
  username: フォルダ名
  password: <%= ENV['フォルダ名_DATABASE_PASSWORD'] %>

4.アプリをデプロイ

デプロイを実行

$ git push heroku master

マイグレーションを実行

$ Heroku run rails db:migrate

5.うまくいかない時

エラーが出る場合は3のherokuの設定もしくはGitでコミットできているかを確認してください。
それでもエラーが出る場合は下の項目を実行してみてください。
修正後はマイグレーションを実行しましょう。

PostgreSQL アドオンの追加

PostgreSQLのインストールができてない可能性があるため、手動でアドオンを追加します。

$ heroku addons:create heroku-postgresql:hobby-dev

環境変数の確認

実行に必要な環境変数が空になっている可能性があります。
configでそれぞれの環境変数に値が入っているかを確認してください。

$ heroku config

値が空だった場合は以下のコマンドで環境変数に値を入れます。

$ heroku config:set HENSU=hensu

エラーログ参照

以下のコマンドでログを確認すれば、エラーの詳細がわかります。

$ heroku logs

リアルタイムで出力したいなら以下のコマンド

$ heroku logs --tail

画像アップローダー

もし画像アップロード機能を実装している場合はローカルではなく
クラウド上にファイルが保存されるように設定を行う必要があります。
設定はこのあたりの記事が参考になりました。
https://qiita.com/junara/items/1899f23c091bcee3b058
https://qiita.com/daichi41/items/af2a56ea46c13ca55fd3
https://qiita.com/hmmrjn/items/479c9e9ce82771f1b6d7
https://pg-happy.jp/rails-aws-s3-upload.html

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

【Rails+MySQL】MySQLのIDを1から振り直す

MySQLを使用してアプリ作成していると、テストデータを追加したり消したりしてIDがぽこぽこ抜けちゃうことが多いと思います。

IDをもう一度1から振り直してデータを綺麗に整えたい時に以下の方法を使うと楽だったので共有できたらと思います。

アプリのデータベースの確認

MySQLに入って以下のコマンドを打ちます。

> use app_name_development;

Railsで作ったテーブルが保存されてるデータベースです。

Idを1から振り直す

「delete from テーブル名」でテーブルのデータを消してから以下を実行

ALTER TABLE `テーブル名` auto_increment = 1;

これで中身が綺麗に振り直されてるのではないでしょうか?

まとめ

テストデータ管理はなんだかんだ大変なので参考になれば幸いです。

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

Rails6 each を使った時にDBのレコードの内容が全て出力されてしまう

目的

  • eachを用いた時に意図しない出力になってしまったので解決方法を記載する。

症状

  • 下記のコードを用いてDBのとあるテーブルのレコードの内容を出力したところレコードの全ての内容が最後に出力されてしまう。

    <%= @posts.each do |post| %>
      <%= post.content %><br>
      <%= post.link %><br>
    <% end %>
    

    ↓出力

    testでーす
    https://qiita.com/miriwo
    [#<Post id: 1, content: "testでーす", created_at: "2020-02-08 00:54:38", updated_at: "2020-02-08 00:54:38", link: "https://qiita.com/miriwo">]
    

原因

  • 単純なミス
  • @posts.each do |post|の部分を<%= %>で囲んでしまっていたため、処理部分が出力されてしまった。

正しいコード

  • 下記に正しいコードを記載する。

    <% @posts.each do |post| %>
      <%= post.content %><br>
      <%= post.link %><br>
    <% end %>
    

    ↓出力

    testでーす
    https://qiita.com/miriwo
    

教訓

  • <%= %>は結果を出力したい時のみ使用する。
  • <% %>は結果を出力したくない時に使用する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsにReactやVueはいらない? ajaxでviewを非同期で操作するgem(ActionPartial)を作りました。

ActionPartial

個人でCrover(クリエイターズプラットフォーム)というサービスを企画&開発&運営しているnirと申します。

Croverの開発で動的なviewの実装が必要になったので、gemを作ることにしました。

それで出来上がったのが、Railsで動的なviewを簡単に実現できるgemActionPartialです。

Crover(クリエイターズプラットフォーム)
https://crover.me

github(ActionPartial)
https://github.com/nir-searchright/actionpartial

なぜ、ReactVueを使わなかったかというと
- 学習コストが高い
- フレームワークにフレームワークを組み込むことに違和感がある
- 設計思想的な問題(RailsRailsのまま使いたい)

自分だけで使うはもったいないので、とりあえず公開することにしました。

注意)
- ReactVueを否定する記事ではありません。

DEMO

簡易的なデモを作成したのでご自由にお試しください。

デモ
https://actionpartial-demo.herokuapp.com

github(ActionPartial DEMO)
https://github.com/nir-searchright/actionpartial_demo

herokuの無料プランなのでデモのページを開くのに時間がかかるかもしれません。

ここから先はデモを元に説明します。

導入方法

Gemfile'actionpartial', github: 'nir-searchright/actionpartial'を追加してbundle installします。

現状、RubyGemsには登録していないのでgithuburlは必須です。

gem 'actionpartial', github: 'nir-searchright/actionpartial'

application_helper.rbrequireするだけで準備は完了です。

application_helper.rb
require 'action_partial'

ActionPatialができること

ActionPartialができることは

  • partialの更新
  • partialの追加
  • partialの削除

これらの単純な機能だけです。

単純故に応用も利きやすい設計となっているはずです。(多分)

demo_a.gif

仕組み

前提として、Railsではlink_toformremote: trueをつけるとajaxで通信が行われます。

<%= form_for(@post, remote: true) do |f| %>
  内容
<% end %>

ajaxでリクエストを送り、サーバーからxxx.js.erbを返します。

def create
  内容
  render 'posts/js_erb/create.js.erb'
end

サーバーから返されたxxx.js.erbview
- innerHTMLpartialを更新(https://developer.mozilla.org/ja/docs/Web/API/Element/innerHTML)
- insertAdjacentHTMLで任意の位置にpartialを挿入(https://developer.mozilla.org/ja/docs/Web/API/Element/insertAdjacentHTML)
- removepartialを削除(https://developer.mozilla.org/ja/docs/Web/API/ChildNode/remove)
などをしています。

helperメソッド一覧

html.erbで使うヘルパー

index.html.erb
<%= ap_render "posts/list/container", class: "posts-padding-bottom", locals: {posts: @posts} %>
<%= ap_render "posts/new" %>

<%= ap_init(path, options={}) %>

  • 最初は表示したくないけど、後からコンテンツを追加したい場所に使います。
  • エラーメッセージなどの後から表示する要素に使います。

<%= ap_render(path, options={}) %>

  • 非同期で更新したいpartialrenderするのに使います。
  • 基本的にRailsが提供するrenderと大して変わりません。

js.erbで使うヘルパー

※サンプルコードは下の個別の説明に載せてあります。

<%= ap_before(path, options = {}) %>

  • ap_initもしくはap_renderしたpartialの外側上部にpartialが挿入されます。

<%= ap_prepend(path, options = {}) %>

  • ap_initもしくはap_renderしたpartialの内側上部にpartialが挿入されます。

<%= ap_append(path, options = {}) %>

  • ap_initもしくはap_renderしたpartialの内側下部にpartialが挿入されます。

<%= ap_after(path, options = {}) %>

  • ap_initもしくはap_renderしたpartialの外側下部にpartialが挿入されます。

<%= ap_replace(path, options = {}) %>

  • ap_initもしくはap_renderしたpartialがサーバーから取得した新しいpartialに置換されます。

<%= ap_remove(id) %>

  • html上に存在する要素をid指定で削除します。

基本の使い方

基本は更新or追加or削除したいpartialid(ap_initもしくはap_renderした場合はpartialpath名)を指定して使います。

partialでインスタンス変数を使っている場合はlocalsで変数を渡します。

add.js.erb
<%= ap_append("posts/list/loop", locals: {posts: @posts}) %>

詳しくはDEMOのadd.js.erbをご確認ください。(このコードは無限スクロールのコードです。)
add.gif

他の要素やpartialpartialを追加(挿入)したい場合

追加したい要素やpartialid(ap_initもしくはap_renderした場合はpartialpath名)を指定することでpartialの追加(挿入)が可能です。

create.js.erb
<%= ap_prepend("posts/list/item", id: "posts/list/loop", class: "posts-list-item", locals: {post: @post}) %>

詳しくはDEMOのcreate.js.erbをご確認ください。(このコードは新規投稿のコードです。)

create_a.gif

複数のpartialから一つのpartialを指定したい場合

js.erbで使えるヘルパーはoptionidを指定することができます。

例えばhtml.erbでこんな感じでeachで回しながらコンテンツにidを指定することでjs.erbでコンテンツを指定することが可能です。

<% posts.each do |post| %>
  <div id="posts_<%= post.id %>">
    内容
  </div>
<% end %>

詳しくはDEMOのupdate.js.erb destroy.js.erb calcel.js.erbをご確認ください。

update_a.gif
delete_a.gif

jsの実行

当たり前と言えば当たり前ですがjs.erbjsのコードを書いておけば一緒に実行できます。

create.js.erb
<%= ap_prepend("posts/list/item", id: "posts/list/loop", class: "posts-list-item", locals: {post: @post}) %>

document.getElementById("post_content").value = "";
scrollTo(0, 0);

ただ、このjsが実行できてしまう仕組みがセキュリティ的に良いのかは分かりません。

理論上だとhttps通信をしているなら問題ないはずですが、どうなのでしょうか?

どなたか詳しい方がいればコメントで教えていただけると助かります。

最後に

今回やったことは「ネット上でよく見かけるjs.erbでの非同期更新を簡単に使えるようにgem化した」だけです。

作ってみて思ったよりも使いやすかったのでとりあえず公開しました。

ActionPartialは学習コスト低めでhtmljsrailsだけ理解していれば使えるのでスピーディーな開発ができると思います。

スピードを重視するスタートアップのプロジェクトとの相性が良いのではないでしょうか?

機能的にはシンプルなgemなので導入で不具合を吐くことはほとんどないと思います。

そのうち気が向いたらrails ujsを使ったコンテンツの非同期更新の記事もアップします。

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

RailsにReactやVueは不要!? ajaxでviewを非同期で操作するgem(ActionPartial)を作りました。

ActionPartial

個人でCrover(クリエイターズプラットフォーム)というサービスを企画&開発&運営しているnirと申します。

Croverの開発で動的なviewの実装が必要になったので、gemを作ることにしました。

それで出来上がったのが、Railsで動的なviewを簡単に実現できるgemActionPartialです。

Crover(クリエイターズプラットフォーム)
https://crover.me

github(ActionPartial)
https://github.com/nir-searchright/actionpartial

なぜ、ReactVueを使わなかったかというと
- 学習コストが高い
- フレームワークにフレームワークを組み込むことに違和感がある
- 設計思想的な問題(RailsRailsのまま使いたい)

自分だけで使うはもったいないので、とりあえず公開することにしました。

注意)
- ReactVueを否定する記事ではありません。

DEMO

簡易的なデモを作成したのでご自由にお試しください。

デモ
https://actionpartial-demo.herokuapp.com

github(ActionPartial DEMO)
https://github.com/nir-searchright/actionpartial_demo

herokuの無料プランなのでデモのページを開くのに時間がかかるかもしれません。

ここから先はデモを元に説明します。

導入方法

Gemfile'actionpartial', github: 'nir-searchright/actionpartial'を追加してbundle installします。

現状、RubyGemsには登録していないのでgithuburlは必須です。

gem 'actionpartial', github: 'nir-searchright/actionpartial'

application_helper.rbrequireするだけで準備は完了です。

application_helper.rb
require 'action_partial'

ActionPatialができること

ActionPartialができることは

  • partialの更新
  • partialの追加
  • partialの削除

これらの単純な機能だけです。

単純故に応用も利きやすい設計となっているはずです。(多分)

demo_a.gif

仕組み

前提として、Railsではlink_toformremote: trueをつけるとajaxで通信が行われます。

<%= form_for(@post, remote: true) do |f| %>
  内容
<% end %>

ajaxでリクエストを送り、サーバーからxxx.js.erbを返します。

def create
  内容
  render 'posts/js_erb/create.js.erb'
end

サーバーから返されたxxx.js.erbview
- innerHTMLpartialを更新(https://developer.mozilla.org/ja/docs/Web/API/Element/innerHTML)
- insertAdjacentHTMLで任意の位置にpartialを挿入(https://developer.mozilla.org/ja/docs/Web/API/Element/insertAdjacentHTML)
- removepartialを削除(https://developer.mozilla.org/ja/docs/Web/API/ChildNode/remove)
などをしています。

helperメソッド一覧

html.erbで使うヘルパー

index.html.erb
<%= ap_render "posts/list/container", class: "posts-padding-bottom", locals: {posts: @posts} %>
<%= ap_render "posts/new" %>

<%= ap_init(path, options={}) %>

  • 最初は表示したくないけど、後からコンテンツを追加したい場所に使います。
  • エラーメッセージなどの後から表示する要素に使います。

<%= ap_render(path, options={}) %>

  • 非同期で更新したいpartialrenderするのに使います。
  • 基本的にRailsが提供するrenderと大して変わりません。

js.erbで使うヘルパー

※サンプルコードは下の個別の説明に載せてあります。

<%= ap_before(path, options = {}) %>

  • ap_initもしくはap_renderしたpartialの外側上部にpartialが挿入されます。

<%= ap_prepend(path, options = {}) %>

  • ap_initもしくはap_renderしたpartialの内側上部にpartialが挿入されます。

<%= ap_append(path, options = {}) %>

  • ap_initもしくはap_renderしたpartialの内側下部にpartialが挿入されます。

<%= ap_after(path, options = {}) %>

  • ap_initもしくはap_renderしたpartialの外側下部にpartialが挿入されます。

<%= ap_replace(path, options = {}) %>

  • ap_initもしくはap_renderしたpartialがサーバーから取得した新しいpartialに置換されます。

<%= ap_remove(id) %>

  • html上に存在する要素をid指定で削除します。

基本の使い方

基本は更新or追加or削除したいpartialid(ap_initもしくはap_renderした場合はpartialpath名)を指定して使います。

partialでインスタンス変数を使っている場合はlocalsで変数を渡します。

add.js.erb
<%= ap_append("posts/list/loop", locals: {posts: @posts}) %>

詳しくはDEMOのadd.js.erbをご確認ください。(このコードは無限スクロールのコードです。)
add.gif

他の要素やpartialpartialを追加(挿入)したい場合

追加したい要素やpartialid(ap_initもしくはap_renderした場合はpartialpath名)を指定することでpartialの追加(挿入)が可能です。

create.js.erb
<%= ap_prepend("posts/list/item", id: "posts/list/loop", class: "posts-list-item", locals: {post: @post}) %>

詳しくはDEMOのcreate.js.erbをご確認ください。(このコードは新規投稿のコードです。)

create_a.gif

複数のpartialから一つのpartialを指定したい場合

js.erbで使えるヘルパーはoptionidを指定することができます。

例えばhtml.erbでこんな感じでeachで回しながらコンテンツにidを指定することでjs.erbでコンテンツを指定することが可能です。

<% posts.each do |post| %>
  <div id="posts_<%= post.id %>">
    内容
  </div>
<% end %>

詳しくはDEMOのupdate.js.erb destroy.js.erb calcel.js.erbをご確認ください。

update_a.gif
delete_a.gif

jsの実行

当たり前と言えば当たり前ですがjs.erbjsのコードを書いておけば一緒に実行できます。

create.js.erb
<%= ap_prepend("posts/list/item", id: "posts/list/loop", class: "posts-list-item", locals: {post: @post}) %>

document.getElementById("post_content").value = "";
scrollTo(0, 0);

ただ、このjsが実行できてしまう仕組みがセキュリティ的に良いのかは分かりません。

理論上だとhttps通信をしているなら問題ないはずですが、どうなのでしょうか?

どなたか詳しい方がいればコメントで教えていただけると助かります。

最後に

今回やったことは「ネット上でよく見かけるjs.erbでの非同期更新を簡単に使えるようにgem化した」だけです。

作ってみて思ったよりも使いやすかったのでとりあえず公開しました。

ActionPartialは学習コスト低めでhtmljsrailsだけ理解していれば使えるのでスピーディーな開発ができると思います。

スピードを重視するスタートアップのプロジェクトとの相性が良いのではないでしょうか?

機能的にはシンプルなgemなので導入で不具合を吐くことはほとんどないと思います。

そのうち気が向いたらrails ujsを使ったコンテンツの非同期更新の記事もアップします。

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