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

【Rails6】cocoon_動的にフォームを追加

◆開発環境

  • Rails 6.0.3.4
  • yarn 1.22.4

◆目次
1)cocoon導入方法
2)動的にフォームを追加
  前置き
  1. モデル作成
  2. コントローラー(アクション)
  3. コントローラー(ストロングパラメーター)
  4. ビュー(通常のform_with)
  5. ビュー(動的に追加したい部分)
3)追加するフォームにidやdata属性を付与する

★GIF画像(こんな感じになります)
Image from Gyazo

2)動的にフォームを追加

 1. モデル作成

(親)model/recipe.rb
class Recipe < ApplicationRecord
  # cocoonで子フォームを親と一緒に保存するための記述。accepts_nested_attributes_forというメソッドを使っています。
  has_many      :ingredients, dependent: :destroy
  accepts_nested_attributes_for :ingredients, allow_destroy: true
end
(子)model/ingredient.rb
class Recipe < ApplicationRecord
  belongs_to :recipe
end

 2. コントローラー(アクション)

(親)controller/recipe_controller.rb
  def new
    @recipe = Recipe.new
    @ingredient = @recipe.ingredients.build
    # ↑この記述が大事
    # paramsでこのように運ばれるため(カラム等だいぶ省略してます)
    # recipe{"title"=>"ポテトサラダ","ingredients_attributes"=> {"thing_name"=>"1", "amount"=>"1" }}
  end

  def create
    @recipe = Recipe.new(recipe_params) 
    if @recipe.save
       # @recipe.saveでrecipeもingredientも同時に保存しています
      redirect_to my_recipe_user_path(id: current_user)
    else
      render :new
    end
  end

 3. コントローラー(ストロングパラメーター)

**書き方とっても大事です!ここでだいぶ詰まりました...

(親)controller/recipe_controller.rb
 private
  def recipe_params
    params.require(:recipe).permit(
      {images: []},:title,:feature,:eat,:category_id,:cold_date,:frozen_date,:time,:text,
      ingredients_attributes:[:id, :recipe_id, :thing_id, :amount, :_destroy])
      .merge(user_id: current_user.id)
  end

一番大事な記述↓↓↓ 

ingredients_attributes:[:id, :recipe_id, :thing_id, :amount, :_destroy]
  • ingredients・・・複数形、単数形はmodelと合わせてください。(多分複数形になると思いますが)
  • attributes・・・ここはmodel等は関係なく、複数形でOKです。
  • :id・・・理由はわかりませんが、:idの記述も必要なようです。
  • :_destroy・・・cocoonの削除ボタンと関係あるのかなと思ってます。(詳しい方ぜひ教えてください)

 4. ビュー(通常のform_with)

GIF動画のcocoonで作成している材料の箇所以外省略しました。

view/recipe/new.html.erb
<%= form_with(model: @recipe, class:"recipes-post-main", local: true) do |f| %>
  <!-- 省略 -->
  <div class="parent">
    <div class="form-box">
      <div class="forms-item">
        材料
      </div>
      <!-- 1)追加ボタンの記述 -->
      <div class="add-botton-box">
        <%= link_to_add_association "追加ボタン", f, :ingredients,
        class: 'add_fields',
        id: 'add-btn',
        data: {
          association_insertion_node: '#detail-association-insertion-point',
          association_insertion_method: 'after'
          }
        %>
      </div>
    </div>

    <!-- 動的に追加するフォームを挿入する箇所(なくてもいい*詳細は1)で説明します) -->
    <div id="detail-association-insertion-point"></div>

    <!-- 2)動的に追加するフォーム(renderで飛ばしている先が追加される) -->
    <%= f.fields_for :ingredients do |form| %>
      <%= render "ingredient_fields", f: form %>
    <% end %>
  </div>
  <!-- 省略 -->
<% end %>

順を追って説明

 1) 追加ボタンの記述

<!-- これだけあれば動く。あとはオプション -->
<%= link_to_add_association "追加ボタン", f, :ingredients%>

 **補足 ここの「f」はform_withのブロック変数 
 **補足2 必須箇所以外については公式サイトのREADMEに詳しく書いてあるので、簡単に説明。

  • association_insertion_node: 追加フォームを挿入する位置を指定できる。上記の例の場合は<div id="detail-association-insertion-point"></div>の位置に挿入される。
  • association_insertion_method: 追加するフォームをどこに増やすか。上なのか下なのか...(before, after, append, prepend)Default: before。以外と難しいので色々試してみてください。

 2) 動的に追加するフォーム(renderで飛ばしている先が追加される)

追加フォームの部分
<%= f.fields_for :ingredients do |form| %>
  <%= render "ingredient_fields", f: form %>
<% end %>
  • :ingredients 複数形・単数形はmodelの記述に合わせる
  • ingredient_fields ファイル名:「_ingredient_fields.html.erb」保管場所は親のディレクトリ。今回の場合はrecipe

5. ビュー(動的に追加したい部分)

_ingredient_fields.html.erb
<!-- class="nested-fields" ←ここ絶対に必要です -->
<div class="nested-fields">
  <table>
    <tr>
      <td class="td-fixed-select">

        <!-- 選択ボックス(プルダウン)の箇所 -->
        <% thing_options = Thing.order(:id).map { |c| [c.thing_name, c.id, data: { unit: c.unit, spoon:c.spoon }] } %>
        <%= f.select(:thing_id, thing_options, { prompt: "---"}, {class:"select", data:{select:0}, id:"thing-select" }) %>

      </td>
      <td class="td-fixed-spoon">
        <span class="box-spoon" id='spoon-auto' data-spoon='0'></span>
      </td>
      <td class="td-fixed-amount">
        <%= f.number_field :amount, class:"ingredients-amount",id:"ingredient-amount", min:"0",step:"0.1"%>
      </td>
      <td class="td-fixed-unit">
        <span class="box-unit" id='unit-auto' data-unit='0'></span>
      </td>
      <td class="td-fixed-delete">

        <!-- 削除ボタン -->
        <%= link_to_remove_association "削除", f %>

      </td>
    </tr>
  </table>
</div>

**補足 今回は動的に追加するフォームをtableで書いていますが、自由に書いて大丈夫です。

  • _ingredient_fields.html.erb ファイル名のルールを守る。「ingredient」は好きな名前にしてください。
  • class="nested-fields" 追加したいフォーム内容を囲む。
  • link_to_remove_association "削除", f 削除ボタンの記述を忘れない。

以上です。
抜け漏れなどご指摘あればコメントいただけると嬉しいです!

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

【Rails6】cocoon_動的にフォームを追加する

◆開発環境

  • Rails 6.0.3.4
  • Ruby 2.6.5
  • MySQL 5.6.47

◆目次
1)cocoon導入方法
2)動的にフォームを追加 ←
  前置き
  1. モデル作成
  2. コントローラー(アクション)
  3. コントローラー(ストロングパラメーター)
  4. ビュー(通常のform_with)
  5. ビュー(動的に追加したい部分)
3)追加するフォームにidやdata属性を付与する

★GIF画像(こんな感じになります)
*注意* 「にんじん」を選択するとピンクのボックスの後ろに「本」出力されるのはcocoonとは全く関係ないので悪しからず。cocoonは追加ボタンをクリックするとフォームの一部が追加されていくgemです。
Image from Gyazo

**【親】recipe 【子】ingredient (材料の増えていってるところ)

2)動的にフォームを追加

 1. モデル作成

(親)model/recipe.rb
class Recipe < ApplicationRecord
  # cocoonで子フォームを親と一緒に保存するための記述。accepts_nested_attributes_forというメソッドを使っています。
  has_many      :ingredients, dependent: :destroy
  accepts_nested_attributes_for :ingredients, allow_destroy: true
end
(子)model/ingredient.rb
class Recipe < ApplicationRecord
  belongs_to :recipe
end

 2. コントローラー(アクション)

(親)controller/recipe_controller.rb
  def new
    @recipe = Recipe.new
    @ingredient = @recipe.ingredients.build
    # ↑この記述が大事
    # paramsでこのように運ばれるため(カラム等だいぶ省略してます)
    # recipe{"title"=>"ポテトサラダ","ingredients_attributes"=> {"thing_name"=>"1", "amount"=>"1" }}
  end

  def create
    @recipe = Recipe.new(recipe_params) 
    if @recipe.save
       # @recipe.saveでrecipeもingredientも同時に保存しています
      redirect_to my_recipe_user_path(id: current_user)
    else
      render :new
    end
  end

 3. コントローラー(ストロングパラメーター)

**書き方とっても大事です!ここでだいぶ詰まりました...

(親)controller/recipe_controller.rb
 private
  def recipe_params
    params.require(:recipe).permit(
      {images: []},:title,:feature,:eat,:category_id,:cold_date,:frozen_date,:time,:text,
      ingredients_attributes:[:id, :recipe_id, :thing_id, :amount, :_destroy])
      .merge(user_id: current_user.id)
  end

一番大事な記述↓↓↓ 

ingredients_attributes:[:id, :recipe_id, :thing_id, :amount, :_destroy]
  • ingredients・・・複数形、単数形はmodelと合わせてください。(多分複数形になると思いますが)
  • attributes・・・ここはmodel等は関係なく、複数形でOKです。
  • :id・・・理由はわかりませんが、:idの記述も必要なようです。
  • :_destroy・・・cocoonの削除ボタンと関係あるのかなと思ってます。(詳しい方ぜひ教えてください)

 4. ビュー(通常のform_with)

GIF動画のcocoonで作成している材料の箇所以外省略しました。

view/recipe/new.html.erb
<%= form_with(model: @recipe, class:"recipes-post-main", local: true) do |f| %>
  <!-- 省略 -->
  <div class="parent">
    <div class="form-box">
      <div class="forms-item">
        材料
      </div>
      <!-- 1)追加ボタンの記述 -->
      <div class="add-botton-box">
        <%= link_to_add_association "追加ボタン", f, :ingredients,
        class: 'add_fields',
        id: 'add-btn',
        data: {
          association_insertion_node: '#detail-association-insertion-point',
          association_insertion_method: 'after'
          }
        %>
      </div>
    </div>

    <!-- 動的に追加するフォームを挿入する箇所(なくてもいい*詳細は1)で説明します) -->
    <div id="detail-association-insertion-point"></div>

    <!-- 2)動的に追加するフォーム(renderで飛ばしている先が追加される) -->
    <%= f.fields_for :ingredients do |form| %>
      <%= render "ingredient_fields", f: form %>
    <% end %>
  </div>
  <!-- 省略 -->
<% end %>

順を追って説明

 1) 追加ボタンの記述

<!-- これだけあれば動く。あとはオプション -->
<%= link_to_add_association "追加ボタン", f, :ingredients%>

 **補足: ここの「f」はform_withのブロック変数 
 **補足2: 必須箇所以外については公式サイトのREADMEに詳しく書いてあるので、簡単に説明。

  • association_insertion_node: 追加フォームを挿入する位置を指定できる。上記の例の場合は<div id="detail-association-insertion-point"></div>の位置に挿入される。
  • association_insertion_method: 追加するフォームをどこに増やすか。上なのか下なのか...(before, after, append, prepend)Default: before。以外と難しいので色々試してみてください。

 2) 動的に追加するフォーム(renderで飛ばしている先が追加される)

追加フォームの部分
<%= f.fields_for :ingredients do |form| %>
  <%= render "ingredient_fields", f: form %>
<% end %>
  • :ingredients 複数形・単数形はmodelの記述に合わせる
  • ingredient_fields ファイル名:「_ingredient_fields.html.erb」保管場所は親のディレクトリ。今回の場合はrecipe

5. ビュー(動的に追加したい部分)

_ingredient_fields.html.erb
<!-- class="nested-fields" ←ここ絶対に必要です -->
<div class="nested-fields">
  <table>
    <tr>
      <td class="td-fixed-select">

        <!-- 選択ボックス(プルダウン)の箇所 -->
        <% thing_options = Thing.order(:id).map { |c| [c.thing_name, c.id, data: { unit: c.unit, spoon:c.spoon }] } %>
        <%= f.select(:thing_id, thing_options, { prompt: "---"}, {class:"select", data:{select:0}, id:"thing-select" }) %>

      </td>
      <td class="td-fixed-spoon">
        <span class="box-spoon" id='spoon-auto' data-spoon='0'></span>
      </td>
      <td class="td-fixed-amount">
        <%= f.number_field :amount, class:"ingredients-amount",id:"ingredient-amount", min:"0",step:"0.1"%>
      </td>
      <td class="td-fixed-unit">
        <span class="box-unit" id='unit-auto' data-unit='0'></span>
      </td>
      <td class="td-fixed-delete">

        <!-- 削除ボタン -->
        <%= link_to_remove_association "削除", f %>

      </td>
    </tr>
  </table>
</div>

**補足: 今回は動的に追加するフォームをtableで書いていますが、自由に書いて大丈夫です。

  • _ingredient_fields.html.erb ファイル名のルールを守る。「ingredient」は好きな名前にしてください。
  • class="nested-fields" 追加したいフォーム内容を囲む。
  • link_to_remove_association "削除", f 削除ボタンの記述を忘れない。

参考文献

おすすめ順
Qiita_【Rails】cocoonを用いて親子孫関係のテーブルに複数のデータを同時保存する方法
Qiita_【Rails】cocoon 親子孫の複数テーブル&複数データを同時保存
Qiita_Rails6でのcocoon
ブログ_Rails 6 で Cocoon を使ってネストしたフォームを作る方法

以上です。
抜け漏れなどご指摘あればコメントいただけると嬉しいです!

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

非同期でいいね機能が切り替わらない時の解決方法

状態

同期状態でいいね機能を実装

そこから、非同期化

エラーもちらほらでましたが、エラーが出ないように修正。

エラーがなくなった、でも非同期でいいね機能が動かない。

って人は見てください。

同じようなパターンかもしれないです。

いいね機能を実装している方はたくさんいて
記事もたくさんありますので、自分と同じ状況を探してください。

詳しい詳細は下記を見てください。
https://teratail.com/questions/304798

非同期処理の流れをおさらい

Ajaxの流れ
①イベント発生
②非同期でリクエスト送信
③受け取った情報を処理
④処理結果をJSON形式で応答
⑤レスポンスを受けてDOMでページ更新

自分の状態を確認することで原因がどこか見つける作業が早くなる。
イベントを発生しているかの確認だったら、アラートを出すように記述して確かめるみたいな感じですね。

問題箇所

<tr class="idea_<%= @idea.id %>">
 <%= render 'like', idea: @idea %>
</tr>

ここの記述が問題でした。
記事等を参考に書いていたのですが、tableタグを使用していないのにtrタグを使っていたのが原因でした。
タグの性質を理解せずに使っていたのが駄目だったわけですね。

まさかと、思いdivタグに変更したらすんなりできました。

今回得た教訓は、タグの性質を知ること!

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

Rails+WebpackerでBootstrap4を入れる方法

背景

railsではBootstrapをwebpackerを使って入れるとのことだったので、試してみました。

やり方

Gemfileに追加

# webpacker
gem 'webpacker', '~> 4.0'

Dockerを使ってbuildし直す

ターミナルでwebpackerのインストール

$rails webpacker:install

yarnコマンドでjQueryやbootstrapをインストール

$yarn add jquery bootstrap popper.js

config/webpack/environment.jsに以下を追加

const { environment } = require('@rails/webpacker')

module.exports = environment

// ここから
  const webpack = require('webpack')
  environment.plugins.append(
    'Provide',
    new webpack.ProvidePlugin({
      $: 'jquery/src/jquery',
      jQuery: 'jquery/src/jquery',
      Popper: ['popper.js', 'default']
    })
  }
// ここまで

app/javascript/stylesheets/application.scssを新規作成し、追加

@import '~bootstrap/scss/bootstrap';

app/javascript/packs/application.jsに以下を追加

  import 'bootstrap';
  import '../stylesheets/application';

app/views/layouts/application.html.erbに以下を追加

<%= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

あとは、bootstrapのクラスを当ててみて、動作すればOKです。

参考

https://qiita.com/rhistoba/items/f724dae231d7e28bf477

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

スクールに通い始めて

はいどうも!
某プログラミングスクールに通い始めた37歳のおっさんです。
プログラミングは完全に未経験。
そんなおっさんが転職するまでの軌跡(奇跡?)をぼちぼち綴っていこうと思います。

先日、中間試験なるものを受けてみたところなんと、44点という結果・・・
学生時代に受けたテストでもこんな点数は取った記憶がなかった。
問題文を見ても何を求められているかわからないし、そもそもコードを読んでてもわからない部分が多すぎて理解ができない。
あれ?これってもしかして記憶力がだいぶ悪くなってる?
そう、気付き始めてしまったのです。
同期の人々よりも時間をかけて自分なりにわかる説明をしてくれる記述を手繰り寄せてなんとか消化する。
google先輩の力でなんとかやってきました。
自分がスペックとして他人に勝てるのは①「情熱」と②「モチベーション」この二つだと思ってます。
①他人が休んでる時にしっかり学習して
②他人が学習しているときに倍の密度で学習する
①も②も関係ないやないかーい!
というツッコミはおいておいて。

実際勝てる方法はこれしかないと思ってます。
そして、後がない。
同年代は役職に就いて、レベルアップした内容の仕事をして、家も買ってetc
人生というストーリーで見たらきっといい結末が待っているんじゃないかなと思える内容を歩んでいるわけです。
年齢という取り返せない条件で負けている以上、自分に何か付加価値がないと同じスタートラインに立った時にかなり後ろからスタートしなければいけないわけです。
人よりできないぶん、時間をかける
人より遅いぶん、密度を濃くする
この二つで対応していくのが絶対条件なんじゃないかなと思っています。
そりゃあ負けたら悔しいですよ。
だけど悔しがったってしょうがない。
もう結果として出てしまっているんだから受け止めるしかない。
考えなければいけないのは、じゃあこれからどうするか?っていうところだと思います。
負けた→もういいや→向いてなかった!goodbye!
この流れは簡単です。誰でもできる。
ここから反発してどれだけできるかだと思うんですよね。

「諦めたらそこで試合終了ですよ」
有名なバスケットボールの監督が言ってましたね(ちょうど世代)
この言葉を胸に胸に刻んで明日からも頑張ります。

ちなみに
「〜していきます」って希望的欲求感に聞こえませんか?
「〜します」って言い切ると決断なんだなって聞こえますよね。
決断すると自分がその目標に向かって行くんですよ。
○○ザップのコミットメント、と同じですよね。
言ったからにはやらなきゃいけない。
良い意味で自分を追い込んで頑張る。
その背中が他の人に勇気を与えられたらいいなって思います。

三回目に受けた試験でようやく突破したおっさんより
それではでは:wave:

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

[Rails] [ransack] link_toで検索条件を作りclassをつける

link_toにclassをつけるのに苦戦したので備忘録もかねて。

ransackで検索機能を作っており、あるdivをクリックした時にあらかじめ決めておいた検索結果にリンクさせようとlink_toを使い苦戦。

最初に書いていたコード

<%= link_to  controller: "shops", action: "index", q: { name_cont: '東京'}, class: "反映させたいクラス" do %>
 <div>このdivをクリックしたら「東京」の検索結果にとぶ</div>
<% end %>

こちらを参考に書きました。やってることはほぼ同じで、リンクテキストを踏ませるかdivを踏ませるかの違いです。
リンクテキストを指定する代わりにdo endで囲んでブロックにし、controller: ~ 東京 }までがパスで、その後にclassを書いているというイメージです。

エラーは出ずリンクは機能するものの、classが反映されず...

うまくいったコード

<%= link_to nil, controller: "shops", action: "index", q: { name_cont: '東京'}, class: "反映させたいクラス" do %>
 <div>このdivをクリックしたら「東京」の検索結果にとぶ</div>
<% end %>

リンクテキストをnilとして明記してあげたらうまく機能しました。

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

【Rails】 link_toへのpathの渡し方

はじめに

  • link_toで飛ばしたいページを指定する際、つまづいたので備忘録。
  • showページを表示させる時の、:idを指定する書き方。

結論

application.html.erb
<%= link_to '店舗詳細', company_path(:id) %>

こう書いたら表示できました。

エラーになった書き方

<%= link_to '店舗詳細', company_path %>
<%= link_to '店舗詳細', company_path(@company) %>
これではエラーになりました。

ルーティングです。
routes.rb
get 'company/:id', to: 'companies#show'

コントローラーです。
controller.rb
@company = Company.find_by(params[:id])

?Railsガイドには、

get '/patients/:id', to: 'patients#show', as: 'patient'
アプリケーションのコントローラに以下のコードがあるとします。

@patient = Patient.find(params[:id])
上記に対応するビューは以下です。

<%= link_to 'Patient Record', patient_path(@patient) %>
これで、ルーターによって/patients/17というパスが生成されます。これを利用することでビューが改修しやすくなり、コードも読みやすくなります。このルーティングヘルパーではidを指定する必要がない点にご注目ください。

同じようにasでルーティングに名前を指定しようとしたのですが、私の場合は、resourcesを使っていたので、それをするとエラーになってしまいました。
なので、

ルーティングです。
routes.rb
get 'company/:id', to: 'companies#show'

シンプルにこのように書いてあげたら普通に動きました。

感想

ルーティング一つとっても場合によって書き方も変わってくるので難しいと思いました。
Railsガイド様にはこれからもお世話になります。

参考
https://railsguides.jp/routing.html

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

開発環境別にseed ファイルを分けて管理する

はじめに

アプリの初期データ用のseed ファイルを開発環境別に管理する必要があったので備忘録です。

環境

  • macOS 10.15.6
  • Ruby 2.5.7
  • Rails 5.2.3
  • rspec-rails 4.0.1
  • capybara 3.32.2

参考URL

https://pikawaka.com/rails/env
https://prokyou.com/rails/seeds/
https://qiita.com/kimihito_/items/753390a5fe42a41b1d60
https://qiita.com/mogya/items/76430e32ffca1ef93f27

目標

  • 開発環境別でseed ファイルを管理できる
  • seed ファイルの修正後に環境別に初期データを追加/削除ができる

実装

1. seeds ディレクトリを作成

通常のRails アプリでのseed ファイルのディレクトリはdb/seeds.rb です。
これを以下のように修正します。

#修正前
db/seeds.rb
#修正後
db/seeds/development.rb #追加
db/seeds/test.rb #追加
db/seeds/production.rb #追加
db/seeds.rb
  • seeds ディレクトリの中に環境別のseed ファイルを作成
  • 元々あったseeds.rb の中身は現時点ではそのままでok

2. 環境別にseed ファイルを作成

元々あったseeds.rb の中身を元にして各環境別に初期データ用の処理を追加します。
私の場合はCSV を使った初期データをテスト環境でも使いたかったのでtest.rb に下記を追加しました。
development.rb は元々あったseeds.rb を複製しました。

test.rb
#Country CSV import
require "csv"

CSV.foreach("country.csv", headers: true) do |row|
  Country.create!(
    country_name: row["国・地域名"],
    region: row["場所"]
  )
end

3. seeds.rb に読み込み処理を追加

環境別にseed ファイルが準備できたので各ファイルの読み込み処理をseeds.rb に追加します。
初期データ追加用の処理は各環境別に分けているはずなので削除してokです。

seeds.rb
#環境別にseed ファイルを読み込む
load(Rails.root.join("db", "seeds", "#{Rails.env.downcase}.rb"))

#元の初期データ追加の処理は削除

seed ファイルの読み込みディレクトリを指定しています。

seeds.rb
#seeds.rb の読み込み処理の詳細
load(Rails.root.join("親ディレクトリ", "子ディレクトリ", "#{開発環境名.downcase}.rb))

4. 環境別にseed ファイルで初期データを導入

環境別にseed ファイルを読み込んで初期データを導入します。
初期データを削除してseed ファイルを読み込みます。

# development 環境の初期データ削除
$ rails db:migrate:reset

# development 環境の初期データ追加
$ rails db:seed

# test 環境の初期データ削除
$ rails db:migrate:reset RAILS_ENV=test

# test 環境の初期データ追加
$ rails db:seed RAILS_ENV=test

5. コンソールで導入されたデータの確認

環境別にコンソールを立ち上げて初期データの件数を確認します。
-e オプションをつける事で環境別にコンソールを立ち上げます。

#development 環境で初期データ件数の確認
$ rails console
Loading development environment (Rails 5.2.3)
> Country.count
249

#test 環境で初期データ件数の確認
$ rails console -e test
Loading test environment (Rails 5.2.3)
> Country.count
249

学び

  • 環境別にseed ファイルを用意する事で初期データの管理が楽になった
  • テスト用の初期データにseed を使うのは一部だけにして、テストに必要なデータはなるべくfixture を使う方が良さそうです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerでyarnを入れたはずなのに「ERROR: There are no scenarios; must have at least one」と怒られた時の対処法

背景

Dockerでyarnを入れたはずでしたが、いざ以下コマンドを叩いてみると怒られた

Dockerfileの中身

FROM ruby:2.5
RUN apt-get update && apt-get install -y \
    build-essential \
    libpq-dev \
    nodejs \
    postgresql-client \
    yarn
WORKDIR /kosare
COPY Gemfile Gemfile.lock /kosare/
RUN bundle install

yarnコマンドでエラー発生

root@5847e387581e:/kosare# yarn -v
ERROR: There are no scenarios; must have at least one.

解決策(一時的な解決策)

基本的には参考に記載のURLと同じことをすれば解決します。
私の場合はすでにrootで入っていたので、sudoと記載している部分はなくして順番に実行したら解決しました。

古いyarnを削除

sudo apt remove cmdtest
sudo apt remove yarn

最新を取得

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

update

apt-get update 

再インストール

apt-get install yarn

バージョン確認

yarn -v

解決策(根本的な解決策)

上記のやり方だと、dockerで再度bundle installし直した時に再度同じ事象が発生する。
Dockerfileでbuildした際に解決したいため、以下の2行をdockerfileに追記する。

RUN curl https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list

Dockerfile(最終形)

FROM ruby:2.5
RUN curl https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN apt-get update && apt-get install -y \
    build-essential \
    libpq-dev \
    nodejs \
    postgresql-client \
    yarn
WORKDIR /kosare
COPY Gemfile Gemfile.lock /kosare/
RUN bundle install

これでbuildしなおせば、yarnがちゃんと使えるはず。

参考

https://k-koh.hatenablog.com/entry/2020/04/02/143017
https://github.com/yarnpkg/yarn/issues/7329
https://qiita.com/MasatoraAtarashi/items/3f0317cd648ff63fa92c

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

[初心者]ブラウザでRailsのルーティングを確認する方法[小ワザ]

ブラウザでルーティングを確認するコマンド

$ rails sでサーバーを立ち上げてから、(URL)の後ろに

/rails/info/routes

とつけるだけです。

image.png
↑のような感じで、検索窓に直接打ち込めば大丈夫です。
image.png
こんなに見づらかったルーティングが、
image.png
ブラウザで確認できるようになります。

見やすくなるのに加え、毎回毎回サーバーを落として、立ち上げて、という作業からも解放されるので、作業時間短縮には非常にオススメです!

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

Railsを使ったToDoリストの作成(4.CRUDのRead機能)

概要

本記事は、初学者がRailsを使ってToDoリストを作成する過程を記したものです。
私と同じく初学者の方で、Railsのアウトプット段階でつまづいている方に向けて基礎の基礎を押さえた解説をしております。
抜け漏れや説明不足など多々あるとは思いますが、読んでくださった方にとって少しでも役に立つ記事であれば幸いです。

環境

  • Homebrew: 2.5.10 -> MacOSのパッケージ管理ツール
  • ruby: 2.6.5p114 -> Ruby
  • Rails: 6.0.3.4 -> Rails
  • node: 14.3.0 -> Node.js
  • yarn: 1.22.10 -> JSのパッケージ管理ツール
  • Bundler: 2.1.4 -> gemのバージョン管理ツール
iTerm
$ brew -v => Homebrew 2.5.10
$ ruby -v => ruby 2.6.5p114
$ rails -v => Rails 6.0.3.4
$ npm version => node: '14.3.0'
$ yarn -v => 1.22.10
$ Bundler -v => Bundler version 2.1.4

第4章 CRUDのRead(index/show)

第4章では、CRUDを使ってWebアプリケーションの基本的なデータ操作を実装します。CRUDは、Create(作成)、Read(参照)、Update(更新)、Delete(削除)を表します。今回実装したい機能は以下の通りです。

  • 一覧表示機能(indexアクション)
  • 詳細表示機能(showアクション)
  • 新規登録機能(newアクション/createアクション)
  • 編集機能(editアクション/updateアクション)
  • 削除機能(destroyアクション)

では、それぞれの機能実装について詳しく見ていきましょう。
まずはCRUDのReadについてです。

1 indexアクション

まずは、indexアクションを使って一覧表示機能を実装していきます。

1 データベースからレコードを取得する

Boardsコントローラを使ってデータベースからレコードを取ってきます。
今回は全てのレコードを取得したいのでActiveRecordのallメソッドを使い、メソッドの返り値をインスタンス変数に代入します。

app/controllers/boards_controller.rb
class BoardsController < ApplicationController
    def index
        @boards = Board.all
    end  
end
#インスタンス変数(@xxx)に代入したものはhaml内で<%=%>で囲うことで
#Viewsで自由に表示することができます。

先ほど全てのデータを取ってくるとは言ったものの、現在はデータが1つしかないためダミーデータを作成します。

ダミーデータを作成するためにはdb/seeds.rbにレコードを書きます。

db/seeds.rb
Board.create({name: 'seeds-title1', description: 'seeds-description1'})
Board.create({name: 'seeds-title2', description: 'seeds-description2'})

レコードを書くことができたら、ターミナルでseeds.rbを読み込むコマンドを実行し、ファイルを読み込みます。

iTerm
$ rails db:seed

これで新たに2つのレコードがデータベースに保存されているはずです。
試しにコンソールで確認してみましょう。

irb(main):002:0> Board.all

=> #<ActiveRecord::Relation [#<Board id: 1, name: "console-name1", description: "console-description1", created_at: "2020-11-17 05:38:06", updated_at: "2020-11-17 05:38:06">, 
#<Board id: 2, name: "seeds-title1", description: "seeds-description1", created_at: "2020-11-17 11:41:05", updated_at: "2020-11-17 11:41:05">, 
#<Board id: 3, name: "seeds-title2", description: "seeds-description2", created_at: "2020-11-17 11:41:05", updated_at: "2020-11-17 11:41:05">]>

このようにインスタンスが3つ作成されていればOKです。

2 一覧画面で全てのデータを表示する

次に、先ほど取ってきたデータをviewsで表示します。
全てのレコードを表示したいのでRubyのeachメソッドを使います。

app/views/boards/index.html.haml
.container
  - @boards.each do |board|
    .card
      .card_content
        .card_top
          %p.card_title 
            = board.name
          .dropdown
            = image_tag 'Combined Shape.png', class: 'card_dropdown dropbtn'
            .dropdown-content
              %a{:href => "#"} Edit
              %a{:href => "#"} Delete
        .card_description
          = board.description
        .card_detail
          = image_tag 'xxx.png'

ポイントは3つあります。

  • - @boards.each do |board|という繰り返し表現を使って@boardsの値全てををboardに渡しています。コンソールではBoard.allはArrayで返ってきていたため上記のような表現になっています。
  • = board.nameで繰り返し表現されたboardのnameカラムを表示しています。このようにカラム名はメソッドのように使用することができます。
  • = board.descriptionで繰り返し表現されたboardのdescriptionカラムを表示しています。

以上で、記事一覧機能は実装できました。

ダミーデータについて

先ほどはseeds.rbを使って手動でダミーデータの作成を行いましたが、ダミーデータを自動的に作成できるfakerという便利なgemがあるのでインストールして使っていきましょう。

Gemfile
gem 'faker'

Gemfileにgem 'faker'と書いたら、bundle installをします。
これでfakerのインストールは完了です。

では、実際にseeds.rbでfakerを使用してみましょう。
今回はfakerの中でも文章を作成してくれるFaker::Loremを使用しています。
また、より多くのレコードを作成したいので10回Board.createを繰り返しています。

db/seeds.rb
10.times do
    Board.create(
        name: Faker::Lorem.sentence(word_count: 3), 
        description: Faker::Lorem.sentence(word_count: 20)
    )
end

レコードを書くことができたら、ターミナルでseeds.rbを読み込むコマンドを実行し、ファイルを読み込みます。

iTerm
$ rails db:seed

これで新たに10個のダミーデータの入ったレコードがデータベースに保存されました。

2 showアクション

次に、showアクションを使って詳細表示機能を実装していきます。

1 ルーティング

まずは、詳細表示ページに遷移するURLが必要なのでroutes.rbでURLを作成します。

config/routes.rb
Rails.application.routes.draw do
  resources :boards
end

resourcesはCRUDの基本となる以下7つのアクションへのルーティングを定義する時に使います。

CRUD アクション   
Create(作成) index show
Read(参照) new create
Update(更新) edit update
Delete(削除) delete

また、以下のようにonlyオプションをつけると、ルーティングを限定することができます。

resources :boards, only: [:show, :edit, :update]
#resources :コントローラ名, only: [アクション名]

今回は、boardsコントローラにresourcesを指定したので、rails/infoで確認すると全てのアクションのルーティングが定義されていることがわかると思います。
スクリーンショット 2020-11-18 12.19.58.png

2 コントローラ

では、次にコントローラでデータを取得します。
データを取得するためにActiveRecordのfindメソッドを使用します。
findメソッドはテーブルから引数に入っているidのレコードを取ってきます。

app/controllers/boards_controller.rb
class BoardsController < ApplicationController
    def show
        @board = Board.find(params[:id])
    end
end

ポイントは2つあります。

  • ビューで表示したいので取得したデータをインスタンス変数に代入しています。今回は1つのレコードを取ってくるので単数形になっています。
  • 引数の値をparams[:id]としています。'params'とは送られてきた値を受け取るためのメソッドで[:カラム名]で値を受け取ることができます。

以上で、データの取得は完了です。

3 Views

取得したデータをViewsで表示していきます。
先ほど取得した値を@board.nameで表示しています

app/views/boards/show.html.haml
.container
  .board_title
    = @board.name
  .card
    .card_image
      = image_tag 'ToDoPhoto.png'
    .card_content
      .card_top
        %p.card_title 
          card_name
      .card_description
        card_description
      .card_detail
        = image_tag 'default-avatar.png'
        .card_detail_img
          %div
            %span 23
            %span 
              = image_tag 'Shape.png'

これでshowアクションを使った詳細表示機能の実装は完了です。

4 一覧表示画面にリンクを貼る

最後に、一覧表示ページから詳細表示ページに遷移できるようにリンクを貼っていきましょう。

app/views/boards/index.html.haml
.container
  - @boards.each do |board|
    = link_to board_path(id: board.id) do
      .card
        .card_content
          .card_top
            %p.card_title 
              = board.name
            .dropdown
              = image_tag 'Combined Shape.png', class: 'card_dropdown dropbtn'
              .dropdown-content
                %p Edit
                %p Delete
          .card_description
            = board.description
          .card_detail
            = image_tag 'default-avatar.png'

スクリーンショット 2020-11-18 17.10.28.png

ここではカード全体をリンクにしています。
では具体的にコードを見ていきましょう。

重要なのは以下の一文です。ここでリンクを作成し、ネストすることでカード全体をリンクにしています。

= link_to board_path(id: board.id) do
# = link_to 'リンクの名前' Helperのpath 
  • link_toはaタグを生み出すためのメソッドです。
  • pathの引数ではidを指定しています。idは@boardsからeachメソッドによって作成されたboardのidカラムなのでboard.idとしています。しかしRailsでは引数にインスタンスを代入するだけでidを勝手に取得してくれるためid: board:idの部分をboardと書くこともできます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

バズったTwitterの動画、画像を自動収集するWebサイト

概要

twitterでバズった動画や画像のツイートをいいね順、リツイート順、日付順で閲覧できるwebサイトを作った
https://twipop-app.tontonton.work/

使用した技術

  • サーバサイド
    Rails

  • フロントエンド
    React

  • API
    公式TwitterAPI

  • DB
    firebase Realtime Database

  • ホスティング
    Heroku

  • ssl
    cloud flare

全体像

こんな感じ
export_img.png

なぜRails

サーバサイドはRailsしか扱ったことがないから。Rails以外の技術を勉強する時間が勿体無い。

なぜReact

前々からSPAに興味がありReactの勉強を進めていた。Vueはちょっと勉強して挫折。
ページの部分更新するにもstate更新するだけで実現できてお手軽なのが魅力的。
gemはreact-rails

[Reactを知る前の貧弱な僕]
ビューにpartialファイル用意して、コントローラで専用のインスタンス変数用意して:sweat_drops:
あとpartialを返すようにして、それとそれとjavascript側でDOM更新してっ:dizzy_face:アワワワ

⬇️

[Reactを知った頑強な僕]
「フンッ:muscle_tone4:」取得データでstate更新
シャッ(華麗にページ更新される音)

なぜRealtime Database

なるべく無料DBを使用したいと思っていた。
最初はFireBaseのCloud Fire Storeを使おうとしていた。

Cloud Fire Storeについて

CRUDについて無料プランは以下の制限がある。
書き込み 2 万/日
読み取り 5 万/日
削除 2 万/日

nosqlは初めて扱った。RDBしか使ったことのない僕は最初

「1日でこんなにselectしないでしょw」
とか見当違いのことを思っていた。

実装を進めるにつれ、使用状況がおかしいことに気づいた。

ラッコ.jpeg
「うわ・・・私の読み取り数多すぎ?」

どうやらこの制限はデータ件数のことみたいだった。データ件数自体は1万も2万も扱うつもりはないが、読み取るたびに累積していくので断念。

RealTime DataBaseについて

RealTime DataBaseはCRUDの制限はない。容量は1GB。
gemはfirebase。
今の所問題なく動いているが以下のようなデメリットもある。

  • 日本語の関連記事があまりない
  • データの降順機能がない。今の所、昇順で取得したリストを自力で降順するしかない
  • FireBase自体はCloud FireStoreを推奨している

参考
FireBase公式料金プラン

なぜHeroku

アクセス数などの制限が無くRailsと親和性の高いHerokuを使用することにした。

最初はFireBaseでホスティングしようと思っていたけど、Twitter APIを使ってデータ取得するのにNode.jsが必要なので断念。
FireBaseでNode.js利用するときはCloud Functionを使う必要がある。
Cloud FunctionはBlazeプランに変えないと使用できない。
プランを変えたからといってすぐ課金されるわけではないけど、なるべく慎重にいきたい。

まとめ

DBの差し替えとかHeroku環境変数の設定とか、結構つまづいたことが多かったが学びも多かった。
今後も少しずつ改良を加えていきたい

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

Sessionとcookieをがよくわからないので、できるだけかみ砕いてまとめたよ

SessionとCookie

Railsチュートリアルでログイン機能の実装まで学習したが、いかんせん【わからない】
本当に分からない。自分が今何をやっているか理解できない。
こういう「分からない事が分からない」のは基礎的な知識が抜け落ちているパターンが多い。

どの基礎を抑えれば理解できるのかわからないが、まずSessionとCookieというこの2つのキーワードがよくわからないので、自分なりにまとめていく。


私は現職が医療従事者であり、専門知識の理解は対象(自分 or 他人)が理解できる内容までかみ砕く(対象によって流動食、ペースト食とレベルを変える)必要があると考えている。
このページでの内容はプログラミング学習前の自分レベルでも理解できるように、出来る限りかみ砕いた。内容を簡素にしていくと、認識違いやそもそも間違ってるでという事も起きる。
その場合はご指摘、ご助言いただけるとうれしいです。


※個人的ではあるが、感覚的に理解しやすいようにwebサーバーを「webページ」・ブラウザを「ユーザー」と表現している。

webの仕組みから始まる。

webの仕組みは

  1. ユーザーがwebページにアクセスする(リクエストする)
  2. アクセスしたページが表示される。(レスポンスが返ってくる)

  3. 次のページにアクセスすると、①で送られてきたユーザーのリクエストは2つ目に引き継がれない

といった仕組みになっている

HTTP通信は情報の引継ぎはできないようになっている。
※HTTPの通信には「HTTPリクエスト」と「HTTPレスポンス」の2つに分けられる。
※①がHTTPリクエスト②がHTTPレスポンス
※HTTPリクエストには「httpリクエストライン」「HTTPリクエストヘッダ」「HTTPメッセージボディ」の3つに分かれてる。
※HTTPリクエストヘッダには「ユーザーエージェント名」や「cookie」情報が含まれている

情報が引き継がれないなら、webページでよくある、ログインする仕組みとかはどうなっているのか???
ここでsessionとcookieという仕組みが登場する。

Sessionとcookieとは、それらの違い

Sessionとcookieは似ている。一見、動作としては同じように感じる。

違いとしては
cokkieはユーザー側(ブラウザ)に保存、覚えられる情報に制限あり(サイズ、文字列のみ)、簡単に盗み見られる

sessionはサーバー側に保存、覚えられる情報に制限はない、簡単に盗み見られない

cookieとは

cookieはユーザー側(ブラウザ)にメモ書きレベルの情報を保存するための仕組み

cokkieを使ったwebの流れはこうなる。

  1. ユーザーがwebページにアクセスする

  2. リクエストされた画面を返す。この時①でユーザーから送られてきた情報を元にwebページはプログラミングで指示された処理をしてユーザーに画面を返す
    (例:「あなたはこのサイトに○○回訪問したよ」とか「指示された色に画面の色を変えたよー」とか。この時にcookieというメモ書きも一緒につけている例でいうと「○○回」「○○色」など)

  3. webページからついてきたメモをユーザーが保存します。次のページにアクセスする時にこのメモ書きの情報も一緒につけて、webページに要求します。

  4. 本来のHTTP通信であれば情報は引き継がれないが、メモ情報がついているのでそのメモ情報を元にユーザーに適した画面が表示される。

このcookieはユーザー(ブラウザ)が保存しているのでデベロッパーツールで編集や削除ができる。つまりメモの内容を見れるし、書き換えられるし、捨てる事もできる。
そのため、大事な情報はcookieで扱ってはいけない(ログイン情報とか)

Sessionとは

Sessionは情報をwebページ側で保存するのでデベロッパーツールで触る事はできない。
つまり安全である。

ただしwebサーバー上で保存しているので、アクセスしてきた人がAさんなのか、Bさんなのか、Cさんなのか識別しないといけない。(これはブラウザが変わっても同様の事が言える)

ユーザー(ブラウザ)とwebページで保存されているsessionを紐づけてあげる必要がある
sessionではそのためだけにcookieを使う。

sessionの流れは

  1. ユーザーがwebページにアクセスする
  2. リクエストされた画面を返す。①でユーザーから送られてきた情報を元にwebページはプログラミングで指示された処理をしてユーザーに画面を返す。そして情報をサーバー側で保持&Session情報をcookieとしてセットするよう送ってくる。
  3. ユーザーはsession情報をセットする。次のページにアクセスする時に、このセッション情報を送信してwebページに要求します。

  4. 本来のHTTP通信であれば情報は引き継がれないが、session情報がついているのでその情報を元にユーザーに適した画面が表示される。

ここまでのまとめ

・通常HTTPではページ毎に情報を引き継げない
・そのためcookieやsessionを使って情報を継続しないといけない
・sessionとcookieは違いがある
・ログイン機能を実装するにはsessionとcookieを使う必要がある

参考リンク

https://wa3.i-3-i.info/word1841.html
https://wa3.i-3-i.info/word1791.html

Webpicks
ドットインストール
railsチュートリアル

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

Rails エラーメッセージの日本語化

はじめに

オリジナルアプリを制作しています。完成後は知人に使ってもらい、感想をフィードバックする予定です。その知人は日本人なので、エラー文は日本語にした方がより親切だと思いました。そのため、エラーメッセージの日本語化を行いました。

目次

1.日本語の言語設定
2.Gemfileの追加
3.日本語化ファイルの作成
4.追加で日本語翻訳文言の設定

1.日本語の言語設定

config.i18n.default_locale = :jaを追加する。この「ja」は日本語を表す。

config/application.rb
module Pictweet
 class Application < Rails::Application
   # Initialize configuration defaults for originally generated Rails version.
   config.load_defaults 6.0

   # 日本語の言語設定
   config.i18n.default_locale = :ja
  # 省略
 end
end

2.Gemfileの追加
Gemfileに追加後、忘れずにターミナルでbundle installを実行、サーバーを再起動する。

公式 GitHub

Gemfile
gem 'rails-i18n'

3.日本語化ファイルの作成

様々な言語に対応する言語ファイルをlocale(ロケール)ディレクトリに作成する。ここでは日本語化ファイルを作成する。ファイルの中身は下記リンク先の内容をコピペする。これにより、コピペした内容に該当するエラー文は日本語化される。

公式 devise-i18n

config/locales/devise.ja.yml
ja:
  activerecord:
    attributes:
      user:
        confirmation_sent_at: パスワード確認送信時刻
        confirmation_token: パスワード確認用トークン
#------------------省略------------------------------
      not_saved:
        one: エラーが発生したため %{resource} は保存されませんでした。
        other: "%{count} 件のエラーが発生したため %{resource} は保存されませんでした。"

4.追加で日本語翻訳文言の設定

最後に日本語文の追加設定を行う。同じくlocalesディレクトリにja.ymlファイルを作成する。追加したい日本語とその英語を記述することで、カスタマイズできる。

config/locales/ja.yml
ja:
 activerecord:
   attributes:
     user:
       nickname: ニックネーム
     desk:
       title: 画像タイトル
       image: 投稿画像
     suggestion:
       place: 掃除場所
       period_cleaning: 清掃期間
       last_cleaned_date: 最後に清掃した日

以下参考画面

image.png

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

【Rails】お気に入り機能の実装手順を公開!!【簡単にできます】

あおい(https://twitter.com/aoi_programming )です。
Ruby on Railsでお気に入り機能を実装したので手順を公開します。

実装したい機能

  • お気に入り登録・解除できること
  • 未ログイン時にはお気に入りできない
  • 未ログイン時にはお気に入り一覧ページにアクセスできない
  • お気に入りボタンをクリックするとAjax通信でデータ保存ができる

1. ブランチ作成

git checkout -b favorites

2. Favoriteモデルの作成

% rails g model Favorite user_id:integer post_id:integer

3. migrationファイルの編集

def change
    create_table :favorites do |t|
      t.integer :user_id
      t.integer :post_id
      t.timestamps
    end
    add_index :favorites, [:user_id, :post_id], unique: true  # 追記
  end

データベースに反映させます。

% rails db:migrate

4. 関連付け

favorite.rb
class Favorite < ApplicationRecord
  belongs_to :user
  belongs_to :post
end
user.rb
has_many :favorites, dependent: :destroy
post.rb
has_many :favorites, dependent: :destroy

5. バリデーションの追加

favorite.rb
belongs_to :user
belongs_to :post
validates :user_id, presence: true  # 追記
validates :post_id, presence: true  # 追記

6. コントローラー作成

% rails generate controller Favorites

7. コントローラー編集

favorites_controller.rb
class FavoritesController < ApplicationController
 before_action :authenticate_user!

 def create
  @post = Post.find(params[:post_id])
    @user = @post.user
    current_user.favorite(@post)
    respond_to do |format|
      format.html { redirect_to request.referrer || root_url }
      format.js
    end
 end

 def destroy
  @post = Post.find(params[:post_id])
    current_user.favorites.find_by(post_id: @post.id).destroy
    respond_to do |format|
      format.html { redirect_to request.referrer || root_url }
      format.js
    end
 end

end

8. ルート編集

route.rb
 post   "favorites/:post_id/create"  => "favorites#create"
 delete "favorites/:post_id/destroy" => "favorites#destroy"
end

9. お気に入り登録のメソッドを作成

user.rb
  def favorite(post)
    Favorite.create!(user_id: id, post_id: post.id)
  end

  # 投稿のお気に入り解除する
  def unfavorite(post)
    Favorite.find_by(user_id: id, post_id: post.id).destroy
  end

  # 現在のユーザーがお気に入り登録してたらtrueを返す
  def favorite?(post)
    !Favorite.find_by(user_id: id, post_id: psot.id).nil?
  end

10. Ajaxの実装

  • app/views/favorites/create.js.erb (新規)
  • app/views/favorites/destroy.js.erb (新規)
create.js.erb
$("#favorite-<%= @post.id %>").html("<%= escape_javascript(render('users/unfavorite')) %>");
destroy.js.erb
$("#favorite-<%= @post.id %>").html("<%= escape_javascript(render('users/favorite')) %>");

11. お気に入りフォームの作成

app/views/users/_favorite_form.html.erb (新規)

app/views/users/_favorite_form.html.erb
<div id="favorite-<%= @post.id %>">
  <% if !current_user.nil? && current_user.favorite?(@post) %>
    <%= render 'users/unfavorite' %>
  <% else %>
    <%= render 'users/favorite' %>
  <% end %>
</div>

12. それぞれのボタンを作成

app/views/users/_favorite.html.erb (新規)

app/views/users/_favorite.html.erb
<%= link_to "/favorites/#{@post.id}/create", method: :post, class: 'like', remote: true do %>
  <div class="favorite">お気に入りに登録</div>
<% end %>

app/views/users/_unfavorite.html.erb (新規)

app/views/users/_unfavorite.html.erb
<%= link_to "/favorites/#{@post.id}/destroy", method: :post, class: 'like', remote: true do %>
  <div class="unfavorite">お気に入り登録済み</div>
<% end %>

13. ボタンによって色を変更

app/assets/stylesheets/~.scss
like {
 color: gray;
}

unlike {
 color: red;
}

14. 表示させたいページに追加

~.erb
<%= render 'users/favorite_form' %>

以上です!
参考になった方はLGTMお願いします?‍♂️

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

herokuで環境変数を設定する際の注意点

この投稿について

ポートフォリを作成の際にGoogle認証を取り入れたいと思い、ユーザー登録や、ログインを行ってくれる"Omniauth"というGemを導入しました。
ところが、環境変数に設定しようとした際に、うまく設定できなかったため、どんな理由でエラーが起きて、どのように解消できたのかを記事にしています。

herokuに環境変数を設定するとき

heroku config:set

というコマンドを使用します

実際には、

heroku config:set GOOGLE_CLIENT_ID="~~~~~"

このようなコマンドで追加します。
このとき、 『=』と『""』の間にスペースを入れるとエラーが発生してしまいます。

heroku config:set GOOGLE_CLIENT_ID = "~~~"

このようにしてしまうとエラーが発生してしまうので、設定できませんでした。

なので、まとめると環境変数に設定するときは、無駄なスペース入れず、コピペして貼ってしまうことをお勧めします。

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

タダ飲みコーヒー

[Ruby] タダ飲みコーヒー

問題

・コーヒーをお買い上げした際に、次のお買い上げの値段を更に P% off!
・毎回の値下げにおいて小数点以下切り捨て

あなたは値下げが累積する事に目をつけました。
コーヒーを何回も飲んでいれば、タダでコーヒーを飲めるようになるのです。
タダで頼みたいあなたは、何円払えば以後タダで注文できるのか計算したくなりました。
実際にプログラムを書いて計算してみましょう。

入力される値

入力は以下のフォーマットで与えられます。

X P
・コーヒーの価格を示す整数 X と 割引き率を示す整数 P が、この順に半角スペース区切りで与えられます。
・入力は 1 行となり、末尾に改行が 1 つ入ります。

期待する出力

以後タダで注文するのに必要な金額を出力してください。

入力例1

300 50

出力例1

596

入力例2

1000 99

出力例2

1010

私の答え

a,b = gets.split(' ').map(&:to_i)
s = 100 - b
int = a
while a.floor > 0 
    a = (a*s/100).floor
    int += a
end

puts int

このコードはほぼ丸パクリしたのですがなるほどという感じです。while文の中身は説明できませんが、a変数(floor)が0になるまで繰り返しているのでしょう。繰り返し文が苦手だという事が分かりました。

以上!悔しい

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

VSCodeでERBの式入力タグを挿入できるようにする拡張機能

はじめに

VSCodeでERBファイルを編集する際に、式出力タグの入力が面倒だったので、スニペットを登録しようかと考えていたのですが、ちょうどいい拡張機能を教えてもらえたのでこちらで紹介します。

Simple Ruby ERB

使い方


Shift⇧ + Ctrl⌃ + ` でタグを挿入、押すたびに種類が切り替わります。
demo
すごい楽。

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

RailsのViewの共通化について

本投稿の目的

・Rails学習の議事録です。


学習に使った教材

Udemyの "はじめてのRuby on Rails入門-RubyとRailsを基礎から学びWebアプリケーションをネットに公開しよう" を教材として使用しました。


①viewの共通化

・全てのviewの共通箇所を1箇所に集約する方法
・集約ファイル (app/views/layouts/application.html.erb)

application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <%= csrf_meta_tags %>

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

  <body>
    <div class="container">
      <%= yield %>
    </div>
  </body>
</html>

【解説】
・html.erbファイル共通フォーマットがここに記載
・大抵は,htmlのheadのcss,js読み込みテンプレ文を記載する
・<%= yield %>の部分には,他のhtml.erbが読み込まれる

②partialについて

・共通部分を記載したファイルをpartialファイルという
・例えば,new と edit など共通のviewなどが該当する

【やり方①】
・パーシャルファイルを partial名.html.erb として保存
・(* " _
" から始めるのに注意)
・viewファイルの共通部分を削除

・下記を記載
・(*名前に" _ "を外すことに注意)

partialを呼び出す側のファイル名.html.erb
<% render partial '名前' %>

【やり方②】
・①のやり方はメンテナンス性が悪い
・(controllerのインスタンス変数に気付き辛い)

記載を下記に修正

partialを呼び出す側のファイル名.html.erb
<% render '名前', locals: {ローカル変数: インスタンス変数} %>

・これによりパーシャルのhtmlにはインスタンス変数ではなくローカル変数が渡される

_partialファイル名.html.erb
<%= form_for ローカル変数 do |f| %>
  共通の内容をここに記載
<% end %>

【やり方③】
・記載を下記に修正

partial導入側のファイル名.html.erb
<% render '名前', locals: {object: インスタンス変数} %>

・objectとすることで,パーシャル名と同様のローカル変数を作成
・これにインスタンス変数を渡す

【やり方④】
・記載を下記に修正

partial導入側のファイル名.html.erb
<% render インスタンス変数 %>

・これは partial名 = ローカル変数 時に使用可能
・インスタンス変数の@を除去した変数=ローカル変数=partial名

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

【Rails非同期処理】Sidekiq vs Resque vs Delayed Job 〜Active Jobとは〜

1. Active Jobとは

バックグラウンドで実行するジョブをRailsアプリケーションで動かすための共通インターフェイスです。
例えばメール送信やCSVアップロード等の時間がかかる重い処理はバックグラウンドで実行することが多いです。

Rails 4.2で導入された機能で、非同期処理を実装する際にまず検討する事が多いかと思います。

2. 共通インターフェイスって?

Active Jobが導入される以前のRailsバージョンの場合、非同期処理を実装するGemを使用していました。
代表的なものではDelayed JobSidekiqResqueがあります。

当然それぞれのGemで記述や機能が異なっていましたが、Active Jobはそうした違いを気にせずジョブを扱うことができるインフラ機能です。

Railsガイド Active Jobの目的によると、Active Jobには下記のようなメリットがあります。

  • Delayed JobとResqueなどのように、さまざまなジョブ実行機能のAPIの違いを気にせずにジョブフレームワーク機能やその他のgemを搭載することができる
  • バックエンドでのキューイング作業では、操作方法以外のことを気にせずに済む
  • ジョブ管理フレームワークを切り替える際にジョブを書き直さずに済む

かんたんなJobを実装

  • ruby 2.7.2
  • rails 6.0.3

この環境でとりあえずJobを実装してみます。

$ rails g job sample
      create  app/jobs/sample_job.rb
      create  app/jobs/application_job.rb

2つのファイルがapp/job/配下に生成されます。
application_job.rbはActiveJob::Baseを継承する親クラスで、処理を実装するジョブファイルでは必ずApplicationJobを継承する必要があります。

app/jobs/application_job.rb
class ApplicationJob < ActiveJob::Base
  # Automatically retry jobs that encountered a deadlock
  # retry_on ActiveRecord::Deadlocked

  # Most jobs are safe to ignore if the underlying records are no longer available
  # discard_on ActiveJob::DeserializationError
end

そしてApplicationJobを継承したSampleJobを実装していきます。
Jobが実行される際には、performメソッドが呼ばれるので、このメソッドに処理を記述します。

app/jobs/sample_job.rb
class SampleJob < ApplicationJob
  queue_as :default

  def perform(*args)
    puts '--------------------------------'
    puts '------------  Test  ------------'
    puts '--------------------------------'
  end
end

railsコンソール上で下記のようにJobを実行できます。

# 「キューイングシステムが空いたらジョブを実行する」とキューに登録する
> SampleJob.perform_later

# 明日正午に実行したいジョブをキューに登録する
> SampleJob.set(wait_until: Date.tomorrow.noon).perform_later

# 5秒後に実行するジョブをキューに登録する
> SampleJob.set(wait: 5.second).perform_later

performメソッドには実際には非同期で実行する重い処理が実装されることになります。
かんたんに実装できましたが、一つ問題があります。

Active JobはキューをRailsのメモリ内に保持するため、Railsを再起動するとジョブは失われてしまいます。

Railsガイドには以下のような記述があります。

デフォルトのRailsは非同期キューを実装します。これは、インプロセスのスレッドプールでジョブを実行します。ジョブは非同期に実行されますが、再起動するとすべてのジョブは失われます。
ーー
Rails自身が提供するのは、ジョブをメモリに保持するインプロセスのキューイングシステムだけです。 プロセスがクラッシュしたりコンピュータをリセットしたりすると、デフォルトの非同期バックエンドの振る舞いによって主要なジョブが失われてしまいます。

アプリケーションが停止、または再起動した際に、ジョブが失われないように、Railsで使うべきサードパーティのキューイングライブラリ、 Active Job のアダプタを決める必要があります。

3. アダプタはどれがいいのか?

代表的なものは、Delayed JobSidekiqResqueがあります。
プロダクトの規模や要件に応じて技術選定することになると思いますが、かんたんに比較してみます。

ストレージ プロセス/スレッド 処理速度
Sidekiq Redis マルチスレッドプロセス
Resque Redis シングルスレッドプロセス
Delayed Job SQL DB シングルスレッドプロセス

3-1. Sidekiq

利点

  • ストレージとしてRedisを使用し、マルチスレッドプロセスで動くので処理速度が速い
  • マルチスレッドであるがゆえに、他の2つと比べて、使用メモリに対するパフォーマンスが良い
  • ダッシュボードがいい感じ

懸念点

  • マルチスレッドなので、スレッドセーフであるように実装しなければならない
  • これもマルチスレッドがゆえに、 メモリが肥大化することがある
  • sidekiqは起動時にソースコードを読み込むため、(workker or jobの)コードを修正した場合に再起動が必要

※Rails、sidekiq、Redisはそれぞれ独立したプロセスでの起動

最初の問題については、下記に詳しく書きましたが、通常考慮する必要はなさそうです。
後ろ2つの問題を解消するためには、本番環境デプロイ時にcapistranoを使用していれば、同時にsidekiqも再起動してあげたら良さそうです。(ジョブ実行中の考慮は必要かも)

→結論:懸念点に大きな問題はないのでは?


(↓以下は、コアな部分に興味のない人は読み飛ばしてください。)

マルチスレッドとは、ひとつのプロセス内で複数のスレッドが動作していることを指します。

まずプロセスとは実行中のプログラムのことで、ひとつのプロセスには1つのメモリ領域(正確にはOSのメモリ空間)が割り当てられます。

そしてスレッドとはプロセス内で作られる並列動作が可能な処理の単位です。1つのスレッドにつき、1つのCPUコアに命令を出し処理を行います。プロセス内のスレッドは、プロセスに割り当てられたメモリ空間を共有できます。

そのため、並列で実行しても互いに影響を与えない実装、つまりスレッドセーフな実装を行う必要があります。

※Rubyは言語仕様上、OSレベルではメモリに対するアクセスが複数並行することはなく、スレッドセーフであることが担保されているようです。この辺までは詳しくないです。。参考記事


Active Jobのアダプタよりも、純粋にSidekiqを導入したほうが良い場合

Rails 6.0.1以前のActive Jobは、Sidekiqのリトライ機能を完全にサポートしていませんでした。
具体的には純粋なSidekiqで使用できるsidekiq_optionsメソッドがあります。sidekiq_optionsを使うとジョブを登録するキューや、ジョブが失敗したときのリトライ処理の有無が設定できます。

sidekiq_optionsでリトライ等の詳細に設定を行い場合、Active Jobではなく、純粋なSidekiqを使うほうが良いです。

Sidekiq 6.0.1 + Rails 6.0.2の組み合わせ以上のバージョンで、オプションが完全に使用できるようになります。

3-2. Resque

利点

  • ストレージとしてRedisを使用し、シングルスレッド・マルチプロセスで動く
  • delayed jobよりも高速
  • ジョブごとにフォークされてメモリ初期化されるからスッキリ(メモリリーク/肥大化の心配が基本的にない)
  • ダッシュボードがある

懸念点

  • 大量にジョブを処理するとフォークオーバーヘッドが大きく、速度面やメモリ面でやや不利
  • 単体でリトライ処理ができない (Railsのメソッド使えば、Active Jobではできるかも?)

3-3. Delayed Job

利点

  • ストレージにDBを使用するので、既存のRailsアプリに導入が容易
  • 登録されたキューもActiveRecordと同じように扱えるので、色々とやりやすい
  • (単体)キューへの登録が簡単 ※Active Jobを使う場合はインターフェイスは統一されている

懸念点

  • シングルスレッドプロセスがゆえの速度面

4. Active Jobを使ったほうが良いの?

利点

  • インターフェイスが統一され可読性が上がった (アダプタ間の移行はそんなに発生しなさそう)
  • Gem依存を減らせる (Railsのレールに沿ったほうがバージョンアップ時などに楽?)

懸念点

  • リトライ制御が弱い
  • 複雑な要件にどこまで耐えられるか

5. 結論

小規模なアプリや、スケーラビリティを考慮しない場合 ----> Delayed Job
パフォーマンスや大量のジョブが発生する場合 ----> Sidekiq

Active Jobか純粋なGemを使用するかは、大きなアプリケーションで複雑な仕様の場合、純粋なGemを使うほうが選択肢が広がりそう。
ただし、最新版のRails + Sidekiq の環境であれば、Active Jobも検討できる。

小規模 or 個人開発レベルであれば、Active Jobに沿って実装すれば良いのでは?といった感じ。

参考サイト

やはり原典をあたるのが一番良く、この辺りは情報が充実していました(英語ですが)。
Qiita記事も大変参考になり、ありがとうございました。

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

【Rails】コールバックについて

基本的な事項ですが忘れやすいので備忘録かつ、まとめとして記事にすることにしました。

【参考記事】
Railsのコールバック

コールバックとは?

モデルを保存、更新、削除後や削除前のタイミングで実行されるメソッドのこと指す。
例) 記事を投稿し保存後には〜をする。

before_save => #保存前
after_save => #保存後
after_update => #更新後
before_destroy => #削除前
after_destroy => #削除後

コールバック使用例

before_save(保存前)コールバック後にメソッド実行する例を挙げる。

user.rb
class << self
  before_save :downcase_email
  def downcase_email
     self.email = email.downcase
  end

上記コードはemail保存前に、大文字のローマ字がある場合、小文字に変換するメソッド。
他の分かりやすい例としては、

posts_controller.rb
class ShopsController < ApplicationController
  before_action :logged_in_user

上記はpostsコントローラーの各アクション前にログインユーザーであることを指定している。
通販で何か購入する時、ログインしていない場合はログインまたは会員登録を促されると思われるが、ログインユーザーでないため、弾かれる(before_action :logged_in_user)のもコールバックによりログインを要求しているものと言える。

学び

普段何気なく上記のメソッド(before_action)等は使用していると思われるが、改めて「Railsにおけるコールバックを端的に説明せよ」と言われるとビシッと答えられない初学者の方が意外に多いと思います(自分もそうです)。意味のないコードなどなく、それぞれが意味を持っているので、普段から自分が書いたコードについて自信を持って説明できるように学習を励んでいきたいと思いました。Railsはとっつきやすいと言われていますが、本当に理解して使いこなそうとするとかなり奥が深いものであることを学びました。

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

【rails】 path url ・・・違いは?

結論

path

  • link_toでよく使われる
  • redirect_to以外で使用する

url

  • redirect_toの時に使用する

ちなみにpathの場合

<%= link_to '店舗様専用登録ページ', 'signup' %>
みたいにシングルクォーテーションで囲ってもいけますし、
<%= link_to '店舗様専用登録ページ', signup_path %>
こう書いても大丈夫でした。

最後に

間違っているところがあればご指摘いただけると幸いです。

参考記事

https://railsguides.jp/routing.html

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

某フリマアプリでSold Out!!を表示させる機能

cssは用意されている物、購入テーブルと商品テーブルを作っている状態。pay.jpで決算できる機能を実装終えている前提として書いています。

購入した商品のみ表示させる

index.html.erb
<% if item.purchase.present? %> 
   <div class='sold-out'>
      <span>Sold Out!!</span>
   </div>
<% end %>

present?

あるかないかを判断できます。ここでいうあるかないかは購入されたかどうかになります。if文を用いることである時のみの表示になります。

アソシエーション

purchase.rb
  belongs_to :user
  belongs_to :item
  has_one    :address
item.rb
  has_one_attached :image
  belongs_to :user
  has_one    :purchase

売れた商品の購入ページにはいけない

orders_controller.rb
   before_action :sold_out_item, only: [:index]
  private
   def sold_out_item
    redirect_to root_path if @item.purchase.present?
   end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

購入機能実装後でSold Out!!を表示

cssは用意されている物、購入テーブルと商品テーブルを作っている状態。pay.jpで決算できる機能を実装終えている前提として書いています。

購入した商品のみ表示させる

index.html.erb
<% if item.purchase.present? %> 
   <div class='sold-out'>
      <span>Sold Out!!</span>
   </div>
<% end %>

present?

あるかないかを判断できます。ここでいうあるかないかは購入されたかどうかになります。if文を用いることである時のみの表示になります。

補足として、アソシエーションも確認し、購入した商品は購入ページに飛ばないように設定しましょう

アソシエーション

purchase.rb
  belongs_to :user
  belongs_to :item
  has_one    :address
item.rb
  has_one_attached :image
  belongs_to :user
  has_one    :purchase

売れた商品の購入ページにはいけない

orders_controller.rb
   before_action :sold_out_item, only: [:index]
  private
   def sold_out_item
    redirect_to root_path if @item.purchase.present?
   end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

文字列からN番目

[Ruby] 文字列からN番目

問題

半角アルファベット文字列 s と 整数 n が入力されます。

文字列 s の1番左の文字を 1 文字目とし n 文字目のアルファベットを出力して下さい。

例えば
paiza 3
と入力された場合
i
と出力して下さい。

入力される値

入力は以下のフォーマットで与えられます。

s n

期待する出力

文字列 s の n 文字目を出力して下さい。

入力例1

thankyou 3

出力例1

a

入力例2

abcdefghij 5

出力例2

e

私の答え

s = gets.split(" ").map(&:chomp)
a = s[1].to_i
puts s[0].slice(a - 1)

今回のポイント

1行目で入力例1の場合だと [thankyou, 3] と分割させている

2行目でa変数に 5 を代入している

3行目で thankyou をsliceメソッドで5番目の文字列を指定している。ここを (a) のままだと0からカウントされる為 -1 をして調整している

以上!

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