20210118のRubyに関する記事は24件です。

Rubyでハッシュをうまく使って注文の合計金額を計算する

この記事ではmacOSにインストールしたRuby2.6.3を使っています。

ゼロからわかるRuby超入門の練習問題(p176)を自分なりの解き方をしたのでメモします。
キーはシンボルで書くものだという思い込みがあったので、最初この方法での解き方ができませんでした。ハッシュの使い所を見極める力が必要だと感じました。

問:メニューは、コーヒー300円とカフェラテ400円の2つがあり、サイズはショートだと+0円、トールだと+50円、ベンティだと+100円になる。メソッドを使って注文をとってください。

ターミナルから注文がとれるバージョン
def price
  puts "ご注文は何にしますか?"
  item = gets.chomp
  puts "サイズは何にしますか?"
  size = gets.chomp
  items = {"コーヒー" => 300, "カフェラテ" => 400}
  sizes = {"ショート" => 0, "トール" => 50, "ベンティ" => 100}
  total = items[item] + sizes[size]
  puts "#{total}円になります"
end

price
キーワード引数とデフォルト値を使う方法(本の答えとほぼ同じ)
def price(item: "コーヒー", size: "トール")
  items = {"コーヒー" => 300, "カフェラテ" => 400}
  sizes = {"ショート" => 0, "トール" => 50, "ベンティ" => 100}
  items[item] + sizes[size]
end

puts price(item: "カフェラテ", size: "ベンティ")

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

Rubyで文字列の出現回数を数える

この記事ではmacOSにインストールしたRuby2.6.3を使っています。

ゼロからわかるRuby超入門の練習問題(p154)を自分なりの解き方をしたのでメモします。
1対1対応しているものについてはハッシュをうまく使う必要があると気づいた。

問:文字列"caffelatte"の中で使われているアルファベットとその回数を数えてください。

hash = {}
hash.default = 0

#文字1つ1つを配列にしてアルファベット順に並び替える
array = "caffelatte".chars.sort 

#文字をキー,回数を値としたhashをつくる
array.each { |x| hash[x] += 1} 

#最後に文字が何回使われていたかを表示する
hash.each { |letter, count| puts "#{letter}#{count}回使われている"} 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RubyでJSの即時関数みたいなことをやる

ちょっと例が悪いですが、自分用メモ。

↓こんなのがRubyで書きたかった。

const filepath = (() => {
  return "hoge/moge/huga";
})();

別に普通に定義しても良いですが、処理部がごちゃごちゃしてくる場合は、変数の定義も処理部と切り離したいと思い、考えてみました。

class Hoge
  def initialize
    @root = root
  end

  def filepath(path)
    File.join(@root.call, path)
  end

  private

  def root
    # pathへの代入は初期化の@rootへ代入された時のみ
    path = "hoge/moge/huga"
    -> { path }
  end
end

hoge = Hoge.new

p hoge.filepath("a") # => "hoge/moge/huga/a"
p hoge.filepath("i") # => "hoge/moge/huga/i"
p hoge.filepath("u") # => "hoge/moge/huga/u"
p hoge.filepath("e") # => "hoge/moge/huga/e"
p hoge.filepath("o") # => "hoge/moge/huga/o"

この例のrootメソッドで定義しているpathは直書きですが、なにかの処理結果をpathに格納して使いまわしたい場合は、何度も処理を走らせる必要がないので、いい感じ。

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

エラー解決を通してdeviseのメソッドuser_signed_in?とcurrent_userの理解が深まった話

開発環境

Mac OS Catalina 10.15.7
ruby 2.6系
rails 6.0系

事の発端

転職用のポートフォリオとしてオリジナルアプリを作っていたある日のことでした。
未ログインユーザーが詳細ページに遷移するリクエストをローカルで行ったところ、以下のエラーが発生

スクリーンショット 2021-01-18 20.02.57.png

解決法

結論から言うと  user_signed_in? &&  の条件を追加することで解決しました。

エラー時のコード

show.html.erb
<% if current_user.id == @post.user.id %>
  <div class = "btn-contents">
    <%= link_to '編集する', edit_post_path(@post.id), class: "edit-btn"%>
    <%= link_to '削除する', "#", class: "delete-btn"%>
  </div>
<% end %>

エラー解決後のコード

show.html.erb
<% if user_signed_in? && current_user.id == @post.user.id %>
  <div class = "btn-contents">
    <%= link_to '編集する', edit_post_path(@post.id), class: "edit-btn"%>
    <%= link_to '削除する', "#", class: "delete-btn"%>
  </div>
<% end %>

なんとも呆気ない話でしたね。

原因

未ログインユーザーに対する条件を忘れたことで、current_userメソッドが未ログインユーザーのリクエストの際でも使われてしまったことが原因でした。

current_userは、現在ログインしているユーザーの情報を取得できるメソッドですが、今回の場合、ログインしていないのに、ログイン情報を取得しようとしました。

そのため、「current_user??? そもそもログインしてないんでそんな情報ないんですけど???」みたいな感じでrails先生に怒られてしまったと言うわけですね。

なので、まずは手前の条件で user_signed_in?メソッドを使うことで、ログインしていなければfalseが返ってくるようにします。
そうすれば、未ログイン時には後ろの条件に遷移しなくなるのでエラーが解決するわけでした。(A && B と言う記述は、Aの条件がfalseだった場合、Bの条件は参照されません)

対策

対策としては「メソッドに対するなんとなくの理解をやめること」だと思っています。

エラーの原因としては上記の通りですけど、根本的な原因は「メソッドをなんとなく覚えて理解したつもりで使っていたこと」だと思っています。
気をつけます、、、

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

[ruby]演算子について

はじめに

こんばんは
私は某スクールに通っているものです
先月卒業したばかりなのですが学んだ事をアウトプットしております
前回、[Ruby]文字列で使えるメソッドを作ったので今回は演算子について触れたいと思います!

演算子とは?

演算子とはプログラミングで計算したり、比較したりする時に使う記号です
これはめちゃくちゃ使うから覚えてくださいね

と言ったけどそのほとんどは小学校でやった+とかーとかばかりだから身構えなくていいかも

足し算
- 引き算
* 掛け算
/ 割り算
% 剰余
** べき乗
Math::PI 円周率(π)を合わせる定数
Math.sqrt 平方根を求める

ぶちゃけ、べき乗以下は半年プログラミング学習してたけど使わんかったから頭の隅に置いておいてください

=> nil
irb(main):115:0> 1 + 1
=> 2
irb(main):116:0> 1 - 1
=> 0
irb(main):117:0> 1 * 2
=> 2
irb(main):118:0> 4 / 2
=> 2

言うまでもないけど四則演算の働き

irb(main):119:0> 3 / 4
=> 0

ちなみに小数点は表示されない
なぜならこの数字はIntegerクラスであって、Integerは数字を整数で表しているからね

irb(main):122:0> n = 3 / 4
=> 0
irb(main):123:0> n.class
=> Integer

代入されたnをclassで確認したら”Integer”と確認できました

%(剰余)

上の図で剰余と記述されていましたが何って話ですよね?
剰余と言うのは割ったあまりを指します

irb(main):128:0> 9 % 7
=> 2

9 / 7 = 1 ・・・ 余り2だから、2が表示されたわけですね

べき乗

べき乗と言うのは簡単に言うと累乗だね

irb(main):131:0> 4 ** 2
=> 16

これを見ていただければわかりますが
4 * 4 = 16だね

Math::PI

Math::PIは円周率
でも3.14よりより細かい計算ができるよ

irb(main):138:0>  3 * 3 *3.14
=> 28.26
irb(main):139:0>  3 * 3 * Math::PI
=> 28.274333882308138

見てわかるかもだけど小数点第二位が違う

Math.sqrt

これは平方根を求めるメソッドだね

Math.sqrt(数字)で使えます

irb(main):140:0> Math.sqrt(4)
=> 2.0

4の場合を求めたけど√4の場合は2だね

irb(main):143:0> Math.sqrt(2)
=> 1.4142135623730951

ひとよひとよにひとみごろ、懐かしい

最後に

演算子はこの先かなり使うから覚えよう
でもべき乗以下は頭に入れる程度でいいよ

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

ActiveRecord_RelationとCouldn't find Item without an IDのエラーについて

まず、ActiveRecord_Relation:とは

例えばコントローラーで

def index
  User.all
end

と定義した場合
Use.allで持ってきたもののことを言います。
この時、allはレコードとカラムの情報のみを持ってきます。

Couldn't find Item without an IDとは

要約するとIDが取得できませんよ。という意味

例えばコントローラーで

def index
  User.find(params[:id])
end

と定義したとします。

この時paramsの[:id]の部分が取得できません。ということになります。

状況

  • このようなエラーが出る場合はどんな時か?

それはルーティングにネストをしていて、親になっているものから情報を取得しようとした場合である。

  • なぜか?

シークエルプロなどでテーブルを確認すれば一目瞭然。
子要素に親要素の情報は入っていないから。

この時、allはレコードとカラムからとってくるという記述の意味がわかります。

解決策

  • paramsの後に親要素となるidの指定をしよう

.find(params(:_id)

具体的には

親にusers,子にcommentsなんていうネストをしていた場合

commentsコントローラーに


@user = User.find(params(:user_id)

終わりに

このエラーの解決に結構時間がかかりましたが、解決してみれば単純なことでした。
まだまだ理解していないことが多いことがよくわかりました。
未熟故に間違ったことを記述している可能性や抜けていること、誤字脱字などがあるかもしれません。
初めての投稿ということで読みにくいが、それも少しずつ改善していければと思いつつ終了とさせていただきたいです。

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

Chefの概要を分かりやすく書いてみました

最近の現場で初めて、chefを使用しました。

あまり情報が少ないため、概要を書いてみました。

Chefとはいったい??

200px-Chef_logo.svg.png

ウィキペディアにはこう書かれていました。

RubyとErlangで記述された構成管理ツールである

ん??どういうこと??
 

すごく簡単に言えば、

chefはインフラ構築を自動化できるプラットフォームなのです。

Chefを使用するメリット

Chefでインフラの構成を自動化すれば、環境構築時にすべて自分で用意する必要はありません。
繰り返し作業を集約でき、無駄な時間を減らせます。
大規模のプロジェクトによく使われるようです。

ChefはRubyで書かれる

ChefはRubyで書けるため、Rubyの基礎知識が必要となります。

Chefの主な構成要素

Recipe

実行する内容の定義を書く場所

Attribute

変数などをまとめている場所

Template

設定ファイルを作るためのテンプレート書く場所

File

バイナリファイルなどを置く場所

Cookbook

上記などをまとめたディレクトリ

Knife

コマンドラインツールで、Cookbook や Recipe を操作します

Chefの名前について

シェフと聞いて、
まず思い浮かぶのは
料理人ですよね!?

ここのchefはそのイメージで名前がつけられた
ようです。

だから
レシピとかクックブックとかの名前になっているんですね
なんかおしゃれーですね

まとめ

chefはインフラ構築を自動化できるのもので
Rubyで書けるんですねーーー!!!

実際の書き方はまた別でまとめます。ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

必ず役に立つ!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で続きを読む

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で続きを読む

wrong number of arguments (given 1, expected 0)

いくつかエラー原因はあるかと思いますが、僕の場合は、paramsと[:id]の間にスペースが空いているという事でした。

#スペースが空いている
@post = Post.find(params [:id])

エラーが消えた。

@post = Post.find(params[:id])
  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

36年前の沢口靖子さんのプロフィールを確認した話

当時の記憶

36年前当時、JR-200 (Wikipedia)というグラフィック機能はイマイチなパソコンを使っていました。歳がバレますね(笑)。
パソコン雑誌に掲載されるプログラムの数も限られており、他機種からの移植をしようにもグラフィック機能がネックで諦めることが多かったように思います。
その当時のPOPCOM (Wikipedia)という雑誌にデビューしたばかりくらいの沢口靖子さんを題材にしたプログラムが掲載されたのですが、自分のパソコンでは実行できないと諦めたと思っていました。自分の記憶では沢口靖子さんの顔をパソコンのグラフィックで表示するプログラムだと思っていました。何故かその事がずっと気がかりというか頭の片隅にあったのですが、偶然メルカリでその雑誌を見かけて思わず買ってしまいました。表紙に見憶えがありすぐに分かりました。目次の写真もあったので記事の内容も確認できました。値段は当時の値段の約6倍の3000円しましたけど。
POPCOM.jpg

実際の記事

届いた雑誌を開いてみると、広告や記事を読むと当時が思い出されとても懐かしかったです。漫画で世界時計のプログラムの解説がされていたりして隔世の感がありました。
そして肝心の沢口靖子さんのプログラムを確認すると…全然グラフィック系の命令は無く、ほぼほぼPRINT文とDATA文という…。昔の記憶なんて当てになりませんね。これやったら全然JR-200でも実行できたやん。多分。
POPCOM.jpg

ソースコード

取り敢えず内容が気になったので、BASICより簡単そうなRubyで書き直してみました。DATA文の部分はスマホでGoogleさんの文字認識に助けてもらい入力しました。
もう36年前で時効?と思いますのでソース掲載してみます(問題はご指摘いただければ幸いです)。沢口靖子さんの当時のプロフィールをご覧になりたい方はお試しください :smile:
一応、動作確認した環境は以下です。

  • macOS 11.2 Beta (20D5042d)
  • ruby 2.6.3p62 (2019-04-16 revision 67580) [universal.x86_64-darwin20]
sawaguchi-yasuko.rb
#!/usr/bin/ruby
#-*- coding:utf-8 -*-
require 'nkf'

questions = [
  "セイネンガッピ : ",
  "シュッシンチ : ",
  "シンチョウ,タイジュウ,B・W・H ノ サイズ : ",
  "ケツエキガタ,セイザ : ",
  "カゾクコウセイ : ",
  "シュミ : ",
  "スキナ スポーツ : ",
  "トクイナ カモク : ",
  "スキナ タベモノ : ",
  "イッテミタイ クニ : ",
  "スキナ タレント ミュージシャン : ",
  "リソウノ タイプノ ダンセイ : ",
  "キライナ タイプノ ダンセイ : ",
  "デビューノ キッカケ : ",
  "ドンナ オンナノヒトニ ナッテイキタイカ : ",
  "スキナ イロ : ",
]

data = [
  161,100,93,89,104,89,95,95,67,372,372,386,374,62,429,62,386,374,364,
  62,389,67,95,102,110,193,214,62,100,102,209,201,62,62,108,93,89,102,
  108,89,108,106,67,126,62,374,478,398,62,429,398,384,478,62,386,478,67,
  401,401,62,424,424,62,362,413,62,421,62,100,413,475,62,374,396,478,
  379,67,372,475,374,478,379,374,475,389,355,367,62,369,364,374,478,374,
  475,389,355,367,67,406,413,391,62,391,364,369,364,67,369,364,384,478,
  62,374,478,62,391,376,62,406,478,389,398,67,386,458,398,478,62,464,
  364,62,411,458,64,62,456,360,379,478,464,408,62,447,67,391,364,391,62,
  77,424,364,389,478,62,374,478,62,364,398,456,367,411,62,408,384,469,
  79,67,453,360,439,475,62,362,475,461,62,372,429,384,360,391,67,401,
  355,357,426,481,461,62,426,355,367,376,475,62,406,478,62,364,357,389,
  355,413,364,406,62,398,421,389,364,62,426,408,62,374,478,62,364,364,
  411,67,389,403,384,379,406,62,389,478,389,475,62,374,389,478,355,367,
  62,421,62,426,408,62,374,478,62,364,450,67,398,478,364,95,374,364,62,
  408,367,434,367,62,389,475,406,478,467,458,62,421,62,372,360,406,478,
  341,389,355,475,62,406,478,62,453,367,389,355,367,62,389,437,389,398,
  67,393,364,374,379,62,374,458,62,391,432,478,406,62,374,472,364,364,
  62,408,62,364,472,467,464,62,372,475,411,62,421,62,426,408,62,413,62,
  411,461,398,364,67,389,469,62,426,481,475,379,67,133,151,126,159,141,
]

ans = ""
data.each {|a|
  b = (300.0*Math.sin(a.to_f/10.0*3.1416/180.0)).to_i
  c = b.chr
  if c == '#'
    puts questions.shift, NKF.nkf("--oc=UTF-8 --ic=CP932", ans),
    ans = ""
    next
  end
  ans << c
}

追記(2021-01-18):
リンクがちゃんと動作していなかったのを修正しました。 @scivolaさん、編集リクエストありがとうございました!:bow:

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

Rakefileでdirectoryタスクを記述してもディレクトリが作成されない

事象

Rakefileでdirectoryタスクを記述してもディレクトリが作成されない。

結論

directoryタスクを記述(宣言)するだけではなくて、そのタスクが
実行されるようにどこからか呼び出さないといけなかった。

詳細

directory "doc"

desc "create task"
task "create" do
  cd "doc"
end
> rake -t create
** Invoke default (first_time)
** Invoke create (first_time)
** Execute create
cd doc
rake aborted!
Errno::ENOENT: No such file or directory @ dir_s_chdir - doc

createタスクの前提タスクとして、ディレクトリタスク(名前はdoc)を指定しないといけなかった。

directory "doc"

desc "create task"
task "create" => "doc" do
  cd "doc"
end
> rake -t create
** Invoke create (first_time)
** Invoke doc (first_time)
** Execute doc
mkdir -p doc
** Execute create
cd doc

4時間くらい悩んだ。
しかし、Rakeの情報源、あんまり見つからないなぁ。

asciidoctor絡みで、少し蛇足情報を↓

蛇足: 話の背景

asciidoctorを使ってadocファイルをhtmlに変換できるようになったのは良いが
さて公開はどうしようと思ったところで、Github Pagesにアップするか、
いっそのことHugoを使ってみるか。

と思って調べてたんだけど、どうも画像を置くディレクトリの
取り扱いが難しい。

contents/posts/api.adoc にコンテンツを置いて、画像ディレクトリは
contents/posts/images/image01.png のようにしたくて、
adocの方では、 :imagesdir: ./images を指定。

参照のところで :image: image01.png[]

$ asciidoctor -b html5 ./api.adoc

これで contents/posts/api.html が作成されよしよし。

hugo service -D でHTML確認しようと思ったら、
hugoが絡むと、 publish/posts/api.html からは
publish/posts/api/images/image01.png を参照するように構成される。

apiディレクトリはどこからきた…?

画像ディレクトリの置き場を contents/posts/api/images/image01.png
置き換えれば、HTML表示上は整合製がとれるから問題ないんだけれど、
そうすると、 asciidoctor のコマンド実行でHTML出力した時のパスと異なるし(marked2でプレビューする際に、プロセッサ、プリプロセッサとしてasciidoctorを使用してHTML出力させてるため困る)。

ということで、 hugoは諦めて、asciidoctor コマンドの挙動でそのままHTMLを作って
生成される画像パス通りに画像ファイルを配置して、、、。

みたいなことをやりたくて rake を使ってみたら、変なところではまった。

蛇足2: marked2でasciidoctorを使う

~/bin/asciidoctor-marked2
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
# frozen_string_literal: true

marked_origin = ENV["MARKED_ORIGIN"] || Dir.pwd
rbenv = File.expand_path("~/.rbenv/bin/rbenv")

asciidoctor = "#{rbenv} exec asciidoctor"

cmd = "#{asciidoctor} -a skip-front-matter --safe-mode safe --base-dir #{marked_origin} --backend html5 -o - -"
system(cmd)

こんな感じのRubyスクリプトを書いて実行権限をつけておいて。

marked2.jpg

こんな感じでCustom Processorで指定すると、marked2でasciidocのプレビューができます。

蛇足3: Hugoで必要なFront Matterをasciidoctorコマンド利用時にスキップする

Hugoがらみだと、Front Matterというのを記事につけなければいけなくて、
asciidoctorコマンドの処理と干渉して困っていたんだけれど。

Hugo側は、adocのH1見だしや、ドキュメントヘッダは良い感じに読み飛ばしてくれる

asciidoctorに -a skip-front-matter を付ければ良いんですね。

https://docs.asciidoctor.org/asciidoctor/latest/html-backend/skip-front-matter/

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

Jekyllで日本語カテゴリをデコードして表示

2021/01/18追記:GitHub Pages上ではデコードが成功していない!原因が解消でき次第この文言は削除する。

URLとエンコードの組み合わせで記事は多いが、Jekyllで作成した記事中のエンコードされた日本語文字列をデコードする話は見かけないので、ここにまとめておく。

環境と状況

  • jekyll 4.2.0
  • ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x64-mingw32]

Jekyllテーマのひとつ、Minimal Mistakesでは2021/01/17時点でベータ機能だが、breadcrumbs機能が使える。

パンくずリスト(breadcrumb list)は、ウェブサイト内でのウェブページの位置を、ツリー構造を持ったハイパーリンクの一覧として示すもの。
パンくずリスト - Wikipedia

Minimal MistakesではJekyllのカテゴリーでツリー構造を作り、"ホーム/親カテゴリー/子カテゴリー/タイトル"というスタイルで記事トップに表示してくれる。つまり、以下のようにFront matterを定義したポストファイルの場合、

---
title: "OMG! OMG!"
categories:
  - OMG
  - Bloody
---

Home/OMG/Bloody/OMG! OMG!のように表示してくれる。それぞれがリンクをもっているので、カテゴリーで他の記事を探す際や親、子カテゴリーを把握するのに便利である。

しかし、カテゴリーを日本語で定義すると、エンコードされた文字列が表示されて非常に見た目が悪い。

image.png

Apple TV+1年間無料で1時間の電話問い合わせ - Mt. Nishiブログより。あ、宣伝です。

ともかく、デコードすれば良い。あとはJekyll的にどう実装するかである。

実装

YAMLファイル(色々と基本設定をしているファイル_config.yml)が./にあるとして、Minimal Mistakesの場合、カテゴリー名をエンコードしてくれちゃっている(breadcrumbsを作ってくれている)プログラムは./_includes/breadcrumbs.htmlで実装されている。以下はその一部。

./_includes/breadcrumbs.html
<a href="{{ crumb | downcase | replace: '%20', '-' | prepend: path_type | prepend: crumb_path | relative_url }}" itemprop="item"><span itemprop="name">{{ crumb | replace: '-', ' ' | replace: '%20', ' ' | capitalize }}</span></a>

今回の場合、リンクそのものは問題ないので無視して(<a href="...>部分)、実際にページ上に表示される{{ crumb ...}}部分をいじればよさそうである。

他のテーマでも、仮にbreadcrumbsが実装されている場合は、その機能をオン・オフするパラメータ(ex. breadcrumbs: true)で全体の検索をかけたら見つかりやすいと思う。

フィルター(上記コードのcapitalizeみたいなもの)を新しく定義していじることにする。まず、./_plugins/decode_category.rbファイルを作成する。_pluginsフォルダがなければ作成する。

Rubyで新たにJekyll用のフィルターを作成する。

./_plugins/decode_category.rb
require "URI"

module CustomFilter
    def decode_category(input)
        URI.decode_www_form(input)
    end
end

Liquid::Template.register_filter(CustomFilter)

inputにはカテゴリー名が入る。Liquid~部分はJekyll用フィルターとしてmoduleを定義するおまじないである。(URI.decode(input)でも可能だが、obsoleteだとwarningメッセージが出る。)

{{ crumb | replace: '-', ' ' | replace: '%20', ' ' | capitalize | decode_category }}のように、修正箇所に| decode_categoryを加える。

image.png

以上。

まとめ

日本語カテゴリーがエンコードされて表示される問題を、新しくフィルターを定義することでデコードさせて対処した。実装し終えてみると超簡単だが、情報が少ないので苦労した。同様にしてエンコード、URLのエンコード・デコードも実装できる。

参考資料

module URI (Ruby 3.0.0 リファレンスマニュアル)
jekyll カテゴリをURLは英語のままに日本語化する方法 - メモ用サブブログ

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