- 投稿日:2020-03-14T22:45:26+09:00
【Ruby】Unicorn で動かす一番小さいサンプルメモ
メモ
- ほとんど参考サイトの写経
- ワーカープロセスを2つ動かす
事前準備
Gemfilesource '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.rbclass SimpleApp def call(env) [ 200, { "Content-Type" => "text/html" }, ["contents"], ] end endconfig.ru# coding: utf-8 require_relative 'simple_app.rb' run SimpleApp.newunicorn.confworker_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
- 投稿日:2020-03-14T22:00:09+09:00
[Rails] DBにデータが登録されない時の対処方法
はじめに
Rails5.0での投稿機能実装ではまったのでメモ
環境
macOS Catalina
Ruby 2.5.1
Rails 5.0.7.2
PostgreSQL困り
form_forでDBにデータを登録しようと思ったが、エラー出てないのにデータ保存されていなかった
対処した方法
- GUIアプリで確認
- 保存されない原因を確認①(pry-rails)
- ハッシュの二重構造を解消
- 保存されない原因を確認②(メソッドの後ろに"!")
- バリデーションによるエラーを解消
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をインストール
Gemfilegroup :development, :test do gem 'pry-rails' endterminal$ bundle install
サーバー再起動(Ctrl + C、rails s)
データを保存するメソッドに binding.pry
post_controller.rbdef create binding.pry #データを保存するメソッドの中に Post.create(post_params) end private def post_params params.permit(:content, :image) end
- もう一度投稿してみる
- 投稿画面は読み込み中になるので、ターミナルでparamsを確認
terminal3: 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.rbdef create Post.create(post_params) #binding.pry は削除 end private def post_params params.require(:post).permit(:content, :image) #requireメソッドを使って二重構造となっているハッシュを取得 end
- もう一度投稿する
- Posticoで保存されているか確認 ⇒ 保存されていれば完了!
4. 保存されない原因を確認②(メソッドの後ろに"!")
- 保存されない場合は別の方法で原因を確認
- 保存するメソッドの後ろに!をつける
post_controller.rbdef create Post.create!(post_params) #binding.pry は削除 #createの後ろに"!" end⇒アソシエーションが組まれている際に、該当の外部キーが入っておらず、バリデーションで弾かれている
5. バリデーションによるエラーを解消
- optional: trueを記載
post.rbclass 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
- 投稿日:2020-03-14T21:14:25+09:00
Rubyで手を動かしてやったことまとめ
- 投稿日:2020-03-14T21:12:48+09:00
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%>
- 投稿日:2020-03-14T20:31:49+09:00
Alfred Workflowsで入力文字列からリストを作るRubyスクリプトの雛形
Alfred Workflowsは便利なんだけど、いざ自分で作ろうとしたとき「動的にリストを作って表示したい」と思った最初の一歩だけでもちょっとめんどくさかったので雛形を作りました。
Alfred Workflowsって何? という人はこちらの記事をどうぞ。
https://qiita.com/jackchuka/items/ccd3f66f6dd00481b98b#workflows実現したいこと
ヘルパーライブラリとしてはPython製の https://www.deanishe.net/alfred-workflow/ とかが見つかります。が、そこまで大層なものは別に欲しくない。
とりあえず改造しやすい最小限のコードで、以下の画像のような感じの入力と出力が欲しい。
あとリスト項目を選択したら、なんかクリップボードに項目がコピーされると嬉しい。コード
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>"使い方
Script Filter
の言語をRubyにしてベタ書きで書いてもいいし、あるいはScript Filter
で bashとかを選んでruby hoge.rb "{query}"
とか書いてもいい。以上です。
- 投稿日:2020-03-14T20:11:24+09:00
[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しようとするとエラリます。
解説
どうやらpresenceは、内部的に
blank?
メソッドを呼び出しているぽいです。
公式railsのここを見てます。間違ってたらごめんなさい。で、
blank?
についてですが、Railsガイドによると、falseはblankと見なされると。なので、falseだと
blank?
で引っかかって、エラっていたわけですね。解決
prsence: true
の代わりにinclusion: { in: [true, false] }
を使えば期待通りの挙動になりますまとめ
trueだと通って、falseだと通らない時、validationを確認する。
presence
が使われていたら、inclusion: { in: [true, false] }
を代わりに試してみる以上です。
読んでいただき、ありがとうございます。
- 投稿日:2020-03-14T19:05:58+09:00
【Ruby】配列とは?
配列とは?
・配列とは複数の値をまとめて管理したいときに使用
・配列は「箱が順番に並んだグループ」配列の作成方法
Rubyにおいて配列を作成する場合は括弧( [] )を使用。
また変数を、複数用意する際は、括弧の中に値をカンマ区切りで記述していく。■使用例
array = [“犬”, “ネコ”, “ハリネズミ”]
- 投稿日:2020-03-14T19:05:26+09:00
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.erbHTML: <form action="/posts" method="post"> </form> form_tag: <%= form_tag('/posts', method: :post) do %> <% end %>form_tagのdo〜endは〜の部分がフォームであることを示しています。
記載を変換する際は、<%= %>(erbタグ)やdoを忘れないように気をつけましょう。
- 投稿日:2020-03-14T18:42:42+09:00
postgreSQLのインストールとrails newをするまでの流れ
herokuでのディプロイのために、推奨とされるpostgre SQLについて調べましたが結構手間取ったので、まとめました。
(herokuは推奨がpostgre SQLで、これまで使用してきた、mysqlは別途設定が必要かつエラー出やすかったりするらしいので、rails new 時点から postgreSQL でアプリ作りました。)※完璧ではないと思うので何かツッコミあればお願いします。
以下ターミナルでのコマンド
postgreSQLのインストール
postgreSQLの有無の確認
~ % psql --versionpostgreSQLのインストール
*下記コマンドはhomebrewインストールが前提
% brew install postgresqlpostgreSQLのバージョン確認
% psql -V psql (PostgreSQL) 11.5postgreSQLのインストール場所の確認
% which psql /usr/local/bin/psqlpostgreSQLの起動
% 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.plistrailaアプリの作成
% rails _5.2.3_ new <appname> -d postgresqlデータベース作成
<appname> % rake db:create以上です。
なお、postgreSQLには、
中間テーブルを作らず、配列で形式で多対多のテーブルを作ったり、位置情報形式の指定など色々便利な機能があるので、postgreSQLは便利。
- 投稿日:2020-03-14T18:42:42+09:00
postgres sql のインストールとrails newをするまでの流れ
herokuでのディプロイのために、推奨とされるpostgressqlについて調べましたが結構手間取ったので、まとめました。
(herokuは推奨がpostgressqlで、これまで使用してきた、mysqlは別途設定が必要かつエラー出やすかったりするらしいので、rails new 時点から postgresslql でアプリ作りました。)※完璧ではないと思うので何かツッコミあればお願いします。
以下ターミナルでのコマンド
postgres sqlのインストール
postgres sqlの有無の確認
~ % psql --versionpostgres sqlのインストール
*下記コマンドはhomebrewインストールが前提
% brew install postgresqlpostgres sqlのバージョン確認
% psql -V psql (PostgreSQL) 11.5postgres sqlのインストール場所の確認
% which psql /usr/local/bin/psqlpostgres 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.plistrailaアプリの作成
% rails _5.2.3_ new <appname> -d postgresqlデータベース作成
<appname> % rake db:create以上です。
なお、postgres sqlには、
中間テーブルを作らず、配列で形式で多対多のテーブルを作ったり、位置情報形式の指定など色々便利な機能があるので、pstgres sqlは便利。
- 投稿日:2020-03-14T18:35:49+09:00
マイクロポストにコメント機能をつける
はじめに
ご訪問いただきありがとうございます。初めての投稿ですが学んだことを備忘録的にまとめようと思い記事にしてみました。レベル的には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:textdb/migrate/_create_comments.rbclass 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:migrate2.モデルの関連付け、バリデーションの設定
作成されたモデルはこんな感じです。
app/models/comment.rbclass 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.rbclass User < ApplicationRecord has_many :microposts, dependent: :destroy has_many :comments, dependent: :destroyapp/models/micropost.rbclass Micropost < ApplicationRecord belongs_to :user has_many :comments, dependent: :destroyUserもMicropostも多数のコメントを持てるためhas_manyで1対多の関連付けを行います。dependent: :destroyでUser、Micropostが消えた際に関連するコメントも消えるようにしています。
3Comenntsコントローラーの作成、ルーディングの設定
続いてコントローラーを作成します。今回はcreateとdestroyアクションのみ使用します。
rails g controller commentsapp/controllers/comments_controller.rbclass CommentsController < ApplicationController def create end def destroy end endルーディングの設定。
config/routes.rbresources :microposts, only: [:new, :show, :create, :destroy] do resources :comments, only: [:create, :destroy] endcommetsは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#destroy4対応するビューの作成
今回はMicropostの詳細ページからコメントするような形をとります。
コメントを表示するのはMicroposts/:idになるのでまずはコントローラーにコメント表示とコメントフォーム用にインスタンス変数を作成しておきます。app/controllers/microposts_controller.rbdef 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.rbclass 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 endcreateアクション失敗後に再度同じページを表示するため、コメントを取得してます。(このやり方があっているかはわかりません。)コメントフォーム専用のページを設ければこの取得はいらないです。
コメントを作成する際に、ユーザーの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#destroyapp/contorollers/comments_contoroller.rbclass 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終わりに
初めての記事作成で至らぬ点ばかりだったと思います。
何か間違いや、認識違い等があればアドバイスいただければ幸いです。
それではここまで読んんでいただいきありがとうございました。
- 投稿日:2020-03-14T17:51:07+09:00
Mailer viewでヘルパーメソッドを使用する
Mailer viewでヘルパーを使いたい
郵便番号にハイフンをいれてくれるメソッド
application_helper.rbdef format_zipcode_include_hyphen(zipcode) zipcode.include?('-') ? zipcode : zipcode.insert(3,'-') endmailer viewでいきなり使用するとエラーになった。
application_mailer.rb
application_mailer.rbclass ApplicationMailer < ActionMailer::Base add_template_helper(ApplicationHelper) endと記述する
view
mailer.ftml.slimp 〒#{format_zipcode_include_hyphen(zipcode)}でうまくいきました。
- 投稿日:2020-03-14T17:16:49+09:00
盲点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メソッドでした。
- 投稿日:2020-03-14T17:11:46+09:00
『非同期でのメッセージ投稿』が理解できる最低限のRailsアプリを丁寧に作る(Ajax苦手の自分とお別れしよう)
この記事の基本的な方針
Ajaxはなんだか難しい!は、勘違いです。一歩一歩きちんと進んでいけば、普通のことだと思えてくるでしょう。
ここではAjax非同期通信を理解するためだけの簡易なアプリを一つ丁寧に作成して、Ajax学習の基礎を完了することを目的としています。この記事は、以下の「登録画面」「ログイン画面」「TOP画面」の3画面の簡単なアプリを元に拡張していきます
【TOP画面(ログイン前)】 【TOP画面(ログイン後)】
![]()
【登録画面】
【ログイン画面】
手を動かしながら読みたいようでしたら、以下でこの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.rbclass 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
します。
これを済ませたら、メッセージテーブルは完成です。一応ちゃんと出来ているかデータベースを見に行ってみましょう。
私はSequelProを使っていてこんな感じです。モデルにテーブル同士の関係を書きましょう。これが無くても投稿できなくはないのですが、投稿した内容を引き出して扱う上で便利なので今済ませてしまいましょう。以下を追記します。
app/models/message.rbbelongs_to :userapp/models/user.rbhas_many :messages②メッセージだけを投稿できるようにする
ルーティング、ビュー、コントローラを編集します。今回はindexにフォームを置くのでnewアクションはなしです。
config/routes.rbRails.application.routes.draw do devise_for :users root 'messages#index' resources :messages, only: [:index, :create] endapp/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
でも
が表示されるはずです。この時点ではこの投稿フォームただの飾りなので、データベースに保存できるよう中身を作ります。
コントローラは以下です。app/controllers/messages_controller.rbclass 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これで一度投稿してみましょう。
③投稿を表示
あとはTOP画面に投稿されたものを全て表示させます。以下を追記します。
app/controllers/messages_controller.rbclass MessagesController < ApplicationController 〜省略〜 def index @messages = Message.all @message = Message.new end 〜省略〜 endapp/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 %>投稿するときに画像の上の丸い矢印が一瞬
×
になって投稿が反映されると思いますが、これなしに反映されるのが非同期通信です。④JQueryを導入し、turbolinksを削除する
turbolinksを削除する理由は、JQueryの動きを阻害する可能性があるからです。(リロードすればjsが動作するのに、リンクで移動すると動作しない、等)
Gemfile
にgem '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/javascripts
にmessages.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属性をみればわかります。
ありました。このように参照される値なので、{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"); }); }) });これで一度投稿してみましょう。データベースを見れば投稿は成功しているのがみて取れます。
以下に同期・非同期の両方でのターミナルでの状態を掲載します。このような違いが出てますね。そしてなぜかコントローラの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.rbrespond_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 %>まとめ
本当に最低限です。
これを、整えて十分な状態にするための続きをまた書きます。
フォローしてお待ち下さい。
- 投稿日:2020-03-14T16:08:50+09:00
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() { }); }; }); })はい、もーなんか既に分かる人には分かりますね。
原因
原因はこれでした。
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)
今度のターミナルエラーはこれ。
Started GET "/categories?id=1" for ::1 at 2020-03-14 15:36:36 +0900
ActionController::RoutingError (No route matches [GET] "/categories"):原因
config/routes.rbRails.application.routes.draw do root "items#new" resources :items ,only: [:index,:new,:create] endただのroutingの設定忘れでした。
config/routes.rbRails.application.routes.draw do root "items#new" resources :items ,only: [:index,:new,:create] resources :categories ,only: :index end修正します。
第三の404 (Not Found)
ターミナルエラーはこれ。
Started GET "/categories?id=1" for ::1 at 2020-03-14 15:45:21 +0900
ActionController::RoutingError (uninitialized constant CategoriesController):原因
categories_controller.rbが無い・・・だと?
ちゃんとあるじゃ・・・ある・・・あ?
/controllers/category_controller.rbclass 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.rbclass CategoriesController < ApplicationController def index @categories = Category.where(ancestry: params[:id]) respond_to do |format| format.json end end end結果
今回の教訓
①エラーが出たらターミナルをよく見よう。
②routingはちゃんとやっておこう。
③controllerは複数形で作ろう。今回は以上です。
- 投稿日:2020-03-14T15:53:08+09:00
営業マンが独学で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アプリを作ることができる
・スクールに通わない道もある面接にお呼びください
- 投稿日:2020-03-14T15:53:08+09:00
営業マンが独学で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アプリを作ることができる
・スクールに通わない道もある面接にお呼びください
- 投稿日:2020-03-14T15:48:54+09:00
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 nodebrew3.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 successfully4.インストールされたnodeを有効化
$ nodebrew ls =>v13.11.0 current: none
current: none
となっているので、先程インストールしたバージョンを有効化する。$ nodebrew use v13.11.0 $nodebrew ls =>v13.11.0 current: v13.11.05.環境パスを通す
次のパスで、bashかzshどちらに記述すべきか調べる。
echo $SHELL =>/bin/zsh上記のようにzshの場合は、下記の記述でファイル内に追加する。
$export PATH=$HOME/.nodebrew/current/bin:$PATH #一度読み込み直す $source ~/.zshrc #node確認 $ node -v =>v13.11.0
- 投稿日:2020-03-14T15:38:01+09:00
古い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を迎えている物をインストールしようと試みること自体が良くない話なのだが、止むに止まれぬ事情で必要になる人もいると思ったので記録を残しておくことにしたのであった。
- 投稿日:2020-03-14T15:05:11+09:00
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"インタープリタ系の言語は楽に実行環境がそろえられて、簡単に実行できることが特徴のようです。JavaScriptもそうですね。
開発環境
Rubyのコードを書くのにサクラエディタ等の普通のテキストエディタでもいいのですが、便利なIDE(統合開発環境)があるのであれば、積極的に使用していこうと思いました。
ネットで検索すると色々とあるようですが、VSCodeを使用しようと思います。
Microsoft製品好きですし・・・。VSCode
ダウンロードインストール後、設定
以下を参考にさせていただきました。
VSCodeでRubyを気軽に実行する環境を作る。あと、インテリセンスもほしい!
以下を参考にさせていただきました。
https://kic-yuuki.hatenablog.com/entry/2019/02/02/160659Rubyの基本と文字列
・ソースコード中のコメントは
#
で実現演習
インストールしたVSCodeの方で実行していこうかと思います。
数値と足し算・引き算・様々な計算
・ほかの言語と一緒・・・
足し算 +
引き算 -
乗算 *
除算 /文字列の連結
・他言語と同様
+
変数の基本
・他言語と同様
=
で変数に代入変数を使ってみよう
・他の言語でやっていたことと同様
変数と文字列の結合val = "test" puts "Progate" + val変数の役割と注意点
・基本的な事。省略。
変数の更新(1)
・基本的な事
上書きできる。name = "god" puts name name = "dog" puts name変数の更新(2)
・他の言語でもあるように
+=
と-=
変数展開
・ダブルクオーテーションで囲った場合
name = "god" puts "名前は#{name}です。" puts '名前は#{name}です'if文
・他の言語とおおむね同様の仕様
真偽値・比較演算子(1)
・他の言語と同様の為、特筆無し
比較演算子(2)
・
==
と!=
else
・他の言語と同様の使い方の為、特筆無し
elsif
・else ifではなく、elsif
個人的に誤りの元・・・。統一してほしい。条件を組み合わせよう・総合課題
・他言語と同様
かつ&&
または||
クリアした
総括
本当に基礎的な事しかやらなくて、経験者からしてみるとあまり新たに吸収できることはありませんでした。
初心者編という位置づけだから仕方ないか・・・次回はRuby on RailsのIをやっていこうと思います。
- 投稿日:2020-03-14T15:04:18+09:00
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.rbputs 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に共有できればと思います。
ご覧いただきありがとうございました。
- 投稿日:2020-03-14T15:04:18+09:00
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.rbputs 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に共有できればと思います。
ご覧いただきありがとうございました。
- 投稿日:2020-03-14T14:17:27+09:00
Active Record 単体で fixture を使う
これは Ruby not on Rails の記事です。
non-RailsでActive Recordを使う方法は検索するといくつか出てきますが、fixtureについては出てこないみたいなので書きます。
Active Recordを単体で使う方法は参考文献に挙げたのでそちらを参照してください。ディレクトリ構成やファイル配置などはいろいろやり方があると思いますので適宜読み替えてください。
fixtureはテストで使うのでテストフレームワークが問題になりますが、わたしはRSpecを使っています。とはいえ必要なことはどのフレームワークでもあまり変わらないはずです。
Active Recordのバージョンは6.0.2.1でやっています。
テスト
- テストのクラスで
ActiveRecord::TestFixtures
をinclude
する。
- minitestなら
MiniTest::Test
のサブクラス、RSpecならexample groupのコンテキスト(describe
やcontext
の中でit
の外)でinclude
するということです。- テストのクラスで
self.fixture_path=
を設定する。
use_transactional_tests
やuse_instantiated_fixtures
などの設定が必要な場合は同様に設定できます。- テストのクラスで
fixtures :users
のようにして使うfixtureを指定する。
fixtures :all
ですべてのfixtureを指定したのと同じになります。- (minitest以外では)lifecycle hookでテストの実行前に
setup_fixtures
が、実行後にteardown_fixtures
が実行されるようにする。
- minitestでは
ActiveRecord::TestFixtures
が面倒を見てくれる(before_setup
とafter_teardown
に追加してくれる)ので大丈夫。- RSpecではそれぞれ
before
とafter
で呼ぶ(before(:context)
でfixtureを使うには工夫が必要そうです)。ActiveSupport::TestCase
で定義されているmethod_name
メソッドが呼ばれるので定義しておく必要がある。(Railsmaster
では修正済み)
- minitestを使っているなら
ActiveSupport::TestCase
はMiniTest::Test
の薄いラッパなのでこちらを使ってしまうのもありかもしれません。どうせActive RecordはActive Supportに依存しているわけですし。- RSpecを使っている場合は
rspec-rails
のRSpec::Rails::FixtureSupport
をinclude
するとこのあたりの面倒を見てくれそうな気がしますが、Rails
がいない場合はそのままでは動かなさそうです。まとめると次のような感じになります。
user_test.rbrequire '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 enduser_spec.rbrequire '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タスクたちを読み込んでやります。Rakefilerequire '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_to
associationを認識できずに落ちます(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
のトップレベルでもいいと思います。参考文献
- 投稿日:2020-03-14T14:06:29+09:00
『2ページ遷移して会員登録』できる最低限のRailsアプリを丁寧に作る(deviseをウィザード形式に拡張)
この記事の基本的な方針
deviseで実装したログイン機能は特にカスタマイズしなければ、1ページでEmailとパスワードの2項目を入力して登録完了です。
これを2ページに拡張し、1ページ目にEmailとパスワードに加えニックネームを、2ページ目でケータイ番号の入力を求めます。この記事は、以下の「登録画面」「ログイン画面」「TOP画面」の3画面の簡単なアプリを元に拡張していきます。
【TOP画面(ログイン前)】 【TOP画面(ログイン後)】
![]()
【登録画面】
【ログイン画面】
手を動かしながら読みたいようでしたら、以下でこの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ページ目が新たに作られます。
想定する読み手
既に一度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.rbclass 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.rbclass 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.rbclass 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.rb
にvalidates :cellphone ,presence: true
を加えます。バリデーションです。これは簡単ですね。
続いて、アソシエーションです。cellphaneモデルとuserモデルを同時に見てください。app/models/cellphone.rbclass Cellphone < ApplicationRecord validates :cellphone ,presence: true belongs_to :user, optional: true endapp/models/user.rbclass 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?
OutputUser Exists (0.8ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'aaa@aaa' LIMIT 1 ↳ (pry):3 => truetrueって出ました。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.rbRails.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これで完成です。
まだケータイ番号用に桁数を制限などはしていません。余裕があったら挑戦してみてください。
- 投稿日:2020-03-14T10:48:28+09:00
【Rails】 関連モデル先の条件で検索する方法(内部結合)
- 投稿日:2020-03-14T09:44:10+09:00
お前らの感情でダウ平均株価は動いている
なんて大それたタイトルにしましたが、この記事の目的は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センチメントスコアとダウの前日比を比較したグラフがこちらです。
結構相関してそうに見える...!!!
これは割とちゃんとセンチメントの指標となりうるのでは?
相場のセンチメント分析ツールとして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] endCOTOHA APIを使ってみる
ここからは、今回の実装から一部抜粋してCOTOHA APIを使ってみようというコーナーです。
この先もRubyで話を進めていきます。
またKENTAさん(@poly_soft)等のインフルエンサーの方が未経験からWeb系企業への転職にRubyをおすすめしているおかげか、Twitter上でRubyを勉強している未経験の方をたくさん見かけます。
そういったRuby勉強し始めた方や、自然言語処理に興味出てきたけどPython書けませんって方に特に参考にしてもらえればと思います。
Pythonじゃなくたって興味さえあればデータ分析してみたっていいじゃない!自然言語処理やってみたっていいじゃない!おもしろそうなんだもの!!!!COTOHA APIの登録
まずはCOTOHA APIに登録していきます。
COTOHA APIのページにアクセスし、「今すぐ無料登録」を押します。
※右上の無料登録が金色になった状態(最上部では白色)で無料登録を押しても同じページにリダイレクトされてしまうので注意してください。
メールが届くので、記載されているURLにアクセスすると、情報入力画面に遷移しますので、それぞれ入力していってください。
特に記入で困る項目はないと思います。
サインアップを押して、確認を終えてから画面遷移するまで少し時間が空きますが、待っていれば自動で遷移します。
焦らず待ちましょう。
サインアップが完了すると、再びメールが届きます。
メールに記載されているURLにアクセスすると、ログイン画面に遷移します。
そこでログイン情報を入力してログインを押します。
ログインすると、アカウント画面が表示されます。
これで晴れて、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も同じ画面に記載してあります。
それでは、実装です。# 変数 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一覧ページから飛べるリファレンスに記載してあります。
ここで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ヶ月くらいしか経っておらず、私の実装自体も改善点だらけだと思いますので、そちらもコメントでご指摘いただけたらと思います。
- 投稿日:2020-03-14T08:19:12+09:00
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 ルーティングにワイルドカードセグメントというものがあることを初めて知りました。
ただ、ワイルドカードで渡すとどんな文字列も渡せるようになるため、セキュリティを考慮してある程度のパラメータチェックは入れたほうが良いのかなと思います。
参考
- 投稿日:2020-03-14T02:27:31+09:00
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 itemsasesst/javascripts/application.jsrails-ujsより上段にjqueryを追加 //= require jquery //= require rails-ujsdb/migrate/202***********_create_categories.rbclass 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 enddb/migrate/202***********_create_items.rbclass CreateItems < ActiveRecord::Migration[5.2] def change create_table :items do |t| t.references :category, null: false, foreign_key: true t.timestamps end end endmodels/category.rbclass Category < ApplicationRecord has_many :items has_ancestry endmodels/item.rbclass Item < ApplicationRecord belongs_to :category endcontrollers/items_controller.rbclass 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 endconfig/routes.rbRails.application.routes.draw do root "items#new" resources :items ,only: [:index,:new,:create] endviews/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.rbary_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:seedassets/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.rbdef index @categories = Category.where(ancestry: params[:id]) respond_to do |format| format.json end end
- routeの記述
config/routes.rbRails.application.routes.draw do root "items#new" resources :items ,only: [:index,:new,:create] resources :categories ,only: :index end
- json.jbulderの作成・記述
views/categories/index.json.jbuilderjson.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() { }); }); })完成?
完成!
と言いたいところですが、このままだと親や子を変更する度に入力欄が無限に増殖してしまいます。
- 条件式を追加
- 「"---"を選択した時」 → 下位の要素を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); }; }) ##後略##というわけで、
完成です!注意事項
・エラー処理は何もしていないので、保存ができない場合が多々ありますが仕様です。
(孫まで入力しないと、form_withで送信するパラメータを拾えないのでレコード登録できません。)以上です。
参考にさせていただいた記事
- 投稿日:2020-03-14T01:21:26+09:00
『メッセージを投稿』できる最低限のRailsアプリを丁寧に作る(これで初心者完全卒業!)
この記事の基本的な方針
メッセージを投稿して一覧表示するだけの簡単なアプリを作ります。
完成するのは以下のたった4画面のアプリです。【TOP画面(ログイン前)】 【TOP画面(ログイン後)】
![]()
【登録画面】
【ログイン画面】
【投稿画面】
これを丁寧に作って、初心者を卒業しましょう。想定する読み手
既に一度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
になってしまいます。)そして
Terminalrails sでサーバーを立ち上げて、ブラウザで
localhost:3000/
にアクセスすると
これが表示されます。続いて、コントローラを作ります。
Terminal$ rails g controller <名前>
名前は自由ですが一番関連のある(データベースの)テーブル名の複数形にするのが一般的です。
今回はTOP画面にmessage一覧を表示するので、Terminal$ rails g controller messages index
今回
index
を加えていますが、このように名前の後にアクションを並べれば、そのアクション用のhtml.erbファイルが作られ、ルーティングとコントローラファイルに必要なことが追記されます。アクションはもちろん複数並べてよく、コンマ無しで隙間だけ開けて並べていきます。ここでエディタを起動します。
まずルーティングを設定をします。上でアクション名を並べていればすでに書かれています。
config/routes.rbget "messages/index"取り急ぎ、適当にビューを書きます。
app/views/messages/index.html.erb<div>こんにちは</div>ここでブラウザで
localhost:3000/コントローラ名/index
にアクセスすれば、「こんにちは」が見れるはずです。通常メインのindex画面をルート画面とするので
config/routes.rbroot "messages#index"を追記しておきます。
localhost:3000
でもアクセスできるようになります。②ログイン機能を作る
ログイン機能は自作することもできますが、deviseというgemを使うことが極めて一般的であるのでこれを今回は使用します。
deviseをインストールします。
Gemfilegem 'devise'こちらを追記し、ターミナルで
Terminal$ bundle installをし、そしてdeviseを使い始めるにはターミナルで
Terminalrails g devise:installをする必要があります。これでconfigディレクトリにファイルができたので、サーバの再起動の必要があります。
さらに、Terminalrails 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
ですね。結果は以下です。
ログインの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画面(ログイン後)】
![]()
【登録画面】
【ログイン画面】
ここまでが難しい場合は過去記事に戻ってみてください。
次に行きます。④メッセージテーブルを作る
マイグレーションファイル
とモデル
を作るため、Terminal$ rails g model message
を打ちます。
マイグレーションファイル
はデータベースのテーブルを作るためのファイルで、モデル
はデータベースからデータを出し入れするときの設定やルールを書くファイルです。もう大丈夫ですね?一般的にモデル名は単数形でコントローラ名は複数形です。
ちなみにこれらのファイルは
Active Record
というライブラリを継承していて、そのおかげでデータベースに関することをRubyで記述できます。本来はMySQLデータベースとやりとりするにはSQLという言語で書かなければならないので、Active Record
が翻訳をしてくれているということになります。続いてマイグレーションファイルを書いて、メッセージテーブルを完成させます。
今回はメッセージの内容とそれを誰が投稿したかの情報を保存できるようにしようと思うので、t.string :message
とt.references :user, foreign_key: true
を追記しdb/migrate/2020xxxxxxxxxx_create_messages.rbclass 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
していないからですね。これを済ませたら、メッセージテーブルは完成です。
一応ちゃんと出来ているかデータベースを見に行ってみましょう。
私はSequelProを使っていてこんな感じです。最後にモデルにテーブル同士の関係を書きましょう。これが無くても投稿できなくはないのですが、投稿した内容を引き出して扱う上で便利なので今済ませてしまいましょう。以下を追記します。
app/models/message.rbbelongs_to :userapp/models/user.rbhas_many :messages⑤メッセージを投稿できるようにする
必要な編集は
ルーティング
、ビュー
、コントローラ
です。
まずルーティング。config/routes.rbRails.application.routes.draw do devise_for :users root 'messages#index' resources :messages, only: [:index, :new, :create] endresourcesを用いた記法に変えました。ここは大丈夫ですね?
次はビュー。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_with(model: @message, local: true) do |f| %>
はHTMLのform要素に対応しています。画面上に表示はされません。最後はコントローラです。少し詳しく行きます。
app/controllers/messages_controller.rbclass 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.rbclass MessagesController < ApplicationController def index end def new @message = Message.new end def create binding.pry @message = Message.new(??) @message.save end endここまで書きます。
??
の値を知りたいですね。
おっとbinding.pry
ってなんですか?デバック用のコードです。ここでは詳しい説明は省きます。これを使うためにGemfilegem '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.rbclass 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)
でも構いません。
これで一度投稿してみましょう。
問題なさそうですね。投稿はうまくいくけれどそのままの画面で止まってしまうので、createアクションの中の最後にredirect_to root_path
を加えます。さてこれで一応できましたが、一般的にはストロングパラメータというものを使ってセキュリティを高めるようです。
以下に直します。app/controllers/messages_controller.rbclass 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 endrequireとpermitはActionController::Parametersクラスのメソッドで、mergeはHashクラスのメソッドです。permitでparamsから値をとり、他の情報をデータベースに保存するリストに加える場合はmergeを使うようですね。
private以下ではアクションでないメソッドを定義します。その他の詳しい説明は省きます。あと、ログインしていない人が
localhost:3000/messages/new
をブラウザに直接入力して投稿することは想定していないですし、current_user.id
の値がなくておかしなことになるので、ログインしていない人は投稿画面に入れずにroot画面に遷移するようにします。
コントローラのclass内の最後にapp/controllers/messages_controller.rbdef to_root redirect_to root_path unless user_signed_in? endこれを置き、最初に
app/controllers/messages_controller.rbbefore_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.rbclass MessagesController < ApplicationController 〜省略〜 def index @messages = Message.all end 〜省略〜 endapp/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アプリを丁寧に作るもよろしければどうぞ。