20210607のRubyに関する記事は21件です。

【Rails】Failed to destroy the recordというエラー(メモ)

エラー文詳細 (以下)コンソールでの表示 Completed 500 Internal Server Error in 29ms (ActiveRecord: 11.2ms | Allocations: 10309) ActiveRecord::RecordNotDestroyed (Failed to destroy the record): Failed to destroy the recordというエラーが出力され、指定したものが削除できない。 task.destroy!までは動いているので、他のところに原因があるか? 解決方法 原因 task.rbの以下のコードが原因。 belongs_to :board, dependent: :destroy BoardとTaskは1対多で関連づけているため、 task.rb側においては、dependent: :destroyの記述は必要ない (Board側においては必要) 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsチュートリアル(第6版) 第13章 アカウントの有効化

第13章 短いメッセージを投稿できるようにするためのリソース「マイクロポスト」機能を追加していく。 手順として ・Micropostデータモデル作成 ・Userモデルとhas_manyおよびbelong_toメソッドで関連付け ・結果を処理し表示するための必要なフォームを作る Micropostモデル 今回のマイクロポストモデルはテストされて、デフォルトの順序を持つ。また、親であるユーザーが破棄されたら自動的に破棄されるものになる。 トピックブランチを作成しておく。 $ git checkout -b user-microposts 基本的なモデル Micropostモデルは、マイクロポストの内容を保存するcontent属性と、特定のユーザーとマイクロポストを関連付けるためのuser_id属性を持つ。 参照:railsチュートリアル String型とText型 ・String型は、255文字まで格納できる ・Text型は、それ以上。また、Text用のテキストエリアを使うので、より自然な投稿フォームになる。将来のことを考えるとこっち。さらに、パフォーマンスでは差は出ないとのこと。 Micropostモデルを生成する $ rails generate model Micropost content:text user:references これでApplicationRecordを継承したモデルが作られた。 app/models/micropost.rb class Micropost < ApplicationRecord belongs_to :user end user:referencesという引数を含めていたため、belongs_toというコードが追加されている。 このreferences型を利用している点が、Userモデルとの最大の違いになる。 これを利用すると、自動的にインデックスと外部キー参照付きのuser_idカラムが追加されて、UserとMicropostを関連付ける下準備をしてくれる。 Userモデルと同様にcreated_atとupdated_atというカラムが追加されてる。 インデックスが付与されたMicropostのマイグレーション db/migrate/[timestamp]_create_microposts.rb class CreateMicroposts < ActiveRecord::Migration[6.0] def change create_table :microposts do |t| t.text :content t.references :user, foreign_key: true t.timestamps end add_index :microposts, [:user_id, :created_at] end end 上のコードでは、user_idとcreated_atカラムにインデックスが付与されている。これで、user_idに関連付けられたすべてのマイクロポストを作成時刻の逆順で取り出しやすくなる。 また、user_idとcreated_atの両方を1つの配列に含めている。これで、Active Recordは両方のキーを同時に扱う複合キーインデックス(Multiple Key Index)を作成する。 マイグレートする $ rails db:migrate Micropostのバリデーション Micropostモデル単体を動くようにする。 Micropostの初期テストは手順 ・setupでfixtureのサンプルユーザーと紐だ付けた新しいマイクロポストを作成 ・次に、作成したマイクロポストが有効かどうかのチェック ・最後に、あらゆるマイクロポストはユーザーのidを持つべきなので、user_idの存在性のバリデーションに対するテストを追加 test/models/micropost_test.rb require 'test_helper' class MicropostTest < ActiveSupport::TestCase def setup @user = users(:michael) # このコードは慣習的に正しくない @micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id) end test "should be valid" do assert @micropost.valid? end test "user id should be present" do @micropost.user_id = nil assert_not @micropost.valid? end end 1つ目のテストは、現実に即しているかどうかをテスト(reality check) 2つ目のテストは、user_idが存在しているかどうか(nilではないか) user_idに対する存在性のバリデーション app/models/micropost.rb class Micropost < ApplicationRecord belongs_to :user validates :user_id, presence: true end 次にcontent属性に対するバリデーションの追加。 test/models/micropost_test.rb test "content should be present" do @micropost.content = " " assert_not @micropost.valid? end test "content should be at most 140 characters" do @micropost.content = "a" * 141 assert_not @micropost.valid? end content属性も存在性と、140文字より長くならないという制限を加える。 マイクロポストにバリデーションを追記する。 app/models/micropost.rb class Micropost < ApplicationRecord belongs_to :user validates :user_id, presence: true validates :content, presence: true, length: { maximum: 140 } end User/Micropostの関連付け 個々のモデル間で関連付けをしておく。 今回は、それぞれのマイクロポストは一人のユーザーと関連付けられ、それぞれのユーザーは潜在的に複数のマイクロポストと関連付けられている。 MicropostとそのUserはbelongs_toの関係性 参照:railsチュートリアル UserとそのMicropostはhas_manyの関係性 参照:railsチュートリアル このような関係を行うことで メソッド  用途 micropost.user  Micropostに紐付いたUserオブジェクトを返す  user.microposts  Userのマイクロポストの集合をかえす  user.microposts.create(arg) userに紐付いたマイクロポストを作成する  user.microposts.create!(arg) userに紐付いたマイクロポストを作成する(失敗時に例外を発生) user.microposts.build(arg) userに紐付いた新しいMicropostオブジェクトを返す user.microposts.find_by(id: 1) userに紐付いていて、idが1であるマイクロポストを検索する 上のメソッドが使えるようになる。 注意点として Micropost.create Micropost.create! Micropost.new ではなく user.microposts.create user.microposts.create! user.microposts.build となっている。 紐づいているユーザーを通してマイクロポストを作成することができる。新規のマイクロポストがこの方法で作成される場合、user_idは自動的に正しい値に設定される。 最初の書き方↓ @user = users(:michael) # このコードは慣習的に正しくない @micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id) 書き換えると↓ @user = users(:michael) @micropost = @user.microposts.build(content: "Lorem ipsum") buildメソッドはオブジェクトを返すがデータベースには反映されない。 一度関連付けを定義すれば、@micropost変数のuser_idには、関連するユーザーidが自動的に設定される。 UserモデルとMicropostモデルを紐づける。 Micropostは、belongs_to :user Userモデルは、has_many :micropostsと追加する app/models/user.rb class User < ApplicationRecord has_many :microposts . . . end setupメソッドを修正し、正しいテストにする。 test/models/micropost_test.rb def setup @user = users(:michael) @micropost = @user.microposts.build(content: "Lorem ipsum") end マイクロポストを改良する UserとMicropostを関連付けを改良する。 ユーザーのマイクロポストを特定の順序で取得できるようにしたり、マイクロポストをユーザーに依存させ、ユーザーの削除と同時にマイクロポストも自動的に削除されるようにする。 デフォルトスコープ user.micropostメソッドはデフォルト状態では読み出し順序に何も保証がない。 ブログやTwitterの慣習に従い、作成時間の逆順、最も新しいマイクロポストを最初に表示する。 これを実装するには、default scopeを使う。 このテストは、一見成功しているように思えるが、「アプリケーション側の実装が本当は間違っているのにテストが成功してしまう」トラップがある。 なので、テスト駆動開発で進める。 テスト内容 データベース上のマイクロポストが、fixture内のマイクロポスト(most_recet)と同じであるか検証する。 test/models/micropost_test.rb require 'test_helper' class MicropostTest < ActiveSupport::TestCase . . . test "order should be most recent first" do assert_equal microposts(:most_recent), Micropost.first end end fixtureでコメント、作成日、ユーザーへの関連付けを行う。 test/fixtures/microposts.yml orange: content: "I just ate an orange!" created_at: <%= 10.minutes.ago %> user: michael tau_manifesto: content: "Check out the @tauday site by @mhartl: https://tauday.com" created_at: <%= 3.years.ago %> user: michael cat_video: content: "Sad cats are sad: https://youtu.be/PKffm2uI4dk" created_at: <%= 2.hours.ago %> user: michael most_recent: content: "Writing a short test" created_at: <%= Time.zone.now %> user: michael fixtureファイル内では日付を更新可能となっている。 続いて、Railsのdefault_scopeメソッドを使う。 このメソッドは、データベースから要素を取得した時のデフォルトの順序を指定するメソッドになる。 特定の順序にしたい場合、default_scopeの引数にorderを与える。 例えば、created_atカラムの順にしたい場合は以下の通り。 order(:created_at) デフォルトの順序が昇順の為、小さい値から大きい値にソートされる。つまり最も古い投稿が最初に来る。 順序を逆にするには、生のSQLを引数に与える。 order('created_at DESC') DESCは、SQLの降順(descending)を指す。これで新しい投稿から古い投稿の順に並ぶ。 なんとRails4.0以降は、Rubyの文法でも書けるようになった。 order(created_at: :desc) default_scopeでマイクロポストを順序付ける app/models/micropost.rb class Micropost < ApplicationRecord belongs_to :user default_scope -> { order(created_at: :desc) } validates :user_id, presence: true validates :content, presence: true, length: { maximum: 140 } end 上のコードでは、ラムダ式という文法を使っている。 これは、Procやlambda(無名関数)と呼ばれるオブジェクトを作成する文法。 ->というラムダ式は、ブロックを引数に取り、Procオブジェクトを返す。 このオブジェクトは、callメソッドが呼ばれたとき、ブロック内の処理を評価する。 Dependent: destroy マイクロポストに第二の要素を追加する。 サイト管理者はユーザーを破棄する権限を持つため、ユーザーが破棄されたらユーザーのマイクロポストも同時に破棄されるべき。 has_manyメソッドにオプションを渡してあげればOK app/models/user.rb class User < ApplicationRecord has_many :microposts, dependent: :destroy . . . end dependent: :destroyというオプションを使うことで、ユーザーが削除されたら、そのユーザーに紐づいたマイクロポストも一緒に削除される。 持ち主の存在しないマイクロポストがデータベースに取り残される問題を防ぐ。 Userモデルを検証する。 テスト内容 ・ユーザーを作成する ・そのユーザーに紐づいたマイクロポストを作成する ・その後、ユーザーを削除してみて、マイクロポストが1つ減っているかどうか確認する test/models/user_test.rb require 'test_helper' class UserTest < ActiveSupport::TestCase def setup @user = User.new(name: "Example User", email: "user@example.com", password: "foobar", password_confirmation: "foobar") end . . . test "associated microposts should be destroyed" do @user.save @user.microposts.create!(content: "Lorem ipsum") assert_difference 'Micropost.count', -1 do @user.destroy end end end マイクロポストを表示する ユーザープロフィールにマイクロポストを表示させるため、最初に極めて神父rなERbテンプレートを作成する。 次に、サンプルデータ生成タスクにマイクロポストのサンプルを追加し、画面にサンプルデータが表示されるようにする。 マイクロポストの描画 ユーザーのプロフィール画面(show.html.erb)で、そのユーザーのマイクロポストを表示させたり、これまでに投稿した総数も表示させる。 Micropostのコントローラーとビューを作成するために、コントローラを生成する。 $ rails generate controller Microposts _micropost.html.erbパーシャルを使って、マイクrポストのコレクションを表示しようとすると以下のようになる。 <ol class="microposts"> <%= render @microposts %> </ol> ulタグではなく、順序付きリストのolタグを使っている。 マイクロポストが特定の順序に依存しているため。 対応するパーシャのコード↓ app/views/microposts/_micropost.html.erb <li id="micropost-<%= micropost.id %>"> <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %> <span class="user"><%= link_to micropost.user.name, micropost.user %></span> <span class="content"><%= micropost.content %></span> <span class="timestamp"> Posted <%= time_ago_in_words(micropost.created_at) %> ago. </span> </li> time_ago_in_wordsというヘルパーメソッドを使っている。これは、「〇分前に投稿」といった文字列を出力するもの。 <li id="micropost-<%= micropost.id %>"> これは、各マイクロポストにCSSのidを割り振っている。将来的に、各マイクロポストを操作したくなった時に役立つ。 ページ分割するため、will_paginateメソッドを使う。 <%= will_paginate @microposts %> 第10章のユーザー一覧画面のコードでは、<%= will_paginate %>という単純なコードだった。 実は、上のコードは引数なしで動作していた。will_paginateがUsersコントーラのコンテキストにおいて、@userインスタンス変数が存在していることを前提としてたから。このインスタンス変数は、ActiveRecord::Relationクラスのインスタンス。 今回の場合は、Usersコントローラのコンテキストからマイクロポストをページネーションしたいため(コンテキストが異なる)、明示的に@microposts変数をwill_paginateに渡す必要がある。 従って、そのようなインスタンス変数をUsersコントローラのshowアクションで定義しなければならない。 @micropostsインスタンス変数をshowアクションに追加する。 app/controllers/users_controller.rb class UsersController < ApplicationController . . . def show @user = User.find(params[:id]) @microposts = @user.microposts.paginate(page: params[:page]) end . . . end マイクロポストの関連付けを経由し、micropostテーブルに到達して、必要なマイクロポストのページを引き出している。 最後に、マイクロポストの投稿数を表示するが、これはcountメソッドでOK user.microposts.count 関連付けを通して、countメソッドを呼び出している。 なんとcountメソッドでは、データベース上のマイクロポストを全部読みだしてから結果の配列にlengthを呼ぶような、無駄な処理はしていない。 (そりゃぁそうでなければね) データーベースに代わりに計算してもらい、特定のuser_idに紐づいたマイクロポストの数をデータベースに問い合わせている。countメソッドよりもさらに高速なcounter cacheも使うことが可能。 プロフィール画面にマイクロポストを表示させる。 if @user.microposts.any?を使って、ユーザーのマイクロポストが1つもない場合には空のリストを表示させないようにしている。 app/views/users/show.html.erb <% provide(:title, @user.name) %> <div class="row"> <aside class="col-md-4"> <section class="user_info"> <h1> <%= gravatar_for @user %> <%= @user.name %> </h1> </section> </aside> <div class="col-md-8"> <% if @user.microposts.any? %> <h3>Microposts (<%= @user.microposts.count %>)</h3> <ol class="microposts"> <%= render @microposts %> </ol> <%= will_paginate @microposts %> <% end %> </div> </div> マイクロポストのサンプル すべてのユーザーにマイクロポストを追加すると時間が掛かるので、takeメソッドを使って最初の6人だけ追加する。 User.order(:created_at).take(6) orderメソッドを経由することで、最初の6人を明示的に呼び出すようにしている。 1ページの表示限界数(30)を越えさせるのに、それぞれ50個分のマイクロポストを追加する。 また、各投稿内容はFaker gemにLorem.sentenceというメソッドを使う。 db/seeds.rb # ユーザーの一部を対象にマイクロポストを生成する users = User.order(:created_at).take(6) 50.times do content = Faker::Lorem.sentence(word_count: 5) users.each { |user| user.microposts.create!(content: content) } end 開発環境のデータベースで生成 $ rails db:migrate:reset $ rails db:seed 参照:railsチュートリアル CSSを整えて、、 app/assets/stylesheets/custom.scss /* microposts */ .microposts { list-style: none; padding: 0; li { padding: 10px 0; border-top: 1px solid #e8e8e8; } .user { margin-top: 5em; padding-top: 0; } .content { display: block; margin-left: 60px; img { display: block; padding: 5px 0; } } .timestamp { color: $gray-light; display: block; margin-left: 60px; } .gravatar { float: left; margin-right: 10px; margin-top: 5px; } } aside { textarea { height: 100px; margin-bottom: 5px; } } span.image { margin-top: 10px; input { border: 0; } } プロフィール画面のマイクロポストをテスト プロフィール画面で表示されるマイクロポストに対して、統合テストを書く。 まずは、プロフィール画面用の統合テストを生成する。 $ rails generate integration_test users_profile マイクロポスト用のfixtureにいくつかデータを追加 test/fixtures/microposts.yml orange: content: "I just ate an orange!" created_at: <%= 10.minutes.ago %> user: michael tau_manifesto: content: "Check out the @tauday site by @mhartl: https://tauday.com" created_at: <%= 3.years.ago %> user: michael cat_video: content: "Sad cats are sad: https://youtu.be/PKffm2uI4dk" created_at: <%= 2.hours.ago %> user: michael most_recent: content: "Writing a short test" created_at: <%= Time.zone.now %> user: michael <% 30.times do |n| %> micropost_<%= n %>: content: <%= Faker::Lorem.sentence(word_count: 5) %> created_at: <%= 42.days.ago %> user: michael <% end %> テスト内容 プロフィール画面にアクセス後 ・ページタイトルとユーザー名 ・Gravatar ・マイクロポストの投稿数 ・ページ分割されたマイクロポスト という順番でテストする。 test/integration/users_profile_test.rb require 'test_helper' class UsersProfileTest < ActionDispatch::IntegrationTest include ApplicationHelper def setup @user = users(:michael) end test "profile display" do get user_path(@user) assert_template 'users/show' assert_select 'title', full_title(@user.name) assert_select 'h1', text: @user.name assert_select 'h1>img.gravatar' assert_match @user.microposts.count.to_s, response.body assert_select 'div.pagination' @user.microposts.paginate(page: 1).each do |micropost| assert_match micropost.content, response.body end end end 第12章と同様に、response.bodyを使っている。これには、そのページの完全なHTMLが含まれている。 従って、そのページのどこかしらにマイクロポストの投稿数があれば、以下のように探してマッチできる。 assert_match @user.microposts.count.to_s, response.body 上のはassert_selectよりも抽象的なメソッド。 特にassert_selectではどのHTMLタグを探すか指定しなければならない。 assert_matchメソッドはその必要がない。 さらに、assert_selectの引数ではネストした文法を使う。 assert_select 'h1>img.gravatar' h1タグの内側にあるgrabatarクラス月のimgタグがあるかどうかをチェックしてる。 マイクロポストを操作する データモデリングとマイクロポスト表示テンプレートが完成したので、続いてWeb経由でそれらを作成するためのインターフェースに取り掛かる。 やること ・ステータスフィード実装 ・ユーザーがマイクロポストをWeb経由で破棄できるようにする 従来のRails開発と子なる点が1つある。 Micropostリソースへのインターフェイスは、主にプロフィールページとHomeページのコントローラを経由し実行されるため、Micropostコント―羅にはnewやeditアクションは不要。 つまり、createやdestroyがあればOK Micropostsリソースは下記になる。 resources :microposts, only: [:create, :destroy] Micropostsリソースが提供するRESTfulルート HTTPリクエスト URL アクション 名前付きルート POST /microposts create microposts_path DELETE /microposts/1 destroy micropost_path(micropost) マイクロポストのアクセス制御 Micropostsリソース開発では、コントローラ内のアクセス制御から始める。 関連付けられたユーザーを通してマイクロポストにアクセスするので、createアクションやdestroyアクションを利用するユーザーはログイン済である必要がある。 ログイン済か調べるテストは、Usersコントローラ用のテストがそのまま使える。 正しいリクエストを各アクションに発行し、マイクロポスト数が変化してないかどうか、リダイレクトされるかどうかを確認する。 test/controllers/microposts_controller_test.rb require 'test_helper' class MicropostsControllerTest < ActionDispatch::IntegrationTest def setup @micropost = microposts(:orange) end test "should redirect create when not logged in" do assert_no_difference 'Micropost.count' do post microposts_path, params: { micropost: { content: "Lorem ipsum" } } end assert_redirected_to login_url end test "should redirect destroy when not logged in" do assert_no_difference 'Micropost.count' do delete micropost_path(@micropost) end assert_redirected_to login_url end end ここでリファクタリングの必要性あり。 第10章では、beforeフィルタのlogged_in_userメソッドを使って、ログインを要求した。第10章では、Usersコントローラ内にこのメソッドがあったので、beforeフィルターを指定した。 なので、Micropostsコントローラでも同様に必要になる。 なので、各コントローラーが継承するApplicationコントローラに、このメソッドを移す。 app/controllers/application_controller.rb class ApplicationController < ActionController::Base include SessionsHelper private # ユーザーのログインを確認する def logged_in_user unless logged_in? store_location flash[:danger] = "Please log in." redirect_to login_url end end end Usersコントローラからは、logged_in_userは削除しておこう。 これでMicropostsコントローラーからもlogged_in_userメソッドを呼び出せるようになった。 これで、createアクションやdestroyアクションに対するアクセス制限が、beforeフィルターで簡単に実装できるようになった。 app/controllers/microposts_controller.rb class MicropostsController < ApplicationController before_action :logged_in_user, only: [:create, :destroy] def create end def destroy end end マイクロポストを作成する 第7章でやった、HTTP POSTリクエストをUsersコントローラのcreateアクションに発行するHTMLフォームを作成することで、ユーザーのサインアップをやった。 今回もこれと似てる。違いとして、別のmicropost/neeページを使う代わりに、ホーム画面にフォームを置くという点。 この節での目標 ・ユーザーのログイン状態に応じて、ホーム画面の表示を変更する事 マイクロポストのcreateアクションを作る。 app/controllers/microposts_controller.rb class MicropostsController < ApplicationController before_action :logged_in_user, only: [:create, :destroy] def create @micropost = current_user.microposts.build(micropost_params) if @micropost.save flash[:success] = "Micropost created!" redirect_to root_url else render 'static_pages/home' end end def destroy end private def micropost_params params.require(:micropost).permit(:content) end end ユーザー用アクションと似てるが、違いは新しいマイクロポストをbuildするためにUser/Micropost関連付けを使っているというところ。 micropost_paramsでStrong Parmetersを使っていることで、マイクロポストのcotent属性だけがWeb経由で変更可能になっている。 マイクロポスト作成フォーム構築のために、サイト訪問者がログインしてるかどうかに応じ、異なるHTMLを提供するコード app/views/static_pages/home.html.erb <% if logged_in? %> <div class="row"> <aside class="col-md-4"> <section class="user_info"> <%= render 'shared/user_info' %> </section> <section class="micropost_form"> <%= render 'shared/micropost_form' %> </section> </aside> </div> <% else %> <div class="center jumbotron"> <h1>Welcome to the Sample App</h1> <h2> This is the home page for the <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a> sample application. </h2> <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %> </div> <%= link_to image_tag("rails.svg", alt: "Rails logo", width: "200px"), "https://rubyonrails.org/" %> <% end %> 上のコードを動かすためパーシャルを作る必要がある。 Homeページの新しいサイドバー app/views/shared/_user_info.html.erb <%= link_to gravatar_for(current_user, size: 50), current_user %> <h1><%= current_user.name %></h1> <span><%= link_to "view my profile", current_user %></span> <span><%= pluralize(current_user.microposts.count, "micropost") %></span> ユーザー情報に、そのユーザーが投稿したマイクロポスト総数が表示される。 pluralizeメソッドを使って、"1 micropost"や"2 microposts"とするよう調整してる。 マイクロポスト作成フォームを定義する。ユーザー登録フォームに似てる。 app/views/shared/_micropost_form.html.erb <%= form_with(model: @micropost, local: true) do |f| %> <%= render 'shared/error_messages', object: f.object %> <div class="field"> <%= f.text_area :content, placeholder: "Compose new micropost..." %> </div> <%= f.submit "Post", class: "btn btn-primary" %> <% end %> フォームを動かすため、2か所変更が必要。 1つは、関連付けを使って以下のように@micropostと定義すること @micropost = current_user.microposts.build app/controllers/static_pages_controller.rb class StaticPagesController < ApplicationController def home @micropost = current_user.microposts.build if logged_in? end current_userメソッドはユーザーがログインしている時しか使えないので、@micropost変数もログインしているときのみ定義されるようになる。 2つめは、エラーメッセージのパーシャルを再定義すること。 <%= render 'shared/error_messages', object: f.object %> 今回は@mircropost変数を使う。フォーム変数fをf.objectとし、関連付けられたオブジェクトにアクセスすることができる。 form_with(model: @user, local: true) do |f| 上のようにf.objectが@userとなる場合と form_with(model: @micropost, local: true) do |f| f.objectが@micropostになる場合がある。 パーシャルにオブジェクトを渡すため、値がオブジェクトで、キーがパーシャルでの変数名と同じハッシュを利用する。 object: f.objectはerror_messagesパーシャルの中でobjectという変数名を作成してくれるので、この変数名を使ってエラーメッセージを更新すれば良い。 app/views/shared/_error_messages.html.erb <% if object.errors.any? %> <div id="error_explanation"> <div class="alert alert-danger"> The form contains <%= pluralize(object.errors.count, "error") %>. </div> <ul> <% object.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> この時点でテストは失敗する。error_messagesパーシャルの他の出現場所がヒント。 このパーシャルは、他の場所で使われていたため、ユーザー登録、パスワード再設定、ユーザー編集のそれぞれのビューを更新する必要がある。 各ビューを更新した結果を下記に示す。 app/views/users/new.html.erb <% provide(:title, 'Sign up') %> <h1>Sign up</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_with(model: @user, local: true) do |f| %> <%= render 'shared/error_messages', object: f.object %> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %> </div> </div> app/views/users/edit.html.erb <% provide(:title, "Edit user") %> <h1>Update your profile</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_with(model: @user, local: true) do |f| %> <%= render 'shared/error_messages', object: f.object %> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit "Save changes", class: "btn btn-primary" %> <% end %> <div class="gravatar_edit"> <%= gravatar_for @user %> <a href="https://gravatar.com/emails">change</a> </div> </div> </div> app/views/password_resets/edit.html.erb <% provide(:title, 'Reset password') %> <h1>Reset password</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_with(model: @user, url: password_reset_path(params[:id]), local: true) do |f| %> <%= render 'shared/error_messages', object: f.object %> <%= hidden_field_tag :email, @user.email %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit "Update password", class: "btn btn-primary" %> <% end %> </div> </div> フィードの原型 マイクロポスト投稿フォームが動作するようになった。 しかし、現時点では投稿した内容をすぐにみる事ができない。なぜなら、Homeページにまだマイクロポストを表示する部分が実装されないから。 フォームが正しく動作してるかチェックする場合 正しいエントリー投稿→プロフィールページに移動→ポストを表示 になるが、これは面倒だ。 なので、ユーザー自身のポストを含むマイクロポストのフィードがないと不便。 参照:railsチュートリアル すべてのユーザーがフィードを持つため、feedメソッドはUserモデルで作るのが良い。 フィードの原型では、まずは現在ログインしているユーザーのマイクロポストをすべて取得する。 第14章で完全なフィードを実装するので、今回はwhereメソッドでこれを実現する。 Micropostモデルに変更を加える。 app/models/user.rb class User < ApplicationRecord . . . # 試作feedの定義 # 完全な実装は次章の「ユーザーをフォローする」を参照 def feed Micropost.where("user_id = ?", id) end private . . . end 上のコードにある疑問符は、セキュリティ上重要だ。 Micropost.where("user_id = ?", id) 疑問符があることで、SQLクエリに代入する前にidがエスケープされるため、SQLインジェクション(SQL Injection)と呼ばれる深刻なセキュリティホールを避けられる。 この場合、id属性は単なる整数(self.idはユーザーのid)であるため危険でないが、SQL文に変数を代入する場合は常にエスケープしておこう。 サンプルアプリケーションにフィード機能を導入するために、ログインユーザーのフィード用にインスタンス変数@feed_itemsを追加、Homeページにはフィード用のパーシャルを追加する。 先ほどあった下記のコード @micropost = current_user.microposts.build if logged_in? これが if logged_in? @micropost = current_user.microposts.build @feed_items = current_user.feed.paginate(page: params[:page]) end 前置if文に変わる。 (1行の時は後置if文、2行の時は前置if文を使うのがRubyの慣習) homeアクションにフィードのインスタンス変数を追加する。 app/controllers/static_pages_controller.rb class StaticPagesController < ApplicationController def home if logged_in? @micropost = current_user.microposts.build @feed_items = current_user.feed.paginate(page: params[:page]) end end ステータスフィードのパーシャル app/views/shared/_feed.html.erb <% if @feed_items.any? %> <ol class="microposts"> <%= render @feed_items %> </ol> <%= will_paginate @feed_items %> <% end %> ステータスフィードのパーシャルは、Micropostのパーシャルとは異なる。 <%= render @feed_items %> このとき、@feed_itemsの各要素がMicropostクラスを持っていたため、RailsはMicropostのパーシャルを呼び出すことができた。 このように、Railsは対応する名前のパーシャルを、渡されたリソースのディレクトリ内から探しにいくことができる。 あとはいつものようにフィードパーシャルを表示すればHomeページにフィードを追加できる。 この結果はHomeページのフィードとして表示される。 app/views/static_pages/home.html.erb <% if logged_in? %> <div class="row"> <aside class="col-md-4"> <section class="user_info"> <%= render 'shared/user_info' %> </section> <section class="micropost_form"> <%= render 'shared/micropost_form' %> </section> </aside> <div class="col-md-8"> <h3>Micropost Feed</h3> <%= render 'shared/feed' %> </div> </div> しかし、マイクロポストの投稿が失敗すると、Homeページは@feed_itemsインスタンス変数を期待しているため、現状では壊れる。 解決法として、Micropostコントローラのcreateアクションへの送信が失敗した場合に備え、必要なフィード変数をこのブランチで渡しておくこと。 app/controllers/microposts_controller.rb def create @micropost = current_user.microposts.build(micropost_params) if @micropost.save flash[:success] = "Micropost created!" redirect_to root_url else @feed_items = current_user.feed.paginate(page: params[:page]) render 'static_pages/home' end end しかし、この時点ではわざと長いマイクロポストを投稿するとエラーが出る。 ページネーション用のリンクを表示すると、"2"のリンクと"Next"のリンクがどちらも同じ次のページを指している。 createアクションはMicropostsコントローラにあるので、このURLは「/microposts?page=2」となります。しかし、これはMicropostsの存在していないindexアクションを開こうとしている。 その結果、どちらのリンクをクリックしてもエラーが発生する。 この問題は、Homeページに対応するcontrollerパラメータとactionパラメータを明示的にwill_paginateに渡せばOK 例として、static_pagesコントローラとhomeアクションを渡すと次の通り。 app/views/shared/_feed.html.erb <% if @feed_items.any? %> <ol class="microposts"> <%= render @feed_items %> </ol> <%= will_paginate @feed_items, params: { controller: :static_pages, action: :home } %> <% end %> マイクロポストを削除する 削除機能を追加する。 これは、ユーザー削除と同じように"delete"リンクでOK。 今回は自分が投稿したマイクロポストのみ削除リンクが動作するようにする。 まずは、マイクロポストのパーシャルに削除リンクを追加する。 app/views/microposts/_micropost.html.erb <li id="micropost-<%= micropost.id %>"> <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %> <span class="user"><%= link_to micropost.user.name, micropost.user %></span> <span class="content"><%= micropost.content %></span> <span class="timestamp"> Posted <%= time_ago_in_words(micropost.created_at) %> ago. <% if current_user?(micropost.user) %> <%= link_to "delete", micropost, method: :delete, data: { confirm: "You sure?" } %> <% end %> </span> </li> 次に、Micropostsコントローラのdestroyアクションを定義する。 ユーザーの実装とほぼ同じ。違いは、admin_userフィルターで@user変数を使うのではなく、関連付けを使いマイクロポストを見つけるようにする点。 これで、あるユーザーが他のユーザーのマイクロポストを削除しようとすると、自動的に失敗するようになる。 correct_userフィルター内でfindメソッドを呼び出すことで、現在のユーザーが削除対象のマイクロポストを保有してるかどうか確認する。 app/controllers/microposts_controller.rb class MicropostsController < ApplicationController before_action :logged_in_user, only: [:create, :destroy] before_action :correct_user, only: :destroy . . . def destroy @micropost.destroy flash[:success] = "Micropost deleted" redirect_to request.referrer || root_url end private def micropost_params params.require(:micropost).permit(:content) end def correct_user @micropost = current_user.microposts.find_by(id: params[:id]) redirect_to root_url if @micropost.nil? end end destroyメソッドではリダイレクトを使っている。 request.referrer || root_url ここでrequest.referrerというメソッドを使っている。 このメソッドはフレンドリーフォワーディングのrequest.url変数と似ており、一つ前のURLを返す。(今回なら、Homeページ) なので、マイクロポストがHomeページから削除された場合でもプロフィールページから削除された場合でも、request.referrerを使うことによりDELETEリクエストが発行されたページに戻れる。 また、元に戻すURLが見つからなくても||演算子でroot_urlをデフォルトに設定してるので問題なし。 マイクロポスト作成 マイクロポスト削除 フィード画面のマイクロポストをテストする Micropostモデルとそのインターフェイスが完成した。 あとは、Micropostsコントローラの認可をチェックするテストと、それらをまとめる統合テストだ。 マイクロポスト用のfixtureに、別々のユーザーに紐づけられたマイクロポストを追加する。 別のユーザーに所属しているマイクロポストを追加 test/fixtures/microposts.yml ants: content: "Oh, is that what you want? Because that's how you get ants!" created_at: <%= 2.years.ago %> user: archer zone: content: "Danger zone!" created_at: <%= 3.days.ago %> user: archer tone: content: "I'm sorry. Your words made sense, but your sarcastic tone did not." created_at: <%= 10.minutes.ago %> user: lana van: content: "Dude, this van's, like, rolling probable cause." created_at: <%= 4.hours.ago %> user: lana 続いて、自分以外のユーザーのマイクロポストは削除しようとした時、適切にリダイレクトされることを確認する。 test/controllers/microposts_controller_test.rb test "should redirect destroy for wrong micropost" do log_in_as(users(:michael)) micropost = microposts(:ants) assert_no_difference 'Micropost.count' do delete micropost_path(micropost) end assert_redirected_to root_url end 最後に、統合テストを書く。 統合テストの内容 ・ログイン ・マイクロポストのページ分割確認 ・無効なマイクロポストを投稿 ・有効なマイクロポストを投稿 ・マイクロポストの削除 ・他のユーザーのマイクロポストには[delete]リンクが非表示 という順番でテストする。 統合テストを生成する。 $ rails generate integration_test microposts_interface マイクロポストUIに対する統合テスト test/integration/microposts_interface_test.rb require 'test_helper' class MicropostsInterfaceTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end test "micropost interface" do log_in_as(@user) get root_path assert_select 'div.pagination' # 無効な送信 assert_no_difference 'Micropost.count' do post microposts_path, params: { micropost: { content: "" } } end assert_select 'div#error_explanation' assert_select 'a[href=?]', '/?page=2' # 正しいページネーションリンク # 有効な送信 content = "This micropost really ties the room together" assert_difference 'Micropost.count', 1 do post microposts_path, params: { micropost: { content: content } } end assert_redirected_to root_url follow_redirect! assert_match content, response.body # 投稿を削除する assert_select 'a', text: 'delete' first_micropost = @user.microposts.paginate(page: 1).first assert_difference 'Micropost.count', -1 do delete micropost_path(first_micropost) end # 違うユーザーのプロフィールにアクセス(削除リンクがないことを確認) get user_path(users(:archer)) assert_select 'a', text: 'delete', count: 0 end end マイクロポストの画像投稿 ここでは、画像付きマイクロポスト投稿の機能を実装する。 手順として、まずは開発環境のβ版を実装し、その後いくつかの改善を通して本番環境用の完成版を実装する。 画像アップロード機能を追加するための2つの視覚要素 ・1つ目は、画像をアップロードするためのフォーム ・2つ目は、1つ目に投稿された画像そのもの 参照:railsチュートリアル モックアップ画像 基本的な画像アップロード Railsでファイルをアップロードする簡単な方法は、Railsに組み込まれているActive Storage機能を使う事。 Active Storageで画像を簡単に扱えて、画像に関連付けるモデルも自由に指定可能となる。 また、Active Storageは汎用性が高く、平文テキスト、PDFファイルや音声等様々なバイナリファイルも扱える。 Active Storageインストールコマンド $ rails active_storage:install 上のコマンドで、添付ファイルの保存に用いるデータモデルを作成するためのデータベースマイグレーションが1つ作られる。 次にマイグレーションを実行 $ rails db:migrate Active Storageの中で最初に知るべきことは、has_one_attachedメソッドだ。 これは、指定のモデルとアップロードされたファイルを関連付けるのに使う。 下の場合は、imageを指定してMicropostモデルと関連付けてる。 app/models/micropost.rb class Micropost < ApplicationRecord belongs_to :user has_one_attached :image default_scope -> { order(created_at: :desc) } validates :user_id, presence: true validates :content, presence: true, length: { maximum: 140 } end このアプリケーションでは、「マイクロポスト1件につき画像は1件」という設計をしているが、Active Storageは他にもhas_many_attachedオプションも提供してる。文字通り、Active Recordオブジェクト1件につき複数のファイルを添付できるもの。 Homeページに画像をアップロードを追加するのに、マイクロポストフォームにfile_fieldタグを含める。 マイクロポストのcreateフォームに画像アップロードを追加 app/views/shared/_micropost_form.html.erb <%= form_with(model: @micropost, local: true) do |f| %> <%= render 'shared/error_messages', object: f.object %> <div class="field"> <%= f.text_area :content, placeholder: "Compose new micropost..." %> </div> <%= f.submit "Post", class: "btn btn-primary" %> <span class="image"> <%= f.file_field :image %> </span> <% end %> 最後に、Micropostsコントローラを更新し、新たに作成したmicropostオブジェクトに画像を追加できるようにする。 Active Storage APIにはそのためのattachメソッドが提供されており、これを使う。 具体的にMicropostsコントローラのcreateアクションの中で、アップロードされた画像を@micropostオブジェクトにアタッチする。 このアップロード許可のために、micropost_paramsメソッドを更新し、:imageを許可済み属性リストに追加して、Web経由で更新できるようにする。 app/controllers/microposts_controller.rb class MicropostsController < ApplicationController before_action :logged_in_user, only: [:create, :destroy] before_action :correct_user, only: :destroy def create @micropost = current_user.microposts.build(micropost_params) @micropost.image.attach(params[:micropost][:image]) if @micropost.save flash[:success] = "Micropost created!" redirect_to root_url else @feed_items = current_user.feed.paginate(page: params[:page]) render 'static_pages/home' end end def destroy @micropost.destroy flash[:success] = "Micropost deleted" redirect_to request.referrer || root_url end private def micropost_params params.require(:micropost).permit(:content, :image) end def correct_user @micropost = current_user.microposts.find_by(id: params[:id]) redirect_to root_url if @micropost.nil? end end 一度画像がアップロードされれば、micropostパーシャルのimage_tagヘルパーを用いて、関連付けられたmicropost.imageを描画できる。 また、画像の無いテキストのみのマイクロポストでは画像を表示させないようにするため、attached?という論理値を返すメソッドを使っている。 app/views/microposts/_micropost.html.erb <li id="micropost-<%= micropost.id %>"> <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %> <span class="user"><%= link_to micropost.user.name, micropost.user %></span> <span class="content"> <%= micropost.content %> <%= image_tag micropost.image if micropost.image.attached? %> </span> <span class="timestamp"> Posted <%= time_ago_in_words(micropost.created_at) %> ago. <% if current_user?(micropost.user) %> <%= link_to "delete", micropost, method: :delete, data: { confirm: "You sure?" } %> <% end %> </span> </li> 画像の検証 画像アップロード機能を持たせたが、欠点がある。それは、アップロードされた画像に対する制限がないため、もしユーザーが巨大なファイルを上げたり、無効なファイルを上げると問題が発生する。 この欠点を直すために、画像サイズやフォーマットに対するバリデーションを実装する。 Railsチュートリアル執筆時点では、Active Storageは、こうしたフォーマット機能やバリデーションがネイティブでサポートされていないとのこと。 なので、そのような機能をgemで追加する。 gem 'active_storage_validations', '0.8.2' いつものようにbundle installする。 このgemでは、content_typeを検査する事で画像をバリデーションできる。 content_type: { in: %w[image/jpeg image/gif image/png], message: "must be a valid image format" } 上記のコードは、サポートする画像フォーマットに対応する画像MIME typeをチェックしてる。 配列構文%w[]がある。 同じく、ファイルサイズも以下のようにバリデーション可能。 size: { less_than: 5.megabytes, message: "should be less than 5MB" } 上はtimeヘルパーの時に使った構文と同じで、画像の最大サイズを5MBに制限してる。 これらのバリデーションをまとめた結果をMicropostモデルに追加する。 app/models/micropost.rb class Micropost < ApplicationRecord . . . validates :image, content_type: { in: %w[image/jpeg image/gif image/png], message: "must be a valid image format" }, size: { less_than: 5.megabytes, message: "should be less than 5MB" } end 参照:railsチュートリアル 先ほどのモデルに追加バリデーションを強化するために、クライアント側でも画像アップロードのサイズやフォーマットをチェックする仕組みを入れる。 まずは、JavaScript(jQuery)にて、ユーザーがアップロードしようとする画像が大きすぎたらアラートを表示するようにする。 jQueryでファイルサイズをチェックする。 app/views/shared/_micropost_form.html.erb <script type="text/javascript"> $("#micropost_image").bind("change", function() { var size_in_megabytes = this.files[0].size/1024/1024; if (size_in_megabytes > 5) { alert("Maximum file size is 5MB. Please choose a smaller file."); $("#micropost_image").val(""); } }); </script> 最後に、acceptパラメータをfile_field入力タグで用いれば、有効なフォーマットでないとアップロードできない事をユーザーに伝えられる。 app/views/shared/_micropost_form.html.erb <%= form_with(model: @micropost, local: true) do |f| %> <%= render 'shared/error_messages', object: f.object %> <div class="field"> <%= f.text_area :content, placeholder: "Compose new micropost..." %> </div> <%= f.submit "Post", class: "btn btn-primary" %> <span class="image"> <%= f.file_field :image, accept: "image/jpeg,image/gif,image/png" %> </span> <% end %> <%= f.file_field :image, accept: "image/jpeg,image/gif,image/png" %> このコードは最初に有効な画像フォーマットだけを選択可能にしておき、それ以外のファイルタイプを灰色で表示するもの。 ブラウザ側に色々コードを追加したが、このコードは無効なファイルをアップロードしにくくするだけ。 その気になればcurl等でPOSTリクエストを直接発行して、無効なファイルをアップロードできてしまう。 なので、サーバー側のバリデーションは必要なのだ。 画像のリサイズ ファイルサイズのバリデーションは実装した。 今度は、画像サイズ(縦横の長さ)に対する制限がない。なので、大きすぎる画像サイズがアップロードされるとレイアウトが崩壊する。 かといって、ユーザーで画像サイズを変更させるのは大変不便だ。 そのため、画像を表示させる前にサイズを変更するようにする。 参照:railsチュートリアル 画像を操作するプログラムが必要なので、ImageMagickを使う。 開発環境にインストールする。 $ sudo apt-get -y install imagemagick 続いて、画像処理のためにいくつかgemを追加する。 image_processinggem、Ruby製ImageMagickプロセッサのmini_magickgemが必要だ。 gem 'image_processing', '1.9.3' gem 'mini_magick', '4.9.5' いつものようにbundle installする。 これで、Active Storageが提供するvariantメソッドで変換済画像を作成できるようになる。 特にresize_to__limitオプションを用いて、下記のように画像の幅や高さが500ピクセルを越えないように制約をかけとく。 image.variant(resize_to_limit: [500, 500]) 上記のコードをdisplay_imageメソッドにおいて利便性を高めよう。 app/models/micropost.rb # 表示用のリサイズ済み画像を返す def display_image image.variant(resize_to_limit: [500, 500]) end display_imageをmicropostパーシャルで使えるようになった。 リサイズ済のdisplay_imageを使う。 <li id="micropost-<%= micropost.id %>"> <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %> <span class="user"><%= link_to micropost.user.name, micropost.user %></span> <span class="content"> <%= micropost.content %> <%= image_tag micropost.display_image if micropost.image.attached? %> </span> <span class="timestamp"> Posted <%= time_ago_in_words(micropost.created_at) %> ago. <% if current_user?(micropost.user) %> <%= link_to "delete", micropost, method: :delete, data: { confirm: "You sure?" } %> <% end %> </span> </li> 上記のコードに <%= image_tag micropost.display_image if micropost.image.attached? %> があるが、そこにdisplay_imageメソッドを使ってる。 variantによるリサイズは、<%= image_tag micropost.display_image if micropost.image.attached? %>で最初に呼ばれる時にオンデマンドで実行される。以後、結果をキャッシュするので効率が良い。 本番環境での画像アップロード 画像アップロード機能を実装したが、このままで本番環境に適さない。 本番環境では、ファイルシステムではなくクラウドストレージサービスに画像を保存するようにする。 今回はAWSのS3(Simple Storage Service)を使う。 S3を使うためgemを設定する。 gem 'aws-sdk-s3', '1.46.0', require: false 例のごとくbundle installを実行する。 AWSの設定方法は省く。 railsチュートリアルに画像付きで細かく載っているため。 最後に Micropostsリソースによって、良い感じにTwitterっぽくなった。 次章は、ユーザーをお互いフォローする仕組みを作っていく。 ユーザー同士のリレーションシップモデリングを学んで、マイクロポストフィードにどんな感じで関連するか学ぶ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Ruby on Rails] render後のURLが気になっちゃう件について

redirect_toやrenderを何気なく使っていましたが、URLの違いに気付き色々調べたのでまとめておこうと思いました。 例)Userのアップデート app/controllers/users_controller.rb def update @user = User.find(params[:id]) if @user.update(user_params) redirect_to user_path, notice:"ユーザー情報を更新しました。" else flash.now[:alert] = "入力項目に不備があります。" render :edit end end 入力項目に不備があった場合、renderで編集ページに戻るようにしていますが、ここでURLが気になっちゃう件が発生します。 欲しいurl: /users/edit/:id render後のurl: /users/:id となります。 他にはcreateなども同様です。 欲しいurl: /users/new render後のurl: /users 「ふぁ!!めちゃ気になる!!これは嫌だ!!」 redirectとrenderの違いから考えよう そもそも.... redirect_to =>controllerからリダイレクト先のURLを返す。 render =>controllerから指定されたviewファイルを返す。 renderでは、viewファイルのみになるので、表示画面はeditやnewにはなっていますが、URLまでは変えられないんですね。 edit(/users/edit/:id)やnew(/users/new)もpathはshow(/users/:id)やindex(/users)と同じだもんね。 そうなると... app/controllers/users_controller.rb def update @user = User.find(params[:id]) if @user.update(user_params) redirect_to user_path, notice:"ユーザー情報を更新しました。" else flash[:alert] = @user.errors.full_message //エラーメッセージを表示させる redirect_to request.referer //直前の画面へ遷移 end end redirect_toで返せば一応URL問題は解決しますね。 flash.nowもflashに変えて対応。 表示もエラー文を用いれば、どこの箇所に不備があるのかユーザーはわかりますね。 他にもjavascriptを用いてURLを変えたりするやり方もあるらしいけど、今回はここまで。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】刃物のノコギリじゃないよ、gemのNokogiriだよ

Nokogiriとは Nokogiriを利用すると、RubyからXML/HTMLを簡単に生成することが出来ます。 これは、ドキュメントの読み取り、書き込み、変更、およびクエリを行うための、賢明で理解しやすい API を提供します。 インストール方法 Gemfileに以下を記載し、bundle installする。 gem 'nokogiri' 使い方 app/lib/html_builder.rb module HtmlBuilder def markup(tag_name = nil, options = {}) root = Nokogiri::HTML::DocumentFragment.parse("") Nokogiri::HTML::Builder.with(root) do |doc| if tag_name doc.method_missing(tag_name, options) do yield(doc) end else yield(doc) end end root.to_html.html_safe end end def notes markup(:div, class: "notes") do |m| m.span "*", class: "mark" m.text "印の付いた項目は入力必須です。" end end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails(api) × react(frontend)のSPA環境構築 github pushまで

はじめに こんにちは、ゴンと申します。転職のために、railsとreactでSPAのポートフォリオを作成中です。 この記事では、railsとreactの環境構築と、githubにpushするまでを書いてみました。 最初の環境構築は情報が違ったりして迷うので、参考にして頂けたら幸いです!! 前提環境 ruby 2.7.1 node 14.3 rails 6.0.3.7 目次 プロジェクトフォルダ作成 railsファイル作成 reactファイル作成 github登録 事前準備 yarn node ruby,rails 以上のインストールができていることを前提にして進めます プロジェクトフォルダ作成 プロジェクトフォルダを作成 $ mkdir [フォルダ名] railsファイル作成 プロジェクトフォルダに移動 $ cd [フォルダ名] rails プロジェクトを作成します。(apiモードで作成する場合は2行目を実行) $ rails new api -d postgresql $ rails new api --api -d postgresql # apiモードで作成する場合はこちらを実行 reactファイル作成 reactプロジェクトを作成します。 (typescriptを作成する場合は2行目実行) console $ npx create-react-app frontend $ npx create-react-app frontend --template typescript // typescriptを導入したい場合はこちらを実行 確認 ファイル構成確認 下記の様なファイル構成になっていたら大丈夫です。 [プロジェクトフォルダ] ├ api └ frontend サーバーが動くか確認 rails サーバー console $ cd api $ rails s react サーバー 起動時にWould you like to run the app on another port instead?と聞かれますが、yesでOKです。 console $ cd frontend $ cd npm start それぞれ、以上のような画面になれば大丈夫です。 npm startの度にyesと打つのは面倒なので、.envファイルを作成して、以下のように記述しましょう。 frontend/.env PORT=3001 CORS設定 railsとreactのローカルサーバーでデータをやり取りするために、CORSの設定をしていきます。 CORSの概要については以下の記事を御覧ください。 gemfileに以下を記述し、bundle installします。 gemfile gem 'rack-cors' $ cd api $ bundle install api/app/config/initializersのcors.rbに以下を記述します。 (cors.rbがない場合は作成してください) api/app/config/initializers/cors.rb Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins 'http://localhost:3001' resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head] end end これで、apiとフロント間でデータをやり取りすることができます。 以上で、railsとreactの環境構築ができたので、最後にgit hubにpushしていきましょう。 githubにpush プロジェクトフォルダで、git add .をすると以下のエラーが起きます。 error: 'api/' does not have a commit checked out これはapiフォルダに.gitのファイルが存在するため、エラーが起きてしまいます。なので以下のコマンドで削除します。 $ rm -rf /api/.git $ rm -rf /frontend/.git これで、gitコマンドが使えるようになりました。 ルートフォルダにgitignoreを作成しましょう。 以下のコードはrailsとreactのgitignoreをコピペしてディレクトリを編集しました。 プロジェクトフォルダ/.gitignore # See https://help.github.com/articles/ignoring-files for more about ignoring files. # # If you find yourself ignoring temporary files generated by your text editor # or operating system, you probably want to add a global ignore instead: # git config --global core.excludesfile '~/.gitignore_global' # Ignore bundler config. api/.bundle # Ignore all logfiles and tempfiles. /api/log/* /api/tmp/* /api/!/log/.keep /api/!/tmp/.keep # Ignore pidfiles, but keep the directory. /api/tmp/pids/* /api/!/tmp/pids/ /api/!/tmp/pids/.keep # Ignore uploaded files in development. /api/storage/* /api/!/storage/.keep /api/.byebug_history # Ignore master key for decrypting credentials and more. /api/config/master.key # production /frontend/build # misc /frontend/.DS_Store /frontend/.env.local /frontend/.env.development.local /frontend/.env.test.local /frontend/.env.production.local /frontend/npm-debug.log* /frontend/yarn-debug.log* /frontend/yarn-error.log* あとは、github上でレポジトリを作成し、ローカルでadd,commit,pushをしましょう。 最後に 以上で、rails × reactで環境構築 + gitのpushができました。 初学者の方の参考になれば幸いです!! 次の記事では、rails側でapiを実装し、react側で、データを表示する処理を紹介します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

暗号文を元に戻す[Ruby]

問題 あなたは友達と秘密を共有するために暗号文を作り、その暗号文を元に戻すプログラムを作成することになりました。暗号文を元に戻す際のルールは以下の通りです。 ・全ての文は大文字アルファベットのみを使う。 ・文字の番号は左から順番に 1 番目から数え始めるものとする。 ・暗号文の元に戻す操作として... 奇数番目の文字に対しては、アルファベットの逆方向に N文字ずらし、 偶数番目の文字に対しては、順方向に N 文字ずらしたものにそれぞれ変換する 暗号化された文字列と、元の文をずらした文字数 N が、与えられる → 元に戻された文字列を出力するプログラムを作成してください。 ただし、文字をずらした時、アルファベットの先頭、及び末尾をはみ出してしまった場合、 それぞれ、アルファベットの末尾、アルファベットの先頭から続きを数える。 入力される値 入力される値 X Y ・1 行目には、ずらした文字数を表す整数 X が与えられます。 ・2 行には、暗号文字列を表す文字列 Y が与えられます。  入力は 2 行となり、末尾に改行が 1 つ入ります。 ・Y は暗号化文字された文字列を表す 英字大文字 からなる文字列 条件 ・1 ≦ X ≦ 25 ・S は 英字大文字 で構成される文字列。 ・1 ≦ (Y の長さ) ≦ 500 例 入力例1 4 QEPG 出力例1 MILK 入力例2 19 KXKPMQVI 出力例2 RQRITJCB 回答 input_lines = readlines.map { _1.chomp } count = input_lines[0].to_i word = input_lines[1].chars array = [*"A".."Z"].map.with_index { |i, num| [i, num] } answer = [] word.each.with_index(1) do |alph, num| ans = array.select { |u| u[0] == alph }.flatten if num % 2 == 1 index = ans[1] - count elsif num % 2 == 0 index = ans[1] + count index -= 26 if index > 25 end answer.push(array.values_at(index).flatten[0]) end puts answer.join 学び values_atメソッド ary = %w( a b c d e ) p ary.values_at( 0, 2, 4 ) #=> ["a", "c", "e"] p ary.values_at( 3, 4, 5, 6, 35 ) #=> ["d", "e", nil, nil, nil] p ary.values_at( 0, -1, -2 ) #=> ["a", "e", "d"] p ary.values_at( -4, -5, -6, -35 ) #=> ["b", "a", nil, nil] p ary.values_at( 1..2 ) #=> ["b", "c"] p ary.values_at( 3..10 ) #=> ["d", "e", nil, nil, nil, nil, nil, nil] p ary.values_at( 6..7 ) #=> [nil, nil] p ary.values_at( 0, 3..5 ) #=> ["a", "d", "e", nil] 配列のインデックスを指定する事で、配列の要素を柔軟に取り出すメソッド 反省 array = [*"A".."Z"].map.with_index { |i, num| [i, num] } #=> [["A", 0], ["B", 1]..["Z", 25]] アルファベットの大文字とそのインデックスを合わせた配列を生成する為に、mapメソッドとwih_indexメソッドを組み合わせて冗長的な方法を選択している。 もっとスタイリッシュに書けそう...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby on rails】ルート設定できないときに確認すること

元々root以外に設定していたページをルートに書き換えるときによくやってしまうミス Missing :controller key on routes definition, please check your routes. (ArgumentError) config/routes.rb root 'inquiries/new' 解決策  /を#書き換えれば解決します。 config/routes.rb root 'inquiries#new'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】ルート設定できないときに確認すること

元々root以外に設定していたページをrootに書き換えるときによくやってしまうミス Missing :controller key on routes definition, please check your routes. (ArgumentError) config/routes.rb root 'inquiries/new' 解決策  /を#書き換えれば解決します。 config/routes.rb root 'inquiries#new' 無事、viewが表示されました。 余談 初学者なため何も考えずに、rootに書き換えたときに、postで設定していたページまで#を使用した記述にしていて、rails modelコマンドを使用したところ作成できない事象が発生しました。 Missing :controller key on routes definition, please check your routes. (ArgumentError) config/routes.rb Rails.application.routes.draw do root 'inquiries#new' post 'inquiries#confirm' #本来は#でなく/を記述 post 'inquiries#thanks' #本来は#でなく/を記述 end 解決策 下記、記述に直したところ、rails modelコマンドを使用できるようになりました。 config/routes.rb Rails.application.routes.draw do root 'inquiries#new' post 'inquiries/confirm' post 'inquiries/thanks' end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

if文でinclude?(xxx)のor条件を連続で何度も書くのを、簡潔でシュッとした書き方にする。

備忘録用。 前提条件 例えば、「メッセージ文の中に、東京・神奈川・千葉・埼玉が含まれるか」の場合、 if msg.include?("tokyo") || msg.include?("kanagawa") || msg.include?("chiba") || msg.include?("saitama") となってしまい、ちょっと見づらい。 少し見やすい書き方として、 if msg.include?( "tokyo" || "kanagawa" || "chiba" || "saitama" ) なんて書き方も悪くないかもしれないけど、チェックする県が増えたらどんどん横長ブサイクになってしまう。 .any? とブロックを使えばシュッとなる list = [ "tokyo", "kanagawa", "chiba", "saitama" ] if list.any? { |n| msg.include?(n) } 「msg.include?(n) がtrueとなる要素」が1つでもlistにあればtrueを返す。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

killコマンドでサーバーをダウンする

cloud9でrails serverを立ち上げた後control + Cでサーバーを落とせなくなりました。 エラー文 A server is already running. Check /app/tmp/pids/server.pid. 解決方法 上記のコードに書いてある/home/ec2-user/environment/develop/tmp/pids/server.pidを開くと ポート番号が書いてあったので、kill -9で強制終了させます。 kill -9 番号 これで、rails sで再起動すればOKです 参考: https://qiita.com/pippi0920/items/4baadd537a3213c4b30d
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

attr_accesorで詰まった話

備忘録です。 student.rb class Student attr_accessor :name, :height def initialize(name, height) @name = name @height = height end def info puts '名前:' + @name puts '身長:' + @height end end student1 = Student.new('山浦', 167) student2 = Student.new('土田', 178) student1.info student2.info これで実行するとなぜかエラーになる。 `+': no implicit conversion of Integer into String (TypeError) @height の後に to_sをつけることで解決。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

attr_accesorの利用中、integerで詰まった話

備忘録です。 student.rb class Student attr_accessor :name, :height def initialize(name, height) @name = name @height = height end def info puts '名前:' + @name puts '身長:' + @height end end student1 = Student.new('山浦', 167) student2 = Student.new('土田', 178) student1.info student2.info これで実行するとなぜかエラーになる。 `+': no implicit conversion of Integer into String (TypeError) @height の後に to_sをつけることで解決。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby,Python】初心者がじゃんけんゲーム作ってみた

前提 某プログラミングスクールでRubyを学習しました。 Rubyの復習、Pythonの学習を兼ねてじゃんけんゲームを作って見ました。 Ruby.ver # <じゃんけんゲーム> # 間違った番号を入れた場合のループ def validate(player_hand, hands) if player_hand < 0 || player_hand > 2 loop{ puts "0~2の番号を選択してください" puts "0.#{hands[0]} 1.#{hands[1]} 2.#{hands[2]}" player_hand = gets.chomp.to_i if player_hand >= 0 && player_hand <= 2 break end } end end # 入力フォーム hands = ["グー","チョキ","パー"] puts "__________________________" puts "名前を入力してください" player_name = gets.chomp puts "0.#{hands[0]} 1.#{hands[1]} 2.#{hands[2]}" puts "0~2の番号を選択してください" player_hand = gets.chomp.to_i validate(player_hand, hands) puts "#{player_name}さんの選択された手は#{hands[player_hand]}です。" # コンピュータとの勝敗判定 program_hand = rand(3) if player_hand == program_hand puts "コンピュータの手は#{hands[program_hand]}あいこです" elsif (player_hand == 0 && program_hand == 1) || (player_hand == 1 && program_hand == 2) || (player_hand == 2 && program_hand == 0) puts "コンピュータの手は#{hands[program_hand]}で#{player_name}さんの勝ちです" else puts "コンピュータの手は#{hands[program_hand]}で#{player_name}さんの負けです" end Python.ver # <じゃんけんゲーム> import random # 間違った番号を入れた場合のループ def validate(player_hand, hands): if player_hand < 0 and player_hand > 2: while player_hand != 0 or player_hand != 1 or player_hand != 2: print ('0~2の番号を選択してください') player_hand = int(input(f'0.{hands[0]} 1.{hands[1]} 2.{hands[2]}')) if player_hand >= 0 and player_hand <= 2: break # 入力フォーム hands = ['グー','チョキ','パー'] print ('__________________________') print ('名前を入力してください') player_name = input('') print (f"0.{hands[0]} 1.{hands[1]} 2.{hands[2]}") print ('0~2の番号を選択してください') player_hand = int(input('')) validate(player_hand, hands) print (f'{player_name}さんの選択された手は{hands[player_hand]}です。') # コンピュータとの勝敗判定 program_hand = random.randint(0,2) if player_hand == program_hand: print (f'コンピュータの手は{hands[program_hand]}あいこです') elif (player_hand == 0 and program_hand == 1) or (player_hand == 1 and program_hand == 2) or (player_hand == 2 and program_hand == 0): print (f'コンピュータの手は{hands[program_hand]}で{player_name}さんの勝ちです') else : print (f'コンピュータの手は{hands[program_hand]}で{player_name}さんの負けです') じゃんけんゲームで学んだRubyとPythonの違い Rubyと違いPythonはインデントに注意する必要がある。(インデントを間違えると正しい挙動を示さない) メソッド(関数)やif文などpythonではendをつけず、:(コロン)をつける。 式展開を行う際、Rubyは#{}で記述を行うがPythonは(f'{}')で記述(一例) まとめ 同じオブジェクト指向の言語であったが、色々と記述方法が違うのでこれから引き続き学習を行って行きたいと思います。 また、Pythonの式展開は一例なので今後、まとめて行きたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsでsessionによるwhere検索を行うときの注意点

障害内容 コントローラのある1つのアクション内でsessionを利用後にデータを削除するような処理を想定して、下記コードを書いたとする hoge_controller.rb def action # イメージ的には検索処理のような感じ # category_idはInteger型でDBに登録されている @lists = Book.where(category_id: session[:category_id]) session[:category_id].clear end 上記のコードではBookモデルからデータを取得することはできない 実際に発行されているSQLも SELECT * FROM books WHERE category_id = '' のような形で出力されることが確認できるはず 通常、Railsで定義される変数類は先行評価の形式をとっているため、 例えwhere検索等の遅延評価系メソッドの後に値をnilにしても正しくデータを取得することができる 反対に、sessionは基本的に遅延評価であるため遅延評価系メソッドで使用した後にsessionの中身をクリアするとデータの取得ができなくなってしまう 解消方法 今回の例ではwhere検索であるため、それを先行評価メソッドにしてしまえば問題ない hoge_controller.rb(whereの先行評価ver) def action # SQLを直書きする @lists = Book.where('category_id = :id', id: session[:category_id]) session[:category_id].clear # take、firstなどで即時発行させる @lists = Book.where(category_id: session[:category_id]).take @lists = Book.where(category_id: session[:category_id]).first session[:category_id].clear end # to_aなどで変換を行う(ActiveRecord::Relationクラスではなくなるため要注意) @lists = Book.where(category_id: session[:category_id]).to_a session[:category_id].clear end また、それとは逆にsessionの値をdupでコピーするなど、sessionとは別で保持させることでも解消できる 格納させるときにはid = session[:category_id]のように代入で行わないようにさえすれば大体何とかなる(代入は結局のところメモリ参照のため、参照先が変われば代入された変数の値も変化する) 参考記事 ActiveRecord各メソッドのクエリ実行タイミングについて ActiveRecordクラスのメソッドについて、詳しい解説が載っています
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

request specでDRYなコードを書きましょう

はじめに Rspecを書き始めたばかりなのであまり詳しいことはかけませんが自分の備忘録として書いているので間違えている部分等あれば教えていただけると幸いです。 そもそもRequest specってなに? もともとRailsのcontrollerをテストする際にはRails4までのバーションだとController specが用いられてましたがRails5以降になるとそれらが非推奨となりRequest specで書くことが推奨されるようになりました。 要するにcontollerに書くテストを書けばいい!!と私は思います Railsガイドによるとコントローラの機能テストは 以下引用 Webリクエストが成功したか 正しいページにリダイレクトされたか ユーザ認証が成功したか レスポンスのテンプレートに正しいオブジェクトが保存されたか ビューに表示されたメッセージは適切か このようになっています。上記を参考にRequest specを書いていきましょう 本題 それでは上記を参考にDRYなコードを書いていきましょう!! 待った!!DRYってなに!?!? ってなった方もいると思います。僕もそうでした。 DRYというのはDRY原則と言ってWebフレームワークRuby on Railsが基本理念としの一つとして採用している開発原則です。簡単にいうとコードを重複させないという意味です。 難しい事はここでは省きますがrequest specを下記に書いていくのでそこで雰囲気だけ掴んでいってください! 例1 HTTPレスポンスコードが200かどうかを判定する場合 require 'rails_helper' Rspec.describe 'Qiita', type: :request do describe 'GET Qiita index' do it 'statusが200であること' get qiita_path expect(response).to have_http_status(200) end end describe 'GET Qiita new' do it 'statusが200であること' do get new_qiita_path expect(response).to have_http_status(200) end end end 例ですが上記のようなコードがあった場合、indexとnewで重複している場所があります。 このような場合にDRYに書くことができます。 DRYに書いた場合 require 'rails_helper' shared_examples 'statusが200であること' do it 'statusが200であること' do expect(response).to have_http_status(200) end end Rspec.describe 'Qiita', type: :request do describe 'GET Qiita index' do get qiita_path it_behaves_like 'statusが200であること' end describe 'GET Qiita new' do get new_qiita_path it_behaves_like 'statusが200であること' end end これだけで圧倒的にみやすくなりました。ここででできたshared_examplesとit_behaves_likeは何かというとshared_examplesを利用してコードを共有しit_behaves_likeでそれを呼び出すと言ったものです。 詳しい説明は下記に記事を貼るのでそこから参照してください! 参考にした記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【未経験の登竜門!】RubyのブロックとyieldとcallとProcとラムダ式を超わかりやすく解説してみた(Rails6.0でもよく出てくるよ)

はじめに 未経験エンジニアやエンジニア1年生のあなたは、「ブロック・yield・call・Proc・ラムダ式」を人に説明することができますか? この辺りは避けて通ってきた人も多い知識だと思うので、不安な人はこの機会に全部まとめて学んじゃいましょう!!! では超わかりやすい解説スタートです! そもそもブロックとは 【記法】 do ... end, { } のどちらか。この記号と記号に囲まれた中身を含めてブロックという。 【ブロックの定義】 メソッド呼び出しの際に引数と一緒に渡すことのできる処理のかたまり | たのしいRuby P199 メソッド呼び出しの際というのがポイント。 てことで例えば、 exmaple def greeting puts 'おはよー!' end # メソッドの呼び出し greeting do # 特に意味はないけど、メソッド呼び出し時に # こうやってdo ... end で囲めばブロックを渡したことになる。 end この場合の出力は、もちろん 'おはよー!' です。 ブロックの活用法 さっきのgreetingメソッドの例は全く意味のない使い方ですね。 まあ、ブロックはメソッドに渡す事ができるって理解できたらOKです! さあ、ここからはブロックの有効的な活用法をご紹介しますが、みなさんがdo ... end を見て最初に思い浮かぶのは、eachじゃないでしょうか? (私だけだったらすみません...) 未経験の方は、 「ブロックってメソッドの後につけるんだよね。そもそもeachってメソッドなの?」 こんな疑問の声が聞こえた気がしたので、一応説明しておくと、eachもメソッドです! ・「メソッドはクラスに定義されているもの」 ・「(インスタンス)メソッドはオブジェクトに対して使用する」 っていう基本ルールはOKですかね? 実はeachメソッドは、Enumerable クラスに定義されているメソッドなのです! Enumerable は、日本語で「列挙可能な」的な意味です。データが列挙されているデータ型をイメージしてみてください。 ご名答! 配列・ハッシュ・集合などが全てEnumerableクラスに属しています。(すぐ思い浮かんだ未経験の方はすごい!!!) 話がそれましたが、だからeachメソッドは、 eachメソッド [1, 2, 3].each って感じで配列の後ろに付けられるんですね! メソッドの後ろにブロックをつけることができるので、 each do ... end [1, 2, 3].each do |ele| end ってできるわけですね。 ここで、eachとブロックの役割について解説しておきます。 each ... 配列の要素を一つずつブロックの中に渡す。 ブロック ... 要素を受け取った後の処理内容。 ブロックの概要は伝わりましたでしょうか! yield 次はyieldです! yieldの役割は、「メソッドの呼び出しをブロック付きで行った際、メソッドの中のyieldでブロックの処理内容を実行」することです。 こちらの理解はなかなか難しいと思うので、またgreetingを使ったコードで説明します! 下のコードを見てください! greetingメソッド def greeting puts 'おはよー!' yield # greeting メソッドをブロック付きで呼び出した時にブロックの処理内容がここに入ります。 end # greetingメソッドをブロック付きで呼び出す greeting do puts 'こんばんはー!!' end 出力 'おはよー!' 'こんばんはー!' yieldの位置にブロック内の処理であるputs 'こんばんはー!!'が入ることで、出力に示したような結果になりました。 ※ メソッド内にyieldを書いている時に、ブロックなしでメソッドを呼び出すとエラーが発生します。お気をつけください。 greetingメソッド def greeting puts 'おはよー!' yield # greeting メソッドをブロック付きで呼び出した時にブロックの処理内容がここに入ります。 end # greetingメソッドをブロックなしで呼び出す greeting 出力 LocalJumpError: no block given (yield) こういった場合は、block_given?メソッドを使います。このメソッドはブロックが渡されているときにtrueを返します。 greetingメソッド def greeting puts 'おはよー!' if block_given? # ブロックが渡された場合にyieldを実行する yield end end # greetingメソッドをブロックなしで呼び出す greeting 出力 'おはよー!' ブロックを引数で渡す。Callでブロックを実行する。 ブロックを引数に渡したい場合は、以下の記法を使います。 ブロックを引数で渡す def method(&ブロック) # ブロックを実行する ブロック.call end ブロックを渡してブロックの処理内容をcallで実行するってことですね! これも例を見せておきます。 example.rb def greeting(&b) puts 'おはよー!' text = b.call('こんばんはー') # ブロックの処理内容を実行 puts text end # ブロックを引数に渡して実行 greeting do |text| puts "#{text}!!" end 出力 おはよー! こんばんはー!! callでブロックを呼び出す際は引数も取ることができます。 yieldとcallの違いは何? yieldもcallもブロックを呼び出すという点では同じです。 では何が違うのかというと、「callは引数から明示的にブロックを渡す事ができる」ということですね! はて?って人はもう一度yieldとcallの説明を見直していただければ納得できると思います。 yieldではメソッドの定義の際、引数としてブロックを受け取りません。メソッド呼び出し時にブロックを渡していれば、yieldでブロック内の処理内容を実行します。 対してcallは、メソッドの定義の際、引数として「&ブロック」という表記でブロックを受け取ります。ブロックを渡している場合は、ブロック.callでブロック内の処理内容を実行します。 どちらも「ブロック内の処理内容を実行する」ということを理解しておいてください! Proc Rubyには、Procクラスというクラスが存在します。 Procクラスは、今までさんざん説明してきたブロックをオブジェクト化するためのクラスです。 Procクラスのオブジェクトを作成する時に、引数でブロックを渡します。 Procオブジェクト作成 greeting = Proc.new { 'おはよー!' } このままgreetingを呼び出してもブロックオブジェクトを保存しているメモリアドレスを返すだけでブロック内の処理内容は実行されません。 (メモリアドレスの話も今度詳しく解説する予定なので、気になる方は僕のTwitterで最新情報をチェックしてみてください!) ブロックの処理内容を実行するメソッド覚えてます? そうです。callメソッドです! なので、下みたいにすれば実行できます。 Procオブジェクト(ブロック)を実行 greeting = Proc.new { 'おはよー!' } greeting.call #=> 'おはよー!' もちろん引数も渡せます! 引数ありのProcオブジェクトを実行 greeting = Proc.new {|text1, text2| text1 + text2 } greeting.call('おはよー!', 'からのこんばんはー!') #=> 'おはよー!からのこんばんはー!' まとめると、Procオブジェクトはブロックそのもの。a = Proc.new {} した時のaはProcオブジェクトの保存先のメモリアドレスが入っているだけなので、実行する際は、a.callのように呼び出す。 Procオブジェクトを引数に渡す ブロックを引数に渡す方法を思い出しましょう。 ブロックを引数に渡す def greeting(&b) puts 'おはよー!' text = b.call('こんばんはー') # ブロックの処理内容を実行 puts text end # ブロックを引数に渡して実行 greeting do |text| puts "#{text}!!" end 出力 おはよー! こんばんはー!! ここで重要なポイントは、引数に渡しているものはブロックそのものであるということ。ブロックそのものを渡す際は、&(アンパサンド)が必須です。でなければ、Rubyはブロックを普通の引数として認識してしまいます。また、ブロックは一つのメソッドに一つまでしか渡せません。 これらを解決するのがProcオブジェクト! ブロックの代わりにProcオブジェクトを渡すことを考えてみましょう。 オブジェクトを渡すのであれば普通の引数で渡せますね!てことで&が必要なくなります。 また、一つのメソッドに一つまでの制限もなくなります。 では、実際にProcオブジェクトを渡した場合のコードを見てみましょう。 ブロックの代わりにProcオブジェクトを渡す def greeting(b) puts 'おはよー!' text = b.call('こんばんはー!', 'せい!') # ブロックの処理内容を実行 puts text end # ブロックを引数に渡して実行 greeting_proc = Proc.new {|text1, text2| puts text1 + text2 } greeting(greeting_proc) 出力 おはよー! こんばんはー!せい! どうですか?Procオブジェクトがなんとなく理解できましたか? まだクリアになっていない方はこの章を何度か読み直してみてください! ラムダ式 最後の章です! 後少し頑張ってください! ラムダ式はコンピュータサイエンスの基礎であり、JavaScriptなどでも登場する呼び出し可能オブジェクトです。 ラムダ式に関して説明しようと思うとそれだけで記事が書けてしまうので、ここではProcとの絡みにのみ着目してお話します。 ラムダ式とは、以下のような表記のことです! ラムダ式 ->(a, b){ a + b } or lambda {|a, b| a + b } そしてこの表記で何ができるかというと、Procと同じくブロックのオブジェクトが作れます。 ここでProcの表記と比較してみます。 意味は一緒です! Procとラムダの比較 block_obj = Proc.new {|a, b| a + b } block_obj = ->(a, b){ a + b } 二つの挙動に細かい違いはありますが、まず使えるようになることが目的であればまずはこれでオッケーです。 では最後に例のごとくgreetingメソッドを使ってラムダ式を実践してみましょう! greeting.rb def greeting(b) puts 'おはよー!' text = b.call('こんばんはー') puts text end block_obj = ->(text){ puts "#{text}!!" } greeting(block_obj) 出力 'おはよー!' 'こんばんはー!!' まとめ ブロックとyieldとcallとProcとラムダ式について学んできました。 この辺りは未経験にとっての登竜門で、避けて通ってきた未経験の方やエンジニア1年生の方も多いのではないでしょうか。 Railsなどのフレームワークを学ぶことはもちろん大事ですが、それと同じかそれ以上にコンピュータサイエンスの基礎や、言語の理解を深めることは大事だと思っています! この記事が、皆様がそういった基礎概念に目を向けるきっかけになれば幸いです。 Twitterで「未経験の方・エンジニア1年目の方に向けて、エンジニア1年目の学びを発信」してますので、興味がある方は是非のぞいてみてください! では長らくお付き合いいただきありがとうございました! また、次の記事でお会いしましょう〜!? -END-
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RubyでオレオレVMとアセンブラとコード生成器を2週間で作ってライフゲームを動かした話

きれいにまとまってないですが、箇条書き+α程度で雑にメモ。 ブログの方に書いていたもの1を Qiita に引っ越してきました。元の記事公開日は 2019-05-04 です。 TechRacho さんの週刊Railsウォッチ(20210209後編)で紹介されました(ありがとうございます )。こちらもあわせて読んでいただけるとよいかと。 できたもの 2018年5月のゴールデンウィーク残り2日というところ(5/5)で思い立って初めて、 5/18 完成。 もっと早く公開したかったんですが仕事が忙しくて燃え尽きたりしていて結局1年かかってしまいました。 去年の言語実装 Advent Calendar に超初心者枠で参加しようかと考えてたんですが、間に合わず……。 それを原型を損ねない程度に名前を変えたり最終的に不要になった部分を消したりしたものです。 実装言語は Ruby。いちばん慣れていてサッと始められるので。 完成直後(ライフゲームが最初に動いた時点)の状態も見られるようにタグを付けています。 https://github.com/sonota88/vm2gol-v1/tree/20180518 汚いしひどいコードなので正直恥ずかしいんですが、この状態も生々しくてコンテンツとしては面白いかもしれません。やけくそか。ヒマな人向け。 ※ その後作り直した v2 もあります。その後の改良も加わったものが見たい、パーサまで揃ったものが見たいという方はこちらを見てください。 ※ (2021-02-21 追記) v2 にいくつか機能を足してセルフホストできるようになりました。 製作過程のメモ どういうステップを踏んで何を実装していったか、具体的な話はこちらに書きました。あまり省略せずにその都度 diff を貼っていくスタイルです。 動かし方 サードパーティなライブラリへの依存はなく、Ruby(と標準ライブラリ)だけあれば動きます。 # コード生成 ruby vgcg.rb gol.vgt.json > gol.vga.yaml # アセンブル ruby vgasm.rb gol.vga.yaml > gol.vge.yaml # VMで実行 ruby vgvm.rb gol.vge.yaml これらの処理をまとめて行うために run.sh というシェルスクリプトを使って動かしていました。 ./run.sh gol.vgt.json 概要 VM 600行弱 CPU レジスタは a, b, c, d, pc, sp, bp, zf, of c, d, of は途中で不要になった 命令セット … x86 っぽいオレオレ 数値演算は Ruby に丸投げ メモリ ただの Ruby の配列。整数 or 文字列(!)のどちらかが入る。 3つの領域に分けた メイン領域(機械語コードを配置する場所) スタック領域 VRAM領域 配列の添字がそのままアドレス 機械語コード こういう YAML ファイル。バイナリではなくテキストですが、機械語のつもり。 --- - call - 1029 - exit - label - vram_set - push - bp - cp - sp - bp - sub_sp - 1 - set_reg_a - "[bp+4]" - set_reg_b - "[bp+2]" - mult_ab - cp - reg_a ... これを YAML.load_file したものをそのままメインメモリ領域にズボッと入れる。 富豪的。 こういうものなので、「バイトコード」とは言えないかなと思って「機械語コード」と呼んでます 実在するCPU向けではなくオレオレVM向けなので「仮想機械語〜」のように呼ぶのがより適切? アセンブラ・アセンブリコード アセンブリコードも YAML。 - call main - exit - label to_vi - push bp - cp sp bp - sub sp 1 - sub sp 1 - sub sp 1 - set_reg_d [bp+4] - set_reg_a [bp+2] - cp reg_d reg_b - mult_ab - cp reg_a [bp-3] - set_reg_d [bp-3] - set_reg_a [bp+3] - cp reg_d reg_b - add_ab_v2 - cp reg_a [bp-1] ... ジャンプ先などのアドレス指定が実アドレスでなくラベル名になっている他は、 内容的には上の機械語コードとほぼ同じ。 アセンブラは 60行弱 実アドレスへの変換以外にアセンブラでやっているのは入れ子の構造を flatten して1次元にしてるくらい。 この 1次元化の処理も削っても良かったかも(そうすれば CPU 部分もさらに簡略化できる(そこまでやると簡略化しすぎという気も)) パースをサボるために YAML にしたものの、インデントなどで構造を把握しやすくした方が良かったかなと思います。こんな感じで: call main exit label vram_set push bp cp sp bp sub_sp 1 ... この程度なら行ごとに line.strip.split(" ") で済みそう(v2 ではそうしています)。 コード生成器・高水準言語部分 コード生成器は 600行弱。 コード生成器に与える高水準言語相当のコードは、フォーマットとしてはJSON(ただし、 // でのコメントあり)。 ["stmts" // バッファ配列用 ,["func" ,"to_vi" ,["w", "x", "y", "offset"] ,[ ["var", "vi"] // vram index ,["var", "vi_buf"] ... ,["func" // 関数名 ,"main" // 引数 ,[] // 本体 ,[ ["_debug", "start ----"] ,["var", "w"] ,["set", "w", 5] ,["var", "h"] ,["set", "h", 5] // 配列の初期化 5x5, グライダー / x, y, val ,["set", "vram[1]", 1] ,["set", "vram[7]", 1] ,["set", "vram[10]", 1] ,["set", "vram[11]", 1] ,["set", "vram[12]", 1] // メインループ ,["var", "cnt"] ,["while", ["eq", 1, 1], [ ["set", "cnt", ["+", "cnt", 1]] // gen_next_step ,["call", "gen_next_step_loop", "w", "h"] // バッファの内容で置換 ,["call", "replace_with_buf_v2"] ]] ] ] ] これを JSON.parse すれば構文木がゲットできる。ずるい。 S式ですねー C言語のような何かを想定したオレオレ 変数の型は数値のみ 中間コード生成はやってません。 素朴に構文木を辿ってアセンブリコードに変換していくだけ。 グラフィック ライフゲームなので2次元のグラフィック表示が必要 VRAM はサイズ 50 の配列で、これを半分に分けて(片方は次世代生成用のバッファ)、 それぞれを縦横 5x5 としてターミナルに print するだけ。かわいい。 ---- memory (vram) ---- ..... ..... @.@.. ..@.. .@@.. @.@.. .@... .@@.. ..... ..... VRAM といいつつ、1個の配列専用のヒープ領域みたいなものでもあり、 なんとなくずるい気がしますが、とにかくこれでやりました。 VRAM ではなくヒープ領域的なものであり、 そこを覗き見ているだけだ、ということにしてもいい? どうなんでしょう。 「VRAMっていうのはこういうものだ」という知識がないので…… ↑ この、よく分かってなさをお楽しみください…… 方針 期限は2週間 理論とかを全部理解してから作ろうとしない CPU の専門家やアセンブラの専門家やコンパイラの専門家になりたいのではない とにかく動けばいい 調べものは最小限 これは意識的にそうした。極力調べずに(=どうしても分からなかったらそのとき最低限調べる)、それまでに聞きかじったボンヤリした知識を動員して憶測で適当にやる。 寄り道しない・脇道にそれない 不可欠でない部分はさくっと捨てる 「不可欠」というのは、「今回自分が知りたいこと」「完走するのに最低限必要なもの」 「後からすげかえたりするのが難しそうなもの」くらいの意味 VM〜コード生成部分を作る 加算器部分からは作らない。前にちょっとやったので。 高級言語のパース部分はやらない。なんとなくだけど知ってるので。 汚くていい 自分が学習して満足できればよい。自分の中に知見なり経験値なりが貯まればよい。 できたものはゴミでよい。どうせずっと使い続けるものではない。 作りつつその都度ブログ書いていくのもライブ感あって良さそうとも考えたけど、これもスピードが落ちそうなのでボツ。外部公開のことは考えないことに。 途中で更新止まると悲しいし 達人プログラマーでいうところの曳光弾。あれをやる。 自分の性格的にもその方が向いてる(早く見通しを得て不安をなくして余裕を得たい) 少し例えが暴力的でしたが、この手は新規プロジェクト、特に今まで構築されたことがないようなものを実現する場合に適用することができます。あなたは射撃手のように暗闇の目標を狙わなければならないのです。元になるシステムが存在していない場合、ユーザーの要求は曖昧なものとなります。さらに、不慣れなアルゴリズム、開発技法、言語、ライブラリを使って、未知の世界に直面していくことになるのです。また、プロジェクトの完了まで長い期間がかかるため、作業環境も変化していく可能性が高いでしょう。 (達人プログラマー ピアソン・エデュケーション版 p48) 「正しく」やる時間がない人間は、重要な箇所だけに集中して枝葉を無視する。その結果、いくつかの細かい点は次のバージョンに回そうと計画する。ここで注意する点は、次のバージョンなど永久に作られないかもしれないということだ。しかし、「足りない部分は後でなんとかすればいい」と思い込むことで、脇道に迷い込むことを避けられる。それはまた、初期バージョンの細かな欠点についてのよい言い訳にもなる。 (UNIXという考え方 p34) なぜ2週間か 頓挫するから。 平日昼間は仕事だし、夜・朝もそんなに時間使えないし、土日もいろいろやることある。 これまでの経験でも、長期化するとなんか仕事が忙しくなってきて中断してそれっきりとか、他におもしろそう(で簡単そうなもの)なものを見つけてそっちをやり始めて逃避したりで結局頓挫するパターンが多くて身に染みているから。絶対頓挫する。100%頓挫する。自信を持って断言できる。 で、いったんオレオレの適当な方法で動くものを作れば、その後で「正しいやり方」の解説を見聞きしたときに「あーなるほど、似たようなことやってたわ~」とか「こうすれば良かったのかーっ」ってなるはず。それでよい。それは2週間を終えた後でよい。 とにかく頓挫だけはしたくなかった。頓挫しないことが最も重要な意思決定方針。 感想 おもしろかった!!!! 楽しかった!!!!! !!!!!!!! 十分自己満足できた(大事)!!!! コード生成~VM実行部分に対するイメージがだいぶ解像度上がった感じ 全部いっぺんに作ったの良かった たとえば既存の何か(たとえば x86 だとか GNU as だとか)に合わせて作ろうとすると調べるのに時間取られる 下位レイヤーのことを全部知ってるし、何が起こってるか全部把握できる(なぜなら自分で作ってるから) 謎挙動は発生するけど、自分が書いた1000行ちょっとのコードのどこかに必ず原因がある ダンプでも何でもできるし、なんなら下位レイヤーを自分の都合のよいように決めて変えてしまえばよい 下から上に向かって作ったの良かった 低水準から高水準へ向かう発展の歴史を辿るような流れになっていて、おもしろい 上位レイヤーがなぜこうなっているのか?(下位レイヤーによる制約) が自然な流れで分かる つねに下位レイヤーのドッグフーディングをしている感じ。 「動くものがなかなかできなくてつまらない」にはならなかった。 目標をライフゲームにしたの良かった 仕様や機能が必要かの判断で悩まなくて済む・無駄なものを作らなくて済む ライフゲームを動かすのに必要そうだったらやる。必要なさそうだったらやらない。 簡単すぎず、難しすぎず、ちょうどよい負荷(自分にとって) Turing Complete FM の内容がちょびっと分かるようになった!!気がする!!!!!!! これでやっと nand2tetris やふつパイラの入り口に立ったぞ…… 10年前の自分に送りつけて「教材作ってあげたから今すぐやれ」「Ruby とエディタとターミナルだけあればできるから」と言いたい 他の人に薦められるかというと微妙(オレオレなので) 困ったこと 教材の問題 どれが(自分にとって)良い教材なのか分からない 入門者だし、界隈の事情に詳しくないので判断基準がない 現代の最先端のもの、専門家(を目指している人)向けのものは難しすぎる いきなり 64bit の世界から始まるし 昔のCPU向けのものだと今のものより素朴で入りやすいのでは? とか、現代のものでも組み込み向けはどうかな? などと思って調べたりもしたけど、資料の探し方もよく分からないし、ある程度知識のある人向けに書かれていたりしてよく分からない(効率がとても悪い) TCFM #29 で hikalium さんが「道が見えるようになるためには一回そこに行ってみないと分からない」という話をされてましたが、ほんとそれ ブートストラップ問題 昔のものだとそもそも実行環境どうするの? ってとこから始めないといけない。 エミュレータがあっても、まずはその使い方を知らないといけない。 目移りする 「x86 でやることにしようかな」 → 「あ、なんか良さげな解説見つけた…けど ARM 向けだなあ…」みたいになってなかなかターゲットを決められない(例は適当です) 結局エイヤでオレオレすることに どのみち自分に完全にぴったりな教材は存在しないので、教材を自分で作るみたいになった 入れ子の式 x = (2 * (3 + 4)) のように入れ子になっている式を ,["var", "x"] // 変数の宣言 ,["set", "x", ["*", 2, ["+", 3, 4]]] みたいに書きたくてあれこれ検討したものの、まじめに取り組むと何日か溶けそうな気がしたため ,["var", "temp"] ,["set", "temp", ["+", 3, 4]] ,["var", "x"] ,["set", "x", ["*", 2, "temp"]] のように一時変数を使う形にして回避しました。うーん、これは悔しい。 自動テスト これはちょっとした賭けでしたが、テストコードは書かずに進めました できあがりがどういう形になるのか分からない状態でのスタートだったため、下手にテストコードを書いてしまうとそっちの修正で時間取られそうな気がして…… 既存の仕様に準拠するならともかく、今回は上から下までオレオレで、作りながら仕様が変わっていくと予想された(実際はあまり変わらなかったけど、それも作ってみないと分からないので) バグが出ると「やっぱり書いておけばよかったかな……」とは思ったものの、結果的にはなんとかなった 運が良かった とはいえ、まったくの無策だった訳ではなく、↓ のようなことはしていました 動作確認程度のちょっとしたものはその都度書いたり 生成されたアセンブリコード・機械語コードのファイルも git のトラッキング対象にしておいて、 壊れていないことを git diff で確かめたり どういう状態から始めたか CPU CPU のことをよく知らなかったので 2016年末~2017年頭頃に何冊か流し読みして、半加算器動かして足し算できる、みたいなのは書いてみた(この頃はアセンブラ、コード生成器まで作る気はなかった)。 マンガでわかるCPU 自作エミュレータで学ぶx86アーキテクチャ 達人出版会 CPUの創りかた 30日でできる! OS自作入門 達人出版会 CPUの本ではないですがなんとなく流れでこの頃に読みました。 アセンブリ部分もあるし今回のをやるのに参考になっていると思います。 ※なので、2週間でまったくのゼロから始めたわけではなくて、構想期間が1,2年くらいあり、その間に本やネットであれこれ読んだりいろいろ妄想したりしてました。その上で、考えても埒が明かないのでやっぱり実際に書いてみよう! となった、という流れです。 アセンブリ 聞きかじりでなんとなく知ってるくらい。 アセンブリ言語でプログラムを書いたことはない。 mov があって比較とジャンプとかするらしいぞ、くらい。 なるほど分かったぞそれを組み合わせればいろいろできるんだな!? くらいの認識。 コンパイラ パーサ部分はなんとなくは知ってる だいぶ前に読んだ(両方ともさらっと読んだだけで実際には自分で書いたりしてない)(どっちも青木さんの本ですね): Rubyを256倍使うための本 無道編 ふつうのコンパイラをつくろう これを読んだときは構文解析部分に興味があったので後半(コード生成の部分)は読んでなかった気がする BNFで文法定義して構文木作って何かやるんでしょ? …何かというのはつまり、えーとアセンブリ言語のコードを生成する(?)んですよね、 で、具体的にどうやるかというと…(分からない という感じ。つまり、半加算器~構文木の間で何が起こっているのかが分かっていなかった。 C言語どのくらい知ってる? 学生の頃は趣味プログラムをCで書いてた 十数年前なのでもうだいぶ忘れた 当時入門書として読んでいたのは前橋さんの C言語 体当たり学習 徹底入門 で、この本+αくらいの知識 OpenGL で3Dグラフィック描いたりとかはしてたけど クリティカルなメモリ管理が必要なものはあまり書いてなかったはず その後 Ruby を使うようになり、 趣味プログラムの99%くらいは Ruby で用が足りるのでC言語で書かなくなった 元ネタ・きっかけなど nand2tetris https://www.nand2tetris.org/ 日本語版の書籍は コンピュータシステムの理論と実装 企画というか発想の元はこれ たしか 2016年頃に知って、読んだ 最初に nand2tetris のコンセプトを知ったときは、 2週間〜1ヶ月くらい根詰めればどうにかなるかなあと甘い期待を抱いたけど無理で、 流し読みだけでやめてしまっていた もっと簡略化した教材かなと思ってたけど、 思ったよりきっちり進めてくスタイルの、教科書っぽい本だった 問題をもっと簡単にしてハードルを下げまくらないといけない ライフゲームならテトリスより簡単そう(ユーザからの入力もない)だし、簡単な割には面白い動きが生成されるので自己満足度高そう。「ライフゲームが動いたら嬉しいだろうなぁ…」と、完成したときの満足感をモワモワ~と妄想しつつ…… rebuild.fm るいさんゲスト回(2016-08-09) 40分あたりから 8cc の話。インクリメンタルに作ると良いという話。これを聞いたとき、やっぱりそうですよねえ、そうだろうなあと思った記憶があります。明らかに当企画のきっかけの一つです。 その後 Turing Complete FM が始まり、なんか楽しそうなのでやってみたい! という気分が盛り上がりました。 TODO 時間とやる気が揃ったら続きをやりたい。下記は「今回やってないこと」「今回捨てた・削ったもの」のリストでもあります。 nand から作って nand2gol にする (2020-05-03) 1bit CPU 作るところまではやりました: リレー式論理回路シミュレータを自作して1bit CPUまで動かした VM [bp+1] とかを VM が直接(正規表現で…)解釈しているので、もうちょっと機械語っぽく 高級言語・コンパイラ部分 入れ子の式の計算をできるようにする 今回回避してしまったので、最低限ここまではちゃんとやりたい (2019-12-15) できるようにしました 配列 構造体 ポインタ 再帰 型 字句解析・構文解析(ちゃんとCっぽいフォーマットのソースをパースする) (2020-05-04) パーサも作りました グラフィック あると楽しい リンカ OS 難しそう いろいろ削りまくればあるいは……? ざっくり工程 まず簡単なVM 30行くらいの簡単なものからスタート メモリにプログラムを直接書く ファイルからロードするようにする 機械語コード書きつつ 命令を追加していく アセンブラを作ってラベルを実アドレスに変換 条件分岐、ループ、サブルーチンなど、必要そうなものを用意 ある程度できてきたらコード生成器を作りだす 変数、条件分岐、ループ、関数などをアセンブリコードに変換 部品が揃ってきたらライフゲームを動かす ざっくり書くとこんな感じですが、実際は行きつ戻りつしていて、終盤でもライフゲーム書きながらVM部分を修正したりしてました。 こちらで製作過程を書いていますので詳しくはそっちを見ていただければと思います。 その他の参考書籍など v1 を作る前〜作っている時に読んだもの つくって学ぶプログラミング言語 RubyによるScheme処理系の実装 - 達人出版会 RubyでつくるRuby ゼロから学びなおすプログラミング言語入門(紙書籍+PDF版) – 技術書出版と販売のラムダノート 32ビットコンピュータをやさしく語る はじめて読む486【委託】 - 達人出版会 ひとりでCPUとエミュレータとコンパイラを作る Advent Calendar 2017 - Qiita 下の方が半導体から始まっていて、すごいなーと思いながら読んでいました。 > 最適化は理解の敵 くじけちゃいけない! マシン語入門 平塚憲晴 : 平塚憲晴 : Free Download, Borrow, and Streaming : Internet Archive MSX v1 を作った後に読んだもの ※流し読み含む 低レイヤを知りたい人のためのCコンパイラ作成入門 速攻MinCamlコンパイラ概説 Compiler basics (1): lisp to assembly | notes.eatonphil.com JavaScript Rubyのしくみ Ruby Under a Microscope | Ohmsha この本に限らず、Ruby の VM の話もうっすら分かるようになって良かった Rubyで作る奇妙なプログラミング言語 プレミアムブックス版 | マイナビブックス 達人出版会 2019-03 頃に読んだ この本の存在は昔から知ってはいましたが、 esoteric language というのは上級者の遊戯であって自分にはハードルが高そうというイメージがあり、 なんとなく読まずに過ごしてきました。 しかし、今実際に読んでみると、難解そうに見えるのは表面だけで、 中身は良い意味で普通の(汎用的な、使い回しの効く)言語処理系の話です。 コードもコンパクトですし、もっと早く読んでいればよかった。 先に読んで写経でもしていれば、 vm2gol の内容ももう少し違ったものになっていたかもしれません。 ブックマーク はてなブックマーク - CPUに関するsonota88のブックマーク はてなブックマーク - Compilerに関するsonota88のブックマーク はてなブックマーク - Assemblyに関するsonota88のブックマーク はてなブックマーク - Emulatorに関するsonota88のブックマーク 他に Ruby 関連で書いたもの https://memo88.hatenablog.com/entry/2019/05/04/232236 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsでメールが急に送れなくなった時の解決法

どうも、三町哲平です! 夜中3時にふと手動でポートフォリオのテストをしようと思い投稿機能やログイン機能などをいじり倒していたのですがその時にアカウントの新規登録をしようと思いました。 今までだと、ユーザー名/メールアドレス/パスワード/再度パスワードを入力して新規登録するボタンを押したら、認証メールが登録したメールアドレスへ送られて、そのメールのリンクをクリックすることでユーザー登録完了な訳なのですが、なぜか今回はメールが送れない... ちなみに出先だった為、本番環境で試していたのですが、 新規登録するをポチッ ・・・ このページは表示できません ちなみにこのページは、500 Internal Server Errorの表示なので、Webサーバーに何らかの問題が発生した際のエラーになります。 今回は、このエラーの解決までをまとめました。 ※新規登録は、下記の内容で登録 ユーザー名: test メールアドレス: f●●●●●●@rakuten.jp エラーを調べよう 今回発生した500 Internal Server Errorですが、あくまでこの表示は本番環境になりますので、エラー表示はデフォルトで非表示になっています。よって詳細までは分かりません。 ここで詳細を調べようと思ったら、開発環境で試してみる事が一番なのかもしれませんが、あくまで出先で本番環境しか触れない中で試せる事といったら、似たような動きをする所を手当たり次第次第調べていくことになりますが、今回の事象で考えられる仮説は、 deviseのgemがおかしな挙動をしているのではないか? メール機能が使えなくなっているのではないか? 上記の2点が思い付いたので一つずつ仮説を試していきましょう! 1. deviseのgemがおかしな挙動をしているのではないか? まず、deviseの挙動がおかしくないか調べてみますが、今回やったのは、 ① 既に登録しているアカウントでログインしてみる テストユーザーでログインできました! ② 今回、新規登録したユーザーが仮登録されているか調べる test(新規登録したユーザー)が一覧に仮登録されています! deviseは、問題無いですね。 2. メール機能が使えなくなっているのではないか? メール機能が使えるかどうか調べるには、他にメールを使用している機能を試してみたら分かりますね。 ① 新規登録したアカウントの確認メールを送ってみる ⬇︎ エラーが発生! ② 今回、新規登録したユーザーが仮登録されているか調べる ⬇︎ またしてもエラーが発生! どうも本番環境で、メールを送れなくなっているみたいです。 開発環境で調べる ここからは、自宅に帰って本番環境で行った対応と同じ対応をしましたが結果は、本番環境も開発環境も同じタイミングでエラーが発生しました。 しかし、本番環境と開発環境の大きな違いは、エラー内容を詳細に見れることです。 ちなみに今回のエラー内容の詳細は、 初めて見るエラーですね...。 とにかくわからないエラーに関しては、ググってみるに限るのでググってみました。 535-5.7.8 Username and Password not accepted. Learn more atで、検索 上記の記事がヒットしたので、記事を参考にエラー解決を試みました。 ...が、エラー解決には至りませんでしたが、答えは実は単純でした。 Username and Password not acceptedを翻訳してみると、ユーザー名とパスワードが受け入れられませんと日本語化されます。 ユーザー名とパスワードは、絶対登録してあるし、誤字脱字もない筈なのに...と思いポートフォリオで使用していたgmailのGoogleアカウントのセキュリティからアプリ パスワードの項目を見てみると、 なしって書いているんですが... つまりアプリ パスワードが登録していないことになっていました。 アプリ パスワードを消した心当たりが無いのに、何で消えているのかはこの時直ぐには分からなかったのですが、これしか考えられないということで、アプリ パスワードを再設定し直して、環境変数に追加後、開発環境でお問い合わせフォームを使用してみると、 お問い合わせいただきありがとうございました。 無事、お問い合わせ機能使用出来ていますね! ※その他、最初の本題であった新規登録や確認メールも無事復旧しました。 新規登録をしてみましょう! ⬇︎ 本人確認用のメールを送信しました。メール内のリンクからアカウントを有効化させてください。 出来ましたね! 認証メールも問題無く届いていました! この後、開発環境だけではなく本番環境でも試しましたが共に正常に動くようになりました。 良かったです。 さいごに メールは送れるようになりましたが、結局なぜ一度登録していたはずのアプリ パスワードが消えていたのか!?その問題は解決していませんが、これに関しては実はこの記事の中にヒントががありました。 ↑一度使用したこの画像です。 画像のこの部分です。 前回の変更: 5月30日 1週間前にGoogleアカウントのパスワードをセキュリティ対策の為、再設定していました。(この記事のエラー発生は、6月6日) 100%これが原因とまでは言い切れませんが、変化点と言えばここしかないのでほぼ100%でこれが原因と言って間違い無いでしょう。 次回、パスワード変更時に確認が必要ですね。 Googleパスワードを再設定すると、アプリ パスワードが自動的に削除される。 ひとまず解決です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsでメールが送れなくなった時の解決法

どうも、三町哲平です! 夜中3時にふと手動でポートフォリオのテストをしようと思い投稿機能やログイン機能などをいじり倒していたのですがその時にアカウントの新規登録をしようと思いました。 今までだと、ユーザー名/メールアドレス/パスワード/再度パスワードを入力して新規登録するボタンを押したら、認証メールが登録したメールアドレスへ送られて、そのメールのリンクをクリックすることでユーザー登録完了な訳なのですが、なぜか今回はメールが送れない... ちなみに出先だった為、本番環境で試していたのですが、 新規登録するをポチッ ・・・ このページは表示できません ちなみにこのページは、500 Internal Server Errorの表示なので、Webサーバーに何らかの問題が発生した際のエラーになります。 今回は、このエラーの解決までをまとめました。 ※新規登録は、下記の内容で登録 ユーザー名: test メールアドレス: f●●●●●●@rakuten.jp エラーを調べよう 今回発生した500 Internal Server Errorですが、あくまでこの表示は本番環境になりますので、エラー表示はデフォルトで非表示になっています。よって詳細までは分かりません。 ここで詳細を調べようと思ったら、開発環境で試してみる事が一番なのかもしれませんが、あくまで出先で本番環境しか触れない中で試せる事といったら、似たような動きをする所を手当たり次第調べていくことになりますが、今回の事象で考えられる仮説は、 deviseのgemがおかしな挙動をしているのではないか? メール機能が使えなくなっているのではないか? 上記の2点が思い付いたので一つずつ仮説を試していきましょう! 1. deviseのgemがおかしな挙動をしているのではないか? まず、deviseの挙動がおかしくないか調べてみますが、今回やったのは、 ① 既に登録しているアカウントでログインしてみる テストユーザーでログインできました! ② 今回、新規登録したユーザーが仮登録されているか調べる test(新規登録したユーザー)が一覧に仮登録されています! deviseは、問題無いですね。 2. メール機能が使えなくなっているのではないか? メール機能が使えるかどうか調べるには、他にメールを使用しているところを試してみたら分かりますね。 ① 新規登録したアカウントの確認メールを送ってみる ⬇︎ エラーが発生! ② 今回、新規登録したユーザーが仮登録されているか調べる ⬇︎ またしてもエラーが発生! どうも本番環境で、メールを送れなくなっているみたいです。 開発環境で調べる ここからは、自宅に帰って本番環境で行った対応と同じ対応をしましたが結果は、本番環境も開発環境も同じタイミングでエラーが発生しました。 しかし、本番環境と開発環境の大きな違いは、エラー内容を詳細に見れることです。 ちなみに今回のエラー内容の詳細は、 初めて見るエラーですね...。 とにかくわからないエラーに関しては、ググってみるに限るのでググってみました。 535-5.7.8 Username and Password not accepted. Learn more atで、検索 上記の記事がヒットしたので、記事を参考にエラー解決を試みました。 ...が、エラー解決には至りませんでした。 しかし、Username and Password not acceptedを翻訳してみると、ユーザー名とパスワードが受け入れられませんと日本語化されます。 ユーザー名とパスワードは、絶対登録してあるし、誤字脱字もない筈なのに...と思いポートフォリオで使用していたgmailのGoogleアカウントのセキュリティからアプリ パスワードの項目を見てみると、 なしって書いているんですが... つまりアプリ パスワードが登録していないことになっていました。 アプリ パスワードを消した心当たりが無いのに、何で消えているのかはこの時直ぐには分からなかったのですが、これしか考えられないということで、アプリ パスワードを再設定し直して、環境変数に追加後、開発環境でお問い合わせフォームを使用してみると、 お問い合わせいただきありがとうございました。 無事、お問い合わせ機能使用出来ていますね! ※その他、最初の本題であった新規登録や確認メールも無事復旧しました。 新規登録をしてみましょう! ⬇︎ 本人確認用のメールを送信しました。メール内のリンクからアカウントを有効化させてください。 出来ましたね! 認証メールも問題無く届いていました! この後、開発環境だけではなく本番環境でも試しましたが共に正常に動くようになりました。 良かったです。 さいごに メールは送れるようになりましたが、結局なぜ一度登録していたはずのアプリ パスワードが消えていたのか!?その問題は解決していませんが、これに関しては実はこの記事の中にヒントががありました。 ↑一度使用したこの画像です。 画像のこの部分です。 前回の変更: 5月30日 1週間前にGoogleアカウントのパスワードをセキュリティ対策の為、再設定していました。(この記事のエラー発生は、6月6日) 100%これが原因とまでは言い切れませんが、変化点と言えばここしかないのでほぼ100%でこれが原因と言って間違い無いでしょう。 次回、パスワード変更時に確認が必要ですね。 Googleパスワードを再設定すると、アプリ パスワードが自動的に削除される。 ひとまず解決です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【エラー対応】Railsでメールが送れなくなった時の解決法

どうも、三町哲平です! 夜中3時にふと手動でポートフォリオのテストをしようと思い投稿機能やログイン機能などをいじり倒していたのですがその時にアカウントの新規登録をしようと思いました。 今までだと、ユーザー名/メールアドレス/パスワード/再度パスワードを入力して新規登録するボタンを押したら、認証メールが登録したメールアドレスへ送られて、そのメールのリンクをクリックすることでユーザー登録完了な訳なのですが、なぜか今回はメールが送れない... ちなみに出先だった為、本番環境で試していたのですが、 新規登録するをポチッ ・・・ このページは表示できません ちなみにこのページは、500 Internal Server Errorの表示なので、Webサーバーに何らかの問題が発生した際のエラーになります。 今回は、このエラーの解決までをまとめました。 ※新規登録は、下記の内容で登録 ユーザー名: test メールアドレス: f●●●●●●@rakuten.jp エラーを調べよう 今回発生した500 Internal Server Errorですが、あくまでこの表示は本番環境になりますので、エラー表示はデフォルトで非表示になっています。よって詳細までは分かりません。 ここで詳細を調べようと思ったら、開発環境で試してみる事が一番なのかもしれませんが、あくまで出先で本番環境しか触れない中で試せる事といったら、似たような動きをする所を手当たり次第調べていくことになりますが、今回の事象で考えられる仮説は、 deviseのgemがおかしな挙動をしているのではないか? メール機能が使えなくなっているのではないか? 上記の2点が思い付いたので一つずつ仮説を試していきましょう! 1. deviseのgemがおかしな挙動をしているのではないか? まず、deviseの挙動がおかしくないか調べてみますが、今回やったのは、 ① 既に登録しているアカウントでログインしてみる テストユーザーでログインできました! ② 今回、新規登録したユーザーが仮登録されているか調べる test(新規登録したユーザー)が一覧に仮登録されています! deviseは、問題無いですね。 2. メール機能が使えなくなっているのではないか? メール機能が使えるかどうか調べるには、他にメールを使用しているところを試してみたら分かりますね。 ① 新規登録したアカウントの確認メールを送ってみる ⬇︎ エラーが発生! ② 今回、新規登録したユーザーが仮登録されているか調べる ⬇︎ またしてもエラーが発生! どうも本番環境で、メールを送れなくなっているみたいです。 開発環境で調べる ここからは、自宅に帰って本番環境で行った対応と同じ対応をしましたが結果は、本番環境も開発環境も同じタイミングでエラーが発生しました。 しかし、本番環境と開発環境の大きな違いは、エラー内容を詳細に見れることです。 ちなみに今回のエラー内容の詳細は、 初めて見るエラーですね...。 とにかくわからないエラーに関しては、ググってみるに限るのでググってみました。 535-5.7.8 Username and Password not accepted. Learn more atで、検索 上記の記事がヒットしたので、記事を参考にエラー解決を試みました。 ...が、エラー解決には至りませんでした。 しかし、Username and Password not acceptedを翻訳してみると、ユーザー名とパスワードが受け入れられませんと日本語化されます。 ユーザー名とパスワードは、絶対登録してあるし、誤字脱字もない筈なのに...と思いポートフォリオで使用していたgmailのGoogleアカウントのセキュリティからアプリ パスワードの項目を見てみると、 なしって書いているんですが... つまりアプリ パスワードが登録していないことになっていました。 アプリ パスワードを消した心当たりが無いのに、何で消えているのかはこの時直ぐには分からなかったのですが、これしか考えられないということで、アプリ パスワードを再設定し直して、環境変数に追加後、開発環境でお問い合わせフォームを使用してみると、 お問い合わせいただきありがとうございました。 無事、お問い合わせ機能使用出来ていますね! ※その他、最初の本題であった新規登録や確認メールも無事復旧しました。 新規登録をしてみましょう! ⬇︎ 本人確認用のメールを送信しました。メール内のリンクからアカウントを有効化させてください。 出来ましたね! 認証メールも問題無く届いていました! この後、開発環境だけではなく本番環境でも試しましたが共に正常に動くようになりました。 良かったです。 さいごに メールは送れるようになりましたが、結局なぜ一度登録していたはずのアプリ パスワードが消えていたのか!?その問題は解決していませんが、これに関しては実はこの記事の既出画像の中にヒントががありました。 ↑一度使用したこの画像です。 画像のこの部分です。 前回の変更: 5月30日 1週間前にGoogleアカウントのパスワードをセキュリティ対策の為、再設定していました。(この記事のエラー発生は、6月6日) 100%これが原因とまでは言い切れませんが、変化点と言えばここしかないのでほぼ100%でこれが原因と言って間違い無いでしょう。 次回、パスワード変更時に確認が必要ですね。 Googleパスワードを再設定すると、アプリ パスワードが自動的に削除される。 ひとまず解決です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ActiveRecordやRubyの!?について

絶賛Rails勉強中なので適当に使っていた!や?をまとめます ActiveRecordの! create,save,update,destroy等のメソッドの後ろに!をつけた場合、それぞれのCRUD処理が失敗した場合に例外(ActiveRecord::RecordNotFound)を返すようになります。 !を付けない場合は成功した場合true、失敗した場合falseが返却されます。 Rubyの! rubyのメソッドに!をつけた場合は「破壊的メソッド」になるそうです。なに言ってんのかわかんないですね。 破壊的メソッドとは? この方の記事がわかりやすかったです。 https://qiita.com/tentom/items/0164b68dff94702e3880 破壊的メソッドにした場合はオブジェクトが再生成されます。 非破壊的メソッどはオブジェクトは変わらないまま値だけが変わります。 ? メソッドに?を付けることで真偽値を返すメソッドだということを直感的に表すことができます。 つけなくても真偽値が返ってくることには変わりありませんが、付けた方がわかりやすくなっていいと思います。 メソッドに?を付けた場合、メソッドを呼び出すときにも?を付ける必要があります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む