20210120のRailsに関する記事は16件です。

rails test がデータベースの参照エラーで失敗する

経緯

Docker の中で Rails と MySQL を立て、 rails test でテストを実行しようとしたところ、エラーとなりました。

docker-compose.yml
services:
  mysql_test:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: aaa
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      TZ: 'Asia/Tokyo'
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
$ rails test test/integration/aaa_test.rb
Error:
AaaTest#aaa:
ActiveRecord::ConnectionNotEstablished: Access denied for user 'user'@'%' to database 'aaa-1'

エラー内容を見ると aaa-1 を参照しており、そのデータベースはないのでエラーとなっています。

解決策

test_helper.rb にある parallelize(workers: :number_of_processors) の影響なので、 PARALLEL_WORKERS を変更して suffix がつかないようにします。

$ PARALLEL_WORKERS=1 rails test test/integration/aaa_test.rb

これで動作します。

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

【Bootstrap】いきなりログアウトできなくなった〜なんでや?CSRF問題らしい〜

解決したいこと

Bootstrapを導入してからログアウトができなくなってしまった。

発生している問題・エラー

ログイン後、ヘッダー部分にあるログアウトボタンを押すとエラーメッセージが出て、一生ログアウトできない。

a746095752c272c27d28d5b332bbf41f.png

該当するソースコード

application.html.erb
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
    <meta name="generator" content="Jekyll v4.0.1">
    <title>Party Freak</title>

    <%= stylesheet_link_tag 'application', media: 'all'%>

〜以下略〜

以前のソースコード

application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Party Freak</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

〜以下略〜

自分で試したこと

<html><head>部分の記述を導入前に戻すと、ログアウトができるようになる。
最終的には<head>から下を入れ替えることでエラーが出ず、以前のようにログアウトができるようになった。
なお、ログインと新規投稿については問題のあったソースコードでもできていた。
部分的に入れ替えてみたり色々試してみて数十分、記述を追加するとログアウトできるようになるものを発見。

application.html.erb
<%= csrf_meta_tags %>
<%= csp_meta_tag %>

この二つでした。
調べてみるとクロスサイトリクエストフォージュリに関する記述。
Railsがセキュリティ的に危ないからやめときなさい!と注意をしてくれているということでした。
ありがとうRailsさん、いつもエラーで怒ってごめんね。

エラーが出なくなったソースコード(head以下を変更しました

application.html.erb
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
    <meta name="generator" content="Jekyll v4.0.1">
    <title>Party Freak</title>
    <%= csrf_meta_tags %>       ⬅️追加
    <%= csp_meta_tag %>        ⬅️追加  
    <%= stylesheet_link_tag 'application', media: 'all'%>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>   ⬅️なぜかこれも追加
〜以下略〜

一番下のJSを入れないとログアウトできなかったのが謎です。
何度やってもこれがないとダメなようですが理由がわかりません。
どなたか理由がわかる方がいらっしゃれば教えていただければ幸いです。

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

画像のプレビュー機能

はじめに

画像投稿時に、画像ファイルの名前だけが表示されるだけで、きちんと出来ている分かりづらいと感じたので、プレビュー機能を実装しました。

プレビュー機能実装前

1.準備

1. プレビュー機能を実装させるためのjsファイルを作成する。app/javascript/packsにpreview.jsを作成
app / javascript / packs / preview.js

2.preview.jsを読み込めるようapplication.jsを編集する。

app/javascript/packs/application.js
require("@rails/ujs").start()
require("@rails/activestorage").start()
require("channels")
require('./preview') # 追記する

3.viewファイルに画像が表示される場所を指定する。

views/ideas/new.html.erb
<div class="img-upload">
  <div class="left-img-upload">
    <div class="weight-bold-text">
      関連画像(関連する画像があれば添付してください)
    </div>
    <div class="click-upload">
      <p>クリックしてファイルをアップロード</p>
      <%= f.file_field :image, id:"idea-image" %>
    </div>
  </div>
  <div class="right-img-upload">
    <div id="image"></div>  <!--追記する-->
  </div>
</div>

2.プレビュー機能実装

1 で作成したpreview.jsにプレビュー機能のコードを記述する。

app/javascript/packs/preview.js
if (document.URL.match( /new/ ) || document.URL.match( /edit/ )) {
  document.addEventListener('DOMContentLoaded', function(){
    const ImageList = document.getElementById('image');

    const createImageHTML = (blob) => {
       // 画像を表示するためのdiv要素を生成
      const imageElement = document.createElement('div');
      // 表示する画像を生成
      const blobImage = document.createElement('img');
        blobImage.className="preview"; //←createElementで生成したimgにクラス名を付けている
      blobImage.setAttribute('src', blob);
      // 生成したHTMLの要素をブラウザに表示させる
      imageElement.appendChild(blobImage);
      ImageList.appendChild(imageElement);
    };
    document.getElementById('idea-image').addEventListener('change', function(e){
      // 画像が表示されている場合のみ、すでに存在している画像を削除する
      const imageContent = document.querySelector('img');
      if (imageContent){
        imageContent.remove();
      }
      const file = e.target.files[0];
      const blob = window.URL.createObjectURL(file);
      createImageHTML(blob);
    });
  });
}



最後にCSSで画像のサイズを指定する。

.preview {
  height: 250px;
  width: 250px;
  object-fit: contain;
}

実装完了

画像が表示されるようになったおかげで、自分が選択した画像が分かりやすくなりました。

最後に

javascriptを用いた実装はあまり行っていなかったため、いい復習となりました。
また、createElementで生成した要素にクラス名をつける方法など学ぶことができ、勉強になりました。

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

[個人開発]使い捨てチャットアプリ作ってみた

初めに

今回はweb上で動くチャットアプリ 「スコーチチャット」を作りました。
アカウント登録不要
ダウンロード不要
で使い捨てで使われることを想定してます。

chat-top.png
パスワードも設定可能です

URL
https://scorch-chat.herokuapp.com/

開発環境

rails6
ruby2.7
windows10
heroku
postgresql
ActionCable

gem 'ridgepole'
gem 'slim-rails'
gem 'html2slim'
gem 'pry-rails'
gem 'bcrypt'
gem 'activeadmin'
gem 'devise'
gem 'rack-attack'

いつもと同じです。

開発期間

5日くらいです。
webアプリは当たる確率が6%程で質より量を打ったほうがいいといわれたのでなるべく早く作りました。

チャット画面

chat-smart.png

[自分流]新しい技術の勉強法

結論から言うと、元あるコードを改造することです。

初めて使う技術のコードを一気に書くのは難しいです。
なので作りたいアプリになるべく似たコードを
githubなどで落としてきてそれで開発を進めるんです。
するとインプットとアウトプットを実践でできるので
いい勉強法になると思ってます。

今回はじめてActionCableを使ったのですが最初は全く分かりませんでしたがもう
何となくわかるようになりました

終わりに

ここまで読んでくれてありがとうございました。

スコーチチャットをぜひ使ってみてください!
URL
https://scorch-chat.herokuapp.com/

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

使い捨てのチャットを作成できるサービス「Scorch Chat」をリリースした!

初めに

今回はweb上で動くチャットアプリ 「スコーチチャット」を作りました。
アカウント登録不要
ダウンロード不要
で使い捨てで使われることを想定してます。

chat-top.png
パスワードも設定可能です

URL
https://scorch-chat.herokuapp.com/

開発環境

rails6
ruby2.7
heroku
postgresql
ActionCable
slim

いつもと同じです。

開発期間

5日くらいです。
webアプリは当たる確率が6%程で質より量を打ったほうがいいといわれたのでなるべく早く作りました。

チャット画面

chat-smart.png

[自分流]新しい技術の勉強法

結論から言うと、元あるコードを改造することです。

初めて使う技術のコードを一気に書くのは難しいです。
なので作りたいアプリになるべく似たコードを
githubなどで落としてきてそれで開発を進めます。
するとインプットとアウトプットを実践でできるので
いい勉強法になると思ってます。

今回はじめてActionCableを使ったのですが最初は全く分かりませんでしたがもう
何となくわかるようになりました

Railsはオワコンか?

ほかの記事で話題になっていたので取り上げます。

僕はrailsはオワコンではないと思います。
結構昔から言われ続けてますが、まだオワコンにはなってないでしょう?
それが答えな気がします。

railsを圧倒するようなフレームワークが出ない限りオワコンにはならない気がします。

終わりに

ここまで読んでくれてありがとうございました。

スコーチチャットをぜひ使ってみてください!
URL
https://scorch-chat.herokuapp.com/

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

手軽にチャットを作成できるサービスをリリースした!

初めに

今回はweb上で動くチャットアプリ 「スコーチチャット」を作りました。
アカウント登録不要
ダウンロード不要
で使い捨てで使われることを想定してます。

chat-top.png
パスワードも設定可能です

URL
https://scorch-chat.herokuapp.com/

開発環境

rails6
ruby2.7
heroku
postgresql
ActionCable
slim

いつもと同じです。

開発期間

5日くらいです。
webアプリは当たる確率が6%程で質より量を打ったほうがいいといわれたのでなるべく早く作りました。

チャット画面

chat-smart.png

[自分流]新しい技術の勉強法

結論から言うと、元あるコードを改造することです。

初めて使う技術のコードを一気に書くのは難しいです。
なので作りたいアプリになるべく似たコードを
githubなどで落としてきてそれで開発を進めます。
するとインプットとアウトプットを実践でできるので
いい勉強法になると思ってます。

今回はじめてActionCableを使ったのですが最初は全く分かりませんでしたがもう
何となくわかるようになりました

Railsはオワコンか?

ほかの記事で話題になっていたので取り上げます。

僕はrailsはオワコンではないと思います。
結構昔から言われ続けてますが、まだオワコンにはなってないでしょう?
それが答えな気がします。

railsを圧倒するようなフレームワークが出ない限りオワコンにはならない気がします。

終わりに

ここまで読んでくれてありがとうございました。

スコーチチャットをぜひ使ってみてください!
URL
https://scorch-chat.herokuapp.com/

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

active_hashを使って都道府県のセレクトバーを作成

今回はよく見かける都道府県のセレクトバーを作成していきたいと思います:grin:

① active_hash導入

gem 'active_hash'
$ bundle install

② 関連するモデルにprefecture_id :integerを追加する

私の場合はpetモデルに追加します。

db
  prefecture_id :integer

③ prefectureモデルを手動で作る

modelのなかにprefecture.rbファイルを直接作成し、中身を記述。

prefecture.rb
class Prefecture < ActiveHash::Base
  self.data = [
      {id: 1, name: '北海道'}, {id: 2, name: '青森県'}, {id: 3, name: '岩手県'},
      {id: 4, name: '宮城県'}, {id: 5, name: '秋田県'}, {id: 6, name: '山形県'},
      {id: 7, name: '福島県'}, {id: 8, name: '茨城県'}, {id: 9, name: '栃木県'},
      {id: 10, name: '群馬県'}, {id: 11, name: '埼玉県'}, {id: 12, name: '千葉県'},
      {id: 13, name: '東京都'}, {id: 14, name: '神奈川県'}, {id: 15, name: '新潟県'},
      {id: 16, name: '富山県'}, {id: 17, name: '石川県'}, {id: 18, name: '福井県'},
      {id: 19, name: '山梨県'}, {id: 20, name: '長野県'}, {id: 21, name: '岐阜県'},
      {id: 22, name: '静岡県'}, {id: 23, name: '愛知県'}, {id: 24, name: '三重県'},
      {id: 25, name: '滋賀県'}, {id: 26, name: '京都府'}, {id: 27, name: '大阪府'},
      {id: 28, name: '兵庫県'}, {id: 29, name: '奈良県'}, {id: 30, name: '和歌山県'},
      {id: 31, name: '鳥取県'}, {id: 32, name: '島根県'}, {id: 33, name: '岡山県'},
      {id: 34, name: '広島県'}, {id: 35, name: '山口県'}, {id: 36, name: '徳島県'},
      {id: 37, name: '香川県'}, {id: 38, name: '愛媛県'}, {id: 39, name: '高知県'},
      {id: 40, name: '福岡県'}, {id: 41, name: '佐賀県'}, {id: 42, name: '長崎県'},
      {id: 43, name: '熊本県'}, {id: 44, name: '大分県'}, {id: 45, name: '宮崎県'},
      {id: 46, name: '鹿児島県'}, {id: 47, name: '沖縄県'}
  ]

  include ActiveHash::Associations
  has_many :pets
end

petモデルにもリレーション追加

pet.rb
class Pet < ApplicationRecord

  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to_active_hash :prefecture

end

④ パラメーターに :prefecture_idを追加

pets.controller.rb
  private

  def pet_params
    params.require(:pet).permit(:name, :prefecture_id)
  end
end

⑤ viewを追加

<%= form_with model: @pet, url: pets_path, method: :post, local: true do |f| %>
  <%= f.label :"都道府県" %>
  <%= f.collection_select :prefecture_id, Prefecture.all, :id, :name, prompt: "-- 選択してください--" %>
<%= end %>

完成です!!:blush:

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

Githubにpushするまで

GitHubへPushするまでにかなり手こずったので
また同じようなことにならない為に記録しときたいと思います。

環境

  • macOS Big Sur (11.1)
  • エディター VSCode
  • GitHubアカウントは登録済み
  • 管理しているプロジェクトはrailsで実装済み

1.リポジトリの作成

  • GitHubでRepositoriesのNewをクリック
  • Repository nameで好きな名前(作っているアプリの名前)を入力
  • Public(公開) Private(非公開)はお好きに決めてください
  • create Repositoryでリポジトリの作成完了

2.VSCodeでinitからcommitまで

  • フォルダを開くをクリックしてpushしたいプロジェクトを開き、下のコードを入力
ターミナル
$ git init
$ git add -A
$ git commit -m "コメント"

add -Aで現在のディレクトリ以下の全ファイルを選択
commit -mでコミットメッセージを残す

これでpushする準備が完了!

3. push

ターミナル
$ git remote add origin https://github.com/ユーザー名/リポジトリ名_app.git
$ git push -u origin master

最後

これでpushが出来ました。
またこれで出来なくなったら編集し直そうと思います。
何か他にアドバイス、ご指摘あればお願い致します。

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

Chart.jsで円グラフを書く

chart.jsでデータベースの値から簡単な円グラフを作成
スポーツの大会でよくある勝敗確率のグラフ(最高100%という設定)
スクリーンショット 2021-01-20 16.27.41.png

データベース

forecastsテーブル
numはwin_schoolの勝利確率
win_schoolは勝利予想されているチーム
lose_schoolは負け予想されているチーム

id num user_id win_school lose_school
1 60 1 A学園 B学園

コントローラー

idが1のデータを取ってこれる設定

def show
  @forecast = Forecast.find(params[:id])
end

htmlファイル

<div class="win"><%= @forecast.win_school %></div><!-- A学園 -->
<div class="lose"><%= @forecast.lose_school %></div><!-- B学園 -->
<div class="win-rate"><%= @forecast.num %></div><!-- 60 -->
<div class="pie"><!-- グラフ描画部分 -->
  <div style="width:60%; height:60%";><canvas id="PieChart"></canvas></div>
</div>
<%= javascript_pack_tag 'forecast' %> <!-- js読み込み -->

非表示にさせたい場合はhiddenを使うと良いでしょう

<div hidden class="win">

jsファイル

app > javascript > packs > forecast.js

var ctx = document.getElementById("PieChart");
var win = document.getElementsByClassName("win").textContent //クラス名winから値を取得(A学園)
var lose = document.getElementsByClassName("lose").textContent //クラス名loseから値を取得(B学園)
var winnum = document.getElementsByClassName("win-rate").textContent  //クラス名win-rateから値を取得(60)
var PieChart = new Chart(ctx, {
  type: 'pie', //グラフのタイプは円グラフです、という意味
  data: {
    labels: [win,lose], //円グラフのラベル(円グラフのA学園,B学園と表示されている箇所)
    datasets: [{
      backgroundColor: [ //円グラフの色
        "#FF0000", //1つめの色(ラベルwin)は赤
        "#33CCFF", //2つめの色(ラベルlose)は水色
      ],
      data: [winnum,100-winnum] //グラフの値 下記参照
    }]
  },
  options: { //オプションでカスタマイズ?
    title: {
      display: true,
      text: '勝利確率'//グラフのタイトル
    }
  }
});

data:でグラフに値を挿入しています
変数winnumには60が入っているので60,2つめは100-60で40が入るので
A学園の勝率60%,B学園の勝率40%ということになります

data: [winnum,100-winnum]

今回はhtmlファイルから値を取得しました
コントローラーから取得したい場合は、gonを使えば良いと思います

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

Railsのコントローラでparamをhashに変換する方法(随時更新)

params[:some_key]がStringだったり、ActionController::Parametersだったりしてややこしいので。

class MyController < ApplicatinoController
  def my_action
    #
    hash_of(params[:some_key]) # 呼び出し
    #
    head: :no_content
  end

  private
  def hash_of(param)
    case param
    when Hash              # 本当にhashがあり得るのかは調べてない。
      param
    when String
      JSON.parse(param)    # JSON stringじゃなかったらエラーを吐く
    when ApplicationController::Parameters
      param.to_unsafe_h
    end
  end
end

参考

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

rails 学習 ActiveModelを用いた検索機能の実装方法

form_withを使って検索機能を追加する

admin/article/index.html.slim
li
  = form_with model: @search_articles_form, scope: :q, url: admin_articles_path, method: :get, html: { class: 'form-inline' } do |f|
    => f.select :category_id, Category.pluck(:name, :id) , { include_blank: true }, class: 'form-control'
      .input-group
      = f.search_field :title, placeholder: "タイトル", class: 'form-control'
        span.input-group-btn
        = f.submit '検索', class: %w[btn btn-default btn-flat]

今回検索用にform_withを使い検索機能を実装する。
今回はここに著者、タグ、本文の検索機能を追加する。

まず著者、タグ、本文を記入できるようにform_with内に記入していく

admin/article/index.html.slim
li
= form_with model: @search_articles_form, scope: :q, url: admin_articles_path, method: :get, html: { class: 'form-inline' } do |f|
  => f.select :category_id, Category.pluck(:name, :id) , { include_blank: true }, class: 'form-control'
  => f.select :author_id, Author.pluck(:name, :id) , { include_blank: true }, class: 'form-control'
  => f.select :tag_id, Tag.pluck(:name, :id) , { include_blank: true }, class: 'form-control'
  .input-group
    = f.search_field :title, placeholder: "タイトル", class: 'form-control'
  .input-group
    = f.search_field :body, placeholder: "本文", class: 'form-control'
    span.input-group-btn
      = f.submit '検索', class: %w[btn btn-default btn-flat]

form_witn内の

model: @search_articles_form

の部分でSearchArticleFormクラスに以下の情報(category_id,title)を入れてインスタンスを作成するという処理になっていることがわかる。

しかしSearchArticleFormはDBに保存する必要のないクラスのなのでActiveRecordは使うことができない。つまり

$ article.title

などのメソッドは使えなくなる

ActiveRecordの復習

ActiveRecordとは簡単に言えばRubyとDBの翻訳機みたいなものである。
RubyとDBはそれぞれ言語の種類が違うがActiveRecordを使うとその二つをつなぐ役割になる
例えば

$ article.title

とするとActiveRecordによりDBのなかのarticleのtitleが選ばれることになる。

今回はDBが必要ない、つまりActiveRecordが使えず、article.titleなどの便利なメソッドを使うことができない。

ActiveModel

ActiveRecordを使えない(DBを使って処理を行わない)時、便利になるのがActiveModelである。DBを使っていないクラスにActiveModel::ModelをincludeすることでActiveRecordと同じようにコードをかけるというもの。つまりarticle.titleのように便利なメソッドを使うことができる。

ActiveModelの使い方

class SearchArticleForm
  include ActiveModel::Model
end

これでarticle.titleなどを使える準備は整った。(まだ使えるわけではない)

さらにここからarticle.titleのようなコードのtitleの部分に何を使えるようにするかを決める今回の場合だとカテゴリー、著者、タグ、タイトル、本文の5つの検索機能が必要なので
SearchArticleFromクラスの「カテゴリー、著者、タグ、タイトル、本文」の5つの属性が必要になる。

class SearchArticleForm
  include ActiveModel::Model
attr_accessor :category_id, :integer
attr_accessor :author_id, :integer
attr_accessor :tag_id, :integer
attr_accessor :title, :string
attr_accessor :body, :string  #body自体はtextだがbodyを探す場合の文字の種類はstringになる
end

attr_accessorを使うことでDBを使わないRubyの属性作ることができる
しかしattr_accessorもActiveModelで書き直すことができる

class SearchArticlesForm
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :category_id, :integer
  attribute :author_id, :integer
  attribute :tag_id, :integer
  attribute :title, :string
  attribute :body, :string
end

ActiveModel::Attributesと記入することでActiveRecordで記入している形式と全く同じ形で書くことができる。

これで

$ SearchArticlesForm.category_id
$ SearchArticlesForm.author_id
$ SearchArticlesForm.tag_id
$ SearchArticlesForm.title
$ SearchArticlesForm.body

といった形でメソッドが使えるようになる

search機能の追加

ActiveModelを使うことができるようになったので、次にcategory_id author_id tag_id title bodyの検索機能を上記のメソッドたちを使ってsearchメソッドを作っていく。

class SearchArticlesForm
  def search
    relation = Article.distinct

    relation = relation.by_category(category_id) if category_id.present?#categoryの検索機能
    relation = relation.by_author(author_id) if author_id.present?     #authorの検索機能
    relation = relation.by_tag(tag_id) if tag_id.present?             #tagの検索機能

    title_words.each do |word|
      relation = relation.title_contain(word)   #titleの検索機能
    end
    body_words.each do |word|
      relation = relation.body_contain(word)    #bodyの検索機能
    end
    relation
  end

  private

  def title_words
    title.present? ? title.split(nil) : []
  end

  def body_words
    body.present? ? body.split('') : []
  end
end

解説① distinct

relation = Article.distinct

discountを使うと重複するArticleを1つにまとめることができる

解説② by_category

relation = relation.by_category(category_id) if category_id.present?

省略せずに記入すると

relation = Article.distinct.by_category(category_id) if category_id.present?

となる
distinctで重複を調べるための範囲をby_category(category_id)で指定している
範囲の指定なのでby_category(category_id)はscopeでの範囲指定であるということがわかるscopeが書いてあるarticle.rbを見てみると

article.rb
  scope :by_category, ->(category_id) { where(category_id: category_id) }
  scope :title_contain, ->(word) { where('title LIKE ?', "%#{word}%") }

となっている。
上記のcategoryのスコープは「category_idの範囲」を指定している。
titleのスコープは「wordに入った文字がtitleに含まれている範囲」を指定している。
著者、タグ、本文のスコープも付けないといけないのでつける

article.rb
scope :by_category, ->(category_id) { where(category_id: category_id) }
scope :by_author, ->(author_id) { where(author_id: author_id) }
scope :by_tag, ->(tag_id) { joins(:tags).where(article_tags: { tag_id: tag_id }) }
scope :title_contain, ->(word) { where('title LIKE ?', "%#{word}%")   
scope :body_contain, ->(word) { joins(:sentences).merge(where('sentences.body LIKE ?', "%#{word}%")) }

by_tagとbody_containの書き方だけ違う。
それはArticle自信が持っている属性(メソッド)によるものである。
Articleはcategoryとauthor、titlesの属性は持っているが、tagとbodyの属性は持っていない。つまりArticle.tagやArticle.bodyはできない。Articleとtagに関しては多対多の関係なのでarticle_tagを介して、bodyはsentenceを介している。
そのためその2つに関してはjoinsを使いscopeをかけるようにしている。

解説③ title_words

title_words.each do |word|
  relation = relation.title_contain(word)   #titleの検索機能  
end

private

def title_words
  title.present? ? title.split(nil) : []
end

title_words以下の文は三項演算子であり、このように置き換えられる

def title_words
  if title.present?
    title.split(nil)
  else
    []
  end
end

split(nil)で半角スペースのある単語は分割して調べることができる

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

Rails バリデーション書き方

アウトプットでRailsでのバリデーションの書き方を載せます。

データが正しいかどうかを検証するのがバリデーションになります。

まずモデルにて

user.rb
class User < ApplicationRecord

  validates :nickname, presence: true

            :nickname  #カラム名

                       presence: true  #空でない事

次にコントローラ

user_controller.rb
class UsersController < ApplicationController

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if
      @user.valid?   #ここ!!ここ!!!
      @user.save
      redirect_to root_path
    else
      render 'new'
    end 
  end

    private

  def user_params
    params.require(:item).permit(
      :nickname
  end
end

valid?メソッドでバリデーションが実行。バリデーションが通ればtrueを返します。

流れとしてはnewアクションで生成したものをcreateアクションで保存する時に値が入ってるのかをバリデーションで確認します。
if文を使い入っていれば@user.saveで保存。空ならrender 'new'でアラートを出す。
,
,

user.html.haml
= f.text_field :nickname, class: "nickname"
.error-messages
  = @user.errors.full_messages_for(:nickname)[0]

ビューには、保存するnicknameと、エラー文の表示の = @user.errors.full_messages_for(:nickname)[0]書けば完成。

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

【読書感想】アプリケーションコンフィグの設定パターン 銀座Rails #27

こちらを読んだ感想。
https://speakerdeck.com/morimorihoge/apurikesiyonkonhuigufalseshe-ding-patan-yin-zuo-rails-number-27

詳細

"12 Factor App"

コンテナ化されたモダンなインフラ環境でまさに参考にできることが多い

これは普段から実感することが本当に多い。12 Factorから外れてるRailsアプリは、コンテナ化がしんどいとも言える。

環境変数

便利だけど、数増えてくると辛い。サービスを分ける指標にもできそう。

デフォルト値問題

いい感じに開発環境のデフォルトを設定できていると、皆幸せ。

まとめ

"設定変更方法による違い"を見るとどれを選択すべきかわかりやすい。
頻繁に変わったり、環境ごとに変わるものは構成管理からは外すの良いと思う。

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

Rails がオワコンwww

はじめに

※ この文章はベータ版です。

Yahoo!知恵袋にあった間違いだらけのベストアンサーがあまりにも...だった
ので書いています。
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q10236599945

このベストアンサーの問題点

著者が、よく知りもしない情報について、きちんと調べもせず、以前、聞き齧ったorどこかで
読んだような気がする、うろ覚えの情報をベースに、回答しているようにしか見えない。

自分がうろ覚えの情報で回答することで、ネット上に誤情報をばら撒く可能性を考えていない
時点で、自分はこの著者が技術屋or技術屋を目指す人間なら、アウトと評価します。

(この著者、「はず」「と思う」を多用することで、うろ覚えの情報で回答していますよアピールを
している気が、、、うろ覚えの情報で回答していることに、何ら問題を感じていないようにみえます、
自分のうろ覚えの情報が間違っていた時のための予防線を張っているようにも。。。

「はず」「と思う」が多用されていても、質問者や読者が正しい情報と誤認識する可能性があります、
「はず」「と思う」を付けたからと誤情報かもしれない、うろ覚えの情報で書いてもいい、と考える
のは間違いです。

この質問の質問者は、うろ覚えで書かれた誤情報を正しい情報と、誤認識してしまっている気がします。)

ネット上で書く以上、うろ覚えの情報で書くのではなく、自分のうろ覚えの情報が正しいか、
ネット上で最新情報をキャッチアップし、その情報と照らし合わせて検証してから、正しい情報
をベースに書くべきです、エンジニアなら尚更。Qiita etcに投稿する際も同様です。

(この質問、2021/01/01 に投稿され、その日のうちに締め切られいて、
間違いだらけのベストアンサーを別の回答で正すこともできない状態に
なっています。

うがった見方をすれば、質問の投稿者とベストアンサーの著者が同じ人間で、
わざと間違った情報をネット上に意図的に晒している、とも受け取れます。)

Ruby自体、時代にあわせた変化や進化はもうない(と思います)

Ruby3.0.0で、
型チェック用基盤の RBS/TypeProfと、
並行・並列処理の Ractor & Fiber Scheduler
が入ってますけど。。。

まだ実験的機能ではあるものの、Ruby2.6からJITが入り、Ruby3.0でも
改良が続けられてますが、、、時代の要請に合わせた新機能ですよね、、、
言語仕様ではなく実行環境の新機能ですけど。。。
(3.0.0でも、Railsとの組み合わせでパフォーマンスがおちる場合がまだ
多々あり、実戦投入できるレベルには達していないという判断?でデフォルト
ではoffになっています。)

Ruby3.0.0、他にも新機能あります。
https://techlife.cookpad.com/entry/2020/12/25/155741

「Ruby 新機能」でググれば、すぐにキャッチアップできる情報。。。

Ruby原作者のMatz氏が発言していたのは、構文の大きな変更はしない、
であって、、、言語の大きな変更はしない、ではない、です。

Matz氏は、公式に、Rubyを時代にあわせて変化・進化させていくニュアンスの
発言をしています。

(Rubyは、1.8→1.9→2.0(8年近く前の2013/02にリリース)、で、かなり大きな
痛みを伴う変更が入って、実行環境の実装もそれまでのインタープリタから
バイトコード・インタープリタに置き換えられています。2.xへの移行は、Railsが
割と短期間で2.xに対応したこともあり、わりと短期間(数年?)で完了したように
感じています。

Pythonは 2.x→3.x で、多岐に渡る、かなり大きな?変更が入って、
3.0が12年ちょい前の2008/12にリリースされましたが、2.7が2020/01に
EOF(End Of Life)になったにもかかわらず広く利用されていて、
3.xへの移行は完了していません。

例えば、Google Chromeのビルドツールであるgnは未だに2.7?で
書かれたままで、3.xに移行する気配はないようです。

Perlは5.x→6.0で大きすぎる痛みを伴う変更が計画?されましたが、
6.0の開発は何年も停滞して、ある程度、実装が進んだ頃に、結局、
Perl6.0→Rakuとリネームされ、別の言語になりました。

言語仕様への大きな変更は、大きな痛みを伴う(ユーザーに大きな痛みを
強いる)ので、ユーザーの要望に応える形での変更でもない限り、
ユーザーに痛み以上のメリットを提示できないと、移行が進まないよう
です、ので、仕様の策定者は実施前に熟考に熟考を重ねる、と共に
ユーザーの声を聞く必要があるようです。)

「Railsはオワコン」の問題点

上記のベストアンサーのように、聞き齧った、どこかで読んだ気がする、うろ覚えの情報
を基に「Railsはオワコン」と、書かれたり、言われたり、していることが多い気がします。

Ruby / Rails はオワコンなのか?

Rubyでは、Rails登場以前に、これといったウェブ・アプリケーション・フレームワーク(WAF)
がなく、Rails登場 により、はじめて、RubyがWEBアプリ開発の選択肢の一つになりました。

Railsの完成度が初期から割と高く、時代の変化に対応しながらバージョンアップしてきたこと
もあり、Rails以外のWAFが登場しても、Railsを超えるorRailsと並ぶ完成度のモノはなかった
ので、RailsがRubyのWAFのデファクトであり続けてきました。

デファクトであるRailsによるエコシステムが醸成され、WAFの再発明をしたり、他のWAF用の
ライブラリを開発するよりも、Rails用のライブラリを開発したほうが世の中で広く使われる
状況になっているので、今もRails用のライブラリが多数開発され続けています。

PHPやPythonのように同じような完成度のWAFが多数あると、同じ用途用のライブラリが
WAF毎に存在し、GitHubでのプロジェクトの数も必然的に多くなります。

Rubyの場合、WAFの開発まわりがほぼRailsに集約されているので、同じ用途のRails用
ライブラリが複数あっても、PHPやPythonに比べてWAF関連のGitHubプロジェクト数は
少なくなっています。

なので、Ruby / Rails のGithubでのプロジェクト数が PHP / Python と比べて少ない
ことを「Ruby / Railsはオワコン」の根拠の一つに挙げるのはナンセンスですね。

ちなみに、みんな大好き?GitHub、Rails (Ruby) で構築されています。
https://www.publickey1.jp/blog/19/githubrails_69.html

昨日(2021/01/19)、Google Cloud FunctionsでのRubyのサポートが発表されています。
https://www.publickey1.jp/blog/21/google_cloud_functionsruby.html

つづく&推敲中...

Rails のフロントエンドまわり

Railsのフロントエンドまわりがレガシーにみえることが「Ruby / Rails オワコン」
の根拠の一つになっている気がするので、書いてみます。

今でも偶に、remote: true とjs.erbの組み合わせでAJAX化するコードを、
Qiita etcの新しい記事で見かけますが、昔、書かれたコードをメンテナンス
している人でもない限り、Railsのプロジェクトで見掛けることは、ほとんど、
なくなっています。

Railsに含まれている機能のみ?でフロントエンドを構築するのは、最早、最適解
ではなく、レガシーなスタイルになっています。

Webpacker経由で、Vue.js、React、etcのクライアントサイドJSフレームワーク
と組み合わせて、フロントエンドを構築することが主流になっています。

DHHは先月、Turbolinksの発展系Turbo(JS)、クライアントJSフレームワーク
Stimulus2.0、とRailsを組み合わせた、新たな仕組み、Hotwire を発表しました。
現在はベータ版です。

Hotwire
https://hotwire.dev
速報: Basecampがリリースした「Hotwire」の概要
https://techracho.bpsinc.jp/hachi8833/2020_12_24/102368

HowireがRailsの1機能になるのか、Railsとは別に提供されるのか、は現在不明です。
たぶん、Railsとは別に提供されます。
(Turbo、TurbolinksがTurboの発表にあわせメンテナンス・モードに入ったため、
既存のTurbolinksを利用したRailsアプリのためにTurbo Drive(Turbolinksに相当)が
Railsに含まれる必要があるので、Railsに含まれるようになるはずです。)

Railsに含まれている機能のみ?で構築されたレガシーなフロントエンドを低コストで
モダンにできることを売り?にしているようです。

Railsしか触ったことのないエンジニアの多いプロジェクトで、Hotwire、流行る、
と思います。

推敲中&つづく...

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

M1のmacに変えたらbundle installできなくなった件

先日、M1のmacbook proが届いて「さぁこれでairにイライラする生活ともおさらばだ!」とウキウキで環境構築していたらbundle installの段階で謎のerrorが出て余計にイライラすることになりました。

「新年早々...」と思いつつ色々調べた結果、なんと解決できたのでメモ程度に共有させていただきます。

エラーについて

※すぐにエラー内容が開けなかったため概要だけにさせてください。覚えていたら追記します。

エラーで詰まるまで以下の手順を踏みました。
・githubからrailsプロジェクトをローカルにclone
・bundle install
そして、エラーを起こしたgemは私が確認した限りでは以下のものでした。

- ffi
- sassc

解決方法

私の環境では以下の手順で解決しました。

Finder>アプリケーション>ターミナル(右クリック)>情報を見る>「Rosettaを使用して開く」にチェック

まとめ

これは他の調べものをしていた際にたまたま見つけた解決方法でした。
一応、以下に本件に関連のありそうなappleのサポートページを載せておきます。
Mac に Rosetta をインストールする必要がある場合

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

【Rails】フォームの追加・削除(cocoon)

RailsでWebアプリケーションを作成するとき、入力フォームの追加を行いたかったので、以下に手順をまとめてみました。
「cocoon」というgemを使用すると、簡単に作成可能ですので、今回はそちらの方法で進めていきます。

ゴールのイメージ

ますは、今回のゴールのイメージを見てみましょう。

Image from Gyazo

アジェンダ

  1. テーブル設計
  2. cocoon導入
  3. モデル作成
  4. コントローラー作成
  5. ビュー作成

テーブル設計

「Recipe」を親テーブルとし、その子テーブルとして「RecipeIngredient」と「HowToMake」を用意する。
ここでの親テーブルと子テーブルは1対多の関係。
image.png

cocoon導入

cocoonを導入するためには、jQueryが導入されている必要がある。

Gemfile
gem 'cocoon'
gem "jquery-rails"
$ bundle install
application.js
//= require jquery
//= require rails-ujs
//= require turbolinks
//= require_tree .
//= require cocoon

//= require jquery//= require cocoonを追記。
上記の順番も大事らしい。

モデル作成

$ rails g model Recipe user:references title:string catchcopy:text no_of_dish:string image:string
$ rails g model RecipeIngredient recipe:references ing_name:string quantity:string
$ rails g model HowToMake recipe:references explanation:text process_image:string order_no:integer

マイグレーションの実行

$ rails db:migrate
recipe.rb
class Recipe < ApplicationRecord
  belongs_to :user
  has_many :recipe_ingredients, dependent: :destroy
  has_many :how_to_makes, dependent: :destroy
  accepts_nested_attributes_for :recipe_ingredients, :how_to_makes, allow_destroy: true
end
recipe_ingredient.rb
class RecipeIngredient < ApplicationRecord
  belongs_to :recipe
end
how_to_make.rb
class HowToMake < ApplicationRecord
  belongs_to :recipe
end

「recipe」と「recipe_ingredients」「how_to_makes」は一対多の関係なので、has_manyを使用

accepts_nested_attributes_for

accepts_nested_attributes_forを使用することで、指定したモデルのデータを配列としてパラメーターに含めることができる。(以下でも説明します。)
つまり、「recipe」と「recipe_ingredients」「how_to_makes」モデルのデータをまとめて保存できるようになる。

参考:https://qiita.com/seimiyajun/items/dff057b3eb40434d5c27

dependent: :destroy

dependent: :destroyを指定したクラスが削除された場合、dependent: :destroyを設定したモデルのインスタンスも削除される。

今回の場合、
Recipeが削除された場合、RecipeIngredientsHowToMakeのインスタンスも削除される。

参考:https://qiita.com/eitches/items/1ad419dc705f807735e0

コントローラー作成

$ rails g controller recipes

コントローラー内編集

recipes_controller.rb
class RecipesController < ApplicationController

  def new
    @recipe = Recipe.new
    @recipe_ingredients = @recipe.recipe_ingredients.build ##親モデル.子モデル.buildで子モデルのインスタンス作成
    @how_to_makes = @recipe.how_to_makes.build
  end

  def create
    @recipe = Recipe.new(recipe_params)
    if @recipe.save
      redirect_to root_path
    else
      render :new
    end
  end

  private

  def recipe_params
    params.require(:recipe).permit(:title, :catchcopy, :no_of_dish, :image, 
                                  recipe_ingredients_attributes:[:ing_name, :quantity, :_destroy], 
                                  how_to_makes_attributes:[:explanation, :process_image, :order_no, :_destroy])
  end
end

accepts_nested_attributes_forで指定したrecipe_ingredientsモデルを
recipe_ingredients_attributes: []として一緒に追加して送ることができる。
how_to_makes_attributes: []も同様。

_destroyを入力することで、削除用のパラメータを受け入れられるようにする。

ビュー作成

長くなるため今回のフォームに関係ない部分は省いております。

recipes/new.html.erb
<div class="recipe-post">
  <%= form_with(model: @recipe, local: true) do |f| %>

    <div class="recipe-ingredients">
      <div class="mx-auto">
        <%= f.fields_for :recipe_ingredients do |t| %>
          <%= render "recipes/recipe_ingredient_fields", f: t %>
        <% end %>
      </div>

      <div id="detail-association-insertion-point"></div>

      <div class="col-10 mx-auto mt-2">
        <%= link_to_add_association "+フォームを追加", f, :recipe_ingredients,
        class: "btn btn-secondary btn-block",
        data: {
        association_insertion_node: '#detail-association-insertion-point',
        association_insertion_method: 'after'
        }%>
      </div>
    </div>

  <% end %>
</div>

それでは、以下で説明していきます。

<%= f.fields_for :recipe_ingredients do |t| %>
  <%= render "recipes/recipe_ingredient_fields", f: t %>
<% end %>

fields_forによってform_with内で異なるモデル(今回はRecipeIngredient)を編集することができるようになる。
renderrecipe_ingredient_fields(ファイル名)に飛び、ここで動的に追加するフォームの中身を記載する。(以下で説明します。)

<%= link_to_add_association "+フォームを追加", f, :recipe_ingredients,
  class: "btn btn-secondary btn-block",
  data: {
  association_insertion_node: '#detail-association-insertion-point',
  association_insertion_method: 'after'
  }%>

link_to_add_associationによってフォームが追加される。

association_insertion_node: '#detail-association-insertion-point'
association_insertion_method: 'after'
によってフォームの表示位置を指定。
<div id="detail-association-insertion-point"></div>に代入されます。

_recipe_ingredient_fields.html.erb
<div class="nested-fields">
  <div class="row mx-auto">
    <div class="col-5"><%= f.text_field :ing_name, class: "form-control", placeholder: "材料" %></div>
    <div class="offset-1 col-2"><%= f.text_field :quantity, class: "form-control", placeholder: "分量" %></div>
    <div class="offset-1 col-1 px-0 w-auto">
      <%= link_to_remove_association "削除", f, class: "btn btn-secondary btn-block" %>
    </div>
  </div>
</div>

ファイル名は_モデル名_fields.html.erbで固定。
このパーシャルにnested_fieldsというクラスを設定してフォーム内容を囲む必要あり。
link_to_remove_associationによってフォームの削除ができる。

参考:
https://qiita.com/matata0623/items/8868a7fcb6ec0817d064
https://qiita.com/obmshk/items/0e942177d8a44091bf09

補足や修正点などありましたら、是非コメントお願いします!!

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