20210118のRailsに関する記事は19件です。

deviseコントローラーで出逢ったsuperについて調べてみた

はじめに

ユーザー管理でdeviseを使っています。コントローラーをいじっていると、メソッドの中にsuperとだけ記述があるメソッドがあったので、疑問に思い、調べてみました。

deviseのコントローラー

rails g devise:controllers user

でコントローラーを生成しています。

user/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
  (略)
  def update
    super
  end
  (略)
end

どうやら、Devise::RegistrationsControllerクラスから継承されているようです。
いくつかのメソッドの中にsuperが存在します。

superとは

ずばり、superは、継承元のメソッドを呼び出すことができるメソッドです。
上記のコントローラーを例にすると、
Devise::RegistrationsControllerにもupdateメソッドが記述されており、
このUsers::RegistrationsControllerでも同じ処理をすることができるということです。

他の例では…

以下は調べている中で、よく見かけた説明です。

class Tennis
  def ball
    puts "Tennisball"
  end
end

class Sports < Tennis
  def ball
    super
    puts "balls"
  end
end

sports = Sports.new
sports.ball
#=>Tennisball
sports = Sports.new
sports.ball
#=>balls

自分で例えとして作ったら、意味不なコードになりました…
要は、継承後に同じ名前でメソッドを作っても、superを使えば、オーバーライドする前の処理を呼び出すことができるということです。

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

Railsで個人開発してたら、Railsがめっちゃ叩かれて辛みだった話

こんにちは、だむはです。

去年の12月に個人開発している「sister」というサービスをリリースしたのですが、開発中にRails叩きがおこり、辛みだったので、その時のことをかこうと思います。ちなみに、サービスはまだベータ版です。

「sister」ってどんなサービス?

「sister」は一言で言うと、IT業界に特化した女性向けのキャリア/スキルシェアサービスです。MENTAから多大なる影響を受けています。MENTAは「師弟関係」、sisterは上下関係をもたない「姉妹関係」と言う部分でシスターフット大切にしていこう!って感じです。

興味があったらみてってください。
あなたのsisterを探そう

環境

sisterの開発はこんな感じです。

・Ruby
・Rails
・Stripe
・AWS S3
・Heroku
ちなみにローカルはDockerです。

はい、フロントエンドにモダンな技術も使ってないし、インフラもAWS使ってないです

なぜこの環境なの?

それは、純粋に私一番使える言語がRailsだったからです!
Herokuはデプロイ楽だし、お金の管理がしやすいからです!
理由はこれだけ!

開発期間

2020年9月〜12月の約4ヶ月くらいです。
実はもともとポートフォリオとして、少しだけ作って放置していたものをベースに作りました。

作業量としてはこんな感じです。
・仕事終わりに3時間くらい週3
・土日4−5時間(やったりやらなかったり)

だらだらやっても終わらんと思って、12月に入り、今年中にリリースすると決めてからは、ほぼ毎日夜中まで実装してました。

事件は突然起きる、、、

12月の中旬くらいに突如、Twitterのトレンドに「Rails」があがったのです。
Twitterで定期的に起こる、Railsフルボッコ現象でした。。。

不安になる私

実はsister開発中も何回か「Railsはオワコン」とかの記事を見ていたので、私も、フロントエンドにReactで、Firebaseを導入しようとしてみたり、AWSに環境構築しなおそうかなとか考えてみたり、いろいろとTryしてみようとしたのですが、全て諦めました。

諦めた理由は、時間をかければ取り入れられますが、私が技術力不足で新しいことを取り入れようとすると、インプットにかなり時間をとられるので、サービスリリースを最優先しようと判断したからです。

しかし、、、
12月中旬、、、
Twitterのトレンドに「Rails」があがるほどのフルボッコ、、、

さすがに、うわーやばいのかなー、、Railsで開発するのやばいのかなーーと不安が再熱してしまいました。

私はどっちかというと、技術にめっちゃ興味がある方ではないのですが、やっぱりエンジニアとして、モダンな技術は使いたいし、sisterをリリースしたときに「どんな技術使ってるんですか?」と聞かれたら、モダンな技術つかってますって言ってみたい。

私は立ち止まって考えた

私は立ち止まって考えてみました。

自分は何がしたいんだ?目的はなんだ?
個人開発は技術力向上が目的なのか?
新しい技術を使うために始めたのか?

違うだろ!!
自分が作りたいサービスがあって、それを世にリリースしたいからだろ!!

と、本来の目的を思い出しました。

ちゃんと説明すると、
「モダンな技術を身につけることではなく、サービスをリリースすることがいちばんの目的」
ということです。

そして私はリリースまで駆け抜けた

12月中旬の出来事だったので、ここで新しい技術を導入することはリリースを遅らせることにつながるのもありましたし、サービスをまずリリースすることが目的だったので、雑念を消して、寄り道せず、リリースまで駆け抜けることができました。

こうして、「sister ベータ版」は無事、12月中にリリースされたのでした。

エンジニアと個人開発

「sister」開発中は、エンジニアとしての自分と個人開発者としての自分が葛藤していました。
もちろん、個人開発者はエンジニアなんですけど、違う部分としては自分が開発しているサービスをどうしたいのかが最優先なのかなって、個人開発しながら思いました。

エンジニアとしては、モダンな技術使いたい!流行ってる技術使いたい!って気持ちがあるけど、実際ユーザーが使う時って、裏で何使ってるか全く気にしてないよね?って部分です。

最後に

現在、「sister ベータ版」はリリースして3週間くらいたっていて、80人以上の方にご登録いただいています。

IT業界に特化した女性向けのサービスと言うこともあり、ターゲット層の母数自体が少ないため、最初は20ー30人くらいかな〜と予想していたのですが、予想より反響があったので、嬉しいです。改修がんばろう!ってなります。

ベータ版としてミニマムで早くリリースすることで、ユーザーの反応を早めにみれたのが、本当良かったかなと思ってます。

結論、どんなときも、何を目的をするかがいちばん大事。
そして、個人開発で学べることは多い!

最後まで、読んでいただきありがとうございました!

おまけ

「sister ベータ版」もよろしくお願いします!
あなたのsisterを探そう

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

【Boostrap】なんで動かないの?jQueryちゃん?〜長い長いエラーとの戦い〜

この三日間悩みまくっていたこと

ポートフォリオを制作してエンジニア業界で働き始めた諸先輩方の投稿とgitのソースコードをいくつか拝見させて頂き、フレームワークを使った方が実装が早くなるということを知ったのですが、
自分のアプリケーションもレスポンシブ化したいということと、デザインセンスが皆無という自分のスペックに早めに見切りをつけ、早々にBootstrapを使用すると決めていました。

先日バックエンドの実装がだいたい終わったので、さあBootstrapと戯れるぞと思い公式で目に止まったものを表示できるかどうか試してみることに。

e7edb6a9d019b4695f901555aefe8930.png

良い感じのデザインですね!
これを表示するには・・・ほほう貼り付ければ良いのか。やってみよう。








33a8a372e4c6cac734cdcaadc337aa54.png

どうしてこうなる??

公式からサンプルのソースコードを取ってきて貼り付けているだけなのにその通りに表示されない。
サンプルになってないやんけ???おかしいんとちゃうか???
そう思いながらもHTMLはしっかりと表示されていることに気付き、おかしいのはCSSだということがわかる。
よく見るとソースコードの部分に変なところがある。

<!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>Carousel Template · Bootstrap</title>

    <link rel="canonical" href="https://getbootstrap.com/docs/4.5/examples/carousel/">

    <!-- Bootstrap core CSS -->        ⬅️①コメントアウトされているここと
<link href="../assets/dist/css/bootstrap.css" rel="stylesheet">

    <style>
      .bd-placeholder-img {
        font-size: 1.125rem;
        text-anchor: middle;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
      }

      @media (min-width: 768px) {
        .bd-placeholder-img-lg {
          font-size: 3.5rem;
        }
      }
    </style>
    <!-- Custom styles for this template -->     ⬅️②コメントアウトのここの部分
    <link href="carousel.css" rel="stylesheet">

〜以下略〜

cssファイルを読みこんでいるところの記述がおかしい。

<link href="../assets/dist/css/bootstrap.css" rel="stylesheet">

assets/dist/css/bootstrap.cssなんてファイルがアプリケーションにない。
別でダウンロードしたサンプルソースコードのファイルを覗いてみると、

bootstrap-4.5.0-dist
├CSS
└ JS

こんな構造になっていた。
CSSの中を覗いてみると

ce2c38efa5418025f28785223a632302.png

bootstrap.cssがある!よしよし
app>assets>styleseetsの中に入れました。
これで解決するかと思いきや何も変わらない。
記述のしかたがRailsのルールになっていないからだと気付き、
<link rel="~~~~
という記述から

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

に変更しました。
コメントアウトされていた②のほうも同じ名前のものがあったので同じ場所に配置。
しかしファイルがあってもなくても特に見た目が変わらなかったのですが、
①で記述した'application'の部分の意味は「このアプリケーションの中の」という意味のようでディレクトリを細かく指定しなくてもきちんと表示されました。
これは推測ですが指定する範囲を広げることで②の部分のコードを削除しても問題ないよ、ということなのではないかなと考えました。

さて解決したところでページ更新をしてみると、今度はJavaScriptが動かない。

問題部分(ページ最下部)

〜以下略〜
</main>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
      <script>window.jQuery || document.write('<script src="../assets/js/vendor/jquery.slim.min.js"><\/script>')</script><script src="../assets/dist/js/bootstrap.bundle.js"></script></body>
</html>

同じ原理で<script src=~~~~がおかしい部分だと思いました。

こんな感じにしてみる

〜以下略〜
</main>
<%= javascript_include_tag 'https://code.jquery.com/jquery-3.5.1.slim.min.js' %>
       <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %></body>
</html>

結果、変わらない。
integrityとcrossoriginを取ってみたりしたけど変化なし。
この二つも調べてみたがあまり関係なさそう。ここで気づけば2時間ぐらいハマる。うーん。

その後、turbolinksが邪魔しているのではないかとのことに辿り着き、gemから削除したりしてからCDN版に出ていたソースコードを貼り付ける。
すると表示された!

<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>

わかったこと

Bootstrap側が求めていることに自分が気付かずにあれでもない、これでもないと時間をかけてしまいました。
解決できたらくだらないことだったなと思うのですがやってる時は必死すぎて、あまり周りが見えていないことが多々あります。
明日からフロント部分を実装していきますが今日感じた変換していくという考え方と、あれこれ試して学んでいくことを忘れずにやっていこうと思いました。

間違っている部分、改善点などありましたら教えて頂ければ幸いです。

参考にさせて頂きました

https://yurutsubu.com/rails-5-x-javascript-does-not-work-well-6745.html#i-3

ありがとうございました。

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

【Rails】raty.jsを使った評価機能を作る

raty.jsを使って、railsアプリに星画像付きの評価機能を実装する方法です。jqueryが必要です。

 2021-01-18 21.35.22.png

環境

$ rails -v
Rails 5.2.4.4

jqueryを導入

Gemfile
gem 'jquery-rails'
$ bundle install
app/assets/javascripts/application.js
//= require jquery

jquery.raty.jsを作成

$ touch app/assets/javascripts/jquery.raty.js

作成したファイルに、https://github.com/wbotelhos/raty/blob/master/lib/jquery.raty.js 内の記述をコピペ。その後application.jsでrequire(jqueryより後に書く)

app/assets/javascripts/application.js
//= require jquery.raty.js

星の画像を配置

https://github.com/wbotelhos/raty/tree/master/lib/images からstar-on.pngstar-off.pngstar-half.pngをダウンロードして、app/assets/images以下に配置。

評価を保存するカラムを持つデータを作成

$ rails g migration addEvaluationToMovie evaluation:float
db/migrate/202101011111.rb
class AddEvaluationToMovie < ActiveRecord::Migration[5.2]
  def change
    add_column :movies, :evaluation, :float
  end
end
$ rails db:migrate

評価投稿画面を作成

今回は例として、お気に入りの映画のタイトルを投稿する画面で、評価も一緒に投稿できるようにしてみます。

app/views/movies/new.html.erb
<h2>お気に入りの映画を投稿</h2>
<%= form_with(model: movie, local: true) do |form| %>
  <div>
    <%= form.label :title %>
    <%= form.text_field :title %>
  </div>
  <div id="evaluate_stars">
    <label>評価</label>
  </div>
  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>
<script>
  $('#evaluate_stars').raty({
    starOn: "<%= asset_path('star-on.png') %>",
    starOff: "<%= asset_path('star-off.png') %>",
    starHalf: "<%= asset_path('star-half.png') %>",
    scoreName: 'movie[evaluation]' //登録するモデル名とカラム名を記述
  });
</script>
app/controllers/movies_controller.rb
def movie_params
  params.require(:movie).permit(:title, :evaluation) # evaluationを追加
end

投稿した評価を表示

ここでは例としてindexページに評価を表示します。

app/views/movies/index.html.erb
<h1>映画一覧</h1>
<table>
  <thead>
    <tr>
      <th>タイトル</th>
      <th>評価</th>
    </tr>
  </thead>
  <tbody>
    <% @movies.each do |movie| %>
      <tr>
        <td><%= movie.title %></td>
        <td class="movie-evaluation" data-score="<%= movie.evaluation %>"></td>
      </tr>
    <% end %>
  </tbody>
</table>
<script>
  $('.movie-evaluation').raty({
    readOnly: true,
    score: function() {
      return $(this).attr('data-score');
    },
    path: '/assets/'
  });
</script>

参考

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

テーブルにカラムを追加する方法

今回はmigrateファイルの記述をして追加する方法を実行します。

migrateファイルを編集

まず、README.mdを参考にmigrateファイルを編集します。

README.md
## items テーブル

| Column            | Type       | Options           |
| ----------------- | ---------- | ----------------- |
| name              | string     | null: false       |
| explanation       | text       | null: false       |
| category_id       | integer    | null: false       |
| state_id          | integer    | null: false       |
| delivery_price_id | integer    | null: false       |
| prefectures_id    | integer    | null: false       |
| delivery_time_id  | integer    | null: false       |
| price             | integer    | null: false       |
| user              | references | foreign_key: true |

### Association

- belongs_to :user
- has_one :purchase
20210118070956_create_items.rb
class CreateItems < ActiveRecord::Migration[6.0]
  def change
    create_table :items do |t|
      t.string :name,                   null: false
      t.text :explanation,              null: false
      t.integer :category_id,           null: false
      t.integer :state_id,              null: false
      t.integer :delivery_price_id,     null: false
      t.integer :prefectures_id,        null: false
      t.integer :delivery_time_id,      null: false
      t.integer :price,                 null: false
      t.references :user,               foreign_key: true
      t.timestamps                      null: false
    end
  end
end

rails db:rollback rails db:migrateの実行

記述が終わったら以下2点の実行
Terminal
% rails db:rollback
% rails db:migrate

最後に
テーブルの確認
60cd92386af9d46922673f58e4fc6d2d.png

これでテーブルの追加が完了です!

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

RSpec~Taskモデルのバリデーションテスト作成まで

RSpecとは

Rubyにおけるテスティイングフレームワークのことです。

Rubyでは標準でMinitestという別のフレームワークがあります。Ruby on RailsチュートリアルでもMinitestでテスト方法を教わります。
ですが、実際に現場で使われているテスティイングフレームワークはRSpecが圧倒的に多いようです。

本記事ではRSpecをインストールし、既に定義されているモデルに対してバリデーションテストするまでを記載します。

使用技術のバージョン

・Ruby: 2.6.4
・Ruby on Rails: 5.2.3

モデルの概要

今回取り扱うモデルはuserモデルとtaskモデルです。

[userモデル]

app/models/user.rb
class User < ApplicationRecord

  has_many :tasks, dependent: :destroy

  validates :password, length: { minimum: 3 }, if: -> { new_record? || changes[:crypted_password] }
  validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] }
  validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] }
  validates :email, uniqueness: true, presence: true

end

[taskモデル]

app/models/task.rb
class Task < ApplicationRecord
  belongs_to :user
  validates :title, presence: true, uniqueness: true
  validates :status, presence: true
  enum status: { todo: 0, doing: 1, done: 2 }
end

今回はtaskモデルのバリデーションをテストするため、userモデルのバリデーションの説明は割愛します。
taskモデルから分かる通り、titleカラムとstatusカラムにバリデーション(制限)がかけられています。

titleカラム
presence: true => 値が必ず格納されていなければならない
uniqueness: true => 格納されている値がユニークな存在でなければならない

statusカラム
presence: true => 値が必ず格納されていなければならない

enumについてですが、列挙型と呼ばれているもので、数値のカラムに対してプログラム上で別名を与えることができます。
statusinteger型が格納されていますが、「0がtodoのステータスで、1がdoingのステータスで、、、、」というように、人間にはstatusの数値が何を表すのか理解しづらいです。そこでDB上では数値で扱うが、プログラマから見たら文字列で扱うというように設定できるenumを使用するのです。詳しくはこちらを参照してください。

導入手順

ここから少し長いですが、1つずつ段階ごとに手順を紹介します。

RSpecとFactoryBotをインストール

RSpecは上述した通りテスティイングフレームワークです。
FactoryBotとはRSpecでテストする時にモデルのレコードを作成しテストで使用することができます。

インストール手順は公式のREADMEの手順を参照しています。
RSpecの公式README
FactoryBotの公式README

Gemfile:development グループと :testグループの両方に rspec-railsfactory_bot_railsを追加します。

Gemfile
group :development, :test do
  gem 'factory_bot_rails'
  gem 'rspec-rails', '~> 4.0.2'
  gem 'byebug', platforms: %i[mri mingw x64_mingw]
end

いつも通りbundle install

$ bundle install

それに加えて、$ rails generate rspec:installを実行

`rails generate rspec:install`
Running via Spring preloader in process 31987
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

これでインストールは完了です。

Task model specのファイルを作成

$ rails generate rspec:model task
Running via Spring preloader in process 32051
      create  spec/models/task_spec.rb
      invoke  factory_bot
      create    spec/factories/tasks.rb

念のためtask_spec.rbのテストが実行可能かを確認しましょう。

$ bundle exec rspec spec/models/task_spec.rb
*

Pending: (Failures listed here are expected and do not affect your suite's status)

  1) Task add some examples to (or delete) /Users/sakidendaiki/Downloads/RUNTEQ/tasks/option_tasks/sample_app_for_rspec/spec/models/task_spec.rb
     # Not yet implemented
     # ./spec/models/task_spec.rb:4


Finished in 0.0053 seconds (files took 1.6 seconds to load)
1 example, 0 failures, 1 pending

taskモデルとuserモデルのFactoryBotのテストデータを作成する

taskモデルのFactoryBotをファイルを作成します。
モデルに対してFactoryBotのファイルを作成する場合、$ rails g factory_bot:model モデル名と記述します。

今回はタスクモデルのテストですので、$ rails g factory_bot:model taskのコマンドを実行してみましょう。

$ bin/rails g factory_bot:model task
Running via Spring preloader in process 28632
   identical  spec/factories/tasks.rb

次にtaskFactoryBotを以下のように記述します。

spec/factories/tasks.rb
FactoryBot.define do
  factory :task do
    sequence(:title, "title_1")
    content { "content" }
    status { :todo }
    deadline { 1.week.from_now }
    association :user
  end
end

上記を定義することで、FactoryBot.create(:task)で上で定義されたtaskモデルを呼び出すことができます。
コンソールで試してみましょう。

$ FactoryBot.create(:task)
   (0.1ms)  SAVEPOINT active_record_1
  User Exists (0.7ms)  SELECT  1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "user_1@example.com"], ["LIMIT", 1]]

 略

=> #<Task:0x00007f92a4342b08
 id: 1,
 title: "title_1",
 content: "content",
 status: "todo",
 deadline: Mon, 25 Jan 2021 07:24:22 UTC +00:00,
 created_at: Mon, 18 Jan 2021 07:24:22 UTC +00:00,
 updated_at: Mon, 18 Jan 2021 07:24:22 UTC +00:00,
 user_id: 1>

sequence(:title, "title_1")は一度FactoryBotを呼ばれると、titleカラムに対し"title_1"という文字列を格納します。
二度目に呼ばれる時は文字列は"title_2"となります。つまり、呼び出されるたびに末尾の数字に1を加えます。

何故このようなことをするかというと、titleカラムにはuniqueness: trueのバリデーションがあるためです。
titleカラムに値がかぶるのを防ぐために、呼び出されるたびにtitleカラムの値を変えています。
sequenceメソッドはこのようにuniqueness: trueがある時に使用されます。

deadlineカラムで定義されている1.week.from_nowは文字通りの意味です。
一週間後の同時刻の時間を出力します。
本当にRailsって理解しやすいですよね。

association :userは外部キー参照と非常に似ています。
taskモデルはuserモデルと1:多の関係にあるので、user_idカラムがあります。
association :userとすることで、FactoryBotのファイルで定義されているfactory :userのテストデータを参照します。

Taskにはuser_idも必要となることを踏まえてuserFactoryBotも作成する必要があります。

bin/rails g factory_bot:model user
Running via Spring preloader in process 29285
      create  spec/factories/users.rb

userモデルのFactoryBotを記述

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    sequence(:email) { |n| "user_#{n}@example.com" }
    password { "password" }
    password_confirmation { "password" }
  end
end

sequenceメソッドの記載方法がtaskモデルで記載した時と違いますが、内容は同じです。
つまり、FactoryBotのファクトリである:userが呼び出されるたびに、nの値がどんどん増えていくことで、emailカラムの値がユニークとなるようにしています。

これでテストデータの準備は完了です!

--format documentation を追記(テスト記述前その1)

RSpecのテストを記述する前に、.rspecファイルに --format documentationを追記しましょう。

.rspec
--require spec_helper
--format documentation

これにより、テストの実行結果をドキュメント形式に変更します。
簡単にいうと、テスト結果が人間に読みやすくなります。

config.include FactoryBot::Syntax::Methodsを追記(テスト記述前その2)

テストを記載する際、テストデータの呼び出し方はFactoryBot.create(:task)や、FactoryBot.build(:task)といったように、必ずFactoryBotと記載しなければいけません。しかし、テストデータを呼び出す際に毎回書くのは冗長的です。
そこでconfig.include FactoryBot::Syntax::Methodsruby:spec/rails_helper.rbに記載します。

spec/rails_helper.rb
config.include FactoryBot::Syntax::Methods

こうすることで、テストデータを呼び出す時、create(:task)というように、FactoryBotの記述を省略できます。

task_spec.rbにモデルバリデーションに関するテストを作成

いよいよテストを記述します。

spec/models/task_spec.rb
require 'rails_helper'

RSpec.describe Task, type: :model do
  describe 'validation' do
    it '全ての属性が適切に格納されていれば有効' do
      task = build(:task)
      expect(task).to be_valid
      expect(task.errors).to be_empty
    end

    it 'タイトルがなければ無効' do
      task_without_title = build(:task, title: "")
      expect(task_without_title).to be_invalid
      expect(task_without_title.errors[:title]).to eq ["can't be blank"]
    end

    it 'ステータスがなければ無効' do
      task_without_status = build(:task, status: nil)
      expect(task_without_status).to be_invalid
      expect(task_without_status.errors[:status]).to eq ["can't be blank"]
    end

    it '同じタイトルが重複していれば無効' do
      task = create(:task)
      task_with_duplicated_title = build(:task, title: task.title)
      expect(task_with_duplicated_title).to be_invalid
      expect(task_with_duplicated_title.errors[:title]).to eq ["has already been taken"]
    end

    it 'タイトルが別名であれば有効' do
      task = create(:task)
      task_with_another_title = build(:task, title: 'another_title')
      expect(task_with_another_title).to be_valid
      expect(task_with_another_title.errors).to be_empty
    end
  end
end

it '〜〜〜〜〜〜' doの箇所にテストの内容が記載しています。
1つ目のテスト'全ての値が適切に格納されていれば有効'は、テストデータをそのまま呼び出しテストしています。
テストデータはバリデーションを考慮して値が定義されているので有効であるはずです。

expect(task).to be_validexpect(task.errors).to be_emptyは直感的に理解できるのではないでしょうか。
変数taskbe_valid(有効)であり、task.errorsbe_empty(空値)であることをテストしています。
もしバリデーションに引っかかっていたらtask.errorsには何かしらのエラーメッセージが格納されているはずです。

expect(task_without_title).to be_invalidexpect(task_without_title.errors[:title]).to eq ["can't be blank"]はどうでしょうか。簡単に推測できると思います。

テスト結果を確認

RSpecでテストします。
$ bundle exec rspecをターミナルに実行するだけです。

$ bundle exec rspec 

Task
  validation
    is valid with all attributes
    is invalid without title
    is invalid without status
    is invalid with a duplicate title
    is valid with another title

Finished in 0.14338 seconds (files took 1.39 seconds to load)
5 examples, 0 failures

バリデーションを削除してテストが失敗するか確認

本当にテストが正しいのかを確認するためにあえてテストを失敗させる方法があります。
例えば、2つ目のテスト'タイトルがなければ無効'が成功するのは、taskモデルのtitleカラムに対してpresence: trueのバリデーションがあるためです。では、presence: trueを削除してみましょう。

  validates :title, uniqueness: true

【テストの実行結果】

Failures:

  1) Task validation is invalid without title
     Failure/Error: expect(task.errors[:title]).to include("can't be blank")
       expected [] to include "can't be blank"
     # ./spec/models/task_spec.rb:12:in `block (3 levels) in <top (required)>'

Finished in 0.10893 seconds (files took 0.7813 seconds to load)
5 examples, 1 failure

Failed examples:

rspec ./spec/models/task_spec.rb:9 # Task validation is invalid without title

成功していたテストが失敗したことで、自分の書いたテストが正しいテストであったことを証明できるのです。

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

railsでほぼ静的なページの作り方

引用先
Railsチュートリアル

※ rails new appをしている状況で新しくページを追加するところからです

ターミナルでコマンド実行

まずは作業ディレクトリへ移動

cd ~

移動後コマンド実行

$ rails generate controller <コントローラ名> <アクション名> <アクション名> ・・・

# 例
$ rails generate controller StaticPages home help

上記コマンドを実行すれば

・コントローラの作成
・ルーティングの設定
・ビューファイルの作成
を自動で作成してくれるので非常に便利かつ簡単ですね!

実際これだけでページは作れちゃいます。

新たにページを追加したいとなった時

例えば、新たにaboutページを作成したいとなった時に、手動でファイルを作成したりコードを書けば追加もできます。

手順

①ルーティングの設定
②コントローラにアクション追加
③ビューファイルの作成

①ルーティングの設定

config/routes.rb
Rails.application.routes.draw do
  get  'static_pages/home'
  get  'static_pages/help'
  get  'static_pages/about' ←追加
end

②コントローラにアクション追加

app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController

  def home
  end

  def help
  end

  def about ←追加
  end       ←追加
end

③ビューファイルの作成
app/views/static_pagesにabout.html.erbを右クリックで新規作成

app/views/static_pages/about.html.erb
<h1>About作成</h1>
・・・
・・・
・・・

これだけでページの追加は完了です。

URLは末にstatic_pages/aboutをつければwebで開きます。
 
(https://~~~/static_pages/about)

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

rails学習 rakeタスク・cron・whenever

課題で行ったrakeタスク・cron・wheneverの使い方とその際のコード記述での覚えておきたいポイントを確認する。

rakeタスク

行いたい処理を定義することにとって、アプリケーションを起動せずに定義した処理を実行することができる

cron

「〇時に〇〇のコマンドを実行」というふうに時間によってコマンドを実行させることができる

whenever

cronをrubyの記述方法にして描きやすくしたもの。
schedule.rbにかいた記述をbundle exec whenever --update-crontabすることによってcronに処理が記述される

rakeタスクの書き方

今回rakeタスクに設定する処理は「公開待ちの中で、公開日時が過去になっているものがあれば、ステータスを「公開」に変更されるようにする」である

①まず、公開待ちの中でというところは「記事が公開待ちつまりArticleのstateがwait_publish」になっているものであるため

Article.wait_publish

②次に①の中から公開日時が過去になっているものを探さないといけない
公開日時が過去ということはその記事のpublish_atの時間がTime.currentよりも少ないということになる
よって'published_at < ?', Time.currentとなる。

これをスコープでまとめたものがmodelに書いてある

scope :viewable, -> { published.where('published_at < ?', Time.current) }

になる。つまりviewableをつけると公開日が過去になっている範囲から探すことができる。
よって公開待ちで公開日時が現在より小さい記事は

Article.wait_publish.viewable

こうあらわすことができる

③stateが公開待ちで公開日時が現在よりも小さい(過去になっている)ものを
公開にする(つまりstateをpublishにする)

Article.wait_publish.viewable.publish

こうすれば公開になると思うがもう一つ重要なポイントがある。
Article.wait_publish.viewableで取得した記事は一つではないということ。
なので1つ1つにpublishをつけていかなければならない

取得した複数の値1つ1つにpublishをつけていく場合
find_eachを使う
find_eachは取得した複数のレコードを繰り返し処理していく
また(&:published!)とすることで1つ1つにpublish!をつけていくことができる。

①②③を合わせて「公開待ちの中で、公開日時が過去になっているものがあれば、ステータスを「公開」に変更されるようにする」というコードは

Article.publish_wait.viewable.find_each(&:publish!)

と書くことができる。
これをlib/tasks/article_state.rakeに記述するとrakeタスクが実行できるようになる

lib/tasks/article_state.rake
namespace :article_state do
  desc '公開待ちの記事で公開日時が過去日付の場合、記事のステータスを「公開」に変更する'
  task change_publish: :environment do
    Article.publish_wait.viewable.find_each(&:publish!)
  end
end

article_stateコマンドをターミナルで実行するだけで処理が実行されるようになった

wheneverの書き方

上記で定義したarticle_stateコマンドを1時間に一回起動するようにcronを動かさなければならない、そのためにwheneverを使う(wheneverは簡単に言えばcronの翻訳機みたいなもの)

db/schedule.rb
# Use this file to easily define all of your cron jobs.
#
# It's helpful, but not entirely necessary to understand cron before proceeding.
# http://en.wikipedia.org/wiki/Cron
require File.expand_path(File.dirname(__FILE__) + '/environment')
# Example:
rails_env = ENV['RAILS_ENV']|| :development
set :environment, rails_env
set :output, "#{Rails.root}/log/cron_log"
# every 2.hours do
#   command "/usr/bin/some_great_command"
#   runner "MyModel.some_method"
#   rake "some:great:rake:task"
# end
#
every :hour do
  rake 'article_state:change_publish'
end

# Learn more: http://github.com/javan/whenever
every :hour do
  rake 'article_state:change_publish'
end

この部分が1時間に一度article_stateのなかのchange_publishが実行されるコードになる
記述が終わったらbundle exec whenever --update-crontabでcronに反映させる

覚えておきたいポイント

enumの書き方・ポイント

enumに新しい情報をつける場合に記述方法に気をつける

article.rb
#変更前
enum state: { draft: 0, published: 1}

#NG
enum state: { draft: 0, published: 1, publish_wait: 2 }
#OK
enum state: %i{ draft published publish_wait }

OKコード方が綺麗で見やすい。綺麗なコード記述を心がけるようにする

enumの値でデータを取得するときに記述が楽になる

# NG
article.state = 'publish_wait'
Article.where(state: :publish_wait)

# OK
Article.publish_wait

enumで定義しているとArticle.whereでわざわざ探さなくてもArticle.publish_waitのようにArticleの後にenumの値を入れるとその値に該当したデータを取得することができるようになる

その他のポイント
・enumで定義されたものだと一目でわかる
・コードがスッキリする

article_controller.rbの良い書き方・ポイント

updateアクションでの書き方を確認する

article_controller.rb
def update
  authorize(@article)                                        #Point.1
   if @article.assign_attributes(article_params)             #Point.2
    @article.assign_publish_state unless @article.draft?     #Point.3
    @article.save!                                           
    flash[:notice] = '更新しました'
    redirect_to edit_admin_article_path(@article.uuid)
  else
    render :edit
  end
end

解説ポイント

Point.1 authorizeについて

Punditというgemを使うと認可機能であるauthorizeが使えるようになる。
arthorize (@article)とコントローラー内で定義するとそのコントローラーが@articleを使えるかどうかを確認してくれるというもの。

今回autherize(@article)@articleが使えると許可されたため、@articleの定義を書かなくても@articleを使えるようになった

Point.2 assign_attributes(article_params)を使う理由

assign_attributes(article_params)メソッドはattributesを変更するためのメソッドである。
一見update(article_params)とほとんど一緒だが一つ異なったポイントがある。それが

assign_attributesはDBにデータを保存しない。updateはDBにデータを保存する

である。
もしupdateを使うと以下のようになる

updateの場合

article_controller.rb
def update
  authorize(@article)                                        
   if @article.update(article_params)                      #updateでDBにアクセス
    @article.assign_publish_state unless @article.draft?     
    @article.save!                                         #saveでさらにDBにアクセス  
    flash[:notice] = '更新しました'
    redirect_to edit_admin_article_path(@article.uuid)
  else
    render :edit
  end
end

assign_attributeの場合

article_controller.rb
def update
  authorize(@article)                                        
   if @article.assign_attributes(article_params)           #DBにアクセスしない
    @article.assign_publish_state unless @article.draft?     
    @article.save!                                         #saveでDBにアクセス  
    flash[:notice] = '更新しました'
    redirect_to edit_admin_article_path(@article.uuid)
  else
    render :edit
  end
end

上記のようにどのみちsaveで一回DBにアクセスしておりupdateは余計にDBにアクセスするので処理が重たくなる原因になる。なので基本的にはupdateメソッドとsaveメソッドが共存することはない。

Point.3 assign_publish_stateについて(if文のなかにif文)

このコントローラーは公開日が現在時刻よりも未来ならば「公開待ち」、公開日が現在時刻または現在時刻よりも過去ならば「公開」にstateを変えるという条件分岐の処理が必要になってくる。

単純に考えれば

article_controller.rb
if @article.assign_attributes(article_params)           
  unless @article.draft? 
    @article.state = if @article.published_at <= Time.current
                       :published
                     else
                       :publish_wait
                     end
  end    
  @article.save!                                         
  flash[:notice] = '更新しました'
  redirect_to edit_admin_article_path(@article.uuid)
else
#省略
end

ifのなかにunlessやさらにifが入っておりコントローラーがわかりづらく行数も増えていってしまう(Fatcontroller)
なので

@article.state = if @article.published_at <= Time.current
                       :published
                     else
                       :publish_wait
                     end

の部分を1つのメソッドとして定義し簡略化を図る

article_controller.rb
if @article.assign_attributes(article_params)           
  @article.assign_publish_state unless @article.draft?     
  @article.save!                                         
  flash[:notice] = '更新しました'
  redirect_to edit_admin_article_path(@article.uuid)
else
#省略
end
app/model/article.rb
def assign_publish_state
  self.state = if self.published_at <= Time.current
                     :published
                   else
                     :publish_wait
                   end
end

rspecのポイント

元のstateから日時によってstateが変わるかどうかのテストがしたい
元のstateが下書きの場合、公開待ちの場合、公開の場合などから条件分岐をやっているとキリがなくなる。なので元のstateはランダムにしておきそこから日時によって変わる条件分岐を記述していった方がコードを書かなくて済む

spec/factories/articles.rb
# 例
trait :random_state_article
  # NG
  state { Random.rand(1..2) }

  # OK
  state { Articles.states.values.sample }
 end

ランダムにするtraitはRandom.rand(1..2)ではなくArticles.states.values.sampleにする

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

Docker+Railsにdotenvを導入して環境変数を管理する

実現したいこと

Dockerを使ったRails開発で、公開してはならないAPIキーやパスワードなどを別ファイルでGit管理する方法。

大雑把な順番

  1. gemを追加
  2. .envファイルにプロジェクトで使う環境変数を記述
  3. .gitignoreに.envファイルを含める
  4. erbテンプレートに従って環境変数を渡す

使用したバージョン

  • Rails6
  • ruby 2.7.2

手順

gem 'dotenv-rails'を追加

Gemfile
gem 'dotenv-rails'

コンテナ内にbundle installを行う

bash
docker-compose run コンテナ名 bundle install 

docker-compose.ymlを編集

yml内にenv_fileと書くことで環境変数を管理するファイルを読み込む設定ができる

docker-compose.yml
code:docker-compose.yml
 web:
    省略
    env-file:
        - .env(環境変数を管理したいファイル名を記述)

コンテナをビルドする

docker-compose.ymlを編集したので改めてビルドする

docker-compose build

.envファイルに設定したい環境変数を書く

.env
 MAP_API_KEY="発行したAPIキー"
 PASSWORD="パスワード"

.envファイルをgitignoreに追加

.gitignore
 /.env

viewのerbテンプレートで環境変数として渡す

index.html.erb
 以上省略
 <script src="https://maps.googleapis.com/maps/api/js?key=<%=ENV['MAP_API_KEY']%>&callback=initMap" async defer></script>

ここで重要なのがerbテンプレートに従って、 <%=ENV['']%>と書くという事。
これをせずに、ENV['MAP_API_KEY']と書いて渡しており、上手くviewで表示できず、2日ほど詰まっていた。

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

params flash sessionの寿命を考えたことがあるか。

sessionとかflashってどれくらいの間データを保持しているんだろう?

railsでウェブサイトを作っている時、定義した変数等は画面を遷移した時に、すぐに消えてしまいます。
@user等のインスタンス変数は一度の画面遷移で死んでしまう経験をしたことが多いと思います。
sessionやflashは長生きすると聞いていたので、寿命を見てみたい!という好奇心から記事を書きました。

実験開始

今回は自分のPFに無理やり作ったので少し見にくくなっています。

①コントローラにsession flash paramsの出場選手を定義

とっつきにくいイメージかもしれませんが、案外カジュアルに定義できちゃいます。

コントローラ
  def top
    session[:life]="私はsession。生きています"
    flash[:life]="私はflash。生きています。"
    params[:life]="私はparams。生きています。"
  end

②ビューページに選手達を表示する記述を追加

ビュー
            <%= session[:life]%><br>
            <%= flash[:life]%><br>
            <%= params[:life]%><br>

            <%= yield %>

③画面遷移を何度かして、どれくらい生きられるかを見る

0遷移目

皆表示されていることを確認。
スクリーンショット 2021-01-18 13.11.11.png

1遷移目

早くもparamsが息を引き取ります。
スクリーンショット 2021-01-18 13.11.25.png

2遷移目

ここでflashも力尽きました。
スクリーンショット 2021-01-18 13.11.34.png

結言

名前 寿命
params 1遷移キル
flash 2遷移キル
session キルするまで生きている

sessionにユーザーのログイン状態が保存されていれば、
ずっとログイン状態を保っていることにも納得できます。
ログアウトはsessionに保存しているユーザーのログイン状態をdestroyしていたんですね。

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

必ず役に立つ!Ruby on Railsでの開発で使えるデバッグコード

こ~れなんなん、これなんなん

こんにちは。

開発で最も必要とされる技術ってデバッグ力だと思っています。特にバグを大量生産することが得意な僕にとって、デバッグ力はとても大事。

そこで、普段開発で使ってるデバッグ用のコードをまとめておこうかと思います。

デバッグコード

Ruby on Railsで使える、デバッグで役に立ちそうなコードの一覧を書いていきます。なお、Rubyで使えるコードもあります。

byebug起動時やrails consoleで使用することを想定しています。byebugの使い方よく分からんて人は以下の記事で使えるようにしてください。
https://web-nari.net/2018/03/16/byebug/

メソッド一覧

hogeという変数の参照できるメソッドの中で、メソッド名にmethod_nameが含まれるメソッドの一覧を返します。

hoge.methods.select {|name| name.to_s.include?("method_name") }

メソッド定義場所

hogeという変数の参照できるfooメソッドの定義されてるクラスを返します。

hoge.method(:foo)

ソースファイルのパス

hogeという変数の参照できるfooメソッドの定義されてるファイルのパスを返します。

hoge.method(:foo).source_location

コールバック検索

Hogeというクラスに定義されてるsave時のコールバックの一覧を返します。cb.kind.eql?の引数にafterかbeforeを与えると、それぞれafter_コールバック関数、before_コールバック関数に定義されてるコールバックを返します。

Hoge._save_callbacks.select { |cb| cb.kind.eql?(:after) }.map { |cb| cb.filter }

その他のコールバック関数

saveだけではなくて以下のコールバック関数がそれぞれ利用可能。

=> [:validate,
 :validation,
 :initialize,
 :find,
 :touch,
 :save,
 :create,
 :update,
 :destroy,
 :commit,
 :rollback,
 :before_commit,
 :before_commit_without_transaction_enrollment,
 :commit_without_transaction_enrollment,
 :rollback_without_transaction_enrollment]

例えばafter_validationのコールバックの一覧が知りたい場合は、以下のようにすればいい。

Hoge._validation_callbacks.select { |cb| cb.kind.eql?(:after) }.map { |cb| cb.filter }

クラス化

:stringという名前から該当するクラスに変換します。例の場合だと、Stringが返ってきます。該当するクラスがない場合はNameError: uninitialized constantが発生します。

:string.to_s.classify.constantize

クラスの確認

hogeというオブジェクトのクラスを確認する。

hoge.class

継承元を検索する

hogeというオブジェクトの継承クラスやそのさらに祖先も検索できる。

hoge.superclass # 親クラスを検索
hoge.ancestors # 祖先を検索

インスタンス変数の確認

hogeというオブジェクトが持ってるインスタンス変数を返します。クラス変数はclass_variablesで参照可能。

hoge.instance_variables

まとめ

タイトルの勢いの割に書いてみたらすんごい少なかった。

これからも追加していきます。

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

ブラウザのセッションとCookieの違いとRailsのsession,cookiesメソッドの違いについて

はじめに

この記事はRails初学者が書いています。
ご容赦ください。

また、間違っている点があれば指摘していただければ幸いです。

それぞれの違い

ブラウザのセッション

あるサイトに接続してから切断するまでの一連の流れのこと。

ブラウザのCookie

ユーザー側のブラウザに保存しておく情報のこと。

一時Cookieと永続Cookieがある。
前者はブラウザ終了時に自動で消える。
後者はユーザー側、サーバー側のどちらかで手動で消去するまで残っている。

HTTPプロトコルではログインしているという情報を記憶しておくことができない。
そのため、こういった機能で保存しておき、ページ移動の際に毎回Cookieを確認する必要がある。

Railsのsessionメソッド

ブラウザの一回のセッションに必要なデータを残しておくためのもの。
具体的には、ブラウザの一時Cookieに暗号化したデータを保存するメソッド

Railsチュートリアルでは、ここにログインしているユーザーIDを保存している。
ユーザーIDを保存しておくことでブラウザを閉じるまではログイン後のページを移動することができる。

チュートリアルには、「このデータは攻撃者に盗まれたとしてもログインすることはできないから安全である。」と書いてあるが、
ログイン中であるという情報は残っているので、これを盗めばユーザーになりすますことができるのではないか思う。
僕の勉強不足でここはわからない。

Railsのcookiesメソッド

ブラウザ終了後にも残しておきたいデータを保存しておくためのもの。
具体的には、ブラウザの永続Cookieに、暗号化したデータを保存するメソッド

チュートリアルでは、RememberMeと呼ばれるトークンを保存している。
これを使うと、ユーザー名とパスワードを入力しなくてもログインすることができてしまう。
そのため、扱いには注意する必要がある。

参照

今さら聞けないセッションとCookie、ログイン・ログアウト(Rails編)
Rails セキュリティガイド

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

0からRuby on Railsの環境構築【Cloud9】(Rubyのバージョン変更からRailsのインストールまで)

Ruby on Railsの環境構築(Cloud9)

本記事はRuby on Railsの環境構築を初心者の方でも迷わずできるように解説した記事です。

Ruby on Railsは、広範囲にわたる開発で使用されています。日本のスタートアップでも採用されていることが多く、食べログやnote、会計ソフトのfreeeなどもRuby on Railsによって開発されています。

そんなRuby on Railsですが、2019年8月にバージョン6.0がリリース、2020年12月にはバージョン6.1がリリースされました。また、Ruby自体も2020年12月にバージョン3.0に大型アップデートされ新しい機能が追加されています。

ここでは、Ruby 3.0とRuby on Rails 6.1.1の環境構築方法を説明していきます。

環境

  • Cloud9 (Mac、Windowsどちらも対応可能です)
  • Amazon Linux2
    • Cloud9の環境を立ち上げる際に、Amazon Linux2を選択している必要があります。

Rubyのバージョンを確認する

以下のURLからコンソールにサインインして、Cloud9を立ち上げましょう。

https://us-east-2.console.aws.amazon.com/console/

Cloud9上のTerminalを立ち上げて、以下のコマンドを実行してRubyのバージョンを確認しましょう。

ruby -v

以下のように数字が表示されます。

ruby -v
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]

ここではRubyの環境が「2.6.3」であることがわかります。

Rubyのバージョンを変更する

それではここからRubyのバージョンを変更していきます。

Rubyのバージョン管理ツールであるrvmを使います。

以下のコマンドでインストール済みのRubyのバージョンを確認します。

rvm list

実行結果は以下になります。

rvm list
=* ruby-2.6.3 [ x86_64 ]

# => - current
# =* - current && default
#  * - default

また、以下のコマンドでインストール可能なRubyのバージョンを一覧表示します。

rvm list known

実行結果で、自分のインストールしたいバージョンがあることを確認してください。

rvm list known

//========

# MRI Rubies
[ruby-]1.8.6[-p420]
[ruby-]1.8.7[-head] # security released on head
[ruby-]1.9.1[-p431]
[ruby-]1.9.2[-p330]
[ruby-]1.9.3[-p551]
[ruby-]2.0.0[-p648]
[ruby-]2.1[.10]
[ruby-]2.2[.10]
[ruby-]2.3[.8]
[ruby-]2.4[.6]
[ruby-]2.5[.5]
[ruby-]2.6[.3]
ruby-head

//========

ここでインストールしたいバージョンがない場合は、rvmのアップデートをおこなう必要があります。

アップデートをするには、以下のようにgetコマンドを実行します。

rvm get latest
rvm get latest

//========

Thanks for installing RVM 
Please consider donating to our open collective to help us maintain RVM.

  Donate: https://opencollective.com/rvm/donate


RVM reloaded!

インストールしたrvmのバージョンを確認しましょう。

rvm -v
rvm 1.29.10 (1.29.10) by Michal Papis, Piotr Kuczynski, Wayne E. Seguin [https://rvm.io]

再度インストールしたいバージョンがあるかを確認しましょう。

rvm list known

//========

# MRI Rubies
[ruby-]1.8.6[-p420]
[ruby-]1.8.7[-head] # security released on head
[ruby-]1.9.1[-p431]
[ruby-]1.9.2[-p330]
[ruby-]1.9.3[-p551]
[ruby-]2.0.0[-p648]
[ruby-]2.1[.10]
[ruby-]2.2[.10]
[ruby-]2.3[.8]
[ruby-]2.4[.10]
[ruby-]2.5[.8]
[ruby-]2.6[.6]
[ruby-]2.7[.2]
[ruby-]3[.0.0] #←インストールしたいバージョン
ruby-head

//========

次に、以下のコマンドを実行してRubyのバージョンをインストールします。

<version>の部分には、インストールするバージョンを指定してください。

rvm install <version>

以下のように表示されれば正常にインストールされています。

ここでは3.0を指定しています。

rvm install 3.0

//======

ruby-2.7.2 - #generating global wrappers.......
ruby-2.7.2 - #gemset created /home/ec2-user/.rvm/gems/ruby-2.7.0
ruby-2.7.2 - #importing gemsetfile /home/ec2-user/.rvm/gemsets/default.gems evaluated to empty gem list
ruby-2.7.2 - #generating default wrappers.......

以下のコマンドで使用するバージョンを指定しましょう。

rvm use <version>
rvm use 3.0.0

最後に以下のコマンドで、先ほど指定したバージョンが表示されることを確認してください。

ruby -v
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-linux]

以上でRubyのバージョン変更は終了です。

Bundlerをインストール

Rubyをインストールしたら、次にBundlerをインストールします。

RubyではGemというライブラリを使いパッケージを管理しています。

Gemコマンドで簡単にインストールやアンインストールができますが、複数のGemを使うとGem同士で依存関係が生まれ、バージョン違いによる不具合が出てきます。

そこで、BundlerでGemのそれぞれのバージョンを正確に追跡し管理して、Rubyプロジェクトに一貫した環境を提供します。

参考:
- Bundler
- RubyGems

それではBundlerをインストールしましょう。以下のコマンドを入力します。

gem install bundler

コマンドを実行するとインストールが始まります。

インストールされたBlundlerのバージョンを確認しましょう。

次のコマンドを入力します。

bundler -v

コマンドの実行後、次のように表示されます。

bundler -v
Bundler version 2.1.4

「version 2.1.4」とバージョン情報が表示されました。

これでBlundlerがインストール済みであることを確認できました。

yarnをインストール

次にyarnをインストールします。

yarnはJavaScriptのライブラリの利用に必要なパッケージマネージャです。

yarnと互換性のあるnpmというパッケージマネージャもあります。しかし、Rails6ではWebpackerが標準になったことにより、yarnが必要です。

それでは、yarnのインストールを行いましょう。

次のコマンドを入力します。

npm i -g yarn

yarnが実際にインストールされたかを確認するために、次のコマンドを入力します。

yarn -v

コマンドを実行すると、次のように表示されます。

yarn -v
1.22.10

ここでは「1.16.0」と表示されました。バージョン情報が表示されていれば、yarnがインストールされたことが確認できます。

Ruby on Railsのバージョンを確認する

ここからはRailsのバージョンを確認していきます。

Cloud9のTerminalで以下のコマンドを実行してRailsのバージョンを確認しましょう。

rails -v

以下のようにデフォルトで指定されるRailsのバージョンが表示されます。

rails -v
Rails 5.0.0

rails newを実行する際にバージョンを指定しないとこのバージョンでアプリケーションが作成されます。

次に、インストールされているRailsのバージョンを確認しましょう。

以下のコマンドを実行して下さい。

gem list rails

以下のように表示されるので、railsの項目を確認しましょう。
「5.0.0」がインストールされていることがわかるかと思います。

gem list rails

*** LOCAL GEMS ***

rails (5.0.0)
rails-dom-testing (2.0.3)
rails-html-sanitizer (1.3.0)
sprockets-rails (3.2.2)

Ruby on Railsのインストール

いよいよ、Railsをインストールします。

Railsのインストール時に「-v バージョン番号」とバージョンを指定してインストールできます。

今回はバージョン「6.1.1」をインストールします。

参考:railsの全バージョン履歴

次のコマンドを入力します。

gem install rails -v 6.1.1

コマンドを実行すると、インストールを開始します。インストールの完了までに数分かかることがあります。

Railsをインストールしたら、Railsのバージョンを確認するために次のコマンドを入力します。

rails -v

コマンドを実行すると、次の画面が表示されます。

rails -v
Rails 6.1.1

Rails 6.1.1」と表示されました。

これで無事にRuby on Railsのインストールが完了しました。

※本記事はTechpitの教材を一部修正したものです。

参考

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

Rails 記事詳細リンクは、ループ内でしか機能しない?

背景

Page#homeで記事一覧(Posts#index)を表示し、そこに記事詳細(Posts#show)をリンクを貼った。そのときに学んだ事の備忘録。

結論

記事詳細リンクはこう貼った。(成功したコード)

<% if @posts.any? %>

 <% @posts.each do |p| %>

#記事詳細のリンク↓
 ++<%= link_to post_path(p), class: 'text-body' do %> 
   <div class="card mt-4">
      <%= image_tag p.image.url, class: 'img-fluid' %>
      <div class="card-body">
        <h5 class="card-title"><%= p.title %></h5>
        <p class="card-text"><%= p.tag %></p>
      </div>
    </div>
     <% end %>
    <% end %> 

  <% end %>


エラーが起きたコード

Couldn't find Post without an ID / ActiveRecord::RecordNotFound

<% if @posts.any? %>


 ++ <%= link_to post_path(post), class: 'text-body' do %> 

 <% @posts.each do |p| %>
   <div class="card mt-4">
    <%= link_to post_path(p), class: 'text-body' do %> 
      <%= image_tag p.image.url, class: 'img-fluid' %>
      <div class="card-body">
        <h5 class="card-title"><%= p.title %></h5>
        <p class="card-text"><%= p.tag %></p>
      </div>
    </div>
    <% end %> 

++ <% end %>

  <% end %>

なぜこれでワークしたのか?

restfulのこのコードは、

post_path(post)

繰り返し処理の

<% @posts.each do |post| %>

|post|からきている。


繰り返し処理の外で、

<%= link_to post_path(post), class: 'text-body' do %> 

と書くと、(post)の意味がコンピュータにはわからなくなる。

もう一度うまく行ったコード

<% if @posts.any? %>

 <% @posts.each do |p| %>

#記事詳細のリンク↓
 ++<%= link_to post_path(p), class: 'text-body' do %> 
   <div class="card mt-4">
      <%= image_tag p.image.url, class: 'img-fluid' %>
      <div class="card-body">
        <h5 class="card-title"><%= p.title %></h5>
        <p class="card-text"><%= p.tag %></p>
      </div>
    </div>
     <% end %>
    <% end %> 

  <% end %>

この記事を参考にしました。https://skillhub.jp/courses/158/lessons/835

さいごに

本当にループ内でしか記事詳細は機能しないのかはわかりませんが(ユーザープロフィールなどでは、ループは記述していなくても、記事詳細のリンクはワークしている。)、とりあえずうまく行っているので、深くは追求しません。

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

Ruby on Railsのconfig設定

この記事ではRails6.1のconfig設定をメモしています。

config/application.rb

タイムゾーンをローカルに設定する

config.time_zone = 'Tokyo'
config.active_record.default_timezone = :local

config/puma.rb

developmentではlocalhost:3000、productionではunix socketを使用する

if ENV.fetch("RAILS_ENV", "development") == 'production'
  bind "unix://#{Rails.root}/tmp/sockets/puma.sock"
else
  port ENV.fetch("PORT") { 3000 }
end

config/environments/production.rb

キャッシュにredisを使用する

  cache_servers = %w(redis://localhost:6379/1)
  config.cache_store = :redis_cache_store, {
    url: cache_servers,
    connect_timeout: 30,
    read_timeout: 0.5,
    write_timeout: 0.5,
    reconnect_attemps: 1,
    error_handler: -> (method:, returning:, exception:){
      Raven.capture_exception exception, level: 'warning',
        tags: { method: method, returning: returning }
    }
  }

セッションにredisを使用する

  config.session_store :redis_store,
    servers: [{
      host: 'localhost',
      port: 6379,
      db: 0,
      namespace: 'sessions',
    }],
    key: "_session_name_",
    expire_after: 30.minutes

Railsでredisを使用するためGemfileにgemを追加する

Gemfile
gem 'redis-rails'

config/webpacker.yml

webpackを使用している場合はhtmlの変更も検知できるようにする?
ここの設定は検討が必要

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

不正な config/master.key が原因でRailsの起動に失敗するケースとその対処方法

発生する問題

git cloneでローカルに持ってきたRailsアプリを、rails srails cで起動しようとすると以下のようなエラーが発生する。

$ rails s
=> Booting Puma
=> Rails 6.1.0 application starting in development 
=> Run `bin/rails server --help` for more startup options
Exiting
/Users/jnito/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/message_encryptor.rb:203:in `rescue in _decrypt': ActiveSupport::MessageEncryptor::InvalidMessage (ActiveSupport::MessageEncryptor::InvalidMessage)

エラーの原因

config/master.key の鍵が config/credentials.yml.enc 用の鍵と一致していないため、例外が発生してRailsが起動しなくなった。

もう少し詳しく

config/master.keyは通常、.gitignoreに追加してソースコード管理の対象外とするファイルである。そのため、git cloneしてもconfig/master.keyはローカルにはコピーされない。

その状態で「credentials.yml.encを編集したい」と思ってEDITOR="vi" bin/rails credentials:editのようなコマンドを実行すると、以下のようなメッセージとともに、config/master.keyが新規に作成されてしまう。(加えて、credentials.yml.encの編集もできない)

$ EDITOR="vi" bin/rails credentials:edit
Adding config/master.key to store the encryption key: 6eb4ad(以下略)

Save this in a password manager your team can access.

If you lose the key, no one, including you, can access anything encrypted with it.

      create  config/master.key

Couldn't decrypt config/credentials.yml.enc. Perhaps you passed the wrong key?

ただし、新規に作られたconfig/master.keyはgit cloneしてきたconfig/credentials.yml.encの鍵ではないため、Rails起動時に復号化に失敗して例外(ActiveSupport::MessageEncryptor::InvalidMessage)が発生してしまう。

対処方法

状況によって対処方法は異なる。

git cloneする前にcredentials.yml.encが使われていた場合

開発チーム内で鍵の共有方法が決められているはずなので、その運用ルールに従って適切なconfig/master.keyを自分のローカルに配置する。

credentials.yml.encが今まで使われていなかった場合

自動的に作られたconfig/master.keyを削除すればRailsが起動するようになる。

今後もcredentials.yml.encを使わないのであれば、config/credentials.yml.encごと削除するのも一手。

逆に、これからcredentials.yml.encを使っていく(=自分がチーム内で最初の利用者になる)場合は、新規にconfig/credentials.yml.encを作り直した上でcredentials/master.keyをチーム内で適切に管理・共有する必要がある。
(ただし、どこかから適切なconfig/master.keyを入手できれば新規に作り直す必要はない)

# 既存のcredentials.yml.encを削除
$ rm config/credentials.yml.enc

# credentials.yml.encとmaster.keyを新規に作成して編集する
$ EDITOR="vi" bin/rails credentials:edit
Adding config/master.key to store the encryption key: 917c31e316061bcdd909754a56897f61

Save this in a password manager your team can access.

If you lose the key, no one, including you, can access anything encrypted with it.

      create  config/master.key

File encrypted and saved.

動作確認環境

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

SNS認証時のエラーについて

経緯

ウィザード形式のユーザー登録機能実装後、
SNS認証を追加するために
registrations_controller内に必要な記述をした。
(userモデルは記述済み。)

controller
class Users::RegistrationsController < Devise::RegistrationsController

#一部省略

def create
    if params[:sns_auth] == 'true'
      pass = Devise.friendly_token
      params[:user][:password] = pass
      params[:user][:password_confirmation] = pass
    end
    super                               #ここまでがSNS認証実装に追記した記述
    @user = User.new(sign_up_params)    #ここからウィザード形式のための記述
      unless @user.valid?
        render :new and return
      end
      session["devise.regist_data"] = {user: @user.attributes}
      session["devise.regist_data"][:user]["password"] = params[:user][:password]
      @profile = @user.build_profile
      render :new_profile
  end

#一部省略

end

エラー内容

新規登録を行ってみたところ下記のようなエラーメッセージが出る。

AbstractController::DoubleRenderError in Users::RegistrationsController#create

同一アクション内にrenderもしくはredirectが複数回使われていますよ!という内容。
renderの後にand returnで処理を終わらせているのになぜ?となる。。

エラー原因

SNS認証を実装するために追記した記述の最後にある
superによるもの。
(superとは、継承元のクラスにある、superを記述したメソッドと同名のメソッドを呼び起こすことができる。)

呼び起こし先は下記である。
(https://github.com/heartcombo/devise/blob/master/app/controllers/devise/registrations_controller.rb) 参照元

RegistrationsController
class Devise::RegistrationsController < DeviseController

# 一部省略

def create
    build_resource(sign_up_params)

    resource.save
    yield resource if block_given?
    if resource.persisted?
      if resource.active_for_authentication?
        set_flash_message! :notice, :signed_up
        sign_up(resource_name, resource)
        respond_with resource, location: after_sign_up_path_for(resource)
      else
        set_flash_message! :notice, :"signed_up_but_#{resource.inactive_message}"
        expire_data_after_sign_in!
        respond_with resource, location: after_inactive_sign_up_path_for(resource)
      end
    else
      clean_up_passwords resource
      set_minimum_password_length
      respond_with resource
    end
  end

# 一部省略

end

この中のrespond_withがどうやらエラーの原因みたい。
respond_withは、renderやredirectの役割を持っているため。
よってsuperで呼び起こしたメソッド内でrender or redilectしようとしたけど、その後の記述でまたrenderがあり
どっち?となりエラーが起きたと考えられる。

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

Rails 検索機能の実装

Rails で検索機能を実装

View に form_tag で検索窓を設置

<div class="search_field">
    <p>検索:</p>
    <%= form_tag(posts_path, :method => 'get') do %>
      <%= text_field_tag :search, "",  {class: "search_field_type"}%>
      <%= submit_tag 'Search', :name => nil, class: "search_field_btn" %>
    <% end %>
  </div>

form_tag(posts_path, :method => 'get') の path の部分はクラス名によって任意に変更してください。

Model に検索のためのメソッド search を設定

post.rb

def self.search(search)
    if search
      Post.where(["title LIKE ?", "%#{search}%"])
    else
      Post.all
    end
  end

title カラムの中の一部でも検索した文字列が該当すればヒットするように設定。

詳しくは SQL の where 句を調べれば何となく設定している意味わかります。

https://www.wakhok.ac.jp/biblion/1994/DB/subsection2.4.3.5.html

Controller の調整

posts_controller.rb

def index
    @posts = Post.all.includes(:user).order(id: "DESC").search(params[:search])
  end

これで検索にヒットした記事が @posts に入る。

あとは View 側で一覧表示処理をすれば OK です。

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

【Ruby on Rails】Rails Tutorialを全部終えたので、追加で「ストック機能」を実装してみた

はじめに

Railsで「qiita」のような記事をストックする機能を実装したので、その方法を備忘録としてまとめておきます。
(基本的にはいいね機能と同じやり方)

※また、以下に記載する方法は前提として「Rails tutorial」を最後まで終えた程で記載させていただきます。

実装すること

・記事をストックするボタンを作成する。
・記事がどれくらいストックされているか表示する。
・ストックした投稿をストック一覧ページで確認できるようにする。

Stockモデルを作成する

ストック機能を実装するにあたって、最初のステップとしてStockモデルを作成します。

$ rails g model Stock

カラムは2つ作成します。
post_idカラム
user_idカラム
マイグレーションファイルは以下のように修正します。

xxxxxxxxxxxxx_create_stocks.rb
class CreateStocks < ActiveRecord::Migration[6.0]
  def change
    create_table :stocks do |t|
      t.references :user, foreign_key: true
      t.references :post, foreign_key: true

      t.timestamps
    end
    add_index :stocks, [:user_id, :post_id], unique: true
  end
end
$ rails db:migrate

2つのカラムは、それぞれ外部キーとして追加しておきます。
このテーブルは中間テーブルとして活用し、誰がどの投稿をストックしたかという情報を格納しておきます。
また、user_idカラムとpost_idカラムの組み合わせが一意になるようにインデックスを設定しておきます。

アソシエーションを設定する

以下のようにアソシエーションを設定します。
stock.rbにはアソシエーションのついでにバリデーションも追加しておきます。

user.rb

user.rb
has_many :stocks, dependent: :destroy

post.rb

post.rb
has_many :stocks, dependent: :destroy

stock.rb

stock.rb
belongs_to :user
belongs_to :post

validates :user_id, presence: true
validates :post_id, presence: true

stockコントローラを作成する

次にstockコントローラを作成します。

$ rails g controller Stocks

コントローラに処理を加える

stocks_controller.rb

stockコントローラを作成したら、今回の実装に必要なアクションを定義しておきます。

stocks_controller.rb
class StocksController < ApplicationController

  def create
    @stock = current_user.stocks.create(post_id: params[:post_id])
    redirect_back(fallback_location: root_path)
  end

  def destroy
    @stock = Stock.find_by(
      post_id: params[:post_id],
      user_id: current_user.id
      )
    @stock.destroy
    redirect_back(fallback_location: root_path)
  end

  def index
    @stocks = Stock.where(user_id: current_user.id)
  end
end

posts_controller.rb

次にpostコントローラshowアクションの部分に変数を追加します。
post/showページにストックボタンを表示する為です。

posts_controller.rb
  def show
    @post = Post.find_by(id: params[:id])
    @stock = Stock.find_by(
      user_id: current_user.id,
      post_id: @post.id
    )
  end

ルーティングを設定する

次に必要なルーティングを追加していきます。

routes.rb

routes.rb
resources :stocks, only:[:create, :destroy, :index]

今回はストックする機能(create)とストックを解除する機能(destroy)の他にストックした記事を表示する為の「index」も追加しておきます。

ストックしているかどうかの確認の処理と加える

ユーザーが記事に対して、すでにいストックをしているのかどうかを確認することができるようにstock?メソッドを定義しておきます。

user.rb

user.rb
  #ユーザーがすでにストックしているか確認する
  def stock?(post)
    self.stocks.exists?(post_id: post.id)
  end

Viewを追加&変更する

ストックボタンを実装する為に、まずはパーシャルを3つ作成していきます。

shared/_stock.html.erb

_stock.html.erb
<%= form_with(url:stocks_path, local: true) do |f| %>
  <div><%= hidden_field_tag :post_id, @post.id %></div>
 <%= f.submit "ストック", class:"btn-sm" %>
<% end %>

shared/_unstock.html.erb

_unstock.html.erb
<%= form_with(url: stock_path(@stock), html: {method: :delete}, local: true) do |f| %>
  <div><%= hidden_field_tag :post_id, @post.id %></div>
  <%= f.submit "ストックをやめる", class:"btn-sm" %>
<% end %>

shared/_stock_form.html.erb

_stock_form.html.erb
<% if current_user.stock?(@post) %>
  <%= render "shared/unstock" %>
<% else %>
  <%= render "shared/stock" %>
<% end %>
<%= @post.stocks.count %>

posts/show

ボタンを追加したいところに以下パーシャルを追加します。

show.html.erb
<%= render "shared/stock_form" %>

stocks/index.html.erb

indekページを新しく追加し、ストックした記事を表示させます。

index.html.erb
<h1>ストック一覧</h1>
<h2>あなたがストックしたポートフォリオの一覧です。</h2>

<% @stocks.each do |stock| %>
  <%= link_to stock.post.name, post_path(stock.post) %>
<% end %>

完成画

おわり

以上で終了です。
機能の実装方法を記述しているので、デザインは全く気にしていません。
デザインはお好みで付け足してみてください。

ソースコードはこちら

ポートフォリオ作成中のものですが。以下にあります。
https://github.com/iittaa/Sharing_Portfolio

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