20200314のRubyに関する記事は29件です。

【Ruby】Unicorn で動かす一番小さいサンプルメモ

メモ

  • ほとんど参考サイトの写経
  • ワーカープロセスを2つ動かす

事前準備

Gemfile
source 'https://rubygems.org'

gem "unicorn"
gem "rack"

Rackアプリケーションの要件は以下とのこと。

  • callというメソッドを持っていること
  • callメソッドの引数としてWebサーバからのリクエストを受けること
  • callメソッドは,次の要素を含むレスポンスを返すること
    • ステータスコード
    • レスポンスヘッダ(Hash)
    • レスポンスボディ(Array)

ちなみに以下変数 env の中身はリクエスト時に展開すると、以下のようにハッシュ構造でHTTPリクエストが渡される

{"REMOTE_ADDR"=>"121.87.8.214",
 "REQUEST_METHOD"=>"GET",
 "REQUEST_PATH"=>"/",
 "PATH_INFO"=>"/",
 "REQUEST_URI"=>"/",
 "SERVER_PROTOCOL"=>"HTTP/1.1",
 "HTTP_VERSION"=>"HTTP/1.1",
 "HTTP_HOST"=>"18.182.112.198:8080",
sample.rb
class SimpleApp
  def call(env)
     [
      200,
      { "Content-Type" => "text/html" },
      ["contents"],
    ]
  end
end
config.ru
# coding: utf-8

require_relative 'simple_app.rb'
run SimpleApp.new
unicorn.conf
worker_processes 2

実行

% bundle install --path vendor/bundle
# config.ru は省略可能
# config.ru はアプリケーションのルートファイルとして読み込まれる
% bundle exec unicorn -c unicorn.conf config.ru

参考

第23回 Rackとは何か(1)Rackの生まれた背景:Ruby Freaks Lounge|gihyo.jp … 技術評論社 http://gihyo.jp/dev/serial/01/ruby/0023

unicorn: Rack HTTP server for fast clients and Unix https://yhbt.net/unicorn/README.html

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

[Rails] DBにデータが登録されない時の対処方法

はじめに

Rails5.0での投稿機能実装ではまったのでメモ

環境

macOS Catalina
Ruby 2.5.1
Rails 5.0.7.2
PostgreSQL

困り

form_forでDBにデータを登録しようと思ったが、エラー出てないのにデータ保存されていなかった

対処した方法

  1. GUIアプリで確認
  2. 保存されない原因を確認①(pry-rails)
  3. ハッシュの二重構造を解消
  4. 保存されない原因を確認②(メソッドの後ろに"!")
  5. バリデーションによるエラーを解消

1. GUIアプリで確認

  • PostgreSQLを使用しているので、Posticoをインストール
  • GUIアプリでDBが見れるようになったら、もう一度投稿してみる
  • GUIアプリ上ではデータが保存されていないはず

▼インストール方法とDBへの接続方法
PostgreSQL:PostgreSQLのデータをGUIでいじる(macOS/Postico)
MySQL:Sequel Proを使ってデータベースを視覚化しよう

▼DB名の調べ方
ターミナルで「psql -l」を実行
実行結果の「app-name_development」が対象のDB名

terminal
$ psql -l #PostgreSQLのDBを一覧で表示する

#実行結果
                                 List of databases
          Name           | Owner  | Encoding | Collate | Ctype | Access privileges 
-------------------------+--------+----------+---------+-------+-------------------
 app-name_development    | user   | UTF8     | C       | C     | 
 app-name_test           | user   | UTF8     | C       | C     | 
 postgres                | user   | UTF8     | C       | C     | 
 template0               | user   | UTF8     | C       | C     | =c/user        +
                         |        |          |         |       | user=CTc/user
 template1               | user   | UTF8     | C       | C     | =c/user        +
                         |        |          |         |       | user=CTc/user
(5 rows)

2. 保存されない原因を確認①(pry-rails)

  • pry-railsをインストール
Gemfile
group :development, :test do
  gem 'pry-rails'
end
terminal
$ bundle install
  • サーバー再起動(Ctrl + C、rails s)

  • データを保存するメソッドに binding.pry

post_controller.rb
def create
  binding.pry #データを保存するメソッドの中に
  Post.create(post_params)
end

private
def post_params
  params.permit(:content, :image)
end
  • もう一度投稿してみる
  • 投稿画面は読み込み中になるので、ターミナルでparamsを確認
terminal
    3: def create
 => 4:   binding.pry
    5:   Post.create(post_params)
    6: end

#params と入力
[1] pry(#<PostsController>)> params
=> <ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"zDzgsL3bOIk0QSPOFk7eSOxvqVk18RQ0PW14dC3A7JtsLB9X2pLwvJ41V/f9WeqBDRo4Uz2PKtJDvcT4WpSCRg==", "post"=><ActionController::Parameters {"content"=>"ポストの内容テストテストテスト", "image"=>"test.jpeg"} permitted: false>, "commit"=>"投稿", "controller"=>"posts", "action"=>"create"} permitted: false>

#post_params と入力
[2] pry(#<PostsController>)> post_params
Unpermitted parameters: :utf8, :authenticity_token, :post, :commit
=> <ActionController::Parameters {} permitted: true> #ハッシュの中身が空なので登録できていない

ハッシュである'{}'の中身が空になっているため、
params.permit(:content, :image)では、paramsから該当するキーを取得できていないのが原因

3. ハッシュの二重構造を解消

post_controller.rb
def create
  Post.create(post_params) #binding.pry は削除
end

private
def post_params
  params.require(:post).permit(:content, :image)
  #requireメソッドを使って二重構造となっているハッシュを取得
end
  • もう一度投稿する
  • Posticoで保存されているか確認 ⇒ 保存されていれば完了!

4. 保存されない原因を確認②(メソッドの後ろに"!")

  • 保存されない場合は別の方法で原因を確認
  • 保存するメソッドの後ろに!をつける
post_controller.rb
def create
  Post.create!(post_params) #binding.pry は削除 #createの後ろに"!"
end
  • この状態でもう一度投稿すると画像のようなエラーが出るはず(コードは異なります) validation-failed-user-must-exist.png

⇒アソシエーションが組まれている際に、該当の外部キーが入っておらず、バリデーションで弾かれている

5. バリデーションによるエラーを解消

  • optional: trueを記載
post.rb
class Post < ApplicationRecord
 belongs_to :user, optional: true #optional: ture を記載
end
  • optional: trueとは、belongs_toの外部キーのnilを許可するというもの

Railsのbelongs_toに指定できるoptional: trueとは?

  • 僕の場合はこれで保存できました。

参考記事

https://teratail.com/questions/121213
https://qiita.com/tanaka7014/items/50a1a953b3f440cbe481

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

Rubyで手を動かしてやったことまとめ

書いた目的:いちいち探すのがめんどくさかったことをまとめてみた

詰まるたびに適宜更新する。

取得した文字列を1文字づつ分割して配列に入れたい

split関数で空文字を指定すると取得できる。
sample:

split_char.rb
str = "tukapai"
array = str.split("")
puts array 

output:

ruby split_char.rb                                                             
["t", "u", "k", "a", "p", "a", "i"]

development/Ruby

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

Rails~~投稿とユーザーを紐付けする

テーブルに新しくカラムを追加する

まずターミナルからマイグレーションファイルを作成。データベースへ反映させる。

ターミナル

rails g migration add_user_id_to_posts
※マイグレーションを変更
rails db:migrate

マイグレーションファイルの中身

class AddUserIdToPosts
  def change
    add_column :posts, :user_id, :integer
  end
end

バリデーションの設定

class Post<ApplicationRecord

 validates :user_id,{presence: true}
end

投稿したユーザーのidを保存しよう

controller

def create
   @post=Post.new(
       content: params[:content],
       user_id: @current_user.id
   )
end

新規投稿後、user_idに投稿したユーザーの値を加える

user_idからユーザー情報を取得する

ユーザー名やユーザー画像を表示するためには、user_idカラムの値からそのユーザーの情報を取得する必要がある。投稿詳細ページにて反映させたいため、postsコントローラのshowアクション内で@post.user_idを用いて、そのidに該当するユーザーの情報をデータベースから取得する。

controller

def show
  @post = Post.find_by(id:params[:id])
  @user = User.find_by(id: @post.user_id)
end

@userから始まる処理は@post.user_idの値からユーザー情報を取得するという意味となる

view

<img src="<%="/user_images/#{@user.image_name}"%>">

<%=link_to(@user.name,"/users#{@user.id}")%>

Postモデルにuserメソッドを定義する

models

class Post<ApplicationRecord
  def user
    return User.find_by(id:self.user_id)
  end
end

ユーザー詳細ページに投稿を表示する

find_byメソッドはその条件に合致するデータを1件だけ取得することができる。今回は複数の情報を取得しなければならない。複数の情報を取得するためには「where」メソッドを使用する

rails console
posts = Post.where(user_id:1)
=>[...]

consoleでこのコードを取得することでuser_idの値が「1」である投稿をすべて取得する。

Userモデルにpostsメソッドを定義

models

class User<ApplicationRecord
  def posts
     return Post.where(user_id: self.id)
  end
end

投稿の表示

@user.postsを用いて、各投稿にそれぞれ表示を行う。
whereメソッドで取得した値は配列に入っているので、view側でeach文を用いて1つずつ投稿を表示する

view

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

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

Alfred Workflowsで入力文字列からリストを作るRubyスクリプトの雛形

Alfred Workflowsは便利なんだけど、いざ自分で作ろうとしたとき「動的にリストを作って表示したい」と思った最初の一歩だけでもちょっとめんどくさかったので雛形を作りました。

Alfred Workflowsって何? という人はこちらの記事をどうぞ。
https://qiita.com/jackchuka/items/ccd3f66f6dd00481b98b#workflows

実現したいこと

ヘルパーライブラリとしてはPython製の https://www.deanishe.net/alfred-workflow/ とかが見つかります。が、そこまで大層なものは別に欲しくない。

とりあえず改造しやすい最小限のコードで、以下の画像のような感じの入力と出力が欲しい。
あとリスト項目を選択したら、なんかクリップボードに項目がコピーされると嬉しい。

image.png

コード

def item(q)
    {:title => q, :subtitle => "サブタイトル", :arg => "#{q}ですよ" }
end

queries = ARGV[0].split
items = queries.map do |q| item(q) end

puts "<items>"
items.each_with_index do |item, i| puts <<EOS
  <item uid="#{i}" arg="#{item[:arg]}">
    <title>#{item[:title]}</title>
    <subtitle>#{item[:subtitle]}</subtitle>
    <icon>icon.png</icon>
  </item>
EOS
end
puts "</items>"

使い方

image.png
image.png

Script Filter の言語をRubyにしてベタ書きで書いてもいいし、あるいは Script Filter で bashとかを選んで ruby hoge.rb "{query}" とか書いてもいい。

以上です。

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

[Ruby on Rails]trueだとvalidation通るのにfalseだとvalidation通らない

フロントエンジニアです
apiのつなぎこみしていて、ハマったので書きます。

結論

boolean型の存在確認にpresence: trueを使うと、
trueだとvalidation通り、falseはvalidionが通らない。

具体例

こんな感じのTaskテーブルがあって、

create_table "tasks", force: :cascade do |t|
  t.string "name", null: false
  t.boolean "is_finished", null: false
  t.datetime "created_at", precision: 6, null: false
  t.datetime "updated_at", precision: 6, null: false
end

こんな感じにis_finishedカラムにpresence: trueをかけているとします。

class Task < ApplicationRecord
  validates :is_finished, presence: true
end

こんなサンプルデータがあって、is_finishedをfalseにしてsaveしようとするとエラリます。
スクリーンショット 2020-03-14 19.53.40.png

解説

どうやらpresenceは、内部的にblank?メソッドを呼び出しているぽいです。
公式railsのここを見てます。間違ってたらごめんなさい。

で、blank?についてですが、Railsガイドによると、falseはblankと見なされると。

なので、falseだとblank?で引っかかって、エラっていたわけですね。

解決

prsence: trueの代わりにinclusion: { in: [true, false] }を使えば期待通りの挙動になります

まとめ

trueだと通って、falseだと通らない時、validationを確認する。
presenceが使われていたら、inclusion: { in: [true, false] }を代わりに試してみる

以上です。
読んでいただき、ありがとうございます。

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

【Ruby】配列とは?

配列とは?

・配列とは複数の値をまとめて管理したいときに使用
・配列は「箱が順番に並んだグループ」

配列の作成方法

Rubyにおいて配列を作成する場合は括弧( [] )を使用。
また変数を、複数用意する際は、括弧の中に値をカンマ区切りで記述していく。

■使用例
array = [“犬”, “ネコ”, “ハリネズミ”]

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

Railsでフォーム作成時にHTMLで書いたコードをform_tagに変換する方法

Ruby on Railsでヘルパーメソッドを使って、HTMLのフォームをform_tagに書き換える方法を説明します。

ヘルパーメソッドとは

ある動作を処理する場合にメソッド化して扱えるようにRailsにあらかじめ組み込まれた機能のこと。使うことでビューをシンプルに美しく書くことができるといったメリットがあリます。

form_tagメソッドとは

フォームを実装するためのヘルパーメソッドです。

今回は以下のコードを書き換えます。

sample.html.erb
<form action="/posts" method="post">
  <input type="text" name="content">
  <input type="submit" value="投稿する">
</form>

これをform_tagに書き換えると

sample.html.erb
<%= form_tag('/posts', method: :post) do %> 
  <input type="text" name="content"> 
  <input type="submit" value="投稿する"> 
<% end %>

となります。変わっているのは以下の部分です。

sample.html.erb
HTML:
<form action="/posts" method="post">
</form>
form_tag:
<%= form_tag('/posts', method: :post) do %> 
<% end %>

form_tagのdo〜endは〜の部分がフォームであることを示しています。
記載を変換する際は、<%= %>(erbタグ)doを忘れないように気をつけましょう。

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

postgreSQLのインストールとrails newをするまでの流れ

herokuでのディプロイのために、推奨とされるpostgre SQLについて調べましたが結構手間取ったので、まとめました。
(herokuは推奨がpostgre SQLで、これまで使用してきた、mysqlは別途設定が必要かつエラー出やすかったりするらしいので、rails new 時点から postgreSQL でアプリ作りました。)

※完璧ではないと思うので何かツッコミあればお願いします。

以下ターミナルでのコマンド

postgreSQLのインストール

postgreSQLの有無の確認
~ % psql --version
postgreSQLのインストール

*下記コマンドはhomebrewインストールが前提

% brew install postgresql
postgreSQLのバージョン確認
% psql -V
psql (PostgreSQL) 11.5
postgreSQLのインストール場所の確認
% which psql
/usr/local/bin/psql
postgreSQLの起動
% brew services start postgresql
==> Successfully started `postgresql` (label: homebrew.mxcl.postgresql)
postgreSQLの起動
% brew services start postgresql
==> Successfully started `postgresql` (label: homebrew.mxcl.postgresql)
データベースの起動状況確認
% brew services list
Name       Status  User           Plist
mysql@5.6  started username/Users/username/Library/LaunchAgents/homebrew.mxcl.mysql@5.6.plist
postgresql started username /Users/username/Library/LaunchAgents/homebrew.mxcl.postgresql.plist
railaアプリの作成
% rails _5.2.3_ new <appname> -d postgresql
データベース作成
<appname> % rake db:create

以上です。

なお、postgreSQLには、
中間テーブルを作らず、配列で形式で多対多のテーブルを作ったり、位置情報形式の指定など色々便利な機能があるので、postgreSQLは便利。

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

postgres sql のインストールとrails newをするまでの流れ

herokuでのディプロイのために、推奨とされるpostgressqlについて調べましたが結構手間取ったので、まとめました。
(herokuは推奨がpostgressqlで、これまで使用してきた、mysqlは別途設定が必要かつエラー出やすかったりするらしいので、rails new 時点から postgresslql でアプリ作りました。)

※完璧ではないと思うので何かツッコミあればお願いします。

以下ターミナルでのコマンド

postgres sqlのインストール

postgres sqlの有無の確認
~ % psql --version
postgres sqlのインストール

*下記コマンドはhomebrewインストールが前提

% brew install postgresql
postgres sqlのバージョン確認
% psql -V
psql (PostgreSQL) 11.5
postgres sqlのインストール場所の確認
% which psql
/usr/local/bin/psql
postgres sqlの起動
% brew services start postgresql
==> Successfully started `postgresql` (label: homebrew.mxcl.postgresql)
postgres sqlの起動
% brew services start postgresql
==> Successfully started `postgresql` (label: homebrew.mxcl.postgresql)
データベースの起動状況確認
% brew services list
Name       Status  User           Plist
mysql@5.6  started username/Users/username/Library/LaunchAgents/homebrew.mxcl.mysql@5.6.plist
postgresql started username /Users/username/Library/LaunchAgents/homebrew.mxcl.postgresql.plist
railaアプリの作成
% rails _5.2.3_ new <appname> -d postgresql
データベース作成
<appname> % rake db:create

以上です。

なお、postgres sqlには、
中間テーブルを作らず、配列で形式で多対多のテーブルを作ったり、位置情報形式の指定など色々便利な機能があるので、pstgres sqlは便利。

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

マイクロポストにコメント機能をつける

はじめに

ご訪問いただきありがとうございます。初めての投稿ですが学んだことを備忘録的にまとめようと思い記事にしてみました。レベル的にはrailsチュートリアルを終えたくらいのレベルです。もし間違えているところ等あればアドバイスいただけると幸いです。

作るもの

今回は投稿されたマイクロポストに自由にコメントできるような機能を付けます。(題材は自分のポートフォリオサイトです。尚、すでにUserテーブルとMicropostテーブルは作成済みです。)

対象読者

railsチュートリアルに機能を追加したい等自分と同じ位のレベルの人を対象としています。

作成の流れ

まず初めにコメント機能を追加するまでの流れです。
1.Commentモデルの作成、変更
2.モデルの関連付け、バリデーションの設定
3.Commentsコントローラーの作成、ルーディングの設定
4.対応するビューの作成
5.コントローラのアクションの作成

1.Commentsモデルの作成

まずデータベースとやりとりを行う、Commentモデルの作成を行います。
ちなみにテーブルの中身はこんな形です。

カラム
id integer
user_id integer
micropost_id integer
content text

誰がコメントしたかわかるようにuser_idはUserモデルと関連付けを行い、同じくmicropost_idもどのマイクロポストにコメントされたかわかるようにMicropostモデルと関連付けをこないます。
それではモデルを作成します。

rails g model comment user:references micropost:references content:text
db/migrate/_create_comments.rb
class CreateComments < ActiveRecord::Migration[5.1]
  def change
    create_table :comments do |t|
      t.references :user, foreign_key: true
      t.references :micropost, foreign_key: true
      t.text :content, null: false

      t.timestamps
    end
  end
end

モデルを作成する際にreferencesとすることでindexと外部キー制約(foregin_key: true)が自動で追加されます。indexをuser_idとmicropost_idに追加することによってそれぞれに関連したコメントを探す際にデータを高速に調べられるようになります。また、外部キー制約がつくことによってuser_id(micropost_id)にはUserテーブルに存在するidのみデータベースレベルで保存するようになります。
また、コメントが空だとコメント機能の意味をなさないためnull:falseを追加します。
それではテーブルを作成します。

rails:db:migrate

2.モデルの関連付け、バリデーションの設定

作成されたモデルはこんな感じです。

app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :user
  belongs_to :micropost
  validates :content, presence: true, length: { maximum: 140 }
end

自動でbelongs_toで一対一の関連付けができています。User,Micropostモデルにはそれぞれ手動で追加する必要があるので追加していきます。また、バリテーションも追加しています。テーブル作成の時点でcontentにはnull:falseで空で保存させないようにしていますが空文字("")は保存できます。なのでpresence:trueを追加することによって空文字も拒否するようになります。また、バリデーションに基づいたエラーメッセージも保存されます。文字数制限に関してTwitterと同じく140文字としています。

app/models/user.rb
class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
  has_many :comments, dependent: :destroy
app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  has_many :comments, dependent: :destroy

UserもMicropostも多数のコメントを持てるためhas_manyで1対多の関連付けを行います。dependent: :destroyでUser、Micropostが消えた際に関連するコメントも消えるようにしています。

3Comenntsコントローラーの作成、ルーディングの設定

続いてコントローラーを作成します。今回はcreateとdestroyアクションのみ使用します。

rails g controller comments
app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  def create 
  end

  def destroy
  end
end

ルーディングの設定。

config/routes.rb
resources :microposts, only: [:new, :show, :create, :destroy] do
   resources :comments, only: [:create, :destroy]
end

commetsはmicropostsとネストして親子の関係を持たせます。こうすることによってコメントを作成する際に関連しているmicropostのidを取得することが容易になります。ネスト有無の違いはこんな感じです。commentsがmicropstsの下の階層についているのがわかります。

ネスト有

micropost_comments POST   /microposts/:micropost_id/comments(.:format) comments#create
micropost_comment DELETE /microposts/:micropost_id/comments/:id(.:format) comments#destroy

ネスト無し

comments POST   /comments(.:format) comments#create
comment DELETE /comments/:id(.:format) comments#destroy

4対応するビューの作成

今回はMicropostの詳細ページからコメントするような形をとります。
コメントを表示するのはMicroposts/:idになるのでまずはコントローラーにコメント表示とコメントフォーム用にインスタンス変数を作成しておきます。

app/controllers/microposts_controller.rb
def show

    @comment =Comment.new
    @micropost = Micropost.find(params[:id])
    @comments =micorpost.comments#適時ページネーション等利用してください
end

コメント表示(投稿者と中身のみ)

app/views/microposts/show.html.erb
<% @comments.each do |comment| %>
<%= comment.user.name %>
<%= comment.content %>
<% end %>

コメントフォーム

app/views/microposts/show.html.erb
<%= form_with model: [@micropost, @comment], local: true do |f| %>
  <%= render 'shared/error_messages', object: f.object %>

    <div class = 'form-group'>
      <%= f.text_area :content, class: 'form-control', id:'content'\
         placeholder: "コメントを記入してください" %>
    </div>

    <%= f.submit "コメントする", class: "btn btn-primary" %>
<% end %>

コメントはいずれかのマイクロポストと関連づいているため、どのマイクロポストのコメントなのかという情報が必要になります。
そのため、form_withにmicropostのidも渡します。@comment=Comment.newで新規に値を作成しているので作成ボタンを押すと自動的にcreateアクションが動きます。

5.コントローラのアクションの作成

次はコメントを作成するcreateアクションを作っていきます。

app/contorollers/comments_contoroller.rb
class CommentsController < ApplicationController
  before_action :logged_in_user, only: :create

  def create 
    @comment = current_user.comments.build(comment_params)
    @comment.micropost_id = params[:micropost_id]
    if @comment.save
      flash[:success] = 'コメントしました'
      redirect_to @comment.micropost
    else
      @micropost = Micropost.find(params[:micropost_id])  
      @comments = @micropost.comments
      render template: 'microposts/show'
    end

 private

  def comment_params
    params.require(:comment).permit(:content)
  end
end

createアクション失敗後に再度同じページを表示するため、コメントを取得してます。(このやり方があっているかはわかりません。)コメントフォーム専用のページを設ければこの取得はいらないです。

コメントを作成する際に、ユーザーのidを渡す必要がありますが、current_user.comments.buildとすることによりログイン中ユーザーのidを入れ込みます。このままだとマイクロポストのidがなくコメントを作成できないのでform_withから送られてくるmicropost_idを取得しています。
(ストロングパラメータでmicoropst_idを取得する方法がわかりませんでした。)

これでコメントフォームからコメントをすることができるようになりました。
続いてコメント削除です。
まずはviewsに削除リンクを埋め込みます。

app/views/microposts/show.html.erb
<% @comments.each do |comment| %>
<%= comment.user.name %>
<%= comment.content %>
<%= link_to '削除', micropost_comment_path(@micropost, comment), method: :delete %>
<% end %>

destroyアクションを動かすためmethod: :deleteを指定します。
pathにはコメントがいずれかのマイクロポストに紐づいている関係上(@micropost,comment)を渡す必要があります。

micropost_comment DELETE /microposts/:micropost_id/comments/:id(.:format) comments#destroy
app/contorollers/comments_contoroller.rb
class CommentsController < ApplicationController
  before_action :correct_user,   only: :destroy
  def destroy
    @comment = Comment.find(params[:id])
    @comment.destroy
    flash[:success] = 'コメントを削除しました'
    redirect_to @comment.micropost
  end

 private
  def correct_user
    @comment = current_user.comments.find_by(id: params[:id])
    redirect_to root_url if @comment.nil?
  end
end

終わりに

初めての記事作成で至らぬ点ばかりだったと思います。
何か間違いや、認識違い等があればアドバイスいただければ幸いです。
それではここまで読んんでいただいきありがとうございました。

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

Mailer viewでヘルパーメソッドを使用する

Mailer viewでヘルパーを使いたい

郵便番号にハイフンをいれてくれるメソッド

application_helper.rb
def format_zipcode_include_hyphen(zipcode)
    zipcode.include?('-') ? zipcode : zipcode.insert(3,'-')
end

mailer viewでいきなり使用するとエラーになった。

application_mailer.rb

application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  add_template_helper(ApplicationHelper)
end

と記述する

view

mailer.ftml.slim
p #{format_zipcode_include_hyphen(zipcode)}

でうまくいきました。

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

盲点rubyのメソッドdeleteのそれぞれ

ルビーのシルバー勉強中に盲点だったところが見つかりました。

それはdeleteメソッド

deleteメソッドの挙動について今回は記事をあげたいと思います。

まずdeleteメソッドはarray、string、hashそれぞれのクラスで使えるメソッドです。

Array

まずはarrayクラスのdeleteメソッドの動き

deleteメソッドは引数で与えた値が一致したものも全て削除します。

array = [1,2,3,4,5,1,2,3,4,5]
p array.delete(3)
3
p array
[1,2,4,5,1,2,4,5]

上記のようにdeleteメソッドを使ったタイミングで返ってくる値は削除する対象の値が返ってきます。

そして破壊メソッドなので3が削除されています。

私は、deleteメソッドは他stringやhashも動きそんなに変わらないだろうと思っていました。

そして、模擬問題をとているときに間違えてしまい、
あれ?となったのです。

それはstringのdeleteメソッドの問題でした。

String

string = '1234512345'
p string.delete('3')

さてこのとき何が返ってくるか?

正解は

string = '1234512345'
p string.delete('3')
"12451245"

またもう一度stringを呼び出すとどうなるか?

string = '1234512345'
p string.delete('3')
"12451245"
p string
"1234512345"

というわけで破壊メソッドではないんですね!
もちろんdelete!で破壊メソッドとなります。

string = '1234512345'
p string.delete!('3')
"12451245"
p string
"12451245"

Hash

それではhashクラスはどうなるか?

hash = {a: 1, b: 2, c: 3, d: 4, e: 5}
p hash.delete(:c)
3
p hash
{:a=>1, :b=>2, :d=>4, :e=>5}

という動きをします。

返ってくるのは削除したkeyに対応するvalueです。

また破壊メソッドなので指定した引数が削除されています。

というわけでdeleteメソッドでした。

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

『非同期でのメッセージ投稿』が理解できる最低限のRailsアプリを丁寧に作る(Ajax苦手の自分とお別れしよう)

この記事の基本的な方針

Ajaxはなんだか難しい!は、勘違いです。一歩一歩きちんと進んでいけば、普通のことだと思えてくるでしょう。
ここではAjax非同期通信を理解するためだけの簡易なアプリを一つ丁寧に作成して、Ajax学習の基礎を完了することを目的としています。

この記事は、以下の「登録画面」「ログイン画面」「TOP画面」の3画面の簡単なアプリを元に拡張していきます
【TOP画面(ログイン前)】     【TOP画面(ログイン後)】
a0.png a9.png
【登録画面】
a1.png
【ログイン画面】
a2.png

手を動かしながら読みたいようでしたら、以下でこの3画面アプリを手に入れてください。

Terminal
$ git clone -b 超最低限のRailsアプリ(messageコントローラVer)  https://github.com/annaPanda8170/minimum_rails_application.git
$ bundle install
$ bundle exec rake db:create
$ bundle exec rake db:migrate

これ自体の作り方はこちら

想定する読み手

既に一度Railsアプリをチュートリアルやスクール等で作ったことがある方であり、JQueryの基本文法を理解している方を想定しております。
Mac使用で、パソコンの環境構築は完了していることが前提です。

取り急ぎ同期通信のメッセージ投稿機能をつくる

※本筋ではないので急ぎ足で説明します。詳しい説明が必要な方は、別記事をご覧ください(newアクションを使わず、formをindexに置くという違いがあります)。

① メッセージテーブルを作る

マイグレーションファイルモデルを作るため、

Terminal
$ rails g model message

を打ちます。
マイグレーションファイルに

db/migrate/2020xxxxxxxxxx_create_messages.rb
class CreateMessages < ActiveRecord::Migration[5.2]
  def change
    create_table :messages do |t|
      t.string :message
      t.references :user, foreign_key: true
      t.timestamps
    end
  end
end

となるように追記し、

Terminal
$ rails db:migrate

します。
これを済ませたら、メッセージテーブルは完成です。一応ちゃんと出来ているかデータベースを見に行ってみましょう。
mysql.png
私はSequelProを使っていてこんな感じです。

モデルにテーブル同士の関係を書きましょう。これが無くても投稿できなくはないのですが、投稿した内容を引き出して扱う上で便利なので今済ませてしまいましょう。以下を追記します。

app/models/message.rb
belongs_to :user
app/models/user.rb
has_many :messages

②メッセージだけを投稿できるようにする

ルーティング、ビュー、コントローラを編集します。今回はindexにフォームを置くのでnewアクションはなしです。

config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  root 'messages#index'
  resources :messages, only: [:index, :create]
end
app/views/messages/index.html.erb
<% if user_signed_in? %>
  <%= current_user.email %>
  <%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
  <%= form_with(model: @message, local: true, class: "form") do |f| %>
    <%= f.text_field :message %>
    <%= f.submit "投稿" %>
  <% end %>
<% else %>
  <%= link_to '新規登録', new_user_registration_path %>
  <%= link_to 'ログイン', new_user_session_path %>
<% end %>

この時点で、localhost:3000でもlocalhost:3000/messagesでも
log.png
が表示されるはずです。この時点ではこの投稿フォームただの飾りなので、データベースに保存できるよう中身を作ります。
コントローラは以下です。

app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  before_action :to_root, except: [:index]
  def index
    @message = Message.new
  end
  def create
    @message = Message.new(message_params)
    @message.save
    redirect_to root_path
  end
  private
  def message_params
    params.require(:message).permit(:message).merge(user_id: current_user.id)
  end
  def to_root
    redirect_to root_path unless user_signed_in?
  end
end

これで一度投稿してみましょう。

goodafternoon.png
問題なさそうですね。

③投稿を表示

あとはTOP画面に投稿されたものを全て表示させます。以下を追記します。

app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  〜省略〜
  def index
    @messages = Message.all
    @message = Message.new
  end
  〜省略〜
end
app/views/messages/new.html.erb
〜省略〜
<% @messages.each do |m| %>
  <div style="margin-top: 20px;"><span style="color: red;"><%= m.user.email %></span><%= m.message %></div>
<% end %>

hello.png
大丈夫ですね。

投稿するときに画像の上の丸い矢印が一瞬×になって投稿が反映されると思いますが、これなしに反映されるのが非同期通信です。

④JQueryを導入し、turbolinksを削除する

turbolinksを削除する理由は、JQueryの動きを阻害する可能性があるからです。(リロードすればjsが動作するのに、リンクで移動すると動作しない、等)

Gemfilegem 'jquery-rails'を加え、gem 'turbolinks'をコメントアウトするか消し、

gemfile
×  gem 'turbolinks'

   gem 'jquery-rails'

bundle installしサーバの再起動します。
app/assets/javascripts/application.js//= require jquery//= require jquery_ujs//= require_tree .より上に追記し、//= require turbolinksを消します。

app/assets/javascripts/application.js
× //= require turbolinks

  //= require jquery
  //= require jquery_ujs
  //= require_tree .

以下を修正します。

app/views/layouts/application.html.erb`
× <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
   <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>

       |
       v

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

続いてapp/assets/javascriptsmessages.jsを作ります。app/assets/javascripts/messages.coffeeがあると作成したファイルが機能しないので削除します。

app/assets/javascripts/messages.js
$(function () {
  console.log("OK")
});

を書いて、ブラウザをどの画面でもいいのでリロードします。
コンソールにOKが表示されたら成功です。

これで準備は終わりです。

いよいよ本筋、非同期実装

完成品GitHub(masterではなく一つのブランチなので注意して下さい)

①投稿ボタンを押すとイベント発火させる

以下のようにjsファイルを直しフォームの投稿ボタンが押されたときにコンソールにOkが出てくるか確認します。
function後の()にeをお忘れなく。

app/assets/javascripts/messages.js
$(function (e) {
  $(".form").on("submit", function () {
    console.log("Ok")
  })
});

一瞬だけ表示されてすぐに消えますね。投稿されたらTOP画面に(つまり同じ画面に)リダイレクトするのですから当然ですね。今はリダイレクトせずに投稿が反映されるようにするためにこの動きをjs内で止めます。以下に直してください。

app/assets/javascripts/messages.js
$(function () {
  $(".form").on("submit", function (e) {
    console.log("Ok")
    e.preventDefault();
  })
});

これでOkが残るようになりました。

②formの情報をjsで受け取り、createアクションに渡して投稿する

まずjsでformの情報を受け取る型は以下のような感じです。

app/assets/javascripts/messages.js
$(function () {
  $(".form").on("submit", function (e) {
    e.preventDefault();
    $.ajax({
      url:  (1) ,
      type: (2) ,
      data:  (3) ,  
      dataType: 'json',
    })
  })
});

加わったのは$.ajaxのくだりですね。
4つの項目がありますが、dataTypeはとりあえず'json'でいいです。jsonとはデータの形式で、{a: b,c: d}みたいなやつです。Rubyでいうハッシュ、JavaScriptでいくオブジェクトですね。簡単です。
(json以外にも、XMLやHTMLでもできるみたいですね)

(1)〜(3)を埋めて行きます。
(1)はcreateアクションにいくurlです。rails routeで確認すれば一発ですね。今回の私の場合は/messagesです。
(2)は、HTTPメソッドです。createアクションに行くので、'POST'ですね。
(3)は、検証ツールでメッセージを書き込むinputタグのname属性をみればわかります。
jjjj.png
ありました。このように参照される値なので、{message: {message: <投稿内容> }}のように渡せばいいですね。
では投稿内容はどうすれば良いかというとidがmessage_messageになっているので、$("#message_message").val()で取れます。(詳しい説明は省きます)
これを埋めて、投稿完了したときにたどり着くdoneメソッドをajaxメソッドに連ねて書きます。

app/assets/javascripts/messages.js
$(function () {
  $(".form").on("submit", function (e) {
    e.preventDefault()
    $.ajax({
      url:  "/messages" ,
      type: "POST" ,
      data:  {message: {message: $("#message_message").val() }} ,  
      dataType: 'json',
    }).done(function (data) {
      console.log("ok");
    });
  })
});

これで一度投稿してみましょう。データベースを見れば投稿は成功しているのがみて取れます。
以下に同期・非同期の両方でのターミナルでの状態を掲載します。

html.png
json.png

このような違いが出てますね。そしてなぜかコントローラのcreateアクションの最後のredirect_to root_pathが効かなくなりました。今コンソールにokは見られません。コントローラに残って機能しなくなったredirect_to root_pathが阻害しているようです。これを削除してもう一度投稿すれば、コンソールにokが見られるはずです。

このあと,投稿が完了した時に、formの値を全てなくして、投稿ボタンを蘇るようにします。
formの値をまとめてなくすには$('.form')[0].reset();を追記します。[0]がなぜ必要なのかはよくわかりません。
続いて投稿ボタンはerbファイルでボタンに適当にidを指定して(私は<%= f.submit "投稿", id:"bbb" %>こうしました)、$('#bbb').prop('disabled', false);を追記します。
全体を見てみます。

app/assets/javascripts/messages.js
$(function () {
  $(".form").on("submit", function (e) {
    e.preventDefault()
    $.ajax({
      url:  "/messages" ,
      type: "POST" ,
      data:  {message: {message: $("#message_message").val() }} ,  
      dataType: 'json',
    }).done(function () {
      $('.form')[0].reset();
      $('#bbb').prop('disabled', false);
    });
  })
});

これでリロードしなくでも、何回でも投稿できるようになりました。

あとは表示できるようにすればOKですね。

③投稿内容を非同期で表示させる

Ajaxでやってきたデータを扱うにはrespond_toメソッドを使います。

createアクションの最後のredirect_to root_pathがあった場所に、以下を追記します。

app/controllers/messages_controller.rb
respond_to do |format|
  format.json {render json: { ccc: @message.message , ddd: @message.user.email}}
end

メッセージ投稿内容とメッセージを送った人のEmailをそれぞれcccとdddに格納してjson形式でレンダーしますよってことですね。

あとは、doneイベント内で情報を受け取ってHTMLに整形してappendするだけです。
doneイベント内のfunction後の()内に何か文字をおけばそこに上のjsonデータが格納されます。今回はeeeとしてみました。これをコンソール出力してみます。

app/assets/javascripts/messages.js
〜省略〜
}).done(function (eee) {
  console.log(eee);
  $('.form')[0].reset();
  $('#bbb').prop('disabled', false);
});
〜省略〜

これで投稿してみると、コンソールで

Output
{ccc: "ハロー", ddd: "aaa@aaa"}

大丈夫そうですね。 これがeeeの中に入っているわけですから、"ハロー"を取得するにはeee.cccで、"aaa@aaa"を取得するには…大丈夫ですね。

あとは、これを表示させます。appendするために

app/views/messages/new.html.erb
〜省略〜
<% @messages.each do |m| %>
  <div style="margin-top: 20px;"><span style="color: red;"><%= m.user.email %></span><%= m.message %></div>
<% end %>

これをdivタグで囲って、適当なidをつけます。今回はaaaとしました。

app/views/messages/new.html.erb
〜省略〜
<div id="aaa">
  <% @messages.each do |m| %>
    <div style="margin-top: 20px;"><span style="color: red;"><%= m.user.email %> </span><%= m.message %></div>
  <% end %>
</div>

あとは

app/assets/javascripts/messages.js
$("#aaa").append(`<div style="margin-top: 20px;"><span style="color: red;">${eee.ddd}</span>${eee.ccc}</div>`)

を追記するだけです。
これで完成です。投稿して確認してください。

最後に再掲します。

app/assets/javascripts/messages.js
$(function () {
  $(".form").on("submit", function (e) {
    e.preventDefault();
    $.ajax({
      url: "/messages",
      type: "POST",
      data: { message: { message: $("#message_message").val() } },
      dataType: 'json',
    }).done(function (eee) {
      $('.form')[0].reset();
      $('#bbb').prop('disabled', false);
      $("#aaa").append(`<div style="margin-top: 20px;"><span style="color: red;">${eee.ddd}</span>${eee.ccc}</div>`)
    });
  })
});
app/views/messages/new.html.erb
<% if user_signed_in? %>
  <%= current_user.email %>
  <%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
  <%= form_with(model: @message, local: true, class: "form") do |f| %>
    <%= f.text_field :message %>
    <%= f.submit "投稿" , id: "bbb"%>
  <% end %>
  <div id="aaa">
    <% @messages.each do |m| %>
      <div style="margin-top: 20px;"><span style="color: red;"><%= m.user.email %> </span><%= m.message %></div>
    <% end %>
  </div>
<% else %>
  <%= link_to '新規登録', new_user_registration_path %>
  <%= link_to 'ログイン', new_user_session_path %>
<% end %>

まとめ

本当に最低限です。

これを、整えて十分な状態にするための続きをまた書きます。
フォローしてお待ち下さい。

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

js→railsへのajax通信で404エラーを出しまくった話。

はじめに

はい皆の衆よくお聞き
初心者様による
初心者でもできるエラー殺し講座を始める

よく聞かないと死にます

前回、ancestryとjQueryで多階層型カテゴリの入力フォームを段階的に表示させてみたを投稿するためにサンプルを作ったのですが、さすが初心者。
サンプルを作り終える間に多大なエラーを出しては修正し、修正しては出ししていました。
今回はそのエラーから「404 NotFound」を切り出して、どのように解決していったかを記事にしていきます。

今回取り扱うエラーについて

404 (Not Found)
HTTP 404、またはエラーメッセージ Not Found(「未検出」「見つかりません」の意)は、HTTPステータスコードの一つ。 クライアントがサーバに接続できたものの、クライアントの要求に該当するもの (ウェブページ等) をサーバが見つけられなかったことを示すもの。(wikipediaより抜粋)

第一の404 (Not Found)

まず、「js書くべし」で以下のjsを書きました。

assets/javascript/items.js
$(function() {
  function buildHTML(result){
    var html =
      `<option value= ${result.id}>${result.name}</option>`
    return html
  }

  $("#parent").on("change",function(){
    var int = document.getElementById("parent").value
    if(int == 0){
      $('#child').remove();
      $('#item_category_id').remove();
    }else{
      $.ajax({
        url: "categories/",
        type: 'GET',
        dataType: 'json',
        data: {id: int}
      })
      .done(function(categories) {
        var insertHTML = `<select name="child" id="child">
                          <option value=0>---</option>`;
        $.each(categories, function(i, category) {
          insertHTML += buildHTML(category)
        });
        insertHTML += `</select>`
        if($('#child').length){
          $('#child').replaceWith(insertHTML);
          $('#item_category_id').remove();
        } else {
          $('.items__child').append(insertHTML);
        };
      })
      .fail(function() {
      });
    };
  });
})

はい、もーなんか既に分かる人には分かりますね。

これで親の選択フォームを変更するとエラーになります。
スクリーンショット 2020-03-14 15.18.01.png

原因

原因はこれでした。
url: "categories/",
以下のように変更しました。
url: "categories/", => url: "/categories",

気付いたきっかけ

ターミナルを見た時に以下のようになっていました。
Started GET "/items/categories/?id=1" for ::1 at 2020-03-14 15:17:23 +0900
ActionController::RoutingError (No route matches [GET] "/items/categories"):

?なんでitemにネストしたurlになってるの?
ということで、url修正しました。

第二の404 (Not Found)

しかし、再度親の選択フォームを変更するとエラーになります。
スクリーンショット 2020-03-14 15.18.01.png

今度のターミナルエラーはこれ。
Started GET "/categories?id=1" for ::1 at 2020-03-14 15:36:36 +0900
ActionController::RoutingError (No route matches [GET] "/categories"):

原因

config/routes.rb
Rails.application.routes.draw do
  root "items#new"
  resources :items ,only: [:index,:new,:create]
end

ただのroutingの設定忘れでした。

config/routes.rb
Rails.application.routes.draw do
  root "items#new"
  resources :items ,only: [:index,:new,:create]
  resources :categories ,only: :index
end

修正します。

第三の404 (Not Found)

しかし、再度親の選択フォームを変更するとエラーになります。
スクリーンショット 2020-03-14 15.18.01.png

ターミナルエラーはこれ。
Started GET "/categories?id=1" for ::1 at 2020-03-14 15:45:21 +0900
ActionController::RoutingError (uninitialized constant CategoriesController):

原因

categories_controller.rbが無い・・・だと?
ちゃんとあるじゃ・・・ある・・・あ?
スクリーンショット 2020-03-14 16.00.36.png

/controllers/category_controller.rb
class CategoryController < ApplicationController
  def index
    @categories = Category.where(ancestry: params[:id])
    respond_to do |format|
      format.json
    end
  end
end

・・・controller作る時に単数形にしてますね・・・。
消して作り直します。

terminal.
$ rails d controller category
$ rails g controller categories
/controllers/categories_controller.rb
class CategoriesController < ApplicationController
  def index
    @categories = Category.where(ancestry: params[:id])
    respond_to do |format|
      format.json
    end
  end
end

結果

ちゃんと動作するようになりました!
スクリーンショット 2020-03-14 15.54.46.png

今回の教訓

①エラーが出たらターミナルをよく見よう。
②routingはちゃんとやっておこう。
③controllerは複数形で作ろう。

今回は以上です。

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

営業マンが独学でSNSをリリースし、スクールに「来なくていいよ」と言われた

ITとは無縁の製造業で営業をしながら、友人とRailsアプリを約半年でリリースしました。

お悩み相談SNS "Probless"
機能:ログイン/投稿/タグ付け/2層コメント/リアルタイムチャット/いいね/通知/検索/フィード/メール認証/理論削除/ + インフラ(AWS/自動デプロイ)
使用技術:Ruby on Rails/PostgreSQL/Action Cable/Elastic Search/Devise 認証/acts-as-taggable-on(タグ付け)/無限スクロール/Bootstrap/AWS/GitHub/独自ドメイン/Circle CI/Nginx/Https 認証

結論 ~学んだこと~

・誰かの役に立つという確信をミッションとして持っておくべき
・独学でもWebアプリを作ることができる
・スクールに通わない道もある

ちなみに過去にも独学でiOSアプリをリリースしたことがあり、今回は二つ目のリリースとなります。
営業マンが独学でiOSアプリをリリースし、レビュー4.5をもらうまで

開発経緯

データ分析が仕事の友人と何か作ろう!と意気投合し、様々なアイディアを検討した結果、「お悩み相談SNS」を作ることにしました。既存のお悩み掲示板は、匿名がゆえに中傷的なコメントが多いため、匿名でも親身なアドバイスが得やすい仕組みを備えた相談 SNS を考案しました。

企画段階(1ヶ月)

様々な記事を参考にし、手探りで下記をまとめていきました

  • 解決したい社会課題
  • ミッション定義
  • ユーザ体験&行動の流れをツリーで定義
  • 競合分析(良い点/悪い点/チャンス)
  • DB設計
  • スケジュールと役割分担
  • ワイヤーフレーム作成
  • 言語選び

方法模索段階(1ヶ月)

アイディアをそのまま外注すると数百万円するため断念。スクラッチで作ることにしました。
Ruby、Python、Phpで迷いましたが、プロゲートでRailsのレッスンがあったためRubyを選択。
私はプロゲートのRailsを2週してから1ヶ月で開発スタートしましたが、
友人はプログラミングの経験無く、プロゲートをやってもらいました。(2-3ヶ月)

ローカル開発段階(3ヶ月)

GitHubで2名チーム開発を行いました。
早朝や夜にコードを書き、通勤中にエラーを調べる習慣を続けました。

学んだこと

  • 企画段階でミッションをがっちり定めておかないとブレる
    • 本当にこの機能は必要か?判断基準となる
  • 誰かの役に立つという確信がモチベーションとなり、疲れてても継続できる
  • フロントはBootStrapのテンプレートを使い楽をする
  • デザインは配色は既存のサービスを参考にする
  • 「実現したいこと」を先に決めてから、「そのために必要な技術」を調べ尽くしてとにかくやる!!

特に難しかったのが、Action Cableでした。
Action CableはWebSocket通信技術を用いてリアルタイムな更新を可能にする機能です。
「相談者」と「コメントした人」との間で、悩みという共通のトピックに対してリアルタイムに会話をして欲しいという想いがあり実装しました。1週間何も進まないことありましたが、英語記事を参考になんとか実装しました。

Action Cable 本番使用時のNginxとCable.ymlの設定

デプロイ(2ヶ月)

インフラ環境

  • AWS
  • PostgreSQL
  • Nginx
  • Puma
  • Circle CI
  • Elastic Search (一時は実装しましたが、投稿が増えてからマージ予定)
  • Route 53 (独自ドメイン)
  • Let's Encrypt (SSL)

デプロイにかなり苦労しました。何がわからないのか分からない状況でした。
特に全文検索を可能にするElastic Searchにはかなり苦しめられました。

  • AWSでノード構築
  • AmazonLinuxへインストール、起動(JVM)
  • RailsとElasticSearchとノードの接続
  • 投稿された内容を、ノードにインデックス

しかしせっかく実装し動作確認までしたものの、投稿が増えるまでは威力を発揮しないので、一旦取りやめました。。

その後

Webエンジニアへ転職を決意し、会社を辞めて都内の某著名スクールに通おうとしたところ、スクールの面接で「来なくていいよ」と言われてしまい、結局スクール無しで転職活動中です。
エンジニア特化型のエージェントにも、サービスの目的が「転職のポートフォリオ用」では無く、「社会課題を解決するため」本気で作ってきたことに、企画力と自走力、継続力、実装力を評価頂いております。

感想

偉そうで大変恐縮ですが、何かサービスを作りたいならすぐに取りかかれば良いと思いました。イメージしたことを実現したい時、最初は方法も分からず無理なように感じますが、サービスに情熱と確信を持って調べ尽くせば方法は必ず見つかると思います。SNSの基本的機能くらいなら、独学で調べて実現できることを経験できました。

まとめ

・誰かの役に立つという確信をミッションとして持っておくべき
・独学でもWebアプリを作ることができる
・スクールに通わない道もある

面接にお呼びください

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

営業マンが独学でSNSをリリースし、スクールに「来なくていいよ」と言われた話

ITとは無縁の製造業で営業をしながら、友人とRailsアプリを約半年でリリースしました。

お悩み相談SNS "Probless"
機能:ログイン/投稿/タグ付け/2層コメント/リアルタイムチャット/いいね/通知/検索/フィード/メール認証/理論削除/ + インフラ(AWS/自動デプロイ)
使用技術:Ruby on Rails/PostgreSQL/Action Cable/Elastic Search/Devise 認証/acts-as-taggable-on(タグ付け)/無限スクロール/Bootstrap/AWS/GitHub/独自ドメイン/Circle CI/Nginx/Https 認証

結論 ~学んだこと~

・誰かの役に立つという確信をミッションとして持っておくべき
・独学でもWebアプリを作ることができる
・スクールに通わない道もある

ちなみに過去にも独学でiOSアプリをリリースしたことがあり、今回は二つ目のリリースとなります。
営業マンが独学でiOSアプリをリリースし、レビュー4.5をもらうまで

開発経緯

データ分析が仕事の友人と何か作ろう!と意気投合し、様々なアイディアを検討した結果、「お悩み相談SNS」を作ることにしました。既存のお悩み掲示板は、匿名がゆえに中傷的なコメントが多いため、匿名でも親身なアドバイスが得やすい仕組みを備えた相談 SNS を考案しました。

企画段階(1ヶ月)

様々な記事を参考にし、手探りで下記をまとめていきました

  • 解決したい社会課題
  • ミッション定義
  • ユーザ体験&行動の流れをツリーで定義
  • 競合分析(良い点/悪い点/チャンス)
  • DB設計
  • スケジュールと役割分担
  • ワイヤーフレーム作成
  • 言語選び

方法模索段階(1ヶ月)

アイディアをそのまま外注すると数百万円するため断念。スクラッチで作ることにしました。
Ruby、Python、Phpで迷いましたが、プロゲートでRailsのレッスンがあったためRubyを選択。
私はプロゲートのRailsを2週してから1ヶ月で開発スタートしましたが、
友人はプログラミングの経験無く、プロゲートをやってもらいました。(2-3ヶ月)

ローカル開発段階(3ヶ月)

GitHubで2名チーム開発を行いました。
早朝や夜にコードを書き、通勤中にエラーを調べる習慣を続けました。

学んだこと

  • 企画段階でミッションをがっちり定めておかないとブレる
    • 本当にこの機能は必要か?判断基準となる
  • 誰かの役に立つという確信がモチベーションとなり、疲れてても継続できる
  • フロントはBootStrapのテンプレートを使い楽をする
  • デザインは配色は既存のサービスを参考にする
  • 「実現したいこと」を先に決めてから、「そのために必要な技術」を調べ尽くしてとにかくやる!!

特に難しかったのが、Action Cableでした。
Action CableはWebSocket通信技術を用いてリアルタイムな更新を可能にする機能です。
「相談者」と「コメントした人」との間で、悩みという共通のトピックに対してリアルタイムに会話をして欲しいという想いがあり実装しました。1週間何も進まないことありましたが、英語記事を参考になんとか実装しました。

Action Cable 本番使用時のNginxとCable.ymlの設定

デプロイ(2ヶ月)

インフラ環境

  • AWS
  • PostgreSQL
  • Nginx
  • Puma
  • Circle CI
  • Elastic Search (一時は実装しましたが、投稿が増えてからマージ予定)
  • Route 53 (独自ドメイン)
  • Let's Encrypt (SSL)

デプロイにかなり苦労しました。何がわからないのか分からない状況でした。
特に全文検索を可能にするElastic Searchにはかなり苦しめられました。

  • AWSでノード構築
  • AmazonLinuxへインストール、起動(JVM)
  • RailsとElasticSearchとノードの接続
  • 投稿された内容を、ノードにインデックス

しかしせっかく実装し動作確認までしたものの、投稿が増えるまでは威力を発揮しないので、一旦取りやめました。。

その後

Webエンジニアへ転職を決意し、会社を辞めて都内の某著名スクールに通おうとしたところ、スクールの面接で「来なくていいよ」と言われてしまい、結局スクール無しで転職活動中です。
エンジニア特化型のエージェントにも、サービスの目的が「転職のポートフォリオ用」では無く、「社会課題を解決するため」本気で作ってきたことに、企画力と自走力、継続力、実装力を評価頂いております。

感想

イメージしたことを実現したい時、最初は方法も分からず無理なように感じますが、調べ続ければ方法は必ず見つかると思いました。SNSの基本的機能くらいなら、独学でも実現できることを経験できました。

まとめ

・誰かの役に立つという確信をミッションとして持っておくべき
・独学でもWebアプリを作ることができる
・スクールに通わない道もある

面接にお呼びください

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

Railsバージョン6.0.0 インストールの流れ、エラー解決(MacOS ローカル)

rails6をインストール

gem install rails -v '6.0.0'
=>※エラー Node.js not installed. Please download and install Node.js https://nodejs.org/en/download/

どうやらNode.jsをインストールする必要があるようです。

Node.jsインストールの流れ
1.Homebrewのインストール
http://brew.sh/index_ja.html にあるスクリプトをターミナルに入力しEnter。
インストール完了後、以下のコマンドを打つとバージョンが表示される。

$brew -v
=>Homebrew 2.2.10
Homebrew/homebrew-core (git revision 9e36; last commit 2020-03-13)

2.nodebrewのインストール

$ brew install nodebrew

3.Node.jsをインストール

インストールできるバージョンの確認

$ nodebrew ls-remote

たくさん表示されたけど、必要なバージョンがわからなかったのでとりあえず最新版をインストールします。(以下のコマンド)

$ nodebrew install-binary latest
=>*エラー Fetching: https://nodejs.org/dist/v13.11.0/node-v13.11.0-darwin-x64.tar.gz
Warning: Failed to create the file                                             
Warning: /Users/tech-camp/.nodebrew/src/v13.11.0/node-v13.11.0-darwin-x64.tar.g
Warning: z: No such file or directory
                                                                            0.0%
curl: (23) Failed writing body (0 != 1019)
download failed: https://nodejs.org/dist/v13.11.0/node-v13.11.0-darwin-x64.tar.gz

無いと言われているディレクトリを作成してあげましょう!!

$ mkdir -p ~/.nodebrew/src

では改めてNode.jsをインストールします!!

$ nodebrew install-binary latest
=>成功 Fetching: https://nodejs.org/dist/v13.11.0/node-v13.11.0-darwin-x64.tar.gz
######################################################################### 100.0%
Installed successfully

4.インストールされたnodeを有効化

$ nodebrew ls
=>v13.11.0

current: none

current: none となっているので、先程インストールしたバージョンを有効化する。

$ nodebrew use v13.11.0

$nodebrew ls
=>v13.11.0

current: v13.11.0

5.環境パスを通す

次のパスで、bashかzshどちらに記述すべきか調べる。

echo $SHELL
=>/bin/zsh

上記のようにzshの場合は、下記の記述でファイル内に追加する。

$export PATH=$HOME/.nodebrew/current/bin:$PATH

#一度読み込み直す
$source ~/.zshrc

#node確認
$ node -v
=>v13.11.0
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

古いrubyがMacにインストールできなくて困っているあなたへ

これ何

古いruby(2.4以下のバージョン)がMacにインストールできなくて困っているあなたへ送るやつです。

なぜ古いrubyでビルドエラーが発生するのか

古いrubyではopenssl 1.1系しかない環境だとビルドエラーになる。
しかし、openssl1.0系をインストールしようとしてもここに書いてある通り、openssl 1.0系はEOLを迎えたのでhomebrewで素でインストールする事ができなくなっている。

どうするの?

以下のコマンドで無理やりインストールする。

brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/64555220bfbf4a25598523c2e4d3a232560eaad7/Formula/openssl.rb

上記のコマンドの実行で、 /usr/local/Cellar/openssl/1.0.2t に OpenSSL 1.0.2t を入れることはできる。

さらにこんな感じでシンボリックリンク付け替えておかないとエラーになる。

$ cd /usr/local/opt
$ rm openssl
$ ln -s ../Cellar/openssl/1.0.2t openssl

あとはrbenvでrubyをインストールする時にこれを使えるようにしておく。

RUBY_CONFIGURE_OPTS="--with-openssl-dir=/usr/local/Cellar/openssl/1.0.2t" rbenv install 2.3.8

大変面倒だが、bundle installする時にもopensslを参照しているので該当のリポジトリを触っている間は古いopensslを使いつづなければいけない・・・
bundle installが終わってサービスを起動したらopensslのシンボリックリンクを戻しておく。

$ cd /usr/local/opt
$ rm openssl
$ ln -s ../Cellar/openssl@1.1/1.1.1d openssl

そもそも・・・

本来であれば、EOLを迎えている物をインストールしようと試みること自体が良くない話なのだが、止むに止まれぬ事情で必要になる人もいると思ったので記録を残しておくことにしたのであった。

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

Progate無料版をやってみる【Ruby I】

前回に引きつづき、Progateの無料レッスンをこなしていこうと思います。
今回からRubyになります。
Ruby自体は初めて触る言語になります。
正直、流行っているのを耳にはしていて、いざ自分が学んで、ついていけなかったらどうしようと敬遠していた分野です・・・。
あと、Web業界でよく聞くPHPや、最近話題のPythonも・・・。

Ruby I

公式レッスン

Rubyを実行してみよう

・Rubyはインタープリタ型言語
 コンパイラがいらない。
 プログラム実行時に逐次機械語に翻訳しながら実行される。
・コンソール表示はputsらしい・・・

**ProgateのWebサイト上ではく、自身の環境でも動かしてみたいと思います。

Rubyの導入は以下を参考にさせていただきました。
Rubyの実行環境構築 on Windows

・gemとはRubyの便利な機能が揃っているライブラリのこと。
C#でいうところの.Netの既存のライブラリとかかな?
参考にさせていただきました。
https://techplay.jp/column/529

・MSYS2とは、Windowsで動くターミナル&シェルみたいなものらしい。
 同じようなものにCygwinというのがあって、これは昔ちょろっとだけ使ったことがあった・・・。Git Bashみたいなものかな?ちがうかw
参考にさせていただきました。
MSYS2で快適なターミナル生活

index.rb

puts "Hello World"

  1. ファイルを用意して
    image.png

  2. 実行しました。
    image.png

インタープリタ系の言語は楽に実行環境がそろえられて、簡単に実行できることが特徴のようです。JavaScriptもそうですね。

開発環境

Rubyのコードを書くのにサクラエディタ等の普通のテキストエディタでもいいのですが、便利なIDE(統合開発環境)があるのであれば、積極的に使用していこうと思いました。
ネットで検索すると色々とあるようですが、VSCodeを使用しようと思います。
Microsoft製品好きですし・・・。

VSCode
ダウンロード

インストール後、設定
以下を参考にさせていただきました。
VSCodeでRubyを気軽に実行する環境を作る。

あと、インテリセンスもほしい!
以下を参考にさせていただきました。
https://kic-yuuki.hatenablog.com/entry/2019/02/02/160659

Rubyの基本と文字列

・ソースコード中のコメントは#で実現

演習
インストールしたVSCodeの方で実行していこうかと思います。
image.png

数値と足し算・引き算・様々な計算

・ほかの言語と一緒・・・
 足し算 +
 引き算 -
 乗算 *
 除算 /

文字列の連結

・他言語と同様 +

変数の基本

・他言語と同様 = で変数に代入

変数を使ってみよう

・他の言語でやっていたことと同様
変数と文字列の結合

val = "test"
puts "Progate" + val

image.png

変数の役割と注意点

・基本的な事。省略。

変数の更新(1)

・基本的な事
 上書きできる。

name = "god"
puts name
name = "dog"
puts name

image.png

変数の更新(2)

・他の言語でもあるように+=-=

変数展開

・ダブルクオーテーションで囲った場合

name = "god"
puts "名前は#{name}です。"

puts '名前は#{name}です'

image.png
変数展開のメリット

if文

・他の言語とおおむね同様の仕様

真偽値・比較演算子(1)

・他の言語と同様の為、特筆無し

比較演算子(2)

==!=

else

・他の言語と同様の使い方の為、特筆無し

elsif

・else ifではなく、elsif
個人的に誤りの元・・・。統一してほしい。

条件を組み合わせよう・総合課題

・他言語と同様
かつ&&
または||

演習
image.png

クリアした

image.png

総括

本当に基礎的な事しかやらなくて、経験者からしてみるとあまり新たに吸収できることはありませんでした。
初心者編という位置づけだから仕方ないか・・・

次回はRuby on RailsのIをやっていこうと思います。

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

generateコマンドで生成されるファイルに制限をかける(config.generatorsの設定)

はじめに

generateコマンドっていろんなファイルを作成してくれるけど、「helperファイルの自動生成は必要ないのになー」って時などには、かえって煩わしく感じますよね。

そこで今回は、config/application.rb内の設定によって、generate コマンドで生成されるファイルに制限をかけます。(※スキップオプションを付ける方法もあるけど省略)

まずはrails generation時の挙動を確認

ターミナルでrails generation(rails gに省略可)を行うと、色々なファイルが自動生成される。

> rails g controller user show      
Running via Spring preloader in process 
      create   app/controllers/user_controller.rb
       route   get 'user/show'
      invoke   erb
      create    app/views/user
      create    app/views/user/show.html.erb
      invoke   rspec
      create    spec/controllers/user_controller_spec.rb
      create    spec/views/user
      create    spec/views/user/show.html.erb_spec.rb
      invoke   helper
      create    app/helpers/user_helper.rb
      invoke    rspec
      create    spec/helpers/user_helper_spec.rb
      invoke   assets
      invoke    js
      create    app/assets/javascripts/user.js
      invoke    scss
      create    app/assets/stylesheets/user.scss

便利だけど、必要ないファイルがあれば、それだけ生成されないようにする。
(この自動生成したファイル等を削除したければ、ターミナルでrails destroy controller user showを実行しておく)

自動生成するファイルに制限をかける

今回は、config/application.rb内の設定で、
・assetsファイル
・testファイル
・ルーティング
の生成を無効にしてみる。

config/application.rb
puts class Application < Rails::Application



 #以下のように、generateコマンド時に生成されるファイルに制限をかける
   config.generators do |g|
     g.assets  false
     g.test_framework    false
     g.skip_routes   true
   end
 end

上記の設定によって、

> rails g controller user show      
Running via Spring preloader in process 82700
      create  app/controllers/user_controller.rb
      invoke  erb
      create    app/views/user
      create    app/views/user/show.html.erb
      invoke  helper
      create    app/helpers/user_helper.rb

生成ファイルに制限がかかった。

このように、プロジェクトごとに設定を変えたり、自分好みのカスタマイズができる。

おわりに

今回が初のQiita投稿でした!

これからも新しく学んだこと等をQiitaに共有できればと思います。
ご覧いただきありがとうございました。

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

Rails|generateコマンドで生成されるファイルに制限をかける(config.generatorsの設定)

はじめに

generateコマンドっていろんなファイルを作成してくれるけど、「helperファイルの自動生成は必要ないのになー」って時などには、かえって煩わしく感じますよね。

そこで今回は、config/application.rb内の設定によって、generate コマンドで生成されるファイルに制限をかけます。(※スキップオプションを付ける方法もあるけど省略)

まずはrails generation時の挙動を確認

ターミナルでrails generation(rails gに省略可)を行うと、色々なファイルが自動生成される。

> rails g controller user show      
Running via Spring preloader in process 
      create   app/controllers/user_controller.rb
       route   get 'user/show'
      invoke   erb
      create    app/views/user
      create    app/views/user/show.html.erb
      invoke   rspec
      create    spec/controllers/user_controller_spec.rb
      create    spec/views/user
      create    spec/views/user/show.html.erb_spec.rb
      invoke   helper
      create    app/helpers/user_helper.rb
      invoke    rspec
      create    spec/helpers/user_helper_spec.rb
      invoke   assets
      invoke    js
      create    app/assets/javascripts/user.js
      invoke    scss
      create    app/assets/stylesheets/user.scss

便利だけど、必要ないファイルがあれば、それだけ生成されないようにする。
(この自動生成したファイル等を削除したければ、ターミナルでrails destroy controller user showを実行しておく)

自動生成するファイルに制限をかける

今回は、config/application.rb内の設定で、
・assetsファイル
・testファイル
・ルーティング
の生成を無効にしてみる。

config/application.rb
puts class Application < Rails::Application



 #以下のように、generateコマンド時に生成されるファイルに制限をかける
   config.generators do |g|
     g.assets  false
     g.test_framework    false
     g.skip_routes   true
   end
 end

上記の設定によって、

> rails g controller user show      
Running via Spring preloader in process 82700
      create  app/controllers/user_controller.rb
      invoke  erb
      create    app/views/user
      create    app/views/user/show.html.erb
      invoke  helper
      create    app/helpers/user_helper.rb

生成ファイルに制限がかかった。

このように、プロジェクトごとに設定を変えたり、自分好みのカスタマイズができる。

おわりに

今回が初のQiita投稿でした!

これからも新しく学んだこと等をQiitaに共有できればと思います。
ご覧いただきありがとうございました。

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

Active Record 単体で fixture を使う

これは Ruby not on Rails の記事です。

non-RailsでActive Recordを使う方法は検索するといくつか出てきますが、fixtureについては出てこないみたいなので書きます。

Active Recordを単体で使う方法は参考文献に挙げたのでそちらを参照してください。ディレクトリ構成やファイル配置などはいろいろやり方があると思いますので適宜読み替えてください。

fixtureはテストで使うのでテストフレームワークが問題になりますが、わたしはRSpecを使っています。とはいえ必要なことはどのフレームワークでもあまり変わらないはずです。

Active Recordのバージョンは6.0.2.1でやっています。

テスト

  • テストのクラスでActiveRecord::TestFixturesincludeする。
    • minitestならMiniTest::Testのサブクラス、RSpecならexample groupのコンテキスト(describecontextの中でitの外)でincludeするということです。
  • テストのクラスでself.fixture_path=を設定する。
    • use_transactional_testsuse_instantiated_fixturesなどの設定が必要な場合は同様に設定できます。
  • テストのクラスでfixtures :usersのようにして使うfixtureを指定する。
    • fixtures :allですべてのfixtureを指定したのと同じになります。
  • (minitest以外では)lifecycle hookでテストの実行前にsetup_fixturesが、実行後にteardown_fixturesが実行されるようにする。
    • minitestではActiveRecord::TestFixturesが面倒を見てくれる(before_setupafter_teardownに追加してくれる)ので大丈夫。
    • RSpecではそれぞれbeforeafterで呼ぶ(before(:context)でfixtureを使うには工夫が必要そうです)。
  • ActiveSupport::TestCaseで定義されているmethod_nameメソッドが呼ばれるので定義しておく必要がある。(Rails masterでは修正済み
    • minitestを使っているならActiveSupport::TestCaseMiniTest::Testの薄いラッパなのでこちらを使ってしまうのもありかもしれません。どうせActive RecordはActive Supportに依存しているわけですし。
  • RSpecを使っている場合はrspec-railsRSpec::Rails::FixtureSupportincludeするとこのあたりの面倒を見てくれそうな気がしますが、Railsがいない場合はそのままでは動かなさそうです。

まとめると次のような感じになります。

user_test.rb
require 'minitest/autorun'
require 'active_record'
require './app/models/user'

class UserTest < MiniTest::Test

  include AcitveRecord::TestFixtures

  self.fixture_path = './test/fixtures'

  fixtures :users

  alias method_name name

  def test_example
    assert_equal 'Yukihiro Matsumoto', users(:matz).name
  end
end
user_spec.rb
require 'active_record'
require './app/models/user'

RSpec.describe User do

  include ActiveRecord::TestFixtures

  self.fixture_path = './spec/fixtures'

  fixtures :users

  def method_name
    @example
  end

  before { setup_fixtures }
  after { teardown_fixtures }

  it { expect(users(:matz).name).to eq 'Yukihiro Matsumoto' }

end

必要な記述をひとまとめにしたヘルパーmoduleを作ったりしてもよいでしょう。お好みでどうぞ。

Rake

Rakeタスクdb:fixtures:loadも使えます。

Rakefileでいい感じに設定をしてActive RecordのRakeタスクたちを読み込んでやります。

Rakefile
require 'active_record'

ActiveRecord::Tasks::DatabaseTasks.env = ENV['APP_ENV'] || 'default'
ActiveRecord::Tasks::DatabaseTasks.database_configuration = YAML.load_file('./config/database.yml')
ActiveRecord::Tasks::DatabaseTasks.db_dir = './db'
ActiveRecord::Tasks::DatabaseTasks.fixtures_path = './spec/fixtures'
ActiveRecord::Tasks::DatabaseTasks.root = Dir.pwd

task :environment do
  Dir.glob('./app/models/*.rb') {|file| require file }
  ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration
  ActiveRecord::Base.establish_connection(ActiveRecord::Tasks::DatabaseTasks.env.to_sym)
end

load 'active_record/railties/databases.rake'

db:fixtures:loadを使うにはActiveRecord::Tasks::DatabaseTasks.fixtures_path=が肝心です。他は適宜設定してください。

また、モデルをrequireしておく必要があります。モデルが読み込まれていなくてもタスクを読み込むことはできますが、実行時に下のようにbelongs_toassociationを認識できずに落ちます(fixtureをauthor_idではなくauthorにラベルで指定する形で書いている場合)。

$ rake db:fixtures:load
rake aborted!
ActiveRecord::Fixture::FixtureError: table "books" has no columns named "author".

なお、上ではモデルをenvironmentタスクでrequireしていますが(environmentタスクはActive Recordのタスクが実行される前に呼ばれます)、Rakefileのトップレベルでもいいと思います。

参考文献

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

『2ページ遷移して会員登録』できる最低限のRailsアプリを丁寧に作る(deviseをウィザード形式に拡張)

この記事の基本的な方針

deviseで実装したログイン機能は特にカスタマイズしなければ、1ページでEmailとパスワードの2項目を入力して登録完了です。
これを2ページに拡張し、1ページ目にEmailとパスワードに加えニックネームを、2ページ目でケータイ番号の入力を求めます。

この記事は、以下の「登録画面」「ログイン画面」「TOP画面」の3画面の簡単なアプリを元に拡張していきます。

【TOP画面(ログイン前)】     【TOP画面(ログイン後)】
a0.png a9.png
【登録画面】
a1.png
【ログイン画面】
a2.png

手を動かしながら読みたいようでしたら、以下でこの3画面アプリを手に入れてください。

Terminal
$ git clone -b 超最低限のRailsアプリ(messageコントローラVer)  https://github.com/annaPanda8170/minimum_rails_application.git
$ bundle install
$ bundle exec rake db:create
$ bundle exec rake db:migrate

これ自体の作り方はこちら

最終的に以下のように1ページ目にニックネームの項目が加わり、ケータイ番号の登録をする2ページ目が新たに作られます。

222a.png 333a.png

想定する読み手

既に一度Railsアプリをチュートリアルやスクール等で作ったことがある方を想定しております。
Mac使用で、パソコンの環境構築は完了していることが前提です。

具体的なコーディング手順

完成品GitHub(masterではなく一つのブランチなので注意して下さい)

①取り急ぎ1ページ目のみでニックネームカラムを登録項目として増やす

まず1ページのままでニックネーム入力項目を追加してみましょう。
通常、最初にテーブルを作成するときはrails g model <モデル名>としたときに一緒にマイグレーションファイルも作られますが、今回はすでにあるテーブルにカラムを増やすだけなのでマイグレーションファイルのみを作ります。

Terminal
$ rails g migration <クラス名>

を打ちます。
クラス名はなんでもいいです。rails g migration Aaaでも問題ないですが、rails g migration AddColumn<テーブル名>とするのが一般的です。今回はrails g migration AddColumnUsersとしましょう。
そして、作られたマイグレーションファイルを編集します。

db/migrate/20200xxxxxxxxxxx_add_column_users.rb
class AddColumnUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :nickname, :string 
  end
end

カラムを加えるadd_columnメソッドを使います。第一引数がテーブル名、第二引数がカラム名、第三引数がデータ型です。「ユーザテーブルニックネームカラム文字列型で加えます」ってことですね。極めて直感的です。余談ですが、コードを読むときは悶々とコードのまま黙読で理解しようとせずに、きっちりと日本語(自然言語)に直して読んでみることをオススメいたします。

そして

Terminal
$ rails db:migrate

して、テーブルは完成です。一応データベースを見に行ってカラムが増えているかを確認しましょう。

続いてコントローラで、追加したカラムを受け入れられるようにします。ここはapp/controllers/messages_controller.rbではなく、app/controllers/application_controller.rbを編集します。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?
  protected
  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:<追加したカラム名>])
  end
end

難しいですが、それぞれのメソッドを完全に理解できなくでも大丈夫です。deviseのGitHubにそうしろと書いてあるのでそれに従います。掃除機を正しく使うのに掃除機を作れるようになる必要はありません。説明書をきちんと読みましょう。
今回はdevise_parameter_sanitizer.permit(:sign_up, keys: [:nickname])ですね。

今回はnicknameなしでは登録できないようにしようと思うので、モデルを編集します。以下を追記します。

app/models/user.rb
〜省略〜
  validates :nickname ,presence: true
〜省略〜

あとはビューにニックネームを入力する欄を加えれば完了ですね。
あれ?ビューファイルはどこにありますか?
ありません。deviseをデフォルトのまま使うにはビューファイルとコントローラファイルの編集が必要ないので見えないところに隠されています。これを編集するには表に出させる必要があります。

Terminal
$ rails g devise:views

すると、隠れていたビューファイル達が表に出てきます。当たり前ですが、ただ表に出てきただけなので編集しなければ何も変わりません。
以下を追記します。

app/views/devise/registrations/new.html.erb
<div class="field">
  <%= f.label :nickname %><br />
  <%= f.text_field :nickname %>
</div>

autofocus: trueに関しては適切な場所に一箇所置きます。詳細は省きます。

これで登録してみます。
大丈夫そうですね。

②2ページ目用モデル

2ページ目で入力されるケータイ番号をデータベースに保存するには、usersテーブルにカラムを追加するのではなく新たにテーブルを作ります。新たにテーブルを作るには、

Terminal
$ rails g model cellphone

して、userテーブルに従属するので、

db/migrate/2020xxxxxxxxxx_create_cellphones.rb
class CreateCellphones < ActiveRecord::Migration[5.2]
  def change
    create_table :cellphones do |t|
      t.integer :cellphone
      t.references :user
      t.timestamps
    end
  end
end

と編集して、

Terminal
$ rails db:migrate

です。慣れたもんです。

ここからモデルにバリデーションとアソシエーションを記述します。
まずapp/models/cellphone.rbvalidates :cellphone ,presence: trueを加えます。バリデーションです。これは簡単ですね。
続いて、アソシエーションです。cellphaneモデルとuserモデルを同時に見てください。

app/models/cellphone.rb
class Cellphone < ApplicationRecord
  validates :cellphone ,presence: true
  belongs_to :user, optional: true
end
app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  validates :name ,presence: true
  has_one :cellphone
end

belongs_toメソッドとhas_oneメソッドが使われています。テーブル同士の関係が1対1の場合に使われます。1対1でどちらにどちらのメソッドを使うかというと、外部キーを置いていない側にbelongs_toを置きます。今回は後から作ったcellphoneの側のテーブルにuser_idカラムがあります。だから、cellphoneテーブルがuserテーブルに従属する(belongs)というイメージですね。そして逆側ににhas_oneを置くことになります。
optional: trueが付いていますが、これはウィザード形式を実装するには必須です。(コントローラでCellphoneモデルのみでインスタンスを生成する場面があるからのようです)

③2ページ目用コントローラ

隠れていたビューファイルとコントローラファイルのうち前者は①で表に出していますが、ここでは後者のコントローラファイルを表に出して編集していく必要があります。 表に出してみましょう。

Terminal
$ rails g devise:controllers <コントローラのディレクトリ名>

コントローラのディレクトリ名はなんでも大丈夫ですが、通常モデル名の複数形なのでrails g devise:controllers usersにします。

さてここで話を整理しましょう。普通、登録画面に関するバックグラウンドの流れは、コントローラのnewアクションを通り、newのビューが表示され、そこのformにクライアントが情報が入力してsubmitが押されると、createアクションに来て、バリデーションを通過すればデータベースに登録するというものであります。図示するとこんな感じです。

newアクション → 登録画面 → createアクション → データベース

ここで2ページ目の登録画面を挟まなくてはならないので、

①newアクション → ①登録画面 → ①createと②newのアクション → ②登録画面 → ②createアクション → データベース

となります。
この①createと②newのアクション②createアクションが難しいです。丁寧に行きましょう。

①createと②newのアクションは、registrations_controller.rbにもとからあるcreateアクション(コメントアウトされています)を使います。②createアクションは新たに適当にアクションを作ります。

まず①createと②newのアクションについてです。ここは①登録画面と②登録画面の橋渡しです。①登録画面の情報を受け取って変数(session変数という特別な変数)に格納して②登録画面に遷移します。

まず、最終的なcreateアクションの全体を掲載します。

app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
〜省略〜
  # POST /resource
  def create
    user = User.new(sign_up_params)
    unless user.valid?
      render :new and return
    end
    session[:registration] = {user: user.attributes}
    session[:registration][:user][:password] = params[:user][:password]
    @cellphone = user.build_cellphone
    render :new_cellphone
  end
〜省略〜
end

これを構築してゆきます。
まず、表に出したapp/controllers/users/registrations_controller.rbのコメントアウトしているcreateアクションを復活させ、superを消してbinding.pryをかませてみましょう。

app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
〜省略〜
  # POST /resource
  def create
    binding.pry
  end
〜省略〜
end

こんな感じです。http://localhost:3000/users/sign_upにアクセスして適当に値を埋めてSign upボタンを押すと停止するので、ターミナルを見ます。

まず①登録画面で入力された情報は

Terminal
> sign_up_params

で取れます。私の場合、

Output
=> {"email"=>"aaa@aaa", "password"=>"123123123", "password_confirmation"=>"123123123", "name"=>"annaPanda"}

こんな感じです。sign_up_paramsとはdeviseが準備しているメソッドでしょう。
さてこのsign_up_paramsを代入してUserモデルのインスタンスを生成してみましょう。

Terminal
> aaa = User.new(sign_up_params)
Output
=> #<User id: nil, email: "aaa@aaa", created_at: nil, updated_at: nil, nickname: "annaPanda">

これでバリデーションを通過できるかどうかはvalid?メソッドを使います。

Terminal
> aaa.valid?
Output
  User Exists (0.8ms)  SELECT  1 AS one FROM `users` WHERE `users`.`email` = BINARY 'aaa@aaa' LIMIT 1
  ↳ (pry):3
=> true

trueって出ました。OKなようです。
続いて、②登録画面に引き継ぐべき値はattributesメソッドで取れます。

Terminal
> aaa.attributes
Output
=> {"id"=>nil,
 "email"=>"aaa@aaa",
 "encrypted_password"=>"$2a$11$W/lFietoiCkM6Lj6bsuY2eTsvGSg7nakpLCisng73tpv5bp.8OGFu",
 "reset_password_token"=>nil,
 "reset_password_sent_at"=>nil,
 "remember_created_at"=>nil,
 "created_at"=>nil,
 "updated_at"=>nil,
 "nickname"=>"annaPanda"}

パスワードがencrypted_passwordで暗号化されていますね。ですが引き継ぐ段階では元のパスワードが必要なのでそれを引き出します。params変数から、

Terminal
> params[:user][:password]
Output
=> "123123123"

このように取れるのはわかりますね。
さてここでsession変数を使います。普通変数などの値は、ブラウザが読み込まれるたびに一度クリアになります。これは"ステートレス"というHTTP通信の性質です。これに抗って値を保持できるのがsession変数です。
格納するsessionに付随する[:registration]や[:user]や[:password]の名前はなんでもOKですが今回はこうします。
あとはcellphone用のインスタンスを生成できればOKです。これにはnewメソッド出なく特別なbuild_<テーブル名>メソッドを使います。

Terminal
> aaa.build_cellphone
Output
=> #<Cellphone:0x00007fc4c5d1db20 id: nil, cellphone: nil, user_id: nil, created_at: nil, updated_at: nil>

これらの情報を元にcreateアクションを構築すると上のようになるわけです。
再掲します。

app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
〜省略〜
  # POST /resource
  def create
    user = User.new(sign_up_params)
    unless user.valid?
      render :new and return
    end
    session[:registration] = {user: user.attributes}
    session[:registration][:user][:password] = params[:user][:password]
    @cellphone = user.build_cellphone
    render :new_cellphone
  end
〜省略〜
end

そしてこのコントローラが使用されるにはルーティングを変更する必要があります。

config/routes.rb
Rails.application.routes.draw do
  get 'messages/index'
  devise_for :users, controllers: {
    registrations: 'users/registrations',
  }
  devise_scope :user do
    get 'cellphones', to: 'users/registrations#new_cellphone'
    post 'cellphones', to: 'users/registrations#create_cellphone'
  end
  root "messages#index"
end

devise_for :usersの先に加えられているのがcreateアクションへのルーティングです。これがないと上で書いたcreateアクションが使われません。
2ページ目用はdevise_scopeメソッドを使います。その先は普通にURIを設定しているだけですね。

②登録画面のビューは以下です。ファイル自体もないので作ります。

app/views/devise/registrations/new_cellphone.html.erb
<%= form_for @cellphone do |f| %>
  <%= render "devise/shared/error_messages", resource: @cellphone %>

  <div class="field">
    <%= f.label :cellphone %><br />
    <%= f.text_field :cellphone %>
  </div>

  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>

続いて、②createアクションです。名前はcreate_cellphoneです。

app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
〜省略〜
  def create_cellphone
    @user = User.new(session[:registration]["user"])
    @cellphone = Cellphone.new(cellphone_params)
    unless @cellphone.valid?
      flash.now[:alert] = @cellphone.errors.full_messages
      render :new_cellphone and return
    end
    @user.build_cellphone(@cellphone.attributes)
    @user.save
    sign_in(:user, @user)
    redirect_to root_path
  end

  protected

  def cellphone_params
    params.require(:cellphone).permit(:cellphone)
  end
〜省略〜
end

最終的にこうなります。これを構築してゆきます。

createアクションと同じようにbinding.pryをかませて見てみましょう。

app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
〜省略〜
  def create_cellphone
    binding.pry
  end
〜省略〜
end

②createアクションのアクション名はcreate_<テーブル名>でないとならないみたいですね。

まず、session変数に格納されている値を引き出してみましょう。

Terminal
> session[:registration][:user]
Output
=> nil

あれれ?なんででしょう。仕方ないので[:user]を抜いて見てみましょう。

Terminal
> session[:registration]
Output
=> {"user"=>
  {"id"=>nil,
   "email"=>"bbb@bbb",
   "encrypted_password"=>"$2a$11$Gp6J1spbcfu4EiS6EfqJsuGjZV1GB9LZXpWTzTBNDTY0GBmsIDDgm",
   "reset_password_token"=>nil,
   "reset_password_sent_at"=>nil,
   "remember_created_at"=>nil,
   "created_at"=>nil,
   "updated_at"=>nil,
   "nickname"=>"annaPanda",
   "password"=>"321321321"}}

取れました。"user"になっているみたいなんでそれに変えてみましょう。

Terminal
> session[:registration]["user"]
Output
=> {"id"=>nil,
 "email"=>"bbb@bbb",
 "encrypted_password"=>"$2a$11$Gp6J1spbcfu4EiS6EfqJsuGjZV1GB9LZXpWTzTBNDTY0GBmsIDDgm",
 "reset_password_token"=>nil,
 "reset_password_sent_at"=>nil,
 "remember_created_at"=>nil,
 "created_at"=>nil,
 "updated_at"=>nil,
 "nickname"=>"annaPanda",
 "password"=>"321321321"}

OKです。
次に②登録画面で入力されたケータイ番号を引き出してみましょう。
通常のストロングパラメータと同じですね。

Terminal
> params.require(:cellphone).permit(:cellphone)
Output
=> <ActionController::Parameters {"cellphone"=>"09011111111"} permitted: true>

さてコントローラ全体はこんな感じになります。

app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
〜省略〜
  def create
    user = User.new(sign_up_params)
    unless user.valid?
      render :new and return
    end
    session[:registration] = {user: user.attributes}
    session[:registration][:user][:password] = params[:user][:password]
    @cellphone = user.build_cellphone
    render :new_cellphone
  end

  def create_cellphone
    @user = User.new(session[:registration]["user"])
    @cellphone = Cellphone.new(cellphone_params)
    unless @cellphone.valid?
      flash.now[:alert] = @cellphone.errors.full_messages
      render :new_cellphone and return
    end
    @user.build_cellphone(@cellphone.attributes)
    @user.save
    sign_in(:user, @user)
    redirect_to root_path
  end

  protected

  def cellphone_params
    params.require(:cellphone).permit(:cellphone)
  end
〜省略〜
end

これで完成です。
まだケータイ番号用に桁数を制限などはしていません。余裕があったら挑戦してみてください。

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

【Rails】 関連モデル先の条件で検索する方法(内部結合)

関連モデル先の条件で検索する方法

すでにたくさんの記事が出ていますが、自分がよく使うものを忘れないようにメモしました。

User.joins(:posts).includes(:posts).where(posts: { post_name: 'test1' })

参考

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

お前らの感情でダウ平均株価は動いている

なんて大それたタイトルにしましたが、この記事の目的はTwitterからツイートを取得して市場参加者のセンチメントを測定して値動きと比較してみようというものです。
後半には初心者向けのCOTOHA APIのチュートリアルも記載しています。

相場とセンチメント

相場と参加者

ここ最近、コロナウイルスや原油相場暴落を受けて株価が大暴落だ〜なんてニュースをよく耳にしますよね。
ある記事では「2008年のリーマンショック級だ」なんて話も聞いたりします。
また、少し前にはビットコインを代表する仮想通貨も流行りましたね。
これらの相場の専門用語で「狼狽売り」という言葉があります。

狼狽売りとは、何らかの材料(ニュース)や相場環境により、株価が急激に下落した際に心理的に混乱を生じてパニック状態に陥り、持ち株を慌てて処分(売却)してしまうことを言います。
株式投資は、多分に人間の心理に影響されることから、後から冷静に判断すれば何ら株式価値に影響を及ぼさないことであっても、市場の雰囲気にのまれてしまい、このまま保有すれば膨大な損失が生じるのではないかとパニックに陥り、一刻も早く処分(売却)して損失を最小限に食い止めようとする意識が働くことにより生じる現象といえます。
引用:トレダビ

つまり、株式や仮想通貨の価格は人間の感情で動いている部分が大きいというのです。
そこで今回はTwitterのツイートから実際に相場参加者の感情を分析して、値動きと比較してみたいと思います。
そしてあわよくばツール化したいなあなんて。

COTOHA API

センチメントの測定に使用するのがCOTOHA APIです。
COTOHA APIはNTTコミュニケーションズが40年の研究を活かして作り上げた自然言語処理APIです。
COTOHA APIには、以下の機能(一部抜粋)があります。
・固有表現抽出:人名、地名などの固有名詞を自動で抽出する
・照応解析:これ、あれ、彼のような指示語が何を指しているのか特定する
・キーワード抽出:文章からキーワードを抽出する
・類似度判定:2つの文章の類似度を判定する
・ユーザー属性推定(β):文章からその人の年代、性別、趣味などの属性を推定する
・感情分析:文章からその人の感情がポジティブかネガティブか判定する
・要約(β):渡した文章を指定した行数で要約する
そしてこのCOTOHA APIは1日1000回までなら無料で使えちゃうのです。
これらの機能の中から今回は感情分析を使ってツイートからセンチメントを分析したいと思います。

1週間分の株価とセンチメント

実際に3/4から3/13までの8日間でTwitterから測定したセンチメントとその前日の価格差(前日比)を比較してみました。
センチメントは感情がPositiveなら+1、Negativeなら-1として、1日あたり200件のツイートの合計値としました。
測定対象はダウ平均株価です。
それではTwitterセンチメントスコアとダウの前日比を比較したグラフがこちらです。
スクリーンショット 2020-03-13 23.28.21.png

結構相関してそうに見える...!!!
これは割とちゃんとセンチメントの指標となりうるのでは?
相場のセンチメント分析ツールとしてVIXや板情報を使うだけでなく、実際の感情を分析するというのも可能かもしれません。
しかし、まだ8日間という短い期間でのサンプルになるので、もっと長い期間で測定したら別の結果になるかもしれませんし、ノイズも取り除いてないのでそれによっても変わるかもしれません。
水曜日には「水ダウ」(水曜日のダウンタウン)というワードで拾ったツイートもありました。。
クロちゃんが出てる回だったらネガティブ感情に引っ張られそうです。
ただ少なくとも現時点ではかなり相関してそうな気がします。

実装

今回使ったコードはこちらです。
筆者が1月から勉強を始めたてのプログラミングど素人で、まだRubyしか書けなかったので今回はRubyで実装しました。
ツッコミポイントがたくさんあると思いますので、コメントの方でご指摘いただけたら嬉しいです。

実装折りたたみ
require 'csv'
require 'faraday'
require 'json'
require 'twitter'

# 変数
base_url = 'https://api.ce-cotoha.com'
@target_date_since = '2020-03-12'
@target_date_until = '2020-03-13'
keyword = 'ダウ'
total_score = 0
start_time = Time.new
# COTOHA env
@client_id = 'CLIENT_ID'
@client_secret = 'CLIENT_SECRET'
# Twitter env
@client = Twitter::REST::Client.new do |config|
  config.consumer_key = 'CONSUMER_KEY'
  config.consumer_secret = 'CONSUMER_SECRET'
  config.access_token = 'ACCESS_TOKEN'
  config.access_token_secret = 'ACCESS_TOKEN_SECRET'
end

# Faraday初期化
@connection = Faraday::Connection.new(url: base_url) do |builder|
  builder.use Faraday::Response::Logger
  builder.adapter Faraday::Adapter::NetHttp
end

# COTOHA APIのアクセストークン取得
def get_access_token
  params = {
    'grantType': 'client_credentials',
    'clientId': "#{@client_id}",
    'clientSecret': "#{@client_secret}"
  }
  response = @connection.post do |request|
    request.url '/v1/oauth/accesstokens'
    request.headers["Content-Type"] = 'application/json'
    request.headers["charset"] = 'UTF-8'
    request.body = JSON.generate(params)
  end
  response.body
end

# COTOHA APIにアクセスする際の共通処理
def base_api(url, params)
  response = @connection.post do |request|
    request.url url
    request.headers['Content-Type'] = 'application/json'
    request.headers['charset'] = 'UTF-8'
    request.headers['Authorization'] = "Bearer #{@cotoha_access_token}"
    request.body = JSON.generate(params)
  end

  response.body
end

# 感情分析
def emotional_analize(sentence)
  params = { "sentence": "#{sentence}" }
  base_api('/api/dev/nlp/v1/sentiment', params)
end

# キーワードでツイート検索取得
def get_tweet(query)
  tweets = @client.search(query, result: 'recent', exclude: 'retweets', since: @target_date_since, until: @target_date_until).take(200)
end

### メインの処理
# トークン取得
@cotoha_access_token = JSON.parse(get_access_token)['access_token']
# ツイートを取得
tweets = get_tweet(keyword)
tweets.each_with_index do |tweet, i|
  # ツイートを感情分析
  request = JSON.parse(emotional_analize(tweet.full_text))['result']
  # 感情評価別に読み替え
  if request['sentiment'] == 'Positive'
    total_score += 1
  elsif request['sentiment'] == 'Negative'
    total_score -= 1
  end
  # 取得ツイートと判定結果をcsv書き出し
  CSV.open('sentiment_detail.csv', 'a') do |c|
    c << [@target_date_until, keyword, request['sentiment'], tweet.user.screen_name, tweet.full_text.gsub(/[\r\n]/," "), start_time]
  end
end
# 結果をcsvに書き出し
CSV.open('sentiment_total.csv', 'a') do |c|
  c << [@target_date_until, keyword, total_score, start_time]
end

COTOHA APIを使ってみる

ここからは、今回の実装から一部抜粋してCOTOHA APIを使ってみようというコーナーです。
この先もRubyで話を進めていきます。
またKENTAさん(@poly_soft)等のインフルエンサーの方が未経験からWeb系企業への転職にRubyをおすすめしているおかげか、Twitter上でRubyを勉強している未経験の方をたくさん見かけます。
そういったRuby勉強し始めた方や、自然言語処理に興味出てきたけどPython書けませんって方に特に参考にしてもらえればと思います。
Pythonじゃなくたって興味さえあればデータ分析してみたっていいじゃない!自然言語処理やってみたっていいじゃない!おもしろそうなんだもの!!!!

COTOHA APIの登録

まずはCOTOHA APIに登録していきます。
COTOHA APIのページにアクセスし、「今すぐ無料登録」を押します。
※右上の無料登録が金色になった状態(最上部では白色)で無料登録を押しても同じページにリダイレクトされてしまうので注意してください。
2020-03-13_14h31_58.png

登録するメールアドレスを入力して、送信を押してください。
2020-03-13_14h28_30.png

メールが届くので、記載されているURLにアクセスすると、情報入力画面に遷移しますので、それぞれ入力していってください。
特に記入で困る項目はないと思います。
2020-03-13_14h39_57.png
サインアップを押して、確認を終えてから画面遷移するまで少し時間が空きますが、待っていれば自動で遷移します。
焦らず待ちましょう。
サインアップが完了すると、再びメールが届きます。
メールに記載されているURLにアクセスすると、ログイン画面に遷移します。
そこでログイン情報を入力してログインを押します。
ログインすると、アカウント画面が表示されます。
2020-03-13_14h50_29.png
これで晴れて、COTOHA APIを使えるようになりました!

実装

それでは登録したCOTOHA APIを実際に使ってみましょう!
今回はあくまでCOTOHA APIを使ってみることに主眼を置いているので、Twitterからツイートを取得する処理は省いて、文字列ベタ打ちで値を渡して感情分析を行うことをゴールにします。
これからやることステップに分けると、
1. COTOHA APIのアクセストークンを取得する
2. 取得した結果を変数へ代入する
3. 感情分析APIを叩く
4. 返ってきた分析結果を出力する
になります。

最終的なコードがこちらです。
require 'faraday'
require 'json'

# 変数
base_url = 'https://api.ce-cotoha.com'
sentence = '岡崎最高!'
# COTOHA env
@client_id = 'あなたのClient_ID'
@client_secret = 'あなたのClient_secret'

# Faraday初期化
@connection = Faraday::Connection.new(url: base_url) do |builder|
  builder.use Faraday::Response::Logger
  builder.adapter Faraday::Adapter::NetHttp
end

# 1.COTOHA APIのアクセストークン取得
def get_access_token
  params = {
    'grantType': 'client_credentials',
    'clientId': "#{@client_id}",
    'clientSecret': "#{@client_secret}"
  }
  response = @connection.post do |request|
    request.url '/v1/oauth/accesstokens'
    request.headers["Content-Type"] = 'application/json'
    request.headers["charset"] = 'UTF-8'
    request.body = JSON.generate(params)
  end
  response.body
end

# 3.感情分析APIを叩く
def emotional_analize(sentence)
  params = { "sentence": "#{sentence}" }
  response = @connection.post do |request|
    request.url '/api/dev/nlp/v1/sentiment'
    request.headers['Content-Type'] = 'application/json'
    request.headers['charset'] = 'UTF-8'
    request.headers['Authorization'] = "Bearer #{@cotoha_access_token}"
    request.body = JSON.generate(params)
  end
  response.body
end

### メインの処理
# 2.取得したアクセストークンを変数に代入
@cotoha_access_token = JSON.parse(get_access_token)['access_token']
# 4.返ってきた分析結果を出力する
puts JSON.parse(emotional_analize(sentence))['result']

1. COTOHA APIのアクセストークンを取得する

まずは必要なライブラリを読み込みます。

require 'faraday'
require 'json'

もしfaradayがインストールされていない場合はインストールしておいてください。

$gem install faraday

インストールはたったこれだけです。
FaradayというのはHTTP等の通信を簡単にできまっせなライブラリです。

COTOHA APIを使うにはアクセストークンというものを取得する必要があります。
このアクセストークンはAPIを叩くことで取得できます。
叩くAPIのURLはログインした直後の画面に記載してあります。
また、アクセスするときに必要なClient_IDとClient_secretも同じ画面に記載してあります。
スクリーンショット 2020-03-13 23.44.46.png
それでは、実装です。

# 変数
base_url = 'https://api.ce-cotoha.com'
sentence = '岡崎最高!'
# COTOHA env
@client_id = 'あなたのClient_ID'
@client_secret = 'あなたのClient_secret'

# Faraday初期化
@connection = Faraday::Connection.new(url: base_url) do |builder|
  builder.use Faraday::Response::Logger
  builder.adapter Faraday::Adapter::NetHttp
end

# 1.COTOHA APIのアクセストークン取得
def get_access_token
  params = {
    'grantType': 'client_credentials',
    'clientId': "#{@client_id}",
    'clientSecret': "#{@client_secret}"
  }
  response = @connection.post do |request|
    request.url '/v1/oauth/accesstokens'
    request.headers["Content-Type"] = 'application/json'
    request.headers["charset"] = 'UTF-8'
    request.body = JSON.generate(params)
  end
  response.body
end

変数のbase_urlにはCOTOHA APIのアカウントホームに記載してあるBASE_URLを削って代入しています。
これは、その後にこのbase_urlを使って初期化するfaradayをアクセストークン取得と感情分析の両方で使うためです。
COTOHA envのところではご自身のClient_IDとClient_secretを入力してください。
Faraday初期化ではさっきのbase_urlを渡します。
また、ブロックの中ではHTTP通信のログを標準出力する設定を行っています。
最後のget_access_tokenでAPIをたたいてアクセストークンを取得するメソッドを定義しています。
パラメータはparams内に記載して渡しています。
response = @connection.post~~でAPIを叩いて、その結果をresponseに代入しています。
これでAPIを叩いて、アクセストークンを取得する実装は完了です!
そして正直ここができてしまえば残りの工程はそれほど難しくないので、安心してください。

2. 取得したアクセストークンを変数へ代入する

続いて先ほど定義したget_access_tokenで取得した結果の中からアクセストークンだけを取り出し、変数に代入します。

### メインの処理
# 2.取得したアクセストークンを変数に代入
@cotoha_access_token = JSON.parse(get_access_token)['access_token']

APIを叩いて返ってくるデータはJSONという形式になっています。
そこでJSONという形をハッシュに変換してくれるJSON.parseの出番です。
このメソッドでハッシュ化してしまえばこっちのもんです。
アクセストークンはaccess_tokenというキーの値として入っているので、それを変数に代入して感情分析APIで使えるようにしておきましょう。

3. 感情分析APIを叩く

さあ、感情分析のAPIを叩いてみましょう!
ここでは、emotional_analizeメソッドを定義します。

# 3.感情分析APIを叩く
def emotional_analize(sentence)
  params = { "sentence": "#{sentence}" }
  response = @connection.post do |request|
    request.url '/api/dev/nlp/v1/sentiment'
    request.headers['Content-Type'] = 'application/json'
    request.headers['charset'] = 'UTF-8'
    request.headers['Authorization'] = "Bearer #{@cotoha_access_token}"
    request.body = JSON.generate(params)
  end
  response.body
end

上の実装を見てもらえば分かる通り、1番のコードとかなり似てます。
1番を乗り越えた皆さんなら恐るるに足りません。
今回はparamsにsentenceというキーで感情を分析したい文章を渡します。
ダウのセンチメント測定時には取得したツイートを渡していました。
値には引数で受け取る文章を設定しておきましょう。
ブロック内のurlはアクセストークンを取得するときとは異なります。
このURLはCOTOHA APIのAPI一覧ページから飛べるリファレンスに記載してあります。
スクリーンショット 2020-03-14 0.27.52.png
ここでbase_urlの代入時に削った分のパス(/api/dev)を追加して値を渡しましょう。
最後にHTTPヘッダーのAuthorizationに1と2で取得したアクセストークンを指定しましょう。
これで感情分析をするためのメソッド定義は終了です。
完成目前です!がんばりましょう!

4. 返ってきた分析結果を出力する

最後の工程です。
3で定義したメソッドを使って取得した感情分析の結果を画面に出力します。
最後の実装です。

# 4.返ってきた分析結果を出力する
puts JSON.parse(emotional_analize(sentence))['result']

こちらも見ていただければ分かる通り、2番とほぼ同じです。
ただしemotional_analizeを呼び出す際には分析するための文章を忘れずに渡しましょう。
ちなみに渡すための文章を代入した変数は1番でしれっと定義しています。笑
これで実装は全て完了です。
あとは実際に実装したコードを実行してみて下記のような結果が表示されていれば成功です!

{"sentiment"=>"Positive", "score"=>0.591956842833892, "emotional_phrase"=>[{"form"=>"最高", "emotion"=>"P"}]}

おめでとうございます!そしてお疲れ様でした!
他にもCOTOHA APIにはいろいろなAPIがあるので遊んでみてください。
今回のコードを少しいじるだけで他のAPIも使えるはずです。

終わりに

今回はCOTOHA APIを使って、Twitterから他の市場参加者のセンチメントを測定してみました。
また、Ruby初心者でもCOTOHA APIを試してみることができるように感情分析の実装を一緒に進めました。
データ分析の分野なので、COTOHA APIの記事を見ると、やはりPythonの記事ばかりでした。。
なので、この記事がRubyを勉強し始めた人で、「APIを触ってみたい」、COTOHA APIのキャンペーンを見て「自分もやってみたい」と思った人の助けになればと思います。
もし実際に参考にしてやってみたよって方がいましたら、なんならやってみようかなレベルでも大丈夫なのでコメントに書いてもらえると私がすごい喜びます笑(初めてこんなボリューミーな記事を書いたのでリアクション欲しい←)
あと最初にも書いたのですが、まだ勉強し初めて2ヶ月くらいしか経っておらず、私の実装自体も改善点だらけだと思いますので、そちらもコメントでご指摘いただけたらと思います。

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

Rails アプリで S3 の権限制御を外さずに署名付きURLの署名を省いてファイルアクセスする方法を調べた

はじめに

Rails アプリを用いて、S3 の権限制御を外さずに署名付きURLを省いてアクセスする方法について調べたので、その方法をまとめておきます。

背景

S3 バケット内のファイルに権限制御を入れ、そのファイルに対してアクセスするとき、署名付きURLを用いることがあると思います。こんなURLですね。(クレデンシャルはマスクしています)

https://example.s3-ap-northeast-1.amazonaws.com/foo/bar/piyo.txt/?X-Amz-Expires=-0000000000&X-Amz-Date=0000000000000000&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AAAAAAAAAAAAAAAAAAAA/00000000/ap-northeast-1/s3/aws4_request&X-Amz-SignedHeaders=host&X-Amz-Signature=0000000000000000000000000000000000000000000000000000000000000000

しかしURLについた署名が、外部ライブラリを使っているときにノイズになることがあります。

例えば本のビューアーや写真のアルバムのようなライブラリを Rails アプリ上で扱っていると、ライブラリでは画像のファイル群をファイル指定で逐一アクセスするというような処理が走ることがあります。

おおよそそのような外部ライブラリは、アプリの同サーバ上のパブリックにアクセスできるディレクトリに画像ファイル群がおかれ、そこに対してアクセスするというケースを想定して作られていることが多いと思います。外部ライブラリ側では画像ファイルについた署名を考慮せずアクセスし、想定通り動かない、ということが起きがちです。

そのため、権限制御を入れたままで、署名付きURLを省いた URL で S3 バケット内のファイルにアクセスできるようにできないかを調べていました。

どうやったか

Rails アプリのルーティングで、S3 バケットのファイルパスをある種プロキシし、実際の S3 バケットの署名付きURLにリダイレクトするみたいな仕組みにしてみました。

以下、やりかたを簡単にまとめます。

ユースケース

以下のファイルに、署名付きURLなしでリクエストしたいとします。

  • バケットURL: https://example.s3-ap-northeast-1.amazonaws.com
  • ファイルパス:foo/bar/piyo.txt

イメージとしては、Rails の URL を https://rails-sample.com としたら、以下URLで S3 ファイルにアクセスできるようにしたいです。

https://rails-sample.com/remote_storages/proxy/foo/bar/piyo.txt

ワイルドカードセグメントを使った Rails のルーティングを作る

まず、ルーティング用のコントローラーを作りますが、その時のルーティング設定では、ワイルドカードセグメントを使います。

ワイルドカードセグメントとは、最初にアスタリスク(*) がついた部分のパラメータのことで、ルーティングのある位置から下のすべての部分にパラメータを展開させるために利用できます。

例えば RemoteStoragesController というコントローラーで#proxyという get メソッドを作るとしたら、以下のようになります。

resources :remote_storages, only: [] do
  collection do
    get 'proxy/*path', to: 'remote_storages#proxy', as: 'proxy'
  end
end

上記により、path には foo/bar/piyo.txt というようなスラッシュ有りのパラメータを渡すことができるようになります。

コントローラー作成

次にコントローラーを作ります。以下では S3 へのアクセスは fog を使っていますが、AWS SDK を使っても良いと思います。

以下では proxy メソッドに渡ってきた path と拡張子 format に応じて、署名付きURLにリダイレクトするという処理を作っています。
検証してみてわかったのですが、ワイルドカードセグメントには .txt のような拡張子は渡ってきませんでした。代わりに、format というパラメータに txt という文字列が入ってくるため、メソッド内で再度ファイルパスを作り直しています。

また、アクセスキーやバケットは Rails.application.secrets で秘匿化すると良さそうです。

class RemoteStoragesController < ApplicationController
  def proxy
    s3_bucket = Fog::Storage.new(
      provider: 'AWS',
      aws_access_key_id: 'xxx',
      aws_secret_access_key: 'xxx',
      region: 'ap-northeast-1'
    ).directories.get('example')

    # 拡張子は *path に入らず :format に入るためここで調整している
    path = "#{params[:path]}.#{params[:format]}"
    redirect_to s3_bucket.files.get_https_url(path, 1.minutes.since.to_i)
  end
end

これによって、以下にアクセスすると、

https://rails-sample.com/remote_storages/proxy/foo/bar/piyo.txt

以下ファイルパスの署名付きURLにリダイレクトするということができます。

https://example.s3-ap-northeast-1.amazonaws.com/foo/bar/piyo.txt

おわりに

今回 Rails ルーティングにワイルドカードセグメントというものがあることを初めて知りました。

ただ、ワイルドカードで渡すとどんな文字列も渡せるようになるため、セキュリティを考慮してある程度のパラメータチェックは入れたほうが良いのかなと思います。

参考

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

ancestryとjQueryで多階層型カテゴリの入力フォームを段階的に表示させてみた。

何をしたか

ショッピングサイトの検索・購入ページなどでよく見かける
「多階層型カテゴリの入力フォームが順に表示される機能」を
ancestryとjQueryを使って実装してみました。
振り返りを兼ねて記事を書いていきます。

下準備

長いので見たい人だけ展開してください

※コードは載せますがここでは特に説明しません。
※scssは必要ないのですが味気ないので入れました。

terminal.
$ rails _5.2.4_ new ancestry_sample --database=mysql --skip-test --skip-turbolinks --skip-bundle
$ gem install ancestry jquery-rails haml-rails
$ bundle install
$ rails g model category
$ rails g model item
$ rails g controller categories
$ rails g controller items
asesst/javascripts/application.js
rails-ujsより上段にjqueryを追加
//= require jquery
//= require rails-ujs
db/migrate/202***********_create_categories.rb
class CreateCategories < ActiveRecord::Migration[5.2]
  def change
    create_table :categories do |t|
      t.string :name, null: false
      t.timestamps      
    end
    add_index :categories, :name
  end
end
db/migrate/202***********_create_items.rb
class CreateItems < ActiveRecord::Migration[5.2]
  def change
    create_table :items do |t|
      t.references :category, null: false, foreign_key: true
      t.timestamps
    end
  end
end
models/category.rb
class Category < ApplicationRecord
  has_many :items
  has_ancestry
end

models/item.rb
class Item < ApplicationRecord
  belongs_to :category
end

controllers/items_controller.rb
class ItemsController < ApplicationController
  def index
    @items = Item.all
  end

  def new
    @item = Item.new
    @categories = []
    @categories.push(Category.new(id: 0,name:"---"))
    @categories.concat(Category.where(ancestry: nil))
  end

  def create
    Item.create(item_params)
    redirect_to items_path
  end

  private
  def item_params
    params.require(:item).permit(:category_id)
  end
end
config/routes.rb
Rails.application.routes.draw do
  root "items#new"
  resources :items ,only: [:index,:new,:create]
end
views/items/new.html.haml
.items
  =form_with(model:@item,local:true) do |f|
    .items__parent
      = select_tag 'parent', options_for_select(@categories.pluck(:name,:id))
    .items__child
    .items__grandchild
    = f.submit "登録する",class:"button"
views/items/index.html.haml
%table
  %tr 
    %td No.
    %td%td%td-@items.each_with_index do |item,i|
    %tr
      %td 
        = i+1
      %td 
        =item.category.parent.parent.name
      %td
        =item.category.parent.name
      %td
        =item.category.name
%button
  =link_to '戻る',new_item_path,class:"button"
db/seed.rb
ary_tops = [{name: "Tシャツ/カットソー(半袖/袖なし)"},{name: "Tシャツ/カットソー(七分/長袖)"},{name: "その他"}]
ary_jacket = [{name: "テーラードジャケット"},{name: "ノーカラージャケット"},{name: "Gジャン/デニムジャケット"},{name: "その他"}]
ary_shoes = [{name: "スニーカー"},{name: "サンダル"},{name: "その他"}]

lady = Category.create(name: "レディース")
lady_tops = lady.children.create(name: "トップス")
lady_tops.children.create(ary_tops)
lady_jacket = lady.children.create(name: "ジャケット/アウター")
lady_jacket.children.create(ary_jacket)
lady_shoes = lady.children.create(name: "靴")
lady_shoes.children.create(ary_shoes)

men = Category.create(name: "メンズ")
men_tops = men.children.create(name: "トップス")
men_tops.children.create(ary_tops)
men_jacket = men.children.create(name: "ジャケット/アウター")
men_jacket.children.create(ary_jacket)
men_shoes = men.children.create(name: "靴")
men_shoes.children.create(ary_shoes)
terminal.
$ rails db:create
$ rails db:migrate
$ rails db:seed
assets/stylesheets/items.scss
*{
  font-family: Arial,游ゴシック体,YuGothic,メイリオ,Meiryo,sans-serif;
  box-sizing: border-box;
}
%__select-form{
  width: 300px;
  height: 48px;
  background-color: #fff;
  border-radius: 4px;
  font-size: 16px;
  border: 1px solid #ccc;
  color: #222;
}
#parent{
  @extend %__select-form;
}
#child{
  @extend %__select-form;
}
#item_category_id{
  @extend %__select-form;
}
.button{
  width: 300px;
  height: 48px;
  background-color: #f5f5f5;
  border-radius: 5px;
  font-size: 17px;
  transition: 0.2s;
  text-decoration:none;
  line-height: 48px;
  color: #222;
}

下準備ここまで。

いざ、実装

では早速やっていきましょう。
※メインはancestryの値の抽出 → ajax通信のため、js内のhtml作成部分には特に触れません。

親入力欄変更 → 子入力欄表示

  • イベント開始点作成(親カテゴリ"parent"を変更した時にイベント開始)
asesst/javascripts/items.js
$(function() {
  $("#parent").on("change",function(){
  }
}
  • ajax通信に必要な値の抽出(selectタグから選択された項目のvalue値を抽出)
asesst/javascripts/items.js
$(function() {
  $("#parent").on("change",function(){
    var int = document.getElementById("parent").value
  };
}
  • コントローラーへのajax通信処理の記述
asesst/javascripts/items.js
$(function() {
  $("#parent").on("change",function(){
    var int = document.getElementById("parent").value
    $.ajax({
      url: "/categories",
      type: 'GET',
      dataType: 'json',
      data: {id: int}
    })
    .done(function() {
    })
    .fail(function() {
    });
  });
})
  • コントローラー内の処理の記述(ancestryの値が選択した親カテゴリのidと同値のレコードを取得)
controllers/items_categories.rb
  def index
    @categories = Category.where(ancestry: params[:id])
    respond_to do |format|
      format.json
    end
  end
  • routeの記述
config/routes.rb
Rails.application.routes.draw do
  root "items#new"
  resources :items ,only: [:index,:new,:create]
  resources :categories ,only: :index
end
  • json.jbulderの作成・記述
views/categories/index.json.jbuilder
json.array! @categories do |category|
  json.id category.id
  json.name category.name
end
  • 返り値と表示処理
asesst/javascripts/items.js
$(function() {
  function buildHTML(result){
    var html =
      `<option value= ${result.id}>${result.name}</option>`
    return html
  }
#省略#
    .done(function(categories) {
      var insertHTML = `<select name="child" id="child">
                        <option value=0>---</option>`;
      $.each(categories, function(i, category) {
        insertHTML += buildHTML(category)
      });
      insertHTML += `</select>`
      $('.items__child').append(insertHTML);
    })
    .fail(function() {
    });
  });
})

子入力欄変更 → 孫入力欄表示

  • イベント開始点作成(子カテゴリ"child"を変更した時にイベント開始)
asesst/javascripts/items.js
$(function() {
  $("#parent").on("change",function(){
    var int = document.getElementById("parent").value
#中略#
  });
  $("#child").on("change",function(){
  });
})
  • コントローラーでバインドするancestryの値「'親id'/'子id'」を取得、およびコントローラーへのajax通信処理を記述
asesst/javascripts/items.js
#省略#
  $("#child").on("change",function(){
    var intParent = document.getElementById("parent").value
    var intChild = document.getElementById("child").value
    var int = intParent + '/' + intChild
    $.ajax({
      url: "/categories",
      type: 'GET',
      dataType: 'json',
      data: {id: int}
    })
    .done(function() {
    })
    .fail(function() {
    });
  });
})

※ controller.rb、route.rb、json.jbuilderは前述のものを使用するため割愛します

  • 返り値と表示処理
asesst/javascripts/items.js
#省略#
    .done(function(categories) {
      var insertHTML = `<select name="item[category_id]" id="item_category_id">
                        <option value=0>---</option>`;
      $.each(categories, function(i, category) {
        insertHTML += buildHTML(category)
      });
      insertHTML += `</select>`
      $('.items__grangchild').append(insertHTML);
    .fail(function() {
    });
  });
})

完成?

完成!

と言いたいところですが、このままだと親や子を変更する度に入力欄が無限に増殖してしまいます。

スクリーンショット 2020-03-14 1.20.02.png

  • 条件式を追加
    • 「"---"を選択した時」 → 下位の要素をremove
    • 「追加する要素が既に存在する時」 → 要素をreplace
    • それ以外 → append
asesst/javascripts/items.js
##省略##
  $("#parent").on("change",function(){
    var int = document.getElementById("parent").value
    if(int == 0){
      $('#child').remove();
      $('#item_category_id').remove();
    }else{
      $.ajax({
        url: "/categories",
        type: 'GET',
        dataType: 'json',
        data: {id: int}
      })
      .done(function(categories) {
        var insertHTML = `<select name="child" id="child">
                          <option value=0>---</option>`;
        $.each(categories, function(i, category) {
          insertHTML += buildHTML(category)
        });
        insertHTML += `</select>`
        if($('#child').length){
          $('#child').replaceWith(insertHTML);
          $('#item_category_id').remove();
        } else {
          $('.items__child').append(insertHTML);
        };
      })
##中略##
  $(document).on("change","#child",function(){
    var intParent = document.getElementById("parent").value
    var intChild = document.getElementById("child").value
    var int = intParent + '/' + intChild
    if(intChild == 0){
      $('#item_category_id').remove();
    } else {
      $.ajax({
        url: "/categories",
        type: 'GET',
        dataType: 'json',
        data: {id: int}
      })
      .done(function(categories) {
        var insertHTML = `<select name="item[category_id]" id="item_category_id">
                          <option value=0>---</option>`;
        $.each(categories, function(i, category) {
          insertHTML += buildHTML(category)
        });
        insertHTML += `</select>`
        if($('#item_category_id').length){
          $('#item_category_id').replaceWith(insertHTML);
        } else {
          $('.items__grandchild').append(insertHTML);
        };
      })
##後略##

条件式により、無限に増殖することもなくなりました。
スクリーンショット 2020-03-14 1.41.41.png

レコードも無事できました。
スクリーンショット 2020-03-14 1.41.12.png
スクリーンショット 2020-03-14 1.41.24.png

というわけで、
完成です!

注意事項

エラー処理は何もしていないので、保存ができない場合が多々ありますが仕様です。
(孫まで入力しないと、form_withで送信するパラメータを拾えないのでレコード登録できません。)

以上です。

参考にさせていただいた記事

多階層カテゴリでancestryを使ったら便利すぎた

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

『メッセージを投稿』できる最低限のRailsアプリを丁寧に作る(これで初心者完全卒業!)

この記事の基本的な方針

メッセージを投稿して一覧表示するだけの簡単なアプリを作ります。
完成するのは以下のたった4画面のアプリです。

【TOP画面(ログイン前)】     【TOP画面(ログイン後)】
a0.png a4.png
【登録画面】
a1.png
【ログイン画面】
a2.png
【投稿画面】
a3.png
これを丁寧に作って、初心者を卒業しましょう。

想定する読み手

既に一度Railsアプリをチュートリアルやスクール等で作ったことがある方を想定しております。
Mac使用で、パソコンの環境構築は完了していることが前提です。

具体的なコーディング手順

完成品GitHub(masterではなくこの記事のタイトルと同名のブランチなので注意して下さい)

①アプリを立ち上げる

まずターミナルを開きcdコマンドを使い、アプリを立ち上げたい場所に移動します。
今回はデスクトップに作ります。

Terminal
$ cd    #ルートディレクトリに移動する
$ cd Desktop   #デスクトップに移動する

そしてrails newします。

Terminal
$ rails _5.2.4.1_ new <名前> -d myspl

します。今回はのバージョンは5.2.4.1にしました。
MySQLをデータベースとして使用しますので、-d mysqlを付けています。
これでアプリが立ち上げられたと思うので、そのアプリのルートディレクトリに移動します。

Terminal
$ cd <名前>

データベースを作ります。

Terminal
$ rails db:create

(中身が空であってもこの時点でデータベースは作っておかないと、ブラウザでアクセスした時にActiveRecord::NoDatabaseErrorになってしまいます。)

そして

Terminal
rails  s

でサーバーを立ち上げて、ブラウザでlocalhost:3000/にアクセスすると
rails new.jpg
これが表示されます。

続いて、コントローラを作ります。

Terminal
$ rails g controller <名前>

名前は自由ですが一番関連のある(データベースの)テーブル名の複数形にするのが一般的です。
今回はTOP画面にmessage一覧を表示するので、

Terminal
$ rails g controller messages index

今回indexを加えていますが、このように名前の後にアクションを並べれば、そのアクション用のhtml.erbファイルが作られ、ルーティングとコントローラファイルに必要なことが追記されます。アクションはもちろん複数並べてよく、コンマ無しで隙間だけ開けて並べていきます。

ここでエディタを起動します。

まずルーティングを設定をします。上でアクション名を並べていればすでに書かれています。

config/routes.rb
get "messages/index"

取り急ぎ、適当にビューを書きます。

app/views/messages/index.html.erb
<div>こんにちは</div>

ここでブラウザでlocalhost:3000/コントローラ名/indexにアクセスすれば、「こんにちは」が見れるはずです。

通常メインのindex画面をルート画面とするので

config/routes.rb
root "messages#index"

を追記しておきます。localhost:3000でもアクセスできるようになります。

②ログイン機能を作る

ログイン機能は自作することもできますが、deviseというgemを使うことが極めて一般的であるのでこれを今回は使用します。

deviseをインストールします。

Gemfile
gem 'devise'

こちらを追記し、ターミナルで

Terminal
$ bundle install

をし、そしてdeviseを使い始めるにはターミナルで

Terminal
rails g devise:install

をする必要があります。これでconfigディレクトリにファイルができたので、サーバの再起動の必要があります。
さらに、

Terminal
rails g devise <モデル名>

をすれば必要ないくつかのファイルが作られます。この時のモデル名はなんでもいいのですが、userとするのが一般的です。

続いて、上のrails g devise user でマイグレーションファイルも作られているので、

Terminal
$ rails db:migrate

します。これでデータベースにユーザー情報用のテーブルが作られます。

deviseはデフォルトではEmailとPasswordで登録する仕様になっています。EmailやPassword以外を含めて登録する場合やビューやコントローラを編集する場合は別の手続きが必要ですが、今回はこのままでいきます。

この時点でURIをブラウザに直接打ち込めば新規登録画面とログイン画面が表示される段階まで来ていますので確認します。URIは rails routesで確認します。
会員登録はlocalhost:3000/users/sign_upですね。結果は以下です。
in.png
ログインのlocalhost:3000/users/sign_inも同様ですね。
ここで適当に新規登録をしてみて、成功すると自動的にログイン状態になり、『こんにちは』のTOP画面に遷移します。これもログインしたらルート画面に遷移するdeviseのデフォルトの機能です。
これでログイン機能完成です。

③TOP画面作成

さてあとは今『こんにちは』になっているTOP画面を整えて、取り急ぎ会員登録画面ログイン画面TOP画面でぐるぐるできるだけの状態を作ります。
TOP画面は、
ログイン状態であれば、そのユーザーのEmailアドレスとログアウトへのリンク、
非ログイン状態であれば、新規登録へのリンクとログインへのリンクが表示されるようにします。
全体を一度に載せてみます。

app/views/コントローラ名/index.html.erb
<% if user_signed_in? %>
  <%= current_user.email %>
  <%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
<% else %>
  <%= link_to '新規登録', new_user_registration_path %>
  <%= link_to 'ログイン', new_user_session_path %>
<% end %>

まず、このファイルのように拡張子を.erbは、HTMLファイルにRubyのコードを埋め込めるようになります。
<%= %><% %>が重要ですね。<%= %><% %>の違いは「画面上に表示されるか否か」みたいな嘘教えられたことないですか?<%= %><% %>の違いは「HTMLとして出力するか否か」です。きちんと覚えましょう。
if文で条件分岐されてtrueとなった方のみがHTMLとして出力されるので、もちろんif文自体はHTMLとして出力はしませんので<% %>を使います。
後の説明は省きます。
これで以下のような状態ができていると思います。

【TOP画面(ログイン前)】     【TOP画面(ログイン後)】
a0.png a9.png
【登録画面】
a1.png
【ログイン画面】
a2.png

ここまでが難しい場合は過去記事に戻ってみてください。
次に行きます。

④メッセージテーブルを作る

マイグレーションファイルモデルを作るため、

Terminal
$ rails g model message

を打ちます。
マイグレーションファイルはデータベースのテーブルを作るためのファイルで、モデルはデータベースからデータを出し入れするときの設定やルールを書くファイルです。もう大丈夫ですね?

一般的にモデル名は単数形でコントローラ名は複数形です。

ちなみにこれらのファイルはActive Recordというライブラリを継承していて、そのおかげでデータベースに関することをRubyで記述できます。本来はMySQLデータベースとやりとりするにはSQLという言語で書かなければならないので、Active Recordが翻訳をしてくれているということになります。

続いてマイグレーションファイルを書いて、メッセージテーブルを完成させます。
今回はメッセージの内容とそれを誰が投稿したかの情報を保存できるようにしようと思うので、t.string :messaget.references :user, foreign_key: trueを追記し

db/migrate/2020xxxxxxxxxx_create_messages.rb
class CreateMessages < ActiveRecord::Migration[5.2]
  def change
    create_table :messages do |t|
      t.string :message
      t.references :user, foreign_key: true
      t.timestamps
    end
  end
end

となるようにします。
ちなみにこの時点でブラウザからアクセスしようとするとエラーになります。理由はもちろん

Terminal
$ rails db:migrate

していないからですね。これを済ませたら、メッセージテーブルは完成です。
一応ちゃんと出来ているかデータベースを見に行ってみましょう。
mysql.png
私はSequelProを使っていてこんな感じです。

最後にモデルにテーブル同士の関係を書きましょう。これが無くても投稿できなくはないのですが、投稿した内容を引き出して扱う上で便利なので今済ませてしまいましょう。以下を追記します。

app/models/message.rb
belongs_to :user
app/models/user.rb
has_many :messages

⑤メッセージを投稿できるようにする

必要な編集はルーティングビューコントローラ です。
まずルーティング。

config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  root 'messages#index'
  resources :messages, only: [:index, :new, :create]
end

resourcesを用いた記法に変えました。ここは大丈夫ですね?
次はビュー。newのビューファイルがないので、app/views/messages/ディレクトリに作り以下を作ります。

app/views/messages/new.html.erb
<%= form_with(model: @message, local: true) do |f| %>
  <%= f.text_field :message %>
  <%= f.submit "投稿" %>
<% end %>

<%= %><% %>の違いはなんでしたか?
「HTMLとして出力するか否か」です。
<%= f.text_field :message %><%= f.submit "投稿" %>はともかく、<%= form_with(model: @message, local: true) do |f| %>は表示されているとは言い難いですよねぇ?
画面上はこんな感じになるはずです。
form.png

<%= form_with(model: @message, local: true) do |f| %>はHTMLのform要素に対応しています。画面上に表示はされません。

最後はコントローラです。少し詳しく行きます。

app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  before_action :to_root, except: [:index]
  def index
  end
  def new
    @message = Message.new
  end
  def create
    @message = Message.new(message_params)
    @message.save
    redirect_to root_path
  end
  private
  def message_params
    params.require(:message).permit(:message).merge(user_id: current_user.id)
  end
  def to_root
    redirect_to root_path unless user_signed_in?
  end
end

完成品はこれですが、一からこれを構築してみます。
私であればまず

app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  def index
  end
  def new
    @message = Message.new
  end
  def create
    binding.pry
    @message = Message.new(??)
    @message.save
  end
end

ここまで書きます。??の値を知りたいですね。
おっとbinding.pryってなんですか?デバック用のコードです。ここでは詳しい説明は省きます。これを使うために

Gemfile
gem 'pry-rails'

を追記して、

Terminal
$ bundle install

します。
この状態で、rails sでサーバを起動し(すでに起動していたら再起動を忘れずに)、ログイン状態にし、ブラウザでhttp://localhost:3000/messages/newにアクセスし、フォームに適当な文字を入れて、投稿ボタンを押してみます。
するとブラウザが待機状態になり、ターミナルで変数の中身を確認できるようになります。
ここでparamsと打ってみます。

Terminal
> params

paramsはページ遷移時のデータがハッシュ形式で格納されているActionController::Parametersクラスのインスタンスです。

Output
=> <ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"BZIJ8UtNrNugQOI94y8iWX5dKc2Z2bZaKxKXm8X/tV1cvJxIPlC/MaL7o0WrfTZccD1dr7IPC8Nlq8vKHiFhdg==", "message"=>{"message"=>"こんにちは"}, "commit"=>"投稿", "controller"=>"messages", "action"=>"create"} permitted: false>

こんな感じで値が取れます。これがインスタンスの情報です。<直後がクラス名で、その先がハッシュ形式になっているので例えば"✓"の値を取得したければ、

Terminal
> params["utf8"]

普通にこうします。
ちなみに、

Terminal
> params[:"utf8"]
Terminal
> params[:utf8]

これらでも取得できます。親切に準備してくれたんですね。
さて自分が投稿した"こんにちは"を取得するには、二重ハッシュになっているので

Terminal
> params[:message][:message]

これですね。

あれ?これ<>はなんなの?値を取得するとき気にしなくていいの?
いいみたいですね。すみません、完璧には理解できていません。

ちなみに、試しに空っぽのMessageモデルのインスタンスを作ってみると

Terminal
>  Message.new
Output
=> #<Message:0x00007fcfcde17130 id: nil, message: nil, user_id: nil, created_at: nil, updated_at: nil>

ActionController::Parametersのインスタンスであれば

Terminal
> ActionController::Parameters.new
Output
=> <ActionController::Parameters {} permitted: false>

ふむふむ、最初の#はなんだろう?この0x00007fcfcde17130はなんだろう?
誰か教えてください(笑)

この待機状態から抜け出すには、コントローラ内のbinding.pryを削除して、ターミナルでexitを打ちます。

さて、保存すべきは投稿されたmessageの内容と、投稿した自分のidです。もうすでに両方ともわかっていますね。
以下のようになります。

app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  def index
  end
  def new
    @message = Message.new
  end
  def create
    @message = Message.new({message: params[:message][:message], user_id: current_user.id})
    @message.save
  end
end

current_userはgemのdeviseが用意しているものですね。newの引数はハッシュ形式ですが、{}を省略して@message = Message.new(message: params[:message][:message], user_id: current_user.id)でも構いません。
これで一度投稿してみましょう。
goodafternoon.png
問題なさそうですね。投稿はうまくいくけれどそのままの画面で止まってしまうので、createアクションの中の最後にredirect_to root_pathを加えます。

さてこれで一応できましたが、一般的にはストロングパラメータというものを使ってセキュリティを高めるようです。
以下に直します。

app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  def index
  end
  def new
    @message = Message.new
  end
  def create
    @message = Message.new(message_params)
    @message.save
    redirect_to root_path
  end
  private
  def message_params
    params.require(:message).permit(:message).merge(user_id: current_user.id)
  end
end

requireとpermitはActionController::Parametersクラスのメソッドで、mergeはHashクラスのメソッドです。permitでparamsから値をとり、他の情報をデータベースに保存するリストに加える場合はmergeを使うようですね。
private以下ではアクションでないメソッドを定義します。その他の詳しい説明は省きます。

あと、ログインしていない人がlocalhost:3000/messages/newをブラウザに直接入力して投稿することは想定していないですし、current_user.idの値がなくておかしなことになるので、ログインしていない人は投稿画面に入れずにroot画面に遷移するようにします。
コントローラのclass内の最後に

app/controllers/messages_controller.rb
  def to_root
    redirect_to root_path unless user_signed_in?
  end

これを置き、最初に

app/controllers/messages_controller.rb
  before_action :to_root, except: [:index]

これを置きます。詳しい説明は省きます。
これでコントローラの完成品にたどり着きました。

ここら辺でTOP画面の条件分岐のログイン中の側に投稿画面のリンクをのせます。PrefixやURIを調べるのはrails routesでしたね。

app/views/messages/index.html.erb
<% if user_signed_in? %>
  <%= current_user.email %>
  <%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
  <%= link_to '投稿', new_message_path %>
<% else %>
  <%= link_to '新規登録', new_user_registration_path %>
  <%= link_to 'ログイン', new_user_session_path %>
<% end %>

ここで、ログイン状態で投稿できるてTOP画面に戻ることと、ログアウト状態でlocalhost:3000/messages/newを打ってもTOP画面に遷移することをブラウザで確認しましょう。

この状態で、メッセージを投稿する機能が完成しました。
実際に投稿してデータベースで結果を確認してみましょう。大丈夫ですね。

⑥TOP画面にメッセージ一覧を表示する

TOP画面に、メッセージとそれと一緒に投稿された画像全てとそれらの投稿者のEmailを一覧で表示します。これができたら完成とします。

以下を追記します。

app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  〜省略〜
  def index
    @messages = Message.all
  end
  〜省略〜
end
app/views/messages/index.html.erb
<% if user_signed_in? %>
  <%= current_user.email %>
  <%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
  <%= link_to '投稿', new_message_path %>
  <% @messages.each do |m| %>
    <div><span style="color: red;"><%= m.user.email %></span><%= m.message %></div>
  <% end %>
<% else %>
  <%= link_to '新規登録', new_user_registration_path %>
  <%= link_to 'ログイン', new_user_session_path %>
<% end %>

まずメッセージと投稿者のEmailを表示してみました。今回はメッセージはログインした人のみに見れるようにしました。
簡単ですね。詳細は省きます。

以上で完成です。

この続きにあたる、『メッセージと複数画像の投稿』ができる最低限のRailsアプリを丁寧に作るもよろしければどうぞ。

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