20200224のRubyに関する記事は17件です。

【Rails】掲示板アプリでTopicとPostを紐づける方法(アソシエーション)

はじめに

現在、ポートフォリオとして掲示板アプリを作成しており、題目にある「TopicとPostを紐づける」部分でハマった(時間かかった)ので備忘録として。
ちなみに、初投稿なので暖かい目で見守ってやってください。笑

早速ですが、紐づけるために必要な要素として以下の2点を中心に記載します。

  1. model側の修正
  2. controller側の修正

やりたいこと

各modelを関連付けて、「@post.topic.title」のような形で値を取得したい

環境

macOS:10.14.6
Ruby:2.5.7
Rails:5.2.4.1

model側の修正(アソシエーション)

今回登場するmodelは、以下の3つ。
・Topic
・User(deviseを使ってます)
・Post

Topicがスレッド、Postがスレッドに対するレスで、
Topic(1)---(多)Post
User(1)---(多)Post/Topic
というような位置付けになります。

今回の場合は既に各modelを作成(generate)済みだったので、アソシエーション用のマイグレーションファイルを作って、マイグレートしていく形で進めます。

マイグレーションファイルを作成する

$ rails g migration AddUserToTopic
$ rails g migration AddTopicToPost

マイグレーションファイルを編集する

文法は、「add_reference table名、reference名」が基本形になります。
indexはオプションなのでお好みで。
ちなみに超ざっくりですが、indexは読み込み/取得速度を上げてくれるものです
(但し、書き込み速度は遅くなるので注意が必要)

20200222110925_add_user_to_topic.rb
class AddUserToTopic < ActiveRecord::Migration[5.2]
  def change
    add_reference :topics, :user, index: true #追記箇所
  end
end
20200223032448_add_topic_to_post.rb
class AddTopicToPost < ActiveRecord::Migration[5.2]
  def change
    add_reference :posts,:topic #追記箇所
  end
end

マイグレートする

$ rails db:migrate

上記コマンド実行後、以下のようなmigratedが出力されればOK

== 20200223032448 AddTopicToPost: migrating ===================================
-- add_reference(:posts, :topic)
   -> 0.0796s
== 20200223032448 AddTopicToPost: migrated (0.0797s) ==========================

モデルを編集する

topic.rb
class Topic < ApplicationRecord
  validates :title, presence: true
  has_many :posts #追記
  belongs_to :user #追記
end
post.rb
class Post < ApplicationRecord
  belongs_to :topic #追記
  belongs_to :user #追記
end
user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  validates :name,:email,:encrypted_password, presence: true
  has_many :topics, through: :posts #追記
end

これでmodel側はOKです!

controller側の修正

各modelの関連付けできたので以下のような形で、関連先の値を拾ってこれるようになりました。細かく言うとパラメーターの中から明示的に値を取得するイメージです。
params[:topic_id]という風な感じで。

topics_controller.rb
class PostsController < ApplicationController

  def new
    @topic_id = params[:topic_id]
    @post = Post.new
  end

  def create
    @post = Post.new
    @topic_id = params[:topic_id] #paramsからtopic_idを取得し、インスタンス変数@topic_idに代入
    @post.topic_id = @topic_id #上記を@post.topic_idに代入
    @post.body = params[:post][:body]
    @topic = Topic.find(@topic_id)
    @post.user_id = current_user.id
    if @post.save
      redirect_to topic_path(@topic_id), notice: '投稿しました'
    else
      render 'posts/new', alert: '投稿できませんでした'
    end
  end
end

ちょっと汚いですね、、、汗
取得したい値は「paramsから取得してインスタンスに代入」と書く形です。
なので、取得したい値に応じて編集してください。上記はあくまで参考として。

viewの修正(おまけ)

以下はトピック一覧画面(topic#index)ですが、postの数やpostの投稿時間を取得して表示することができます。

index.html.erb
<div class="container mt-5 ml-5">
  <div class="row">
      <% @topics.each do |topic| %>
        <div class="table tabel-hover">
          <%= link_to topic do %>
            <div class="list-group" style="max-width: 500px;">
              <div class="list-group-item list-group-item-action">
                <%= topic.title %> (<%= topic.posts.count %>)  #post数を計算
                <br>
                <small class="text-muted text-right">
                最終投稿日時:<%= topic.posts.last.created_at %> #最新のpost投稿時間を取得する
                </small>
              </div>
          <% end %>
          </div>
      <% end %>
      </div>
  </div>
</div>

こんな表示になります↓

スクリーンショット 2020-02-25 1.42.56.png

非常に便利ですね。

参考文献

Active Record の関連付け

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

数式を含むMarkdownファイルをRe:VIEWにする

はじめに

数式を含む文書を書くことが多いのですが、いつもMarkdownで書いています。Markdownはいろいろなことができませんが、逆に「いろいろなこと」をしないように書くようになるので、慣れるとLaTeXより楽だったりします。とりあえず私は

  • 大きなブロック(章等)が表現できる
  • 箇条書きなどがある
  • リンクを張れる
  • 数式を入れられる
  • 画像を入れられる

だけができれば良く、それ以上のことは要求しません。

VSCodeで書くと、数式も含めてプレビューできるので便利です。そのままGitHubに上げても数式は見えませんが、適当なテンプレートを使ってPandocで変換してやればGitHub Pagesで数式が見えるようになり、ついでにレスポンシブ対応にもできて便利です。例えば一週間でなれる!スパコンプログラマはそうやって作っています。

このようにMarkdownは気軽に書けていいのですが、方言が極めて多く、特に数式の扱いが統一されていないのが困りもので、変換時に様々なトラブルを引き起こします。

というわけで今回、こうやって書いた「数式を含むMarkdown」をRe:VIEW StarterでPDF化する際に困ったことをまとめておきます。以下、Re:VIEW Starterを使う関係で、Re:VIEWのバージョンは2.5を想定します。

方針

マークダウンファイル*.mdmd2review*.reに変換します。ただし、数式が変換できないので、事前に変換スクリプトをかませておきます。また、数式中にアンダースコアがあると変換がおかしくなるので、それもエスケープしておき、後で元に戻す必要があります。

数式

Markdownに数式を入れる方法はいくつか流儀がありますが、ここではインライン数式は$、ブロック数式は$$で囲むことにします。VSCodeではプレビューにKaTeXを使う関係で、alignedまわりとか、微妙にQiitaと違ったりします。

アンダースコアのエスケープ

まず困るのが、アンダースコアが文中に二回出現すると、強調構文と解釈されてしまうことです。md2reviewは変換にRedcarpetを使っていますが、Redcarpetは数式に対応しておらず、数式中に出現するアンダースコアをMarkdownの強調と解釈してしまいます。こんなコードを書いてみましょう。

test.rb
require 'redcarpet'
require 'redcarpet/render/review'
render = Redcarpet::Render::ReVIEW.new()
mk = Redcarpet::Markdown.new(render)
puts mk.render(ARGV[0])

これに$t_1$を食わせてもそのままスルーされます。

$ ruby test.rb '$t_1$'

$t_1$

しかし、From $t_1$ to $t_2$みたいなのを食わせるとこうなります。

$ ruby test.rb 'From $t_1$ to $t_2$'

From $t@<b>{1$ to $t}2$

途中にある二つのアンダースコアに挟まれた部分を強調だと認識されてしまったのです。これは、ブロック内でも同じで、インライン、ブロックともに数式中のアンダースコアをエスケープしてやる必要があります。なんでも良いですが、なんかRe:VIEWっぽく@<underscore>にしておいて、後で戻すことにしましょう。

インライン数式中の中カッコ

Re:VIEWでは、インライン数式は`@<m>{}'で表現できます。しかし、この中で、そのままでは中カッコが使えません。なので、

$T_i^n$

みたいなのは

@<m>{T_i^n}

にすればよいのですが、

$T_i^{n+1}$

をそのまま

@<m>{T_i^{n+1}}

にするとエラーとなります。}\{とエスケープしてやるなど、解決策はいくつかあると思いますが、一番簡単なのはフェンス記法を使うことです。

フェンスには$|が使えますが、$だと変換が面倒になるので$|$を使うことにします。これを使うと、

$T_i^{n+1}$

@<m>|T_i^{n+1}|

と書けます。もちろん、数式中に|を使っている場合は真面目にエスケープする必要があります。

スクリプト

というわけで、*.mdmd2reviewに食わせる前と後に手抜き変換スクリプトをかませることにします。

前処理はこんな感じでしょうか。

pre.rb
def escape_underscore(str)
  str.gsub('_','@<underscore>') 
end

def escape_inline_math(str)
  while str =~ /\$(.*?)\$/
    math = escape_underscore($1)
    str = $` + "@<m>|" + math + "|" + $'
  end
  str
end

def in_math
  while line=gets
    if line=~/\$\$/
      puts "//}"
      return
    else
      puts escape_underscore(line)
    end
  end
end

while line=gets
  if line=~/\$\$/
    puts "//texequation{"
    in_math
  else
    puts escape_inline_math(line)
  end
end

インライン、ブロック中の数式のアンダースコアをエスケープするのと、それぞれRe:VIEW形式に変換しています

ポスト処理は、エスケープしていたアンダースコアを戻すだけです。

post.rb
while line=gets
  puts line.gsub('@<underscore>','_')
end

これだけならsedでもいい感じがしますが、将来なんか面倒な処理が増えた時のためにRubyで書いておきます。

後は、Markdownから参照する画像をRe:VIEWから参照できるディレクトリ(images以下)に適当にコピーすれば、変換が可能です。

まとめ

数式を含むMarkdownファイルを、md2reviweを使ってRe:VIEWファイルに変換してみました。上記の方法で変換して作ったPDFをここに置いておきます。まだ変換ミスなどで「??」になってたり、そこかしこに変なところはありますが、パッと見にはそれっぽくなってることがわかるかと思います。もっと苦労するかと思いましたが、もともと自分の書いてたMarkdownであまり凝ったことをしていなかったせいか、上記の修正だけでそれなりの変換ができました。ただし、印刷用にちゃんとしたものにするためには、変換後にそれなりに手で修正をいれる必要はありそうです。

今回、Re:VIEW Starterを使いましたが、簡単なステップできれいなPDFができて便利でした。また、変換トラブルでエラーが起きた時、Re:VIEW Starter拡張の範囲コメント(#@+++#@---)で二分探索ができたのがデバッグに役に立ちました。

「手軽に書けて、ウェブで簡単に見られるようにして、かつきれいなPDFを生成したい」、文書書きさんならいつも思うことだと思います。「これが最善」という方法はありませんが、とりあえずMarkdown+数式で書いて、PandocでHTMLにして、md2reviewでRe:VIEW経由でPDFにする、という方法を紹介してみました。他にもいろんな方法はあると思いますが、本稿が誰かの参考になれば幸いです。

参考

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

ERROR: While executing gem ... (Errno::EACCES)が出た時の解決法

実現したいこと

railsでWEBアプリケーションを作るための環境構築。

エラー内容

Rubyの拡張機能(gem)を管理するためのbundlerをインストールした時にでたエラー
ERROR: While executing gem ... (Errno::EACCES)

bundlerとは

gem一種
railsでアプリケーションを開発する際には複数のgemを使用します。gem同士の互換性を保ちながらバージョン等を管理してくれる仕組みのこと。複数人、複数環境で開発を行う際に各環境で扱うパッケージの種類やバージョンを合わせてくれて非常に便利である。

解決方法

参考記事

今回このエラーの解決までに参考にした記事です。
https://qiita.com/nachiguro1003/items/4b564b92eca3ba35744a

修正内容

上記参考記事にも記載されていますが、今回のエラー内容は、gemをインストールする時に不必要sudoをつけて実行してしまったため、ファイルへ書き込みする権限を失ってしまったためです。
ですので解決策としてはこの権限を修正します。
ターミナルで以下の通り実行します。
sudo chown -R (ユーザ名):staff /Users/(ユーザ名)/.rbenv
これで権限が戻りますので、再度'gem install bundler'することで無事解決できました。

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

読書ログ『メタプログラミング Ruby 第2版』2章

2章 月曜日: オブジェクトモデル

オブジェクトモデル

全ての 言語要素(クラス、モジュール、インスタンス変数 etc...)が共存しているシステムのこと。

以下の2つの質問に答える場所

  • このメソッドはどのクラスに所属するものなのか
  • このモジュールをincludeしたら何が起きるのか

2.1 オープンクラス

オープンクラス

  • いつでも既存のクラスを再オープンして、そのばで修正できる技法のこと
    • (既存クラスには、String,Arrayなど標準クラスも含む)

classキーワード

  • クラス宣言。主な仕事はクラスのコンテキストに連れて行くこと。

  • Rubyではクラスを定義するコードと、その他のコードに違いはない。

モンキーパッチ

  • オープンクラスを否定的に言う、蔑称。

  • 何も考えずにクラスにコードを追加することで、既存のメソッドを上書きし、意図しない挙動を生み出すこと。

  • Refinementsを使うことで、安全にモンキーパッチを利用できるようになる。

2.2 オブジェクトモデルの内部

インスタンス変数

  • オブジェクトに存在する

  • 値が代入されたときに初めて出現する

    • Rubyのオブジェクトのクラスと、インスタンス変数には何のつながりもない!
  • メソッドを呼び出さなければ、インスタンス変数は存在しない。

class MyClass
    def my_method
        @v = 1
    end
end

obj = MyClass.new
obj.instance_variables => []
obj.my_method => 1
obj.instance_variables => [:@v]

メソッド

  • メソッドはオブジェクトではなく、クラスに存在する
    • オブジェクトにはメソッドはなく、インスタンス変数とクラスへの参照があるだけ

クラスとは

  • クラスはClassクラスのオブジェクト
    • オブジェクトのメソッド → そのクラスのインスタンスメソッド
    • クラスのメソッド → Classクラスのインスタンスメソッド
  • 全てのクラスは、モジュールである
    • クラスとは、インスタンスの生成と継承のための3つのメソッドをもつモジュール
  • クラス名は、定数

定数

  • 大文字で始まる参照は、クラス名やモジュール名を含めて、全て定数
  • プログラムにある全ての定数は、ファイルシステムのようにツリー状に配置
    • モジュール: ディレクトリ / 定数: ファイル
  • 定数ツリーの奥にいるときは、ルートを示すコロン2つで書き始めれば、外部の定数を絶対パスで指定できる
Y = 'ルートレベルの定数'

module M
    Y = 'Mにある定数'
    Y #=> "Mにある定数"
    ::Y #=> "ルートレベルの定数"
end

オブジェクトとクラスのまとめ

オブジェクト

  • インスタンス変数の集まりにクラスへのリンクがついたもの

インスタンスメソッド

  • オブジェクトではなく、オブジェクトのクラスに住む

クラス

  • オブジェクトにインスタンスメソッドの一覧とスーパークラスのリンクがついたもの

クラス名

クラスへのアクセスを行うための参照

2.4 メソッドを呼び出すときに何が起きているの?

  1. メソッドを探す。メソッド探索
  2. メソッドを実行する。selfが必要

メソッド探索

  • Rubyがレシーバのクラスに入り、メソッドを見つけるまで、継承チェーンを上ること。

「右へ一歩、それから上へ」

  • レシーバのクラスに向かって右へ一歩進み、メソッドが見つかるまで継承チェーンを上へ進む。

レシーバ

  • 呼び出すメソッドが属するオブジェクト

継承チェーン

  • スーパークラスを、BasicObjectまで続けたとき、通ったクラスの道筋のこと

モジュールとメソッド探索

  • モジュールをクラスにインクルードすると、Rubyはモジュールを継承チェーンに挿入する
    • include: インクルードしたクラスの上にモジュールが挿入される
    • Prepende: 下にモジュールが挿入される

Kernal

  • Objectクラスが、Kernelモジュールをincludeしているので、全てのオブジェクトの継承チェーンに、Kernelモジュールが挿入される

メソッドの実行

selfキーワード

  • メソッドを呼び出すとき、メソッドのレシーバがselfになる
    • レシーバを明示しないメソッド呼び出しは、全てselfに対する呼び出しになる。
    • 他のオブジェクトを明示してメソッドを呼び出すと、今度はそのオブジェクトがselfになる
  • 最後にメソッドのレシーバとなったオブジェクトが、selfとなる

トップレベル

  • メソッドを呼び出さないときselfになるのは、Objectクラスのインスタンスのmain
self #=> main
self.class #=> Object

クラス定義とself

  • クラスやモジュールの定義の内側(メソッドの外側)では、selfの役割はクラスやモジュールそのもの
class MyClass
    self #=> MyClass
end

Refinements

  • Refinementsが有効になる場所
    • refineブロック
    • usingを呼び出した場所からモジュールの終わりまで(モジュール定義にいる場所 / ファイルの終わりまで(トップレベルにいる場合)
  • クラスをリファインすると言うのは、元のコードにパッチを貼り付けるようなもの
    • リファインされた側のクラスよりも優先される
    • インクルードやプリペンドしたものより優先される

感想

これまで、オブジェクトがメソッド・変数を持つと、漠然とまとめて理解していたので、インスタンス変数は、オブジェクトに存在する / メソッドはオブジェクトではなく、クラスに存在すると整理できたのがかなり、すっきりきたーーーー(ような気がする。)

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

Ruby on Rails 学習メモ.1

Ruby on Rails学習メモ.1

以下、初学者の独学によるメモ(チラシの裏)。
Progateでの学習もある程度進んできたため、アウトプットをすることにした。
学習中のため、誤った解釈もあるかもしれない。

Railsでのサイト作成は、
view(ブラウザ)→
ルーティング(アクションを呼び出す)→
コントローラー(URLに対応したHTMLを送信)
(→view…)
の編集の連続である。

これらの基本をよく理解すること。
また、何をやろうとしているのか(目的を)理解しないまま、コードを覚える事を深追いしながら無理矢理学習を進めようとすると単なる写経になってしまうので、まず目的を理解する事に努めること(理解できない場合は何度も読む)。(先の学習内容まで進めた後に復習すると理解できる場合もあるので、まず進める方が良い場合もある。)

1.新規投稿

HTML(view)の設定

<div class="form-body">
  <%= form_tag("/posts/create") do %>
  <textarea name="content"></textarea>
  <input type="submit" value="投稿">
  <% end %>
</div>

<%= form_tag("/posts/create") do %>のform_tagが設定されていることにより、
<input type="submit" value="投稿">、つまり投稿ボタンを押すと、submitされたデータを「/posts/create」のURLに送信するようになっている。
<textarea name="content"></textarea>
入力したデータにname属性を指定している。
これにより、コントローラーのアクション内(変数:params)で入力したデータを受け取れるようになる。

ルーティングの設定

get "posts/create" => "posts#create"

URL「/posts/create」から情報を受け取り、
「posts」コントローラーの「create」アクションを呼び出す。

コントローラーの設定

def create
    @post = Post.new(content: params[:content])
    @post.save
    redirect_to("/posts/index")
end

ルーティングからcreateアクションが呼び出され、以下の処理が実行される。
@post = Post.new(content: params[:content])
@post.save

変数@postに投稿された内容を代入(受け取るために変数paramsを使用している)し、データベースに保存する。
redirect_to("/posts/index")
URL:/posts/indexに転送(ページ移行)する。

@user = User.new(name: params[:name], email: params[:email])のように、配列として複数のデータを受け取ることもできる。

2.投稿詳細画面の表示

HTML

<% @posts.each do |post| %>
      <div class="posts-index-item">
        <div class="post-right">
          <%= link_to(post.name, "/posts/#{post.id}") %>
        </div>
      </div>
<% end %>

idを取得・送信するように設定。

ルーティング

get "posts/index" => "posts#index"
get "posts/:id" => "posts#show"

showアクションのルーティングはindexアクションより下に書かないといけないことに注意する。

コントローラー

def show
    @posts = Post.find_by(id: params[:id])
end

3.投稿成功・失敗の表示

クラス定義:投稿の制限(Validate:検証の意)

class Post < ApplicationRecord
  validates :content, {presence: true}
  validates :content, {length: {maximum: 140}}
end

Validateはクラス内(models/post/rb)で定義するので、postクラス配下すべてで適用される。
上記は空の投稿と140字以上の投稿にfalseを返すようになっている。

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

def create
    @post = Post.new(content: params[:content])
    if @post.save
    redirect_to("/posts/index")
    flash[:notice] = "投稿を作成しました"
    else
    render("posts/new")
end

if @post.saveがtrueの場合、「/posts/index」に飛び、
「flash[:notice] = "投稿を作成しました"」を実行する。

falseの場合、「posts/new」に飛ぶ

HTML
flashはいろいろな箇所で共通で使っていくのでapplication.html.erbに設定する。
投稿成功の場合:

 <% if flash[:notice] %>
  <div class="flash">
    <%= flash[:notice] %>
  </div>
 <% end %>

投稿失敗の場合
投稿失敗の場合posts/newに飛ぶので、application.htmlではなくnew.htmlを編集する。(2行目から6行目までが該当。)

<%= form_tag("/posts/create") do %>
  <% @post.errors.full_messages.each do |message| %>
       <div class="form-error">
       <%= message %>
       </div>
       <% end %>
       <textarea name="content"><%= @post.content %></textarea>
       <input type="submit" value="投稿">
<% end %>

saveメソッドを呼び出した際にバリデーションに失敗すると、Railsでは自動的に@post.errors.full_messagesの中にエラーメッセージが配列で入るようになっている。
each文を用いることで、配列の中のメッセージ全てを表示させる。
また、<textarea>内に<%= @post.content %>を設定しているため、入力した(投稿に失敗した)文章が出力される。
(newアクションではこの時点では変数postが定義されていないため、@post = Post.newを定義する必要があることに注意。←newアクション作成時に作っておく?)

4.投稿一覧ページの作成

先にターミナルで「rails g model Post content:text」(Post:モデル名、content:カラム名、text:データ型)を実行し、モデルとマイグレーションファイル(データベース)を作成しておく。

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

def index
  @posts = Post.all
end

HTML

<% @posts.each do |post| %>
      <div class="posts-index-item">
          <%= post.content %>
      </div>
<% end %>

each文で変数@posts内の内容を繰り返し(全て)表示させる。

5.投稿の編集

HTML(edit.htmlを作成する必要あり)

<div class="main posts-new">
  <div class="container">
    <h1 class="form-heading">編集する</h1>
    <%= form_tag("/posts/#{@post.id}/update") do %>
    <div class="form">
      <div class="form-body">
        <% @post.errors.full_messages.each do |message| %>
            <div class="form-error">
              <%= message %>
            </div>
          <% end %>
        <textarea name="content"><%= @post.content %></textarea>
        <input type="submit" value="保存">
      </div>
    <% end %>
  </div>
</div>

4行目:idを取得し「/posts/id/update」のURLを送信する。
7行目:アクション(コントローラー)内のifでエラーが出てrenderメソッドを介してページに戻ってきた場合、エラーメッセージを表示させるようになっている。

ルーティング

post "posts/:id/update" => "posts#update"

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

def update
    @post = Post.find_by(id: params[:id])
    @post.content = params[:content]
    if @post.save
      flash[:notice] = "投稿を編集しました"
      redirect_to("/posts/index")
    else
      render("posts/edit")
    end
end

@post = Post.find_by(id: params[:id]):送信されてきたURLとidを参照し、変数@postに代入する。
@post.content = params[:content]:送信されてきた内容をparamsで受け取り、変数@postのcontentに代入する。

if @post.saveが成立したら「投稿を編集しました」のメッセージとともに、「/posts/index"」に戻る。
成立しない(エラーの)場合、renderで「posts/edit」に戻る。
renderで戻らないと「/posts/index」を介し投稿内容の変数が更新されてしまう(編集内容が最初の投稿に戻り、編集したかった投稿がフォームに維持されない。)

6.投稿の削除

HTML(show.html)

 <div class="post-menus">
        <%= link_to("編集", "/posts/#{@post.id}/edit") %>
        <%= link_to("削除", "/posts/#{@post.id}/destroy", {method: "post"}) %>
      </div>

(投稿詳細画面のshow.htmlで完結するので、新しくhtml(view)を追加する必要はない。)
link_toメソッドの第三引数に{method:"post"}を設定しないと、次のルーティングでpostではなくgetを探してしまい、エラーとなってしまう。

ルーティング

post "posts/:id/destroy" => "posts#destroy"

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

def destroy
    @post = Post.find_by(id: params[:id])
    @post.destroy
    flash[:notice] = "投稿を削除しました"
    redirect_to("/posts/index")
end

編集の際と同様、HTMLより送信されたidを変数に代入し、destroyアクションで削除する。
(実際のポートフォリオでは「削除しますか?」の質問を出せるようにする?)

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

deviseのユーザー登録失敗後にビューがずれる。

登録画面ビュー

通常の登録画面でapp/view/devise/registrations/new.html.hamlにCSSを当てたもの
スクリーンショット 2020-02-24 19.37.41.png

これにフォームに空などの無効な値を入れると以下のようになる。

登録失敗後のビュー

スクリーンショット 2020-02-24 19.38.43.png

//登録失敗時のnickname部分のHTML
<span class="nickname">
<div class="field_with_errors"><label for="user_nickname">ニックネーム</label></div>
<div class="field_with_errors"><input type="text" value="" name="user[nickname]" id="user_nickname"></div>
</span>

"field_with_errors"クラスが自動生成されてしまっていることが原因

registrations.scss
.field_with_errors {
  display: contents;
}

上記のCSSを追記して解決

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

Ruby on rails でTodoアプリを作る

今回はRuby on railsを使ってTodoアプリを作っていきます!!!

image.png

完成品が↑

railsの学習のために作ります!

1.プロジェクトを作ろう!

コンソールを起動して、

$ rails new #app名

これでプロジェクトを作ります!
次にcdでプロジェクトに移動しましょう

一旦作成したファイルの方にいってgemfileというものがあると思うのでそれを編集します!
ここをいじってBootstrap等を導入するワケですね!

# gem 'bcrypt', '~> 3.1.7'
gem 'bootstrap', '~> 4.1.1' #この1行だけ追加
# Use ActiveStorage variant

このように記載すればOKです!

ではコンソールに戻りましょう
※ここまででsqliteについてエラーが発生した人がいる場合
ridk exec pacman -S mingw-w64-x86_64-sqlite3
こちらのコマンドを一度うってください

そして全て完了したらコンソールにて

bundle install

こちらのコマンドをうちます
( bundlerというgemを使って、Gemfileに従ってgemをインストールするためのコマンドです!)

ここまで完了したら

rails s

でサーバーを起動してみましょう!
image.png
こういうのが出たら成功です!
成功を確認できたら、ブラウザを開いて
localhost:3000を入力してください!
image.png
このような画面が出てきたら無事、railsアプリの作成準備が整いました!!
(サーバーを終了させたい場合はCtrl+cでyを入力して終了できます)

2.データベースを構築しよう!

RailsはModelを使ってデータベースを操作します
id、created_at、updated_atの3つのカラムがデフォルトで作成されます。
今回はstring型のcontentというカラムを追加したいと思います。
Model名は頭文字が大文字で、単数形でなくてはなりません。
ではコンソールに戻って

$ rails g model Task content:string
$ rails db:migrate

を実行してください!
これで(migurate)データベースにテーブルが作成されます。
このようにTaskというテーブルを作成すると
tasksというように複数形のテーブルができます

以上でデータベースの構築は終わりました!

3.Controllerを使ってみよう!

controllerはMVCを構成するコンポーネントの1つです。
MVCとは
Model:データベースを取り扱う
View:画面表示を取り扱う
Controller:ModelとViewと連携する
といったようなものです。

では今回はタスクの一覧を表示するアクションcontrollerを作っていきましょう!

$ rails generate controller Tasks index

これでタスク一覧を表示するトップページのtasks_controllerを作ります!
作成されたControllerに、登録されている全てのタスクを取得する処理を記述します。

/app/controllers/tasks_controller.rb
 def index
    @tasks = Task.all
  end

Task.allでtasksテーブルの全てのレコードを取得します!

4.ルーティングを設定しよう!

URLに対するリクエストを受けた時にcontrollerのどのアクションを実行するか、を設定します。
resources :{Controller名}で複数のルーティングが一気に設定できるようになります!
これでlocalhostに入った際にindexアクションが動くようになっています

/config/routes.rb
Rails.application.routes.draw do
  root 'tasks#index'
  resources :tasks
end

設定されているルーティングはrails routesで確認しよう!

5.Viewを作ろう!

Controller内のアクション(タスク一覧表示する)が実行されると、対応するViewファイルが呼び出されます!
今回の場合は対応するViewファイルはindex.html.erbです!

/app/views/tasks/index.html.erb
<div class="mx-auto" style="width: 200px;">
<h1>Task list</h1>
</div>
<h2><%= link_to 'Creat new Todo', new_task_path ,class: 'btn btn-info'%></h2>
<table class="table">
  <thead>
    <tr>
    <th>タスク</th>
    <th>編集</th>
    <th>削除</th>
    </tr>
  </thead>
  <tbody>

  <% @tasks.each do |task| %>
    <tr>
      <td><%= task.title %></td>
    </tr>
  <% end %>
  </tbody>
</table>

Bootstrapを使ってテーブルとかを綺麗に表示するようにしてるよ
<% @tasks.each do |task| %>
にて格納されているタスクを表示するようにしています!

https://hackerthemes.com/bootstrap-cheatsheet/#input-group-append
↑もっとBootstrap使いたい方向けにチートシート紹介しときます↑

6.タスクを追加するための準備!

タスク追加画面を表示するためのnewアクションを作ります!

/app/controllers/tasks_controller.rb
  def new
    @task = Task.new
  end

これを追記します!
リクエストがあった時にフォームを表示するために@task = Task.newでTaskのオブジェクトを作成しています!

次に、タスク追加画面のviewファイルを作成します。

/app/views/tasks/new.html.erb

<%= form_for(@task) do |f| %>
  <div><%= f.text_field :title %></div>
  <div><input type="submit" value="追加" class="btn btn-success"></div>
<% end %>
<%= link_to '戻る', tasks_path %

今回はform_forを使っていますがこの説明は長くなるので下記のリンクを参考にしてください!
form_forを使って「フォーム作成→データをテーブルに保存」を実装しています。
https://qiita.com/jumpyoshim/items/ee5af466ef7959567174

7.タスク追加処理を作っていこう!

/tasksにPOSTリクエストがあった時、タスクの追加処理を行うaddアクションを作成します。

/app/controllers/tasks_controller.rb

  def add
    @task = Task.add(task_params)
    redirect_to tasks_path
  end

  private
    def params
      params.require(:task).permit(:title)
    end

POSTで送信された物をデータベースに登録し、その後redirect_to tasks_pathを使ってタスク一覧画面に遷移するようにしています!

またpraivateの部分ですが、Taskモデルのtitleを渡された場合のみ追加処理が実行されるようになっています。

以上で追加処理の部分は完成です!

8.タスクを編集する機能を作ろう!

/tasks/{id}/editにGETリクエストがあった時、タスク編集を表示するeditアクションと
編集した後のデータベースを更新するアクションを一緒に作っときましょう!

/app/controllers/tasks_controllers.rb
  def edit
    @task = Task.find(params[:id])
  end

def update
    @task = Task.find(params[:id])
    @task.update(task_params)
    redirect_to tasks_url
  end

-edit-
リクエスト部分の数値をparams[:id]で取り出しています。

-update-
タスク編集画面から送信された内容を受け取り、データベースを更新します!
findで主キーに対応するレコードを取り出します。

続いて、タスク編集画面のviewファイルを作成します。

/app/views/tasks/edit.html.erb
<h1>タスク編集</h1>
<%= form_for(@task) do |f| %>
  <div><%= f.label :title %></div>
  <div><%= f.text_field :title %></div>
  <div><input type="submit" value="編集" class="btn btn-success"></div>
<% end %>
<%= link_to '戻る', tasks_path %>

これで編集機能は終了です!

9.タスクの削除機能を作ろう!

/tasks/{id}にDELETEリクエストがあった時
idに対応するレコードをデータベースから削除するdestroyアクションを追加します。

/app/controllers/tasks_controller.rb
  def destroy
    @task = Task.find(params[:id])
    @task.destroy
    redirect_to tasks_url
  end

ではこれまで編集機能、削除機能を作ってきたのでそれをタスク一覧画面に追加しよう!

/app/views/tasks/index.html.erb
<tr>
      <td><%= task.title %></td>
      <td><%= link_to '編集', edit_task_path(task) ,class: 'btn btn-success' %></td>
      <td><%= link_to '削除', task_path(task), method: :delete, class: 'btn btn-danger' %></td>
    </tr>

classはBootstrapを使っていい感じのボタンにするため設置

destroyアクションを実行するには、DELETEメソッドのリクエストを送らないとダメなので引数にmethod: deleteを指定しています!

以上で投稿、編集、削除の機能は完成です!!!

10.空の投稿や文字数制限の機能をつけよう!

空の投稿やあまりに多い文字数は色々邪魔だと思うのでこちらを規制しましょう!!

/app/models/task.rb
 validates :title, presence: true, length: { maximum: 50 }

上記のように何か入力内容に規制をしたいとなった場合
validatesを使って titleに入力される内容に規制をかけます
presenceで空白禁止にして、length:{maximum:30}で30文字までしか入力できないようにしました!

/app/views/tasks/new.html.erb
<h1>Task add</h1>
<% if @task.errors.any? %>
  <div class="alert alert-warning">
    <ul>
      <% @task.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
    </ul>
  </div>
<% end %>

を追加して、空白や文字数超えた時にはエラーを表示するように追記しましょう!
これの詳しい説明は
https://qiita.com/ryuuuuuuuuuu/items/1a1e53d062bff774d88a
を参考に!

11.最後に

今回todoアプリを作って、railsでアプリを作る流れは掴めたかと思います。
けど実際にもっと複雑なWebアプリを作るってなるとまだまだ途方にくれそうなのでまずはこういう物を作ったりしてみて
もっと基礎等が掴めてきたら応用に活かせたらいいなと思いました!
https://github.com/t4k3p0n/todo-rails
↑githubです

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

Ruby on railsで、プロジェクトにファイルが生成されない時の対処法

Ruby on railsで、プロジェクトがうまく作成できない時の対処法

gitでこけていたので

rails new --skip-git

と打ってgit関連をスキップするとうまくいった。

gitでソースコードを管理したい場合には、このあとプロジェクトフォルダで

git init

で初期化すればOK。

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

Ruby/GTK3 - Button

gem install gtk3

Button

image.png

"Click me" button was clicked
"Open" button was clicked
Closing application

require 'gtk3'

class ButtonWindow < Gtk::Window
  def initialize
    super
    set_title 'Button Demo'
    set_border_width 10

    hbox = Gtk::Box.new(:horizontal, spacing = 6)
    add(hbox)

    button = Gtk::Button.new(label: 'Click Me')
    button.signal_connect('clicked') { on_click_me_clicked }
    hbox.pack_start(button)

    button = Gtk::Button.new(mnemonic: '_Open')
    button.signal_connect('clicked') { on_open_clicked }
    hbox.pack_start(button)

    button = Gtk::Button.new(mnemonic: '_Close')
    button.signal_connect('clicked') { on_close_clicked }
    hbox.pack_start(button)
  end

  def on_click_me_clicked
    puts '"Click me" button was clicked'
  end

  def on_open_clicked
    puts '"Open" button was clicked'
  end

  def on_close_clicked
    puts 'Closing application'
    Gtk.main_quit
  end
end

win = ButtonWindow.new
win.signal_connect('destroy') { Gtk.main_quit }
win.show_all
Gtk.main
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ruby-dnnとディープラーニングで建物の画像を生成(Pix2pix)

はじめに

ruby-dnnで、Pix2pixを使って、建物の画像変換をやってみます。
コード全文はhttps://github.com/unagiootoro/facade-pix2pixにあります。

Pix2pix

Pix2pixを知らない人向けに大雑把に解説します。
Pix2pixは、主に画像変換に使われるディープラーニングのモデルです。
Pix2pixの最大の特徴は、くっきりとした画像を生成できるというところにあります。
通常の画像変換では、生成した画像が本物の画像にどれぐらい近いかをピクセル値で判断しますが、Pix2pixでは、生成した画像がどれくらい本物の画像に近いかを判断する基準を自動で学習してくれます。ピクセル値の差異は、くっきりした画像より、ぼやけた画像のほうが少なくなります。そのため、ピクセル値に依存しない学習を行うことで、くっきりとした画像が生成できるようになります。1

使用ライブラリ/バージョンなど

Ruby ... v2.6.5
ruby-dnn ... v1.1.3
Numo::NArray ... v0.9.1.5
Numo::Linalg ... v0.1.4
rubyzip ... v2.2.0

ソースコード

ruby-dnnでPix2pixを学習させるコードの大雑把な解説です。
とりあえず動かしてみたい方は、「学習結果の確認」まで飛ばしてください。

データセットの読み込み

facade_dataset.rb

ruby-dnnのdownloaderを使用して、Facadeデータセットをダウンロードします。
やってる処理は以下の通りです。
1. Facadeデータセットをダウンロードして解凍する。
2. ダウンロードしたデータを読み込み、64 * 64のサイズに縮小する。

require "zip"
require "dnn/image"

module DNN
  module Facade
    FACADE_URL = "http://cmp.felk.cvut.cz/~tylecr1/facade/"
    BASE_DIR_NAME = "CMP_facade_DB_base"
    EXTENDED_DIR_NAME = "CMP_facade_DB_extended"
    BASE_PATH = "#{DOWNLOADS_PATH}/downloads/#{BASE_DIR_NAME}"
    EXTENDED_PATH = "#{DOWNLOADS_PATH}/downloads/#{EXTENDED_DIR_NAME}"

    def self.downloads
      unless Dir.exist?(BASE_PATH) 
        zip_file_name = "#{BASE_DIR_NAME}.zip"
        Downloader.download("#{FACADE_URL}/#{zip_file_name}")
        zip_decompression(BASE_PATH)
        File.unlink("#{DOWNLOADS_PATH}/downloads/#{zip_file_name}")
      end
      unless Dir.exist?(EXTENDED_PATH)
        zip_file_name = "#{EXTENDED_DIR_NAME}.zip"
        Downloader.download("#{FACADE_URL}/#{zip_file_name}")
        zip_decompression(EXTENDED_PATH)
        File.unlink("#{DOWNLOADS_PATH}/downloads/#{zip_file_name}")
      end
    end

    def self.zip_decompression(zip_path)
      Zip::File.open("#{zip_path}.zip") do |zip|
        zip.each do |entry|
          zip.extract(entry, "#{zip_path}/#{entry.name}") { true }
        end
      end
    end

    def self.load_images(dir_path)
      downloads
      in_imgs = []
      out_imgs = []
      Dir["#{dir_path}/*.png"].each do |fpath|
        img = DNN::Image.read(fpath)
        img = DNN::Image.resize(img, 64, 64)
        in_imgs << img
      end
      Dir["#{dir_path}/*.jpg"].each do |fpath|
        img = DNN::Image.read(fpath)
        img = DNN::Image.resize(img, 64, 64)
        out_imgs << img
      end
      [in_imgs, out_imgs]
    end

    def self.load_train
      load_images("#{BASE_PATH}/base")
    end

    def self.load_test
      load_images("#{EXTENDED_PATH}/extended")
    end
  end
end

train.rb より抜粋

ダウンロードしたデータは、load_datasetで読み込めるようにします。
このとき、画像データを-1 ~ 1の範囲に正規化します。

def load_dataset
  x, y = DNN::Facade.load_train
  x = Numo::SFloat.cast(x)
  y = Numo::SFloat.cast(y)
  x = (x / 127.5) - 1
  y = (y / 127.5) - 1
  [x, y]
end

モデル定義

dcgan.rb

画像を入力として受け取るDCGANモデルを定義します。各クラスの役割は、以下の通りです。

Generator: 入力画像から変換先の画像を生成します。
Discriminator: 入力画像とGeneratorが生成した画像のセットを受け取り、それらの組み合わせが本物か生成された画像かを判断できるように学習させるためのモデルです。
DCGAN: Discriminatorを騙せるような画像を生成できるようにGeneratorを学習させるためのモデルです。

長いので折りたたんでいます
include DNN::Models
include DNN::Layers

class Generator < Model
  def initialize(input_shape)
    super()
    @input_shape = input_shape
    @l1 = Conv2D.new(32, 4, padding: true)
    @l2 = Conv2D.new(32, 4, strides: 2, padding: true)
    @l3 = Conv2D.new(64, 4, padding: true)
    @l4 = Conv2D.new(64, 4, strides: 2, padding: true)
    @l5 = Conv2D.new(128, 4, padding: true)
    @l6 = Conv2DTranspose.new(64, 4, strides: 2, padding: true)
    @l7 = Conv2D.new(64, 4, padding: true)
    @l8 = Conv2DTranspose.new(32, 4, strides: 2, padding: true)
    @l9 = Conv2D.new(32, 4, padding: true)
    @l10 = Conv2D.new(32, 4, padding: true)
    @l11 = Conv2D.new(3, 4, padding: true)
    @bn1 = BatchNormalization.new
    @bn2 = BatchNormalization.new
    @bn3 = BatchNormalization.new
    @bn4 = BatchNormalization.new
    @bn5 = BatchNormalization.new
    @bn6 = BatchNormalization.new
    @bn7 = BatchNormalization.new
    @bn8 = BatchNormalization.new
    @bn9 = BatchNormalization.new
  end

  def forward(x)
    input = InputLayer.new(@input_shape).(x)
    x = @l1.(input)
    x = @bn1.(x)
    h1 = ReLU.(x)

    x = @l2.(h1)
    x = @bn2.(x)
    x = ReLU.(x)

    x = @l3.(x)
    x = @bn3.(x)
    h2 = ReLU.(x)

    x = @l4.(x)
    x = @bn4.(x)
    x = ReLU.(x)

    x = @l5.(x)
    x = @bn5.(x)
    x = ReLU.(x)

    x = @l6.(x)
    x = @bn6.(x)
    x = ReLU.(x)

    x = @l7.(x)
    x = @bn7.(x)
    x = ReLU.(x)
    x = Concatenate.(x, h2, axis: 3)

    x = @l8.(x)
    x = @bn8.(x)
    x = ReLU.(x)

    x = @l9.(x)
    x = @bn9.(x)
    x = ReLU.(x)
    x = Concatenate.(x, h1, axis: 3)

    x = @l10.(x)
    x = ReLU.(x)

    x = @l11.(x)
    x = Tanh.(x)
    x
  end
end

class Discriminator < Model
  def initialize(gen_input_shape, gen_output_shape)
    super()
    @gen_input_shape = gen_input_shape
    @gen_output_shape = gen_output_shape
    @l1_1 = Conv2D.new(32, 4, padding: true)
    @l1_2 = Conv2D.new(32, 4, padding: true)
    @l2 = Conv2D.new(32, 4, strides: 2, padding: true)
    @l3 = Conv2D.new(32, 4, padding: true)
    @l4 = Conv2D.new(64, 4, strides: 2, padding: true)
    @l5 = Conv2D.new(64, 4, padding: true)
    @l6 = Dense.new(1024)
    @l7 = Dense.new(1)
    @bn1 = BatchNormalization.new
    @bn2 = BatchNormalization.new
    @bn3 = BatchNormalization.new
    @bn4 = BatchNormalization.new
    @bn5 = BatchNormalization.new
    @bn6 = BatchNormalization.new
  end

  def forward(inputs)
    input, images = *inputs
    x = InputLayer.new(@gen_input_shape).(input)
    x = @l1_1.(x)
    x = @bn1.(x)
    x1 = LeakyReLU.(x, 0.2)

    x = InputLayer.new(@gen_output_shape).(images)
    x = @l1_2.(x)
    x = @bn2.(x)
    x2 = LeakyReLU.(x, 0.2)

    x = Concatenate.(x1, x2)
    x = @l2.(x)
    x = @bn3.(x)
    x = LeakyReLU.(x, 0.2)

    x = @l3.(x)
    x = @bn4.(x)
    x = LeakyReLU.(x, 0.2)

    x = @l4.(x)
    x = @bn5.(x)
    x = LeakyReLU.(x, 0.2)

    x = @l5.(x)
    x = @bn6.(x)
    x = LeakyReLU.(x, 0.2)

    x = Flatten.(x)
    x = @l6.(x)
    x = LeakyReLU.(x, 0.2)

    x = @l7.(x)
    x
  end

  def enable_training
    trainable_layers.each do |layer|
      layer.trainable = true
    end
  end

  def disable_training
    trainable_layers.each do |layer|
      layer.trainable = false
    end
  end
end

class DCGAN < Model
  attr_reader :gen
  attr_reader :dis

  def initialize(gen, dis)
    super()
    @gen = gen
    @dis = dis
  end

  def forward(input)
    images = @gen.(input)
    @dis.disable_training
    out = @dis.([input, images])
    [images, out]
  end
end

モデルの作成

train.rb より抜粋

Generator、Discriminator、DCGANのそれぞれのモデルを作成します。
最適化には、Discriminator、DCGANともにAdamを使用しています。
損失関数は、DiscriminatorにはSigmoidCrossEntropyを適用し、DCGANには、出力画像に対する損失関数としてMeanSquaredErrorを、画像の真偽判定に対する損失関数としてSigmoidCrossEntropyを10:1の割合で設定しています。

gen = Generator.new([64, 64, 3])
dis = Discriminator.new([64, 64, 3], [64, 64, 3])
dcgan = DCGAN.new(gen, dis)
dis.setup(Adam.new(alpha: 0.00001, beta1: 0.1), SigmoidCrossEntropy.new)
dcgan.setup(Adam.new(alpha: 0.0002, beta1: 0.5),
            [MeanAbsoluteError.new, SigmoidCrossEntropy.new], loss_weights: [10, 1])

モデルの学習

train.rb より抜粋

学習部分のコードになります。
1. load_datasetでFacadeデータセットを読み込む。
2. gen.predict(x_in)で、画像を生成する。
3. dis.train_on_batch([x_in, x_out], y_real)とdis.train_on_batch([x_in, images], y_fake)で、生成した画像と本物の画像を判断できるようにDiscriminatorが学習させる。
4. dcgan.train_on_batch(x_in, [x_out, y_real])で、Discriminatorを騙せるようにGeneratorを学習させる。

x_in, x_out = load_dataset

iter1 = DNN::Iterator.new(x_in, x_out)
iter2 = DNN::Iterator.new(x_in, x_out)
num_batchs = x_in.shape[0] / batch_size
(initial_epoch..epochs).each do |epoch|
  num_batchs.times do |index|
    x_in, x_out = iter1.next_batch(batch_size)

    images = gen.predict(x_in)
    y_real = Numo::SFloat.ones(batch_size, 1)
    y_fake = Numo::SFloat.zeros(batch_size, 1)
    dis.enable_training
    dis_loss = dis.train_on_batch([x_in, x_out], y_real)
    dis_loss += dis.train_on_batch([x_in, images], y_fake)

    x_in, x_out = iter2.next_batch(batch_size)
    dcgan_loss = dcgan.train_on_batch(x_in, [x_out, y_real])

    puts "epoch: #{epoch}, index: #{index}, dis_loss: #{dis_loss}, dcgan_loss: #{dcgan_loss}"
  end
  iter1.reset
  iter2.reset
  dcgan.save("trained/dcgan_model_epoch#{epoch}.marshal") if epoch % 50 == 0
end

学習結果の確認

さあ、学習の準備が整ったので早速学習開始...と行きたいところですが、ruby-dnnはまだGPUに
対応していないので、学習しようとすると1日以上かかります。そのため、今回はすぐに試せるよう、
学習済みの重みを用意しました。

■学習済みの重みを使用する場合。

$ ruby imgen.rb

■一から学習する場合。(1日以上かかります)

$ ruby train.rb
$ ruby make_data.rb
$ ruby imgen.rb

実行結果

inputが入力画像、outputが出力画像、realが本物の画像です。
モデルが小さいので細かいところは再現できていないですが、雰囲気を掴むのには成功してそうですね。
facade-pix2pix.PNG

おわりに

いかがだったでしょうか。ディープラーニングというと、どうしてもPythonのイメージが強いですが、Rubyでも意外とできるってことが伝わってくれれば嬉しく思います。


  1. 実際には、Pix2pixでは、ピクセル値による平均絶対誤差とDiscriminatorによる画像の真偽判定の両方を損失関数として使用しています。 

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

MacにDocker+Rails6+MySQLで開発環境構築

公式ドキュメント(https://docs.docker.com/compose/rails/) を以下のコンポーネントでやってみた。
- ruby2.6.5
- Rails6系
- MySQL5.7

前提

MacにDocker Desktopがインストールされていること。
適宜、プロジェクト用のディレクトリを用意する。

Dockerfile

Dockerfile
FROM ruby:2.6.5

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

RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs yarn

RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]

Gemfile

source 'https://rubygems.org'
gem 'rails', '~>6'

Gilefile.lock

touch Gemfile.lock

entrypoint.sh

entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

docker-compose.yml

docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root
    ports:
      - "3306:3306"
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db

Railsプロジェクトの作成

# 薄いRailsプロジェクト
docker-compose run web rails new . --force --database=mysql -B -M -C --skip-coffee --skip-turbolinks

build

docker-compose build

database.ymlの修正

config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password # docker-compose.yml の MYSQL_ROOT_PASSWORD
  host: db

コンテナ再起動

docker-compose down
docker-compose up -d

Webpacker

docker-compose run web bundle exec rails webpacker:install

DB作成

docker-compose run web bundle exec rails db:create
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

devise token authの出力をserializerで整形する

はじめに

この記事ではdevise token authでのjsonの出力をactive_model_serializer gemを使って整形する手順を解説するわ。実際の開発中に少し詰まったことを元にしているからゴールとして"ユーザーのログイン成功時に返されるjsonをserializerで整形する"を設定するわ。他のアクション後に返されるjsonにも応用できるように心掛けるから安心しなさい。
devise関連の導入まで含めちゃうと長くなるからそこは割愛するわ、ごめんなさいね。

準備するわよの巻

まずはgemのインストールから始めるわ。

Gemfile.
gem 'active_model_serializers'
bundle install

これでactive_model_serializersのインストールが完了したわ。
このgem自体の説明は他の記事に譲るとしてまずは設定をするわ。
設定用のconfig/initializers/ams.rb ファイルを手動で追加しなさい。

config/initializers/ams.rb
ActiveModel::Serializer.config.adapter = :json_api

この部分では出力するjsonのフォーマットを指定しているわ。オプションの詳しい説明についてはこちらの記事に詳しいわ。

Railsのactive_model_serializerについて学ぶ100DaysOfCodeチャレンジ10日目(Day10:#100DaysOfCode)

ここではjson_apiを指定したけど、あなたの好きなように設定しなさい。

さて、次はいよいよserializerのファイルを作っていくわよ。

rails generate serializer User

最後のUserの部分は整形したいモデル名を指定することね。このコマンドでapp/serializers/user_serializer.rbファイルが作成されたわ、早速いじいじしちゃいましょう。

app/serializers/user_serializer.rb
class UserSerializer < ActiveModel::Serializer
  attributes :id, :name, :nickname, :image, :confirmed_at, :updated_at
end

ここではjsonで返すモデルのカラムを指定しているわ。他にも色々設定できるけど適宜自分で調べなさい、頑張ることね。

本題に入るわよの巻

ここまでで準備は完了よ、configに追加した設定を読み込ませるためにこのあたりで一旦サーバを再起動しておくことね、ん?もうやった? 良い子ね、クマちゃんのビスケットをあげるわ。

さて、まず見て欲しいものがあるの、serializerで整形をしない場合のログイン成功時に返されるjson(ログインに成功したユーザーの情報)よ。

スクリーンショット 2020-02-24 16.16.37.png

続いて同じjsonをserializerで整形した場合よ。

スクリーンショット 2020-02-24 16.22.57.png

データが構造化されているのと設定したカラムのみが出力されていることが分かるわね。
ちなみに下は

app/controllers/api/v1/user_controller.rb
class Api::V1::UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    render json: @user, serializer: UserSerializer
  end
end

こんな感じのコードよ。
見ての通り、deviseの方のjsonも整形しないとアプリケーション全体として返すjsonのフォーマットに統一性が取れないことが分かるわ。これはフロント側としても混乱するしメンテナンス性も大きく損なうわ。
でも、自分で一から書くアクションなら上のコードみたいに簡単に実装できるんだけどdeviseは内部で勝手にやってくれるから分かりづらいわよね。
そこで今回は
1. コントローラを継承する
2. そこで各アクションごとのレンダリングメソッドをオーバーライドしてjsonをserializerで整形する

という手順でdeviseで返されるjsonの整形をするわ。

コントローラの継承をするわよ編

今回はログイン時のjsonを整形したいということで、ログイン時に呼ばれるアクションは
devise_token_auth/sessions#createよ。ということで
devise_token_auth/sessionsコントローラを継承するわ。

app/controllers/api/v1/auth/sessions_controller.rb
class Api::V1::Auth::SessionsController < DeviseTokenAuth::SessionsController 

end 

新しく作ったコントローラが参照されるようにルーティングも変更よ。
私の場合は

config/routes.rb
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :users, only: [:show]
      mount_devise_token_auth_for 'User', at: 'auth', controllers: {
        registrations: 'api/v1/auth/registrations',
        # ここの部分ね
        sessions: 'api/v1/auth/sessions'
      }
    end
  end
end

さて、準備が整ったところでさっきの手順の2の太字になっている各アクションごとのレンダリングメソッドの説明をするわ。出力を制御する、と聞いて最初に対象のアクションをそのままオーバーライドすることを考えた子も多いと思うの。でも、出力だけを変えたいのにいちいち大元のアクションメソッドをオーバーライドするのは面倒じゃない?deviseちゃんでは特定のアクションで成功、失敗した時にjsonをレンダリングするメソッドが個別で用意されてるの。要は必要に応じてそこをいじればいいってわけね。

このメソッドちゃんたちの存在は公式ドキュメントに載ってるの。この記事で日本語訳してくださってる方がいるので貼っておくわ。
【翻訳】devise-auth-token公式ドキュメント

表を見れば分かる通り、今回私が整形したいのはログインの成功時のjsonよ。つまりレンダリングメソッドで言うとrender_create_successね。早速オーバーライドしちゃいましょう。

app/api/v1/auth/session_controller.rb
class Api::V1::Auth::SessionsController < DeviseTokenAuth::SessionsController 

  def render_create_success 
   render json: @resource, serializer: UserSerializer
  end 

end 

ログインが成功した時のリソース、つまりuserの情報は@resourceで取得できるわ。これをserializerで整形すると...

スクリーンショット 2020-02-24 16.48.03.png

この通り!jsonが構造化されてカラムも設定したものしか返されていないわ!

参考文献

情報が比較的少ない中で有益な記事の存在に助けられました。心よりの感謝を申し上げます。

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

devise token authのjsonをserializerで整形するわよの巻

はじめに

この記事ではdevise token authでのjsonの出力をactive_model_serializer gemを使って整形する手順を解説するわ。実際の開発中に少し詰まったことを元にしているからゴールとして"ユーザーのログイン成功時に返されるjsonをserializerで整形する"を設定するわ。他のアクション後に返されるjsonにも応用できるように心掛けるから安心しなさい。
devise関連の導入まで含めちゃうと長くなるからそこは割愛するわ、ごめんなさいね。

準備するわよの巻

まずはgemのインストールから始めるわ。

Gemfile.
gem 'active_model_serializers'
bundle install

これでactive_model_serializersのインストールが完了したわ。
このgem自体の説明は他の記事に譲るとしてまずは設定をするわ。
設定用のconfig/initializers/ams.rb ファイルを手動で追加しなさい。

config/initializers/ams.rb
ActiveModel::Serializer.config.adapter = :json_api

この部分では出力するjsonのフォーマットを指定しているわ。オプションの詳しい説明についてはこちらの記事に詳しいわ。

Railsのactive_model_serializerについて学ぶ100DaysOfCodeチャレンジ10日目(Day10:#100DaysOfCode)

ここではjson_apiを指定したけど、あなたの好きなように設定しなさい。

さて、次はいよいよserializerのファイルを作っていくわよ。

rails generate serializer User

最後のUserの部分は整形したいモデル名を指定することね。このコマンドでapp/serializers/user_serializer.rbファイルが作成されたわ、早速いじいじしちゃいましょう。

app/serializers/user_serializer.rb
class UserSerializer < ActiveModel::Serializer
  attributes :id, :name, :nickname, :image, :confirmed_at, :updated_at
end

ここではjsonで返すモデルのカラムを指定しているわ。他にも色々設定できるけど適宜自分で調べなさい、頑張ることね。

本題に入るわよの巻

ここまでで準備は完了よ、configに追加した設定を読み込ませるためにこのあたりで一旦サーバを再起動しておくことね、ん?もうやった? 良い子ね、クマちゃんのビスケットをあげるわ。

さて、まず見て欲しいものがあるの、serializerで整形をしない場合のログイン成功時に返されるjson(ログインに成功したユーザーの情報)よ。

スクリーンショット 2020-02-24 16.16.37.png

続いて同じjsonをserializerで整形した場合よ。

スクリーンショット 2020-02-24 16.22.57.png

データが構造化されているのと設定したカラムのみが出力されていることが分かるわね。
ちなみに下は

app/controllers/api/v1/user_controller.rb
class Api::V1::UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    render json: @user, serializer: UserSerializer
  end
end

こんな感じのコードよ。
見ての通り、deviseの方のjsonも整形しないとアプリケーション全体として返すjsonのフォーマットに統一性が取れないことが分かるわ。これはフロント側としても混乱するしメンテナンス性も大きく損なうわ。
でも、自分で一から書くアクションなら上のコードみたいに簡単に実装できるんだけどdeviseは内部で勝手にやってくれるから分かりづらいわよね。
そこで今回は
1. コントローラを継承する
2. そこで各アクションごとのレンダリングメソッドをオーバーライドしてjsonをserializerで整形する

という手順でdeviseで返されるjsonの整形をするわ。

コントローラの継承をするわよ編

今回はログイン時のjsonを整形したいということで、ログイン時に呼ばれるアクションは
devise_token_auth/sessions#createよ。ということで
devise_token_auth/sessionsコントローラを継承するわ。

app/controllers/api/v1/auth/sessions_controller.rb
class Api::V1::Auth::SessionsController < DeviseTokenAuth::SessionsController 

end 

新しく作ったコントローラが参照されるようにルーティングも変更よ。
私の場合は

config/routes.rb
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :users, only: [:show]
      mount_devise_token_auth_for 'User', at: 'auth', controllers: {
        registrations: 'api/v1/auth/registrations',
        # ここの部分ね
        sessions: 'api/v1/auth/sessions'
      }
    end
  end
end

さて、準備が整ったところでさっきの手順の2の太字になっている各アクションごとのレンダリングメソッドの説明をするわ。出力を制御する、と聞いて最初に対象のアクションをそのままオーバーライドすることを考えた子も多いと思うの。でも、出力だけを変えたいのにいちいち大元のアクションメソッドをオーバーライドするのは面倒じゃない?deviseちゃんでは特定のアクションで成功、失敗した時にjsonをレンダリングするメソッドが個別で用意されてるの。要は必要に応じてそこをいじればいいってわけね。

このメソッドちゃんたちの存在は公式ドキュメントに載ってるの。この記事で日本語訳してくださってる方がいるので貼っておくわ。
【翻訳】devise-auth-token公式ドキュメント

表を見れば分かる通り、今回私が整形したいのはログインの成功時のjsonよ。つまりレンダリングメソッドで言うとrender_create_successね。早速オーバーライドしちゃいましょう。

app/api/v1/auth/session_controller.rb
class Api::V1::Auth::SessionsController < DeviseTokenAuth::SessionsController 

  def render_create_success 
   render json: @resource, serializer: UserSerializer
  end 

end 

ログインが成功した時のリソース、つまりuserの情報は@resourceで取得できるわ。これをserializerで整形すると...

スクリーンショット 2020-02-24 16.48.03.png

この通り!jsonが構造化されてカラムも設定したものしか返されていないわ!

参考文献

情報が比較的少ない中で有益な記事の存在に助けられました。心よりの感謝を申し上げます。

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

[font-awsomeのエラー]File to import not found or unreadable: font-awesome-sprockets.のエラーについて

1.エラーの様子

スクリーンショット 2020-02-24 15.34.42.png

デスクトップ(裏側で製作していたファイル)では動いていたデータをホームディレクトリ(本番のファイル)に移した時上記のようなエラーが出ました。

2.エラーの原因

1.一般的な原因

・Gemfileのなかにgem 'font-awesome-sass'が抜けている

・app/stylesheets/application.scssの中に@import "font-awesome-sprockets"@import "font-awesome";が抜けている

2.見落としがち(特殊)な原因

@import "font-awesome-sprockets"とするところを@import "font-awesome-compass";としている。またはその逆。

・別フォルダで作成したファイルを移すときに、rails sをしたままgemを追記し、bundle installしている

3.解決方法

1.Gemfileのなかにgem 'font-awesome-sass'が抜けている

Gemfileにgem 'font-awesome-sass'を追記して下記コマンドを打ちましょう

$ bundle install

2.app/stylesheets/application.scssの中に@import "font-awesome-sprockets"@import "font-awesome";が抜けている

app/stylesheets/application.scssの中に下記2行を追記しましょう

@import "font-awesome-sprockets"
@import "font-awesome";

3.@import "font-awesome-sprockets"とするところを@import "font-awesome-compass";としている。

githubに記載されている公式の使用方法(readmeの部分)を読んで、どちらが自分の使用すべきコマンドか確認してみましょう。公式は下記urlです。

https://github.com/FortAwesome/font-awesome-sass

4.別フォルダで作成したファイルを移すときに、rails sをしたままgemを追記し、bundle installしている

・Gemfilはサーバーを起動したままだと反映されないため、railsを再起動しましょう。コマンドはcontroll+cでrailsサーバーを落とし、再度rails sをしましょう

・上記でうまくいかない場合、他のもともとあったgemと干渉しあっている可能性があるためGemfile.lock(gemの実行コードが自動生成されたファイル)を全て削除しbundle installをしましょう。その際もサーバーを起動中の方はcontroll+cでrailsサーバーを落とし、再度rails sをしましょう

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

[Bootstrap]ドロップダウンボタンのアイコンを変更&「ドロップダウンがボタンに対して右下に出ちゃう!」と「選択肢の下に変な余白が!?」を解消

概要

表題の通りですが、長いので下記にまとめます。

1.ドロップダウンボタンのアイコンを変更する。
2.ドロップダウンがボタンに対して右下に出ちゃう!」を解消。
3.「選択肢の下に変な余白が!?」を解消。

すべて簡単ですが、2.3に少しハマったのでメモを兼ねて共有させていただきます。

環境

Ruby:2.6.3
Rails:5.1.6
bootstrap:4.4.1
FontAwesome

方法

1.ドロップダウンボタンのアイコンを変更する。
-button.btn.dropdown-toggleの中にFontawesome(iタグ)を通常通り配置。
-デフォルトのボタンを消す(.dropdown-toggle::after { display: none; })。

2.ドロップダウンがボタンに対して右下に出ちゃう!」を解消。
div.dropdown-menuにdropdown-menu-rightクラスを追加。
→position:absolute関連のleftプロパティの値をいじっていたが、これだけでよかった。
要は、ドロップダウンメニューの起点がボタンに対して左上(デフォルト)になっているのを右上に変更する。

3.「選択肢の下に変な余白が!?」を解消。
div.dropdown-item(下記link_toメソッド(HTMLではaタグ))の後にいくつかの半角スペース
→これが選択肢の内容として認識されていた為、選択肢の下の「変な余白」として反映されていた。

home.html.erb
<div class="dropdown">
  <button class="btn dropdown-toggle" type="button" id="dropdownMenuButton", data-toggle="dropdown">        
    <i class="fas fa-chevron-down"></i>
  </button>
  <div class="dropdown-menu dropdown-menu-right">
  <%= link_to "削除する", dreampost, class: 'dropdown-item', method: :delete,
                                        data: { confirm: "削除してよろしいですか?" } %>
  </div>
</div>
custom.scss
.dropdown-toggle::after { display: none; }

スクリーンショット 2020-02-24 16.11.03.png

スクリーンショット 2020-02-24 15.15.06.png

スクリーンショット 2020-02-24 15.16.38.png

スクリーンショット 2020-02-24 15.19.27.png

スクリーンショット 2020-02-24 16.09.01.png

補足

ドロップダウンボタンのアイコンは、親要素にdisplay:flexとjustify-content:space-betweenで右端寄せしています。

ご指摘などございましたら、ぜひよろしくお願いいたします。

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

Ruby/GTK3 - AboutDialog

gem install gtk3

AboutDialog

image.png
image.png
image.png

require 'gtk3'

unless Gtk::Version.or_later?(3, 4, 2)
  puts "This sample requires GTK+ 3.4.2 or later: #{Gtk::Version::STRING}"
  exit
end

a = Gtk::AboutDialog.new
a.artists            = ['Artist 1 <no1@foo.bar.com>', 'Artist 2 <no2@foo.bar.com>']
a.authors            = ['Author 1 <no1@foo.bar.com>', 'Author 2 <no2@foo.bar.com>']
a.comments           = 'This is a sample script for Gtk::AboutDialog'
a.copyright          = 'Copyright (C) 2020 Ruby-GNOME Project'
a.documenters        = ['Documenter 1 <no1@foo.bar.com>', 'Documenter 2 <no2@foo.bar.com>']
a.license            = 'This program is licenced under the same licence as Ruby-GNOME.'
a.logo               = GdkPixbuf::Pixbuf.new(file: File.join(__dir__, 'gnome-logo-icon.png'))
a.program_name       = 'Gtk::AboutDialog sample'
a.translator_credits = "Translator 1\nTranslator 2\n"
a.version            = '1.0.0'
a.website            = 'https://ruby-gnome2.osdn.jp'
a.website_label      = 'Ruby-GNOME Project Website'

a.signal_connect('activate-link') do |_widget, uri|
  p _widget.class
  p uri
end

p a.run

image.png
image.png
image.png

#!/usr/bin/env ruby
# frozen_string_literal: true

#   aboutdialog2.rb - Ruby/GTK sample script.
#
#   Copyright (c) 2005-2020 Ruby-GNOME Project Team
#   This program is licenced under the same licence as Ruby-GNOME.
require 'gtk3'

unless Gtk::Version.or_later?(3, 4, 2)
  puts "This sample requires GTK+ 3.4.2 or later: #{Gtk::Version::STRING}"
  exit
end

about_dialog = Gtk::AboutDialog.show(
  nil,
  'artists' => ['Artist 1 <no1@foo.bar.com>', 'Artist 2 <no2@foo.bar.com>'],
  'authors' => ['Author 1 <no1@foo.bar.com>', 'Author 2 <no2@foo.bar.com>'],
  'comments' => 'This is a sample script for Gtk::AboutDialog',
  'copyright' => 'Copyright (C) 2005-2020 Ruby-GNOME Project',
  'documenters' => ['Documenter 1 <no1@foo.bar.com>', 'Documenter 2 <no2@foo.bar.com>'],
  'license' => 'This program is licenced under the same licence as Ruby-GNOME.',
  'logo_icon_name' => 'gtk-home',
  'program_name' => 'Gtk::AboutDialog sample',
  'translator_credits' => "Translator 1 <no1@foo.bar.com>\nTranslator 2 <no2@foo.bar.com>\n",
  'version' => '1.0.0',
  'website' => 'https://ruby-gnome2.osdn.jp',
  'website_label' => 'Ruby-GNOME Project Website'
)

about_dialog.signal_connect('delete_event') do
  Gtk.main_quit
end

Gtk.main

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

【Rails男子必見】エラー系女子との会話が弾む3つのテクニック【モテたい】

はじめに

添野です。TECH::EXPERTというプログラミングスクールに通っています。
7日目で基礎本試験を突破しました。

今日はエラー系女子にモテたい男子必見
Railsのエラー問題で苦戦中の方向けの記事です。

モテる男子の会話テクニック

モテる男子がどんな会話をしているのか、渋谷のエラー系女子にインタビューしてみました。

✅ちゃんと話(エラー文)を聞いてくれて、私のことをしっかり考えてくれる(20歳 OL)
✅私が怒って(エラーを出して)も何の話をしてるのか考えてくれて、先回りで行動してくれる(17 歳JK)
✅前に話したこと(エラー)を覚えていて、何度も同じことを言わなくていいと気が楽ですね(26歳 看護師)

話を聞いてくれる男子と一緒にいたいと感じるようですね。
それでは、インタビューで出た女子の意見について詳しく見ていきましょう。

1. ちゃんと話を聞く「エラー文を見る」

皆さんは彼女とケンカになって、一方的に自分の意見を言ってしまった経験はありませんか?

モテる男子になるにはまず、エラー系女子の話をちゃんと聞いてあげましょう
例えば、次のエラー文を見てください。
スクリーンショット 2020-02-24 10.19.17.png

実はエラー文には「直すべきファイルが何か」「どう直せばいいか」が書かれているときがあります。
彼女は「あんたのここがダメだって言ってんでしょ!こうしてよ!」ってわざわざ教えてくれてます
「showビューの6行目"user_signed_in"を"user_signed_in ? "に直せ」と書いてあるので、
これを直すだけで彼女の怒りは収まり、あなたはモテる男に生まれ変わります。

とにかく、最初は彼女の言っていることをしっかり聞きましょう。

2. 何の話か考える「対象のファイルを特定する」

上記のように直すべき点を指摘してくれる場合は楽ですが、世の中親切な女の子ばかりではありません。
それでも、好きな子には振り向いて欲しいですよね。そんなアナタにとっておきの方法です。

次のエラー文を見てください。
これは、pictweetアプリでログアウトボタンをクリックしたら発生しました。
スクリーンショット 2020-02-24 10.36.37.png

これだけでは何のエラーかさっぱりですが、モテる男子はここで「あの話かな?」と先読みをします
そして、先読みをするには「直前にあなたがとった行動」を思い出してください。
例えば、上記のエラーでは直前に「ログアウトボタン」をクリックしました。
ということは「ログアウトボタンに関係する何か」で彼女が怒っているのだと先読みできます。

ログアウトボタンはapplicationビューに書かれているので、ファイルの中を見てみると、
「, method: :delete」という記述が抜けていることに気付きます。これが彼女の怒りの原因です。

before
<%= link_to "ログアウト", destroy_user_session_path>

after
<%= link_to "ログアウト", destroy_user_session_path, method: :delete %>



ここで、「記述抜けなんて、そんなの気づけなくね?」と思うかもです。
そんな時は作りたいアプリの完成形を横に並べておきましょう。補助輪を付けるんです。

「そんなのダサいし勉強にならない」と思うかもしれません。ですが、プライドを捨ててください
この方が早く成長できます。モテる男は、無駄なプライドは捨てています。
モノマネ・パクりから始めましょう。自分磨きは、その後です。

こうして、モテる男は常に女の子の不満を先読みし、自分の行動を変えていきます

3. 前に話したことを覚えている「エラーリストを作る」

これも特に多かった意見です。何度も同じことを言わせる男子はモテません
一度指摘されたり、先読みで特定した彼女の不満をリスト化して、常にエラーに備えましょう。

インタビューでは、「エラー系女子を怒らせたRails男子の行動Top14」も調査してきました。
ランキング形式で発表していきます。モテたい男子の皆さん、要チェックですよ!!

第14位「検索すると全ての投稿が表示される」

検索用パラメータ受け渡し時のキー名の記載ミス
対象ファイル:indexビュー

before
<%= form.text_field :keywords, placeholder: "投稿
検索", class: "search-input" %>

after
<%= form.text_field :keyword, placeholder: "投稿
検索", class: "search-input" %>

第13位「ArgumentError」

引数指定の記載漏れ
対象ファイル:tweetモデル

before
def self.search

after
def self.search(search)

第12位「ActionControl ler::ParameterMissing」

form_withを使用しているのにformの記述が抜けている
対象ファイル:showビュー

before
<%= form_with(model: [@tweet,@comment], local: true) do |form| %>
  <textarea cols="30" name="text" placeholder="" rows="2"></textarea>
  <input type="submit" value="SEND">
<% end %>

after
<%= form_with(model: [@tweet,@comment], local: true) do |form| %>
  <%= form.text_area :text, placeholder: "text" , rows: "2" %>
  <%= form.submit "SEND" %>
<% end %>

第11位「リファクタリング問題」

省略できる記述が省略せずに書かれている
対象ファイル:indexビュー

before
<% @tweets.each do |tweet| %>
  <%= render partial: "tweet", locals: { tweet: tweet } %>
<% end %>

after
<%= render @tweets %>

彼氏のメールが長すぎて何を言いたいか分からない

第10位「Undefined method」

パターン1「ログイン判定記述ミス
対象ファイル:applicationビュー

before
<% if true %>

after
<% if user_signed_in? %>

パターン2「deviseメソッドの記述位置ミス」
対象ファイル:ルーティングファイル

before
resources :tweets
devise_for :users

after
devise_for :users
resources :tweets

第9位「AcctiveRecord::RecordNotFound」

パターン1「ログアウトボタン記述ミス
対象ファイル:applicationビュー

before
<%= link_to "ログアウト", destroy_user_session_path %>

after
<%= link_to "ログアウト", destroy_user_session_path, method: :delete
%>

パターン2「namespaceメソッドとresourcesメソッドの記述順ミス
対象ファイル:ルーティングファイル

before
resources :tweets do
  resources :comments, only: :create
end
namespace :tweets do
  resources :searches, only: :index
end

after
namespace :tweets do
  resources :searches, only: :index
end
resources :tweets do
  resources :comments, only: :create
end

第8位「NotNullViolation」

「deviseパラメータに指定しているキー名の記述ミス」
対象ファイル:applicationコントローラー

before
devise_parameter_sanitizer.permit(:sign_up, keys: [:name])

after
devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname]

イタリアンを食べに行こうって言ってデートにきたら寿司屋に連れてかれた

第7位「空のツイートを保存できてしまう」

バリデーションの記述がない
対象ファイル:tweetモデル

before

after
validates :text, :image, presence: true

第6位「ターミナルでNo template found」

ページ切替のリダイレクトアクション記載漏れ
対象ファイル:commentsコントローラー

before

after
redirect_to tweet_path(@comment.tweet)

第5位「テキストや画像、コメントが表示されない」

ストロングパラメーターの記述ミス
対象ファイル:tweetsコントローラー

before
{ user_id: current_user.id }

after
params.permit(:image, :text).merge(user_id: current_user.id)

LINEで素直な感謝の気持ちを送ったのに、未読スルー

第4位「NoMethodError」

パターン1「renderメソッドで誤字
対象ファイル:newビュー

before
<%= render partial: "form", locals: { form: @form } %>

after
<%= render partial: "form", locals: { form: form } %>

デートの集合場所の住所をLINEで送ってきたのに、間違ってる

パターン2「commentのルーティングがネストされてない
対象ファイル:ルーティングファイル

before
resources :tweets
resources :comments, only: :create


after
resources :tweets do
  resources :comments, only: :create
end

デートの集合場所を、私が入ってないLINEグループにLINEしたことに気づいてない

パターン3「コントローラーの誤字
対象ファイル:tweetsコントローラー

before
def index
  @post = Post.includes(:user)
end


after
def index
  @posts = Post.includes(:user)
end

パターン4「パス指定順のミス
対象ファイル:showビュー

before
<%= form_with(model: [@comment, @tweet], local: true) do |form| %>

after
<%= form_with(model: [@tweet, @comment], local: true) do |form| %>

第3位「ツイート保存不可(表示されない)」

ストロングパラメーターのrequireメソッド記述抜け
対象ファイル:tweetsコントローラー

before
params.permit(:image, :text).merge(user_id: current_user.id)

after
params.require(:tweet).permit(:image, :text).merge(user_id: current_user.id)

第2位「ActionView::MissingTemplate」

パターン1「ビューファイルがない/配置が間違ってる
対象ファイル:_tweetビュー(部分テンプレート)

before
部分テンプレート名が"tweet.html.erb"

after
部分テンプレート名を"_tweet.html.erb"に修正

東京タワーを展望できるホテルというタレコミでデートにきたのに、東京タワーが見えない部屋を予約していた

パターン2「renderメソッドの記述ミス
対象ファイル:showビュー

before
<%= render partial: "/post", locals: { post: post } %>

after
<%= render partial: "posts/post", locals: { post: post } %>

第1位「Rounting Error」

対象ファイル:ルーティングファイル

パターン1 resourcesメソッドの誤字または抜け
indx→indexに修正、:createが抜けているので追加

before
resources :tweets, only: [:indx, :show, :new, :destroy, :edit, :update] do
after
resources :tweets, only: [:index, :show, :new, :create, :destroy, :edit, :update] do

パターン2 rootメソッドの誤字

before
root to: 'post#index'

after
root to: 'posts#index'

デート当日になって、デート先を決めてない/デート先を忘れた/道を間違えた

まとめ

いかがだったでしょうか。
最後にもう一度、モテる男子の特徴をおさらいしましょう。

✅ちゃんと話を聞く「エラー文を見る」
✅何の話か考える「対象のファイルを特定する」
✅前に話したことを覚えている「エラーリストを作る」

大まかに理解できたら、上記でなぜモテるのかを考えて、自分を磨いていきましょう。
以上、Rails男子がエラー系女子にモテる方法でした。

おすすめ記事
Rails消化のコツ
Rails用語集 基礎
Railsは"5つの属性"を意識しろ


添野又吉(@enjoy_omame)
https://twitter.com/enjoy_omame

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