20210113のRailsに関する記事は20件です。

Railsでtext_areaに入力した文章を改行込で表示させる方法

はじめに

Ruby on Railsにて投稿機能のあるアプリを作成しました。
投稿画面にはRailsのヘルパーメソッドであるform_withメソッドを用いてフォームを実装。その際、text_areaで、複数行の入力が可能な入力欄を作成したので、改行を含めた文章を入力し、それを表示させようとしたところ、改行がされていない文章が表示されてしまいました。
今回はその問題を解決する方法を調べたので記事に残しておきます。

環境

macOS Catalina バージョン 10.15.7
Ruby 2.6.5
Ruby on Rails 6.0.3.4

前提として

フォームのコードは以下の通り。

app/views/shared/_form.html.erb
<%= form_with(model: word, local: true) do |f| %>
  <%= f.text_area :remarks, placeholder: "備考", rows: "7" %>
  <%= f.submit "Register" %>
<% end %>

念の為、このフォームで送られてくるデータに改行が含まれているか、Railsのデバックツールであるpry-railsを用いて確認してみました。
まず、以下の改行を含めた文章をフォームで送信しました。

おはよう
こんにちは

ここで、コントローラーに記述したbinding.pryのところで一旦処理が止まるので、コンソールでparamsを実行し、送られてきたパラメーターを確認したところ、
Image from Gyazo
このように、改行した部分に改行コードである「\r\n」が含まれており、ちゃんとデータとして送られてきていることが分かります。

そして、送られてきたデータを表示するためのコードが以下なのですが、

app/views/show.html.erb
<%= @word.remarks %>

このままだと改行が含まれず、以下のような表示になってしまいます。

おはよう こんにちは

この問題を解決する方法を、以下2通りまとめました!

①simple_formatメソッド

以下のサイトを参考にしました。

simple_formatはRailsのヘルパーメソッドで、下記の機能を有します。

  • 文字列を<p>で括る
  • 改行は<br>を付与
  • 連続した改行は</p><p>を付与

では、show.html.erbにsimple_formatを追記します。

app/views/show.html.erb
<%= simple_format(@word.remarks) %>

そして、ブラウザを更新し改めて確認してみると、

おはよう
こんにちは

このように改行がされたまま表示がされました!

ChromeのデベロッパーツールのElementsパネルを確認すると、
Image from Gyazo
たしかに、文字列がpタグで括られており、改行にはbrタグが付与されています。

そこで、試しに以下のようにいくつか連続した改行の場合を調べてみました。

おはよう



こんにちは

しかし、表示を確かめると、

おはよう
こんにちは

このように、連続した改行は実際に入力した通りには表示されませんでした。
ちなみに、Elementsパネルを確認すると、以下のようにpタグが付与されていました。
Image from Gyazo
なので、この場合は改行ではなく、「おはよう」と「こんにちは」それぞれが1つの段落として表示されている事になります。

②safe_joinメソッド

以下のサイトを参考にしました。

safe_joinメソッドもRailsのヘルパーメソッドの一つです。
simple_formatと違い、pタグで括らず、また連続した改行を表示させることができます。

まず、先ほどの記述を以下のように書き換え。

app/views/show.html.erb
<%= safe_join(@word.remarks.split("\n"),tag(:br)) %>

※ splitは文字列を分割するメソッド。引数に区切り文字を指定することで、その区切り文字のところで文字列を区切る。

あとは、同じように複数の連続した改行を含む文章を入力してみると、

おはよう


こんにちは

きちんと入力した通りの表示がされました!!
Elementsパネルを確認すると、
Image from Gyazo
brタグで改行されているのが分かります。

最後に

色々調べる中で、HTMLやタグ、エスケープ処理などの記事を目にしました。これらの理解が深まったと同時に、知らない知識に触れる機会も多かった為、さらに派生させ知識をより深めていきたいと思います。

誤った箇所などありましたら、ご指摘いただけると幸いです。

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

text_areaに入力した文章を改行込で表示させる方法

はじめに

Ruby on Railsにて投稿機能のあるアプリを作成しました。
投稿画面にはRailsのヘルパーメソッドであるform_withメソッドを用いてフォームを実装。その際、text_areaタグで、複数行の入力が可能な入力欄を作成したので、改行を含めた文章を入力し、それを表示させようとしたところ、改行がされていない文章が表示されてしまいました。
今回はその問題を解決する方法を調べたので記事に残しておきます。

環境

macOS Catalina バージョン 10.15.7
Ruby 2.6.5
Ruby on Rails 6.0.3.4

前提として

フォームのコードは以下の通り。

app/views/shared/_form.html.erb
<%= form_with(model: word, local: true) do |f| %>
  <%= f.text_area :remarks, placeholder: "備考", rows: "7" %>
  <%= f.submit "Register" %>
<% end %>

念の為、このフォームで送られてくるデータに改行が含まれているか、binding.pryを用いて確認してみました。
まず、以下の改行を含めた文章をフォームで送信しました。

おはよう
こんにちは

ここでbinding.pryを用いて、コンソールで「params」を実行し、パラメーターを確認したところ、
Image from Gyazo
このように、改行した部分に改行コードである「\r\n」が含まれており、ちゃんとデータとして送られてきていることが分かります。

そして、送られてきたデータを表示するためのコードが以下なのですが、

app/views/show.html.erb
<%= @word.remarks %>

このままだと改行が含まれず、以下のような表示になってしまいます。

おはよう こんにちは

この問題を解決する方法を、以下2通りまとめました!

①simple_formatメソッド

以下のサイトを参考にしました。

simple_formatはRailsのヘルパーメソッドで、下記の機能を有します。

  • 文字列を<p>で括る
  • 改行は<br>を付与
  • 連続した改行は</p><p>を付与

では、show.html.erbにsimple_formatを追記します。

app/views/show.html.erb
<%= simple_format(@word.remarks) %>

そして、ブラウザで更新し改めて確認してみると、

おはよう
こんにちは

このように改行がされたまま表示がされました!

ChromeのデベロッパーツールのElementsパネルを確認すると、
Image from Gyazo
たしかに、文字列がpタグで括られており、改行にはbrタグが付与されています。

そこで、試しに以下のようにいくつか連続した改行の場合を調べてみました。

おはよう



こんにちは

しかし、表示を確かめると、

おはよう
こんにちは

このように、連続した改行は実際に入力した通りには表示されませんでした。
ちなみに、Elementsパネルを確認すると、以下のようにpタグが付与されていました。
Image from Gyazo
なので、この場合は改行ではなく、「おはよう」と「こんにちは」それぞれが1つの段落として表示されている事になります。

②safe_joinメソッド

以下のサイトを参考にしました。

safe_joinメソッドもRailsのヘルパーメソッドの一つです。
simple_formatと違い、pタグで括らず、また連続した改行を表示させることができます。

まず、先ほどの記述を以下のように書き換え。

app/views/show.html.erb
<%= safe_join(@word.remarks.split("\n"),tag(:br)) %>

※ splitは文字列を分割するメソッド。引数に区切り文字を指定することで、その区切り文字のところで文字列を区切る。

あとは、同じように複数の連続した改行を含む文章を入力してみると、

おはよう


こんにちは

きちんと入力した通りの表示がされました!!
Elementsパネルを確認すると、
Image from Gyazo
brタグで改行されているのが分かります。

最後に

色々調べる中で、HTMLやタグ、エスケープ処理などの記事を目にしました。これらの理解が深まったと同時に、知らない知識に触れる機会も多かった為、さらに派生させ、知識をより深めていきたいと思います。

誤った箇所などありましたら、ご指摘いただけると幸いです。

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

Rails の Carrierwave を使用した画像複数アップロード機能

備忘録のため記述しています。

Carrierwave という gem の導入

carrierwaveuploader/carrierwave

Gemfile に以下を記述

gem "carrierwave", "~> 2.0"

終わったらターミナルにて bi

bundle install

Uploader の作成

bundle exec rails g uploader Images

下記が作成される。

images_uploader.rb

アップロードされるファイルの設定を色々弄れるけど今回は見送り。

MVC 設定

Post モデルに対して Memory というモデルで画像登録をできるようにしている

Model

post.rb

post.rb
class Post < ApplicationRecord
  has_many :memories
  accepts_nested_attributes_for :memories, allow_destroy: true
  validates :title, presence: true
end

accepts_nested_attributes_for :memories の設定で posts_controller 上で memories を登録できるようにしている。

memory.rb

class Memory < ApplicationRecord
  belongs_to :post
  mount_uploader :image, ImagesUploader
end

mount_uploader は carrierwave の設定。これで簡単にアップロードできるようになる。

Controller

posts_controller.rb

class PostsController < ApplicationController


  def new
    @post = Post.new
        # @post.memories.build とすることで post に紐づいた memories を保存する準備が整う
    @post_memory = @post.memories.build
  end

  def create
    post = Post.new(post_params)
    if post.save!
        # 以下は memories に保存する処理。 each 文で複数の画像を保存可能。
      params[:memories][:image].each do |image|
        post.memories.create(image: image, post_id: post.id)
      end
    end
    redirect_to root_path
  end

  private

    def post_params
      params.require(:post).permit(:title, memories_attributes: [:image]).merge(user_id: current_user.id)
    end
end

View

new.html.erb

<%= form_for @post, local: true,  html: {class: "form_area"} do |f| %>
        <div class="form_area__field">
          <%= f.text_area :title, id: "post_text", placeholder: "投稿内容を入力", rows: 10%>

          <div class="form_area__image_field">
            <%= f.fields_for :memories do |m| %>
              <%= m.label :image, "画像" %>
              <%= m.file_field :image, multiple: true, name: "memories[image][]" %>
              <%= hidden_field :memories, :post_id, value: @post.id %>
            <% end %>
          </div>

          <div class="form_area__hidden_field">
            <%= hidden_field :post, :user_id, value: current_user.id %>
          </div>

          <div class="form_area__action">
            <%= f.submit "投稿", class: "form_area__action__btn" %>
          </div>

        </div>
      <% end %>

fields_for で1回の投稿で複数のクラスを保存できる。

multiple: true で複数の画像を投稿できるように設定している。

表示するには?

<% @post.memories.each do |m|%>
     <%= image_tag m.image.url %>
<% end %>

上記で表示できる。

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

パーシャルでインスタンス変数は使わない

初学者です。
コントローラの修正をしたときに、どのコントローラと結びついているのかわからなくなってしまったので、備忘録として残します。

index.html.erb
<%= render 'layout/header' %> #ダメな例
_header.html.erb
<%= @product.name %> #ダメな例
  • モデルのデータと関連付けられるので、使いづらくなる。
  • コントローラ側でインスタンス変数の名前や動き方を変更したときにパーシャル側の変更もしなければいけなくなる。

対処

index.html.erb
<%= render 'layout/header', product_name: @product.name %>
_header.html.erb
<%= product_name %>

これでできました!!!

参考

https://qiita.com/mom0tomo/items/e1e3fd29729b2d112a48

locals:を使ったりもするみたいです。その場合はpartial:もつける。

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

ポリモーフィック関連付けをしたModelでのfactoryの作り方

ほぼ自分用なので簡易的に記します。

userとprojectに対してコメントができる。

comment.rb
belongs_to :commentable, polymorphic: true
belongs_to :commenter, class_name: 'User'
user.rb
has_many :comments, as: :commentable, dependent: :destroy
preject.rb
has_many :comments, as: :commentable, dependent: :destroy

両方のfactoryを書いてあげれば良い

factories/comment.rb
FactoryBot.define do
    factory :project_comment, class: 'Comment' do
      association :commenter,   factory: :user
      association :commentable, factory: :project
      commentable_type { 'User' }
      comment          { 'Sample Comment' }
    end

    factory :user_comment, class: 'Comment' do
      association :commenter,   factory: :user
      association :commentable, factory: :user
      commentable_type { 'User }
      comment          { 'Sample Comment' }
    end

end

let(:user_comment) { FactoryBot.create(:user_comment) }

で呼び出せる。

ポリモーフィック関連は関連するモデルの追加が簡単であるべき、
よってfactoryの追加も簡単であるべきだと思う。
このように書けば、今後関連モデルが増えてもcomment_typeを増やして対応できる。

factories/comment.rb
FactoryBot.define do
  comment_type = [:user, :project]

  comment_type.each do |type|
    factory :"#{type}_comment", class: 'Comment' do
      association :commenter,   factory: :user
      association :commentable, factory: type
      commentable_type { type.to_s.camelize }
      comment          { 'Sample Comment' }
    end
  end
end

逆にcomment_typeを増やしても対応できない場合は、ポリモーフィックにすべきではない。
関連モデルによってメソッドやfactoryの構成が変わるようでしたらポリモーフィックではなく,STIなどを検討してみればいかがでしょうか。

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

railsとDockerで(Basic認証)環境変数

Docker上でBasic認証

Docker上でBasic認証を実装したがサイトに入ることができなかったため理由を考えてみた。

結論

超簡単 絶対に下まで読んでください。

Docker上で環境変数を設定してあげる

docker-compose.yml
version: '3'
services:
  web:
    environment:
      BASIC_AUTH_USER: 'admin' #仮のuser
      BASIC_AUTH_PASSWORD: '0000' #仮のpassword

理由として

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


  private

  def basic_auth
    authenticate_or_request_with_http_basic do |username, password|
      username == ENV["BASIC_AUTH_USER"] && password == ENV["BASIC_AUTH_PASSWORD"]# 環境変数を読み込む記述
    end
  end
end

大体上のような記述でBASIC認証を実装していると思う。
ターミナル上で環境変数を設定してあげて呼び出しているかもしれないが、Docker上でも環境変数を設定してあげないといけない
理由としてはDockerは今使っているpcとは別に仮想のマシンとしてコンテナを作りその上でアプリを動かしているから。

感想

一安心かと思ったがちょっと待ってほしい。

Gitにあげれなくないか。。?

今の状態でマージしてしまうとGitのリポジトリからBasic認証のuseridとpasswordが丸見えになっていてセキュリティも何もない。

なので

結論

docker-compose.yml
version: '3'
services:
  web:
    environment:
      BASIC_AUTH_USER: ${BASIC_AUTH_USER:-default}
      BASIC_AUTH_PASSWORD: ${BASIC_AUTH_PASSWORD-default}

上記のように書くことによってサーバーの環境変数を持ってきてセットすることができ、外部からは見えないようになる。
docker-compose.ymlファイルを触ったのでbuildも忘れずに

感想

初学者な為、知ってるわってあったらごめんなさい。。
やばい部分あったら教えていただけると幸いです!
マージする前に気づけてよかったのと、他のAPI使用する際にも活用できそう。

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

Rails must be exist系のエラー(@saveが実行されない)

結論

belongs_toにoptional: trueを追加。

belongs_to :post, optional: true

なぜか?

Rails5から、デフォルトで紐づけているテーブル同士の、どちらかにデータが無いままDBに格納しようとすると、エラーになる。だから、どちらにデータがない状態のままでもokに」するために'optional: true'を書く必要がある。

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

Mysql2::Error: Incorrect string valueのエラーの対処

はじめに

データベースを作成してマイグレーションも実行し、「いざ、データ(日本語)を入れて動かそう!」としたところで、以下のようなエラーが。。。

Mysql2::Error: Incorrect string value: '\xE3\x82\xB2\xE3\x82\xB9...' for column 'name' at row 1

どうやら文字コードが不適切らしい。。。
ということで、本記事では、このエラーについて、僕が試したことや解決策を書いていこうと思います!

技術・環境

Docker/docker-compose
ruby 2.7.2
rails 6.0.2.3

試したこと

テーブルの文字コードを調べてみました。

まずは、MySQLに接続する。

$ docker-compose run web rails db

次に、データベースの文字コードを調べてみる。

> show variables like '%char%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8mb4                    |
| character_set_connection | utf8mb4                    |
| character_set_database   | latin1                     |
| character_set_filesystem | binary                     |
| character_set_results    | utf8mb4                    |
| character_set_server     | latin1                     |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+

やはり、データベースの文字コードが「latin1」だった。。。
※MySQLはdefaultで「latin1」が文字コードに設定されています。

データベースの文字コードをutf8mb4に修正
僕はDockerを使用していたので、以下のようにdocker-compose.ymlを修正。
※以下の公式ドキュメントを参考にしました。
https://hub.docker.com/_/mysql

docker-compose.yml
version: "3"
services:
  db:
    image: mysql:5.7
    # この下の一行を追記しました
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_USER: root
    ports:
      - "3306:3306"
    volumes:
      - ./db/mysql/volumes:/var/lib/mysql

  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/share-read
    ports:
      - "3000:3000"
    depends_on:
      - db
volumes:
  mysql-data:
    driver: local

データベースの文字コードを再度調べてみる。

> show variables like '%char%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8mb4                    |
| character_set_connection | utf8mb4                    |
| character_set_database   | utf8mb4                    |
| character_set_filesystem | binary                     |
| character_set_results    | utf8mb4                    |
| character_set_server     | utf8mb4                    |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+

「よし!修正完了!」と思ったが、まだエラーが出てしまう。
もしかしたらテーブル自体の文字コードが「latin1」のまま?と思い調べてみると。。。

> SHOW CREATE TABLE users;

| users | CREATE TABLE `users` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  # 中略
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |

やはり、テーブルの文字コードが変わっていなかった。。。

テーブルの文字コードをutf8mb4に修正
ということで、テーブルの文字コードを修正するのですが、10個くらいテーブルがあって一つ一つ修正するのは大変!
また、まだテーブルにデータを格納してなかったこともあり、今回はマイグレーションをやり直そうことにしました。

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

ということで無事解決しました。
もし同じようなエラーが出てしまった方は、参考にしてみてください。

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

【備忘録】Rails Formオブジェクトパターン使用時のエラーメッセージ日本語化

概要

  • RailsのFormオブジェクトパターン使用時の入力フォームについてのバリデーションで弾かれたエラー内容を日本語として表示させる事を行ったので備忘録としてまとめておきます。

前提

  • RailsのアプリケーションにてFormオブジェクトパターンを用いている。

Formオブジェクトパターンについて簡単に

  • Railsを利用する開発における実装パターンのひとつ。
  • 主に1つのフォーム送信時に複数のモデルを操作したい場合やテーブルに情報を保存しない情報に対するバリデーションを行いたい場合に使用します。

Formオブジェクト使用時のバリデーションについて

  • まず、1つのフォームから複数のテーブルにデータを保存する場合、Formオブジェクトを使用していないと、フォームから送られてきた値を処理(createやupdate)する際に複数テーブルに関連したモデル内記述のバリデーションで入力値が弾かれてしまうと処理を継続できなくなります。
  • その点を補う為にFormオブジェクトパターンを用います。
  • Formオブジェクトのインスタンス内にバリデーションの記述を行う事で、フォームからの入力値がバリデーションに引っかかった場合は、入力情報と共にエラーメッセージをフォームに返す事が出来ます。   

エラーメッセージの日本語化について

1.作成中アプリケーションのconfigフォルダ内のapplication.rbに
config.i18n.default_locale = :ja ←この一文を追加します。

cnofig>application.rb
module (作成アプリケーション名)
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 6.0

    # 日本語の言語設定
    config.i18n.default_locale = :ja  #←この一文をconfig内のapplication.rbに追加。

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.
  end
end

2.日本語に対応する"rails-i18n"というGemを導入します。
・このGemの導入によりrails-i18nで翻訳されている英文が日本語として使用出来るようになります。
※Gemについての詳細は下記のサイトで確認して下さい。
https://github.com/svenfuchs/rails-i18n
Gemfileにrails-i18nを記述しターミナルにて「bundle install」を行います。

Gemfil
~ 中略 ~
gem 'rails-i18n'
ターミナル
bundle install

3.rails-i18nのGemで対応出来なかった単語に関して日本語翻訳する為のlocaleファイルを作成します。
・localeファイル・・・様々な言語に対応できる言語ファイルの事です。
・まずlocalesフォルダはをconfigフォルダ内に作成します。
・そしてconfig/localesディレクトリに「ja.yml」というファイルを作成します。
スクリーンショット 2021-01-13 16.36.23.png

4.ja.ymlのファイルを作成したら、ファイル内に日本語化したい英文・英単語と日本語訳を記述していきます。
・ja.ymlファイル内の記述に関しては下記のコードを参照して頂ければと思います。
※この時に注意点です!
・Formオブジェクトパターンを使用していないモデルに関しては、ActiveRecordを継承していますが、Formオブジェクトパターンを用いて作成したモデルは、ActiveModelを継承して作成しています。
ですので、Formオブジェクトパターンを用いたモデルに対しての翻訳の部分の記述の際は気をつけて下さい。

ja.yml
ja:
  activerecord:  #← ActiveRecordを継承しているモデルに対しての翻訳部分
    attributes:
      user:               #←モデルファイル名
        nickname: ニックネーム     #←rails-i18nにて翻訳されなかった単語名
        gender: 性別        #←rails-i18nにて翻訳されなかった単語名
        age: 年齢            #←rails-i18nにて翻訳されなかった単語名

  activemodel:  #← ActiveModelを継承して作成したFormオブジェクトパターンのモデルに対しての翻訳
    attributes:
      book_data:                   #←Formオブジェクトパターン使用のモデルファイル名
        title: タイトル         #←rails-i18nにて翻訳されなかった単語名
        publisher: 出版社           #←rails-i18nにて翻訳されなかった単語名
        author: 著者                #←rails-i18nにて翻訳されなかった単語名
        isbn: ISBN                 #←rails-i18nにて翻訳されなかった単語名

5.サーバー再起動後にブラウザで確認して下さい。
・再起動後に、フォームのバリデーションのエラーメッセージが問題なく翻訳されているか確認して下さい。

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

カスタムデータ属性について

カスタムデータ属性とは?

HTML5で新しく導入された、html要素に、カスタムデータと呼ばれる、独自の属性を指定する属性。
オリジナルの属性も作成できる。
カスタムデータ属性の名前は常にdataから始まる。
各種スクリプトで動的に使用可能であり、主にJavaScriptやjQueryで値を取得するときに使用されることが多い。

<div class="tweet" data-genre="movie">
属性名:data-id
属性値:tweet.id

本来的に、HTMLのclass属性の目的は、データを格納するためではなく、開発時にCSS等のスタイル情報を割り当てるため。
しかし、要素への情報が増えるたびに、その都度新しいクラスを追加する必要が出てきてしまう。
そうなると、JavaScriptで実際に必要な情報を取り出すことが難しくなる。
そのような背景のもと、HTML5で新たに導入された。

カスタムデータ属性のルール

HTMLでカスタムデータ属性を設定するルールは次のようなものがあります。

  • data以降に属性名を指定する
  • 属性名に使用できるのは、文字、数字、-(ハイフン)、.(ドット)、_(アンダースコア)のみで、大文字は使用できない
  • 属性値は数字も文字列も使用できるが、慣例的に小文字の使用が大半
  • class名を属性名として格納することはできない(データ属性を扱うのは、他に適切なHTML要素や属性がない場合だけに限るべきだから)

JavaScriptでの使い方

データ属性の取得

<div id="tweet" data-genre="movie"></div>
<script>
    var result = document.getElementById("tweet");
    var dataset = result.dataset;
    console.log(result);
</script>

実行結果

movie

データ属性の変更

<div id="tweet" data-genre="movie"></div>
<script>
    var result = document.getElementById("tweet");
    result.dataset.genre = 'movie';
    console.log(result);
</script>

実行結果

movie

jQueryでの使い方

データ属性の取得

対象要素.data( 属性名 )のように、引数へdata属性名を指定する。

<div class="tweet" data-genre="movie"></div>
<script>
    const result = $(".tweet").data('genre');
    console.log(result);
</script>

実行結果

movie

データ属性の変更

<div class="tweet" data-genre="movie"></div>
<script>
    const result = $(".tweet").data('genre', 'movie');
    console.log(result);
    const result = $(".tweet").data('genre', 'music');
    console.log(result);
</script>

実行結果

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

iso-2022-jpの文字列をutf-8に変換する

メールを受け取った際にiso-2022-jpで送られたために文字化けしてしまっていました。

rubyのencodeを使ってutf-8に変換します

検証するためにiso-2022-jpでエンコードされた文字列を生成します

puts "ほげ".encode('iso-2022-jp')
# => $B$[$2(B

encodeを以下のように記述します

str.encode(変換先, 変換元, 変換オプション)

今回はiso-2022-jpの文字列をutf-8に変換したいので次のようになります

str = "ほげ".encode('iso-2022-jp')
puts str
# => $B$[$2(B
puts str.encode('utf-8', 'iso-2022-jp', invalid: :replace, undef: :replace, replace: '')

今回は変換できない文字を空文字に置き換えるオプションを指定しています

変換元がわかっている場合でないとこの方法で変換することはできませんが、
今回のようにメールが文字化けしている場合などでは有効に使えそうです

参考

https://docs.ruby-lang.org/ja/latest/method/String/i/encode.html

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

Formオブジェクトを用いて作成したデータを、特定のデータのみ削除する方法

要点

  • メルカリクローンのアプリを作るときの参考に
  • dependent: :destroy を用いて外部キーの削除制限を外す
  • 下記のエラーを解消する方法
ActiveRecord::InvalidForeignKey (Mysql2::Error: Cannot delete or update a parent row: a foreign key constraint fails (`-アプリ名-_development`.`-中間テーブル名-`, CONSTRAINT `fk_rails_~~~~~` FOREIGN KEY (`-商品のテーブル名-_id`) REFERENCES `-商品のテーブル名-` (`id`))):

はじめに

メルカリのようなフリマアプリを作成中で、Formオブジェクトを用いて商品にタグ付けして出品できる機能を実装する所まで行いました
各モデルとコントローラーは以下のようになります

  • Item/商品
  • Tag/タグ
  • TagItemRelation/商品とタグの中間テーブル
  • TagsItem/ ItemとTagを同時に保存するためのFormオブジェクト
/app/model/item.rb
class Item < ApplicationRecord
  has_many :tag_item_relations
  has_many :tags, through: :tag_item_relations
end
/app/model/tag.rb
class Tag < ApplicationRecord
  has_many :tag_item_relations
  has_many :items, through: :tag_item_relations
  validates :tag_name, uniqueness: true
end
/app/model/tag_item_relation.rb
class TagItemRelation < ApplicationRecord
  belongs_to :item
  belongs_to :tag
end
/app/form/tags_item.rb
class TagsItem
  include ActiveModel::Model
  attr_accessor :item_name,
                :tag_name

  with_options presence: true do
    validates :item_name
  end

  def save
    item = Item.create(item_name: item_name)
    tag = Tag.where(tag_name: tag_name).first_or_initialize
    tag.save
    TagItemRelation.create(item_id: item.id, tag_id: tag.id)
  end
end

コントローラー

/app/controller/items_controller.rb
class ItemsController < ApplicationController
  def index
    @items = Item.all.order('created_at ASC')
  end

  def new
    @item = TagsItem.new
  end

  def create
    @item = TagsItem.new(items_params)
    if @item.valid?
      @item.save
      redirect_to root_path
    else
      render :new
    end
  end

  private

  def items_params
    params.require(:tags_item).permit(
      :item_name,
      :tag_name
    )
  end
end

その後商品閲覧機能と
問題の商品削除機能を実装した

/app/controller/items_controller.rb
class ItemsController < ApplicationController
  before_action :set_item, only: [:show, :destroy]

###  中略

  def  show
    @tags_item = TagItemRelation.find(params[:id])
  end

  def destroy
    if current_user.id == @item.user_id
      @item.destroy
      redirect_to root_path
    else
      render :show
    end
  end

private

###  中略

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

しかし実際に出品した商品を削除してみると、、、

ActiveRecord::InvalidForeignKey (Mysql2::Error: Cannot delete or update a parent row: a foreign key constraint fails (`-アプリ名-_development`.`tag_item_relations`, CONSTRAINT `fk_rails_~~~~~~` FOREIGN KEY (`item_id`) REFERENCES `items` (`id`))):

のエラーが出てしまい、商品の削除ができませんでした

調べたこと

エラー内容をよく読んでみると、商品idは中間テーブルの外部キー(foreign key)に含まれて
デリートやアップデートができないよ!と書かれているのがわかります

そこで外部キーについて調べてみると、、、
外部キー(外部制約キーとも)は、よくマイグレーションファイルなどに書いてるデータベースのキーで
データベースの特定の項目に好き勝手な値を入れることを防ぎ、外部の項目から選んで入れる制約を持ち
主に外部キーのテーブルから主キーのテーブルのデータに対して、変更を制限するものだそうです
今回の場合外部キーのテーブルはtag_item_relations、主キーのテーブルはitemになることが分かりますね

外部キーの設定変更

今回のエラーの原因はマイグレーションファイルに記載した外部キー(foreign_key: trueのやつ)でした
ですがマイグレーションファイルごと書き換えてしまうと別の不具合が起きてしまう恐れがあります
今回はdestoryで外部キーとして設定している項目を削除させるように、dependent: :destroyを設定しようと思います
この簡単な設定により不具合なく商品の削除ができるようになります

/app/model/item.rb
class Item < ApplicationRecord
  has_many :tag_item_relations, foreign_key: :item_id, dependent: :destroy
  has_many :tags, through: :tag_item_relations
end

参考にしたサイト

外部キー (foreign key)とは

関連記事

外部キー制約でハマったので(rails)
外部キーを持つデータのdestroyを行うための設定

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

RSpec エラー undefined method `feature' for RSpec:Module

rspecコマンドを打った時に出たエラー。

結論

capybaraを読み込んでいなかった。

コード

エラーコード

require 'rails_helper'

RSpec.feature "Users", type: :feature do

  describe "Signup page" do
    before do
      visit signup_path
    end

    it "display Signup contents, title properly" do
      expect(page).to have_css('h1', text: 'ユーザー登録')
      expect(page).to have_title 'Signup hoge'
    end
  end

end

解決したコード capybaraを読み込む

  require 'rails_helper'
+ require 'capybara/rspec'

RSpec.feature "Users", type: :feature do

  describe "Signup page" do
    before do
      visit signup_path
    end

    it "display Signup contents, title properly" do
      expect(page).to have_css('h1', text: 'ユーザー登録')
      expect(page).to have_title 'Signup hoge'
    end
  end

end

一応gemfile


#rspecのprogressbarを表示してくれる。実行のコマンド% bin/rspec spec/ --format Fuubar
gem 'fuubar'

group :test do
 #rspecには、以下の3つのgemが必要。
  gem 'rspec-rails'
  gem 'factory_bot_rails'
  gem 'rails-controller-testing'
  #rspecのfeatureで必要。
  gem 'capybara', '~> 2.13'
  #Capybaraでテスト中に、現在どのページを開いているのか確認するため
  gem 'launchy'
  #便利。validationが一行くらいでかける。
  gem 'shoulda-matchers',
    git: 'https://github.com/thoughtbot/shoulda-matchers.git',
    branch: 'rails-5'
end

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'spring-commands-rspec'
end

group :development do
  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
  gem 'web-console', '>= 4.1.0'
  # Display performance information such as SQL time and flame graphs for each request in your browser.
  # Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md
  gem 'rack-mini-profiler', '~> 2.0'
  gem 'listen', '~> 3.3'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'

end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

参考:

https://stackoverflow.com/questions/26217184/rspec-3-1-undefined-method-feature-for-mainobject

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

RSpec エラー 解決 undefined method `feature' for RSpec:Module

rspecコマンドを打った時に出たエラー。

結論

capybaraを読み込んでいなかった。

コード

エラーコード

require 'rails_helper'

RSpec.feature "Users", type: :feature do

  describe "Signup page" do
    before do
      visit signup_path
    end

    it "display Signup contents, title properly" do
      expect(page).to have_css('h1', text: 'ユーザー登録')
      expect(page).to have_title 'Signup hoge'
    end
  end

end

解決したコード capybaraを読み込む

  require 'rails_helper'
+ require 'capybara/rspec'

RSpec.feature "Users", type: :feature do

  describe "Signup page" do
    before do
      visit signup_path
    end

    it "display Signup contents, title properly" do
      expect(page).to have_css('h1', text: 'ユーザー登録')
      expect(page).to have_title 'Signup hoge'
    end
  end

end

一応gemfile


#rspecのprogressbarを表示してくれる。実行のコマンド% bin/rspec spec/ --format Fuubar
gem 'fuubar'

group :test do
 #rspecには、以下の3つのgemが必要。
  gem 'rspec-rails'
  gem 'factory_bot_rails'
  gem 'rails-controller-testing'
  #rspecのfeatureで必要。
  gem 'capybara', '~> 2.13'
  #Capybaraでテスト中に、現在どのページを開いているのか確認するため
  gem 'launchy'
  #便利。validationが一行くらいでかける。
  gem 'shoulda-matchers',
    git: 'https://github.com/thoughtbot/shoulda-matchers.git',
    branch: 'rails-5'
end

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'spring-commands-rspec'
end

group :development do
  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
  gem 'web-console', '>= 4.1.0'
  # Display performance information such as SQL time and flame graphs for each request in your browser.
  # Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md
  gem 'rack-mini-profiler', '~> 2.0'
  gem 'listen', '~> 3.3'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'

end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

参考:

https://stackoverflow.com/questions/26217184/rspec-3-1-undefined-method-feature-for-mainobject

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

http://localhost:ポート番号を変更する方法

hello rails 画面を好きなポート番号で接続する

スクリーンショット 2021-01-12 15.40.42.png

手順

ポート番号3000で接続してみる

docker-compose.yml
version: "3"

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    tty: true
    stdin_open: true
    command: bash -c "bundle exec rails s -p 3000 -b '0.0.0.0'"
    depends_on:
      - db

  db:
    image: mysql:8.0.16
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      MYSQL_HOST: 127.0.0.1
      MYSQL_DATABASE: app_development
      MYSQL_USERNAME: root
      MYSQL_ROOT_PASSWORD: root
    security_opt:
      - seccomp:unconfined
    ports:
      - "3306:3306"
Dockerfile
FROM ruby:2.6.5-stretch
ENV LANG C.UTF-8

ENV APP_ROOT /app
WORKDIR $APP_ROOT

RUN apt-get update && apt-get install -y --no-install-recommends \
    curl apt-transport-https wget && \
    curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
    echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
    curl -sL https://deb.nodesource.com/setup_8.x | bash - && \
    apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    vim \
    mysql-client \
    yarn \
    nodejs && \
    gem install bundler && \
    rm -rf /var/lib/apt/lists/*


COPY Gemfile $APP_ROOT
COPY Gemfile.lock $APP_ROOT
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
RUN bundle install

COPY . $APP_ROOT

EXPOSE 3000

起動する
docker-compose up --build
コンテナに入る
docker-compose exec app bash
Gemfileの更新
bundle install
新規プロジェクト作成(sqliteでなく、mysqlを選択)
rails new . -d mysql
コンテナを落とす
docker-compose down
database.ymlにdocker-composeのdbコンテナのpasswordを設定
docker-compose up

=> railsが3000ポートが起動する

ポート番号3001で接続する

docker-compose.yml
version: "3"

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3001:3001"
    tty: true
    stdin_open: true
    command: bash -c "bundle exec rails s -p 3001 -b '0.0.0.0'"
    depends_on:
      - db

  db:
    image: mysql:8.0.16
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      MYSQL_HOST: 127.0.0.1
      MYSQL_DATABASE: app_development
      MYSQL_USERNAME: root
      MYSQL_ROOT_PASSWORD: root
    security_opt:
      - seccomp:unconfined
    ports:
      - "3306:3306"
Dockerfile
FROM ruby:2.6.5-stretch
ENV LANG C.UTF-8

ENV APP_ROOT /app
WORKDIR $APP_ROOT

RUN apt-get update && apt-get install -y --no-install-recommends \
    curl apt-transport-https wget && \
    curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
    echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
    curl -sL https://deb.nodesource.com/setup_8.x | bash - && \
    apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    vim \
    mysql-client \
    yarn \
    nodejs && \
    gem install bundler && \
    rm -rf /var/lib/apt/lists/*


COPY Gemfile $APP_ROOT
COPY Gemfile.lock $APP_ROOT
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
RUN bundle install

COPY . $APP_ROOT

EXPOSE 3001

手順

docker-compose.yml

  • rails起動コンテナのapp内、portsを3001:3001を指定。
  • 同じく、commandで解放済みの3001ポートをリッスンするよう指定。

Dockerfile

  • EXPOSEにて、3001ポートを解放するよう指定

http://localhost:3001
でrailsが起動する。

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

Railsで配列をActive Record Relationに変換したい

変換できるのか

誤解を生む可能性がある為はじめに書いておきます。
「配列をActive Record Relationに変換したい」という理由でこの記事を見ていただいている方がいると思いますが、
厳密には配列をActive Record Relationに変換することは出来ません。
ただし、取得した配列を使って、Active Record Relationを取得することは出来ます。

私自身「配列 Active Record Relation 変換」で検索したりしたので、あえてこのようなタイトルにしています。

配列とActive Record Relationについて

はじめに簡単に説明をしますが、分かる方はここまで飛ばしてください。
配列とActive Record Relation、それぞれの例を上げてみます。

#Active Record Relationを作ります。

AdminUser.where(id: [2, 3])
=> [#<AdminUser:0x00007fd2621de8e0
  id: 2,
  name: "山田">,
 #<AdminUser:0x00007fd2621de4f8
  id: 3,
  name: "佐藤">]
#Arrayを作ります。
AdminUser.where(id: [2, 3]).select(&:name)
=> [#<AdminUser:0x00007fd26231ad80
  id: 2,
  name: "山田">,
 #<AdminUser:0x00007fd26231aa88
  id: 3,
  name: "佐藤">]

結果は同じように見えますが、.classをつけてみると違いが分かります。

AdminUser.where(id: [2, 3]).class
=> AdminUser::ActiveRecord_Relation
AdminUser.where(id: [2, 3]).select(&:name).class
=> Array

モデルからデータを取り出す際に、wherefindselectmapなど色々なメソッドを使うことがあると思いますが、Railsでは、使うメソッドによって返り値の型が決まっています。
上記の例の使い方で言うと、whereだとActive Record Relationが返り、selectだとArrayクラスが返ることになります。
※厳密にはselectは使い方によってはActive Record Relationが返ります。

例えばこんな時に困る

Viewに何らかのデータを表示したい。
whereを使ってActive Record Relationにしたはいいが、
そのあとselectでデータの絞り込みなどを行った。
Viewに表示する前に並び替えをしたいので、orderメソッドを使ったら、エラーが発生した。

Railsで開発をする際によく使うorderと言うメソッド。並び替えが出来ます。

#idが大きい順に並び替える
AdminUser.where(id: [2, 3]).order(id: :desc)
=> [#<AdminUser:0x00007fd260347a30
  id: 3,
  name: "佐藤",
  #以下省略

このorderメソッドをArrayクラスに対して使うとこのようなエラーになります。

AdminUser.where(id: [2, 3]).select(&:name).order(id: :desc)
=> NoMethodError: undefined method `order' for #<Array:0x00007fd26027d578>

このような場合に、AdminUser.where(id: [2, 3]).select(&:name)をActive Record Relationに変換したい、という欲望が生まれます。

結論

ここからがタイトルのような欲望を叶える具体的な方法です。
admin_usersというArrayクラスから@admin_usersというActive Record Relationを作りたいなら、こうします。

admin_users = AdminUser.where(id: [2, 3]).select(&:name)
@admin_users = AdminUser.where(id: admin_users.map(&:id))

要はwhere(id: admin_users.map(&:id))をしているだけですが、
何をしているかというと、既にある配列のidだけを取り出して配列を作って、それをwhereの引数に使ってAdminUserから再度データを取得しています。

注意点として、下記があるでしょうか。

  • データの中身の順番が変わる可能性があること
  • データを再取得する処理を行っていること(大量のデータの場合は余計な時間がかかる)

実装方法

この処理を使って実装するにあたって、同じ処理をたくさんするのであれば、スッキリとまとめてしまいたいです。
2つの方法を紹介します。

1. コントローラーやモデルに再取得メソッドを作る
2. Arrayクラスを拡張して再取得メソッドを作る

1.コントローラーやモデルに再取得メソッドを作る

コントローラーやモデルに下記のように書きます。

  def self.get_activerecord_relation(arr)
    where(id: arr.map(&:id))
  end

使い方

admin_users = AdminUser.where(id: [2, 3]).select(&:name) => #Arrayクラス
@admin_users = AdminUser.get_activerecord_relation(admin_users) => #Active Record Relation

ちょっと気持ち悪いかなと思います。

2.Arrayクラスを拡張して再取得メソッドを作る

array.rbというファイルを作ります。

lib/core_ext/array.rb
class Array
  def to_activerecord_relation
    return ApplicationRecord.none if self.empty?

    clazzes = self.map(&:class).uniq
    raise 'Array cannot be converted to ActiveRecord::Relation since it does not have same elements' if clazzes.size > 1

    clazz = clazzes.first
    raise 'Element class is not ApplicationRecord and as such cannot be converted' unless clazz.ancestors.include? ApplicationRecord

    clazz.where(id: self.map(&:id))
  end
end

core_ext.rbというファイルを作ります。

config/initializers/core_ext.rb
require 'core_ext/array'

使い方

admin_users = AdminUser.where(id: [2, 3]).select(&:name) => #Arrayクラス
@admin_users = admin_users.to_activerecord_relation => #Active Record Relation

2の方法は、Rubyのオープンクラスという後からメソッドを追加したり出来る機能を使った方法で、
Arrayクラスに全体に対して、to_activerecord_relationというメソッドが使えるようにしてしまおう、みたいな感じです。

オープンクラスに関しては今回紹介したもの以外には例えば、
Stringクラスにto_boolというメソッドを追加して、"true""false"という文字列が代入されたオブジェクトをboolean型に変換出来るようにする、なども出来ます。

参考にさせていただいた記事

https://qiita.com/shibadai/items/ddbc76a8b980cd8354bc
https://stackoverflow.com/questions/17331862/converting-an-array-of-objects-to-activerecordrelation

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

scopeを使ったリファクタリング

リファクタリングって何がベストなのか? どこから行っていこうか?
初学者からすると難しい。

まだ冗長な箇所や気付けていない箇所もありますが、今回scopeを使ったので残しておこうと思います。

数年後の自分が見たら、「もっとよくできる箇所はあるだろ..」と言われそうですが。(いや..言える自分になりたい ^^;)
そのために、コツコツと日々学んでいきます。

リファクタリング前:

【画像のメタデータから緯度経度を取得して10進数に変換しているコード】

img = Magick::ImageList.new(Rails.root.to_s + "/public#{@post.image.url}")

# 緯度取得
 exif_lat = img.get_exif_by_entry('GPSLatitude')[0][1].split(',').map(&:strip)
# 10進数に変換
 @latitude = (Rational(exif_lat[0]) + Rational(exif_lat[1])/60 + Rational(exif_lat[2])/3600).to_f

# 経度取得
 exif_lng = img.get_exif_by_entry('GPSLongitude')[0][1].split(',').map(&:strip)
# 10進数に変換
 @longitude = (Rational(exif_lng[0]) + Rational(exif_lng[1])/60 + Rational(exif_lng[2])/3600).to_f

...(恥)..めちゃくちゃ長ったらしくて読みたく無い程に見難い
showアクションと、confirmアクションに全く同じ記述があるので、更に格好悪い..

どう切り分けるのか考える

・コントローラの役割とモデルの役割を意識
・10進数に変換する記述は供用する
・緯度経度の取得もscopeして、コントローラ側を短くする

こんな感じでやってみる

10進数への変換を共通化させる

exif_latexif_lngで分けてある箇所は引数で与えてやればいいので、exifとする
スコープの名前はget_exif_gps

app/controller/posts_controller:
  @latitude = Post.get_exif_gps(exif_lat)
  @longitude =  Post.get_exif_gps(exif_lng)
Postモデルに記述:
  scope :get_exif_gps, -> (exif){ (Rational(exif[0]) + Rational(exif[1])/60 + Rational(exif[2])/3600).to_f }

緯度の取得もscopeさせる

ローカル変数imgを引数で渡す
スコープの名前はget_exif_latitude

app/controller/posts_controller:
  exif_lat = Post.get_exif_latitude(img)
Postモデルに記述:
  scope :get_exif_latitude, -> (img){ img.get_exif_by_entry('GPSLatitude')[0][1].split(',').map(&:strip) }

※経度の取得も同様に行う

スコープの名前はget_exif_longitude

app/controller/posts_controller:
  exif_lng = Post.get_exif_longitude(img)
Postモデルに記述:
  scope :get_exif_longitude, -> (img){ img.get_exif_by_entry('GPSLongitude')[0][1].split(',').map(&:strip) }

リファクタリング後

app/controller/posts_controller:

  img = Magick::ImageList.new(Rails.root.to_s + "/public#{@post.image.url}")
  exif_lat = Post.get_exif_latitude(img)
  @latitude = Post.get_exif_gps(exif_lat)

  exif_lng = Post.get_exif_longitude(img)
  @longitude =  Post.get_exif_gps(exif_lng)

Postモデル:
  # 緯度の取得
  scope :get_exif_latitude, -> (img){ img.get_exif_by_entry('GPSLatitude')[0][1].split(',').map(&:strip) }

  # 経度の取得
  scope :get_exif_longitude, -> (img){ img.get_exif_by_entry('GPSLongitude')[0][1].split(',').map(&:strip) }

  # 10進数に変換
  scope :get_exif_gps, -> (exif){ (Rational(exif[0]) + Rational(exif[1])/60 + Rational(exif[2])/3600).to_f }

コントローラを見た時に多少は目で追いやすくなったかな?..

メソッドに切り出したりしたら、もう少し見やすくできそうな気もしますが、今回はここまでをscopeの備忘録がてら残しておきます。
今後も更に勉強して吸収していきたいと思います!!

参考

https://pikawaka.com/rails/scope

最後に

もっとこうすれば良い!というアドバイスございましたら、是非ご指摘ください! ^^

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

TailwindUIのFormの見た目がサンプル通りにならない

困ったこと

同じスタイルクラスを当ててるはずなのに、見た目が違う。

理想の見た目

image.png

実際の見た目

image.png

対応内容

たぶん次のどっちかやればええんちゃうか

https://github.com/tailwindlabs/tailwindcss-forms
https://tailwindcss-custom-forms.netlify.app/

1個目のほうをインストールしたけど怒られた

terminal
ERROR in ./node_modules/tippy.js/animations/perspective.css (./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src??ref--5-2!./node_modules/tippy.js/animations/perspective.css)
Module build failed (from ./node_modules/postcss-loader/src/index.js):
TypeError: Cannot read property 'none' of undefined
    at /Users/XXXXXXX/projects/XXXXX/node_modules/@tailwindcss/forms/src/index.js:38:26
    at /Users/XXXXXXX/projects/XXXXX/node_modules/tailwindcss/lib/util/processPlugins.js:66:5
    at Array.forEach (<anonymous>)
    at _default (/Users/XXXXXXX/projects/XXXXX/node_modules/tailwindcss/lib/util/processPlugins.js:60:11)
    at /Users/XXXXXXX/projects/XXXXX/node_modules/tailwindcss/lib/processTailwindFeatures.js:56:54
    at LazyResult.run (/Users/XXXXXXX/projects/XXXXX/node_modules/postcss/lib/lazy-result.js:288:14)
    at LazyResult.asyncTick (/Users/XXXXXXX/projects/XXXXX/node_modules/postcss/lib/lazy-result.js:212:26)
    at LazyResult.asyncTick (/Users/XXXXXXX/projects/XXXXX/node_modules/postcss/lib/lazy-result.js:225:14)
    at /Users/XXXXXXX/projects/XXXXX/node_modules/postcss/lib/lazy-result.js:217:17

ERROR in ./app/javascript/css/tailwind.css (./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src??ref--5-2!./app/javascript/css/tailwind.css)
Module build failed (from ./node_modules/postcss-loader/src/index.js):
TypeError: getProcessedPlugins is not a function
    at /Users/XXXXXXX/projects/XXXXX/node_modules/tailwindcss/lib/processTailwindFeatures.js:67:83
    at LazyResult.run (/Users/XXXXXXX/projects/XXXXX/node_modules/postcss/lib/lazy-result.js:288:14)
    at LazyResult.asyncTick (/Users/XXXXXXX/projects/XXXXX/node_modules/postcss/lib/lazy-result.js:212:26)
    at LazyResult.asyncTick (/Users/XXXXXXX/projects/XXXXX/node_modules/postcss/lib/lazy-result.js:225:14)
    at /Users/XXXXXXX/projects/XXXXX/node_modules/postcss/lib/lazy-result.js:217:17
ℹ 「wdm」: Failed to compile.

Tailwind CSS v2.0. 用だからかな??

Tailwind CSS v2.0インストール

下記を元に2.0を入れる
https://tailwindcss.com/docs/installation

Terminal
npm install tailwindcss@latest postcss@latest autoprefixer@latest

下記怒られる。

Terminal
> % bin/webpack-dev-server
ℹ 「wds」: Project is running at http://localhost:3035/
ℹ 「wds」: webpack output is served from /packs/
ℹ 「wds」: Content not from webpack is served from /Users/XXX/projects/XXX/public/packs
ℹ 「wds」: 404s will fallback to /index.html
ℹ 「wdm」: wait until bundle finished: /packs/js/application-83db7df6641884880765.js
✖ 「wdm」: Hash: f6dfc3f28099b484dcf6
Version: webpack 4.44.2
Time: 3149ms
Built at: 2021-01-13 7:49:08
                                     Asset       Size       Chunks                         Chunk Names
    js/application-83db7df6641884880765.js   1.88 MiB  application  [emitted] [immutable]  application
js/application-83db7df6641884880765.js.map   1.92 MiB  application  [emitted] [dev]        application
                             manifest.json  364 bytes               [emitted]              

ERROR in ./app/javascript/css/tailwind.css (./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src??ref--5-2!./app/javascript/css/tailwind.css)
Module build failed (from ./node_modules/postcss-loader/src/index.js):
Error: PostCSS plugin tailwindcss requires PostCSS 8. Update PostCSS or downgrade this plugin.
    at Processor.normalize (/Users/XXX/projects/XXX/node_modules/postcss-loader/node_modules/postcss/lib/processor.js:153:15)
    at new Processor (/Users/XXX/projects/XXX/node_modules/postcss-loader/node_modules/postcss/lib/processor.js:56:25)
    at postcss (/Users/XXX/projects/XXX/node_modules/postcss-loader/node_modules/postcss/lib/postcss.js:55:10)
    at /Users/XXX/projects/XXX/node_modules/postcss-loader/src/index.js:140:12

ERROR in ./node_modules/tippy.js/animations/perspective.css (./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src??ref--5-2!./node_modules/tippy.js/animations/perspective.css)
Module build failed (from ./node_modules/postcss-loader/src/index.js):
Error: PostCSS plugin tailwindcss requires PostCSS 8. Update PostCSS or downgrade this plugin.
    at Processor.normalize (/Users/XXX/projects/XXX/node_modules/postcss-loader/node_modules/postcss/lib/processor.js:153:15)
    at new Processor (/Users/XXX/projects/XXX/node_modules/postcss-loader/node_modules/postcss/lib/processor.js:56:25)
    at postcss (/Users/XXX/projects/XXX/node_modules/postcss-loader/node_modules/postcss/lib/postcss.js:55:10)
    at /Users/XXX/projects/XXX/node_modules/postcss-loader/src/index.js:140:12

インストール方法にも書いてるが、こちらを参考にする。

Terminal
npm uninstall tailwindcss postcss autoprefixer
npm install tailwindcss@npm:@tailwindcss/postcss7-compat @tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  }
}
terminal
-> % npx tailwindcss init

   @tailwindcss/postcss7-compat 2.0.2

   ? tailwind.config.js already exists.
tailwind.config.js
module.exports = {
  purge: [],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {},
  plugins: [],
}

既に下記だった

app/javascript/css/tailwind.css
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";

一行目追加

app/javascript/src/js/application.js
import 'tailwindcss/tailwind.css' 
import './dropdown.js'

もう一度、tailwindcss-formsインストールする

今見直したら、2つとも叩いたが、片方でいいと思う。

Terminal
-> % npm install @tailwindcss/forms
+ @tailwindcss/forms@0.2.1
added 2 packages from 1 contributor, removed 7 packages and audited 1824 packages in 8.861s
found 0 vulnerabilities

-> % yarn add @tailwindcss/forms
yarn add v1.22.10
warning package-lock.json found. Your project contains lock files generated by tools other than Yarn. It is advised not to mix package managers in order to avoid resolution inconsistencies caused by unsynchronized lock files. To clear this warning, remove package-lock.json.
[1/4] ?  Resolving packages...
[2/4] ?  Fetching packages...
[3/4] ?  Linking dependencies...
warning " > webpack-dev-server@3.11.1" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
warning "webpack-dev-server > webpack-dev-middleware@3.7.2" has unmet peer dependency "webpack@^4.0.0".
[4/4] ?  Building fresh packages...
success Saved lockfile.
success Saved 4 new dependencies.
info Direct dependencies
├─ @tailwindcss/forms@0.2.1
├─ @tailwindcss/postcss7-compat@2.0.2
└─ tailwindcss@2.0.2
info All dependencies
├─ @tailwindcss/forms@0.2.1
├─ @tailwindcss/postcss7-compat@2.0.2
├─ mini-svg-data-uri@1.2.3
└─ tailwindcss@2.0.2
✨  Done in 19.76s.
tailwind.config.js
module.exports = {
  purge: [],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {},
  plugins: [
    require('@tailwindcss/forms'),
  ],
}

結果

いい感じになった。

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

rails _6.0.3_ new hello_myappコマンドでwarningのメッセージが出た

rails 6.0.3 new hello_myappコマンドでwarningのメッセージが出ました。

そのメッセージは以下のようなもの

warning " > webpack-dev-server@3.10.3" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".

解決策
hello_myappのフォルダの階層で以下のコマンドを実行したことで解決した。

yarn upgrade webpack@^4.0.0

このサイトを参考にさせてもらいました。

https://stackoverflow.com/questions/61565543/rails-webpack-dev-server-has-unmet-peer-dependency

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

【Rails】RSpec キホンのキ

はじめに

RSpecはRailsで定番のテストフレームワーク。
minitestと比べると必要なGemが多い。
とはいえ一度に全てのGemを入れると、どのGemがどこでどういう働きをするのかが曖昧になってしまう。
そこで、RSpecの学習に至っては、テストの内容に応じてGemを逐次追加していく手法を取ることにした。

RSpecとFactoryBot

Gemの導入と設定

RSpecと、テスト用データの生成用となるFactoryBotを導入する。
FactoryBotはminitestにおけるfixture的な役割を担う。
ここでspring-commands-rspecを入れておくと、RSpecをbinstubから実行し、springを使ってテスト開始にかかる時間を短縮できるとのこと。

Gemfile.rb
group :development, :test do
  gem "rspec-rails"
  gem 'spring-commands-rspec'
  gem "factory_bot_rails"
end

bundle installしたら以下のコマンドでRSpec用のデータを生成する。

$ bundle exec rails generate rspec:install

RSpecの設定を追加する。
設定はconfig/application.rbに書く。
ここではgenerateコマンド実行時にRSpec用のファイルを生成するかを設定している。
truefalseかはプロジェクトによって適宜変更する。

config/application.rb
module SampleApp
  class Application < Rails::Application
    .
    .
    .
    config.generators do |g|
      g.test_framework :rspec,
        controller_specs: true,
        fixtures: true,
        helper_specs: true,
        model_specs: true,
        request_spec: true,
        routing_specs: false,
        view_specs: false
    end
  end
end

次にFactoryBotの設定。
以下のように記述することで、FactoryBotクラスの呼び出しを簡略化できる。

spec/rails_helper.rb
RSpec.configure do |config|
  config.include FactoryBot::Syntax::Methods
end
# FactoryBotクラスの呼び出し
user = FactoryBot.create(:user)

#省略版 
user = create(:user)

テスト用データの設定をもう一つ。
以下の一行のコメントアウトを外しておく。
spec/support/配下のファイルを読み込めるようになる。
ここにはテスト用のヘルパーを記述するファイルなどが置かれる。

spec/rails_helper.rb
# The following line is provided for convenience purposes. It has the downside
# of increasing the boot-up time by auto-requiring all files in the support
# directory. Alternatively, in the individual `*_spec.rb` files, manually
# require only the support files necessary.
#
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

最後に、RSpecのbinstubを導入する。
これで$ bundle exec rspec spec/に加えて$ bin/rspec spec/でテストを実行できるようになる。

$ bundle exec spring binstub rspec

# 実行コマンド
$ bin/rspec spec/

specファイルの生成

RSpecで使用されるファイルは全て*_spec.rbというファイル名になっている。
これをコントローラやモデルに合わせて各種用意していく。
例えばusers_controller用のspecファイルは以下のように生成する。

$ rails g rspec:controller users
    create  spec/controllers/users_controller_spec.rb

テスト用データの用意

FactoryBotを使って、各種クラスのテスト用データ(インスタンス)を定義する。
例えばUserクラスなら、/spec/factories/users.rbに定義していく。

/spec/factories/users.rb
FactoryBot.define do

  # Userモデルのテストデータmichaelを定義
  factory :michael, class: User do
    name 'michael'
    email 'michael@example.com'
  end

  # 汎用データをたくさん用意する
  factory :user do
    sequence(:name) { |n| "name-#{n}"}
    sequence(:email) { |n| "test-#{n}@example.com"}
  end
end

定義したテストデータを呼び出してインスタンス変数に格納し、テストで使用する。

/spec/models/users_spec.rb
RSpec.describe User, type: :model do
  before do
    # モデルのみの作成
    @michael = build(:michael)
    # DBへレコード生成
    @michael = create(:michael)

    # 汎用データの活用
    @user1 = build(:user)
    @user2 = build(:user)
    @user3 = build(:user)
  end
  .
  .
  .
end

続く!!!

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