20210512のRailsに関する記事は25件です。

[Ruby]リフレクションマスターになろう!

リフレクションとは? プログラム実行中に、そのプログラムの情報を取得したり操作したりすること。 デバッグや、正しく定義できてるかの確認など開発をする上で欠かせないものだと思うので、きっちり使えるようにした方が開発スピードは向上します。 オブジェクトについて調べる オブジェクトが持つインスタンス変数にアクセス class Foo def initialize @foo = 1 @bar = 2 end end foo = Foo.new # インスタンス変数名の確認 foo.instance_variables #=>[:@foo, :@bar] # インスタンス変数の存在確認(@は必須) foo.instance_variable_defined?(:@foo) #=> true # インスタンス変数の値を参照 foo.instance_variable_get(:@foo) #=> 1 # インスタンス変数の値を更新(存在しない場合は新しく定義) foo.instance_variable_set(:@foo, 2) #=> 2 オブジェクトが持つメソッドにアクセス # 親クラスを定義 class ParentClass def super_public; end private def super_private; end protected def super_protected; end end # 子クラスを定義 class ChildClass < ParentClass def sub_public; end def hello puts 'Hello' end private def sub_private; end protected def sub_protected; end end # 特異クラスを定義 def Child.new.singleton; end child = Cgild.new # プライベートメソッド以外を取得 child.methods #=> [:singleton, :sub_public, sub_protected, ...] # パブリックメソッドを取得 child.public_methods #=> [:singleton, :sub_public, ...] # プライベートメソッドを取得 child.private_methods #=> [:sub_private, :super_private, ...] # プロテクテッドメソッドを取得 child.protected_methods #=> [:sub_protected, :super_protected] # 特異メソッドを取得 child.singleton_methods #=> [:singleton] child.methods(false) #=> [:singleton] # メソッドが定義されている確認 child.respond_to?(:sub_public) #=> true # メソッドの呼び出し child.send(:hello) #=> "Hello" クラスについて調べる クラスが持つ値にアクセス class Foo @@foo = 'foo' FOO = 'foo' def bar @@bar = 'bar' end end # クラス変数を取得する Foo.class_variables #=> [:@@foo] # クラス変数が定義されているか確認(@@必須) Foo.class_variable_defined?(@@foo) #=> true # クラス変数の値を取得 Foo.class_variable_get(@@foo) #=> "foo" # クラス変数に値を設定(存在しない場合は新しく作成) Foo.classvariable_set(@@foo, 'set_foo') #=> "set_foo" # クラス内に定義されている定数を確認 Foo.constants #=> [:FOO] # 定数の値を取得する Foo.const_get(:FOO) #=> "foo" # 定数に値を設定(存在しない場合は新しく作成) Foo.const_set(:FOO, 'set_foo') #=> "set_foo" クラスが持つメソッドにアクセス # 親クラスを定義 class ParentClass def super_public; end private def super_private; end protected def super_protected; end end # 子クラスを定義 class ChildClass < ParentClass def sub_public; end def hello puts 'Hello' end private def sub_private; end protected def sub_protected; end end # プライベートメソッド以外のインスタンスメソッドを取得 ChildClass.instance_methods #=> [:sub_public, :sub_protected, :super_public, ...] # パブリックメソッドを取得 ChildClass.public_instance_methods #=> [:sub_public, :super_public, ...] # プライベートメソッドを取得 ChildClass.private_instance_methods #=> [:sub_private, :super_private, ...] # プロテクテッドメソッドを取得 ChildClass.protected_instance_methods #=> [:sub_protected, super_protected] # 指定されたメソッドがプライベートメソッド以外に定義されているか確認 ChildClass.method_defined?(:sub_public) #=> true # 指定されたメソッドがパブリックメソッドに定義されているか確認 ChildClass.public_method_defined?(:sub_public) #=> true # 指定されたメソッドがプライベートメソッドに定義されているか確認 ChildClass.private_method_defined?(:sub_private) #=> true # 指定されたメソッドがプロテクテッドメソッドに定義されているか確認 ChildClass.protected_method_defined?(:sub_protected) #=> true # クラスの継承構造を参照 ChildClass.ancestors #=> [ChildClass, ParentClass, ...] # スーパークラスを参照 ChildClass.superclass #=> ParentClass おわりに 「リフレクション」という聞き慣れない言葉ではあったかと思いますが、内容は結構日頃から似たようなことをしているかと思います。 いろんな操作があるので、必要な情報を取得したり設定できるようにしておきましょう!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails DM機能 メッセージ編

class MessagesController < ApplicationController before_action :authenticate_user!, :only => [:create] def create #room_idには@room.idを渡される(二人がDM時に開いているルームのID) if Entry.where(:user_id => current_user.id, :room_id => params[:message][:room_id]).present?  #ログインしているユーザーの、二人がDM時に開いているルームに入った情報があるか[:message]には作ったメッセージに渡されたルームIDとコンテンツが入っている @message = Message.create(params.require(:message).permit(:user_id, :content, :room_id).merge(:user_id => current_user.id)) redirect_to "/rooms/#{@message.room_id}" else redirect_back(fallback_location: root_path) end end end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsにてlink_toの文字色を変える

link_toにて各リンク先を作成したが、デフォルトの色が青色なのでこの色を変えたいと思います。 これはスキンケア用品に関するクチコミサイトですが、色合いがおかしいです。 やり方は簡単で、link_toの末尾にclass指定して、cssで変更します。 view <%= link_to category.category_name, categories_path(category_id: category.id) %> これを、 <%= link_to category.category_name, categories_path(category_id: category.id, class:link) %> scss .link { text-decoration: none; color: white ; } これで文字色の指定ができます。 文字色以外もこのscss上で編集できます。 以上、link_to内にclassを作るのがポイントでした。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails でDM機能 rooms編

rooms コントローラー class RoomsController < ApplicationController before_action :authenticate_user! def create @room = Room.create  エントリー情報は一つのルームにつき二つ @entry1 = Entry.create(:room_id => @room.id, :user_id => current_user.id) #ログインしているユーザーのエントリー情報を登録 @entry2 = Entry.create(params.require(:entry).permit(:user_id, :room_id).merge(:room_id => @room.id)) #showで受け取った情報 redirect_to "/rooms/#{@room.id}" #roomのshowへ end def show @room = Room.find(params[:id]) #二通りのもらい方 新しいのか前のか if Entry.where(:user_id => current_user.id, :room_id => @room.id).present? #エントリー情報があるか @messages = @room.messages #ルームが持つメッセージ @message = Message.new  @entries = @room.entries #ルームはエントリーを二つもっている。 else #なければ違う人のルームに入ろうとしているということ redirect_back(fallback_location: root_path) end end end ルームのshowビュー <% @entries.each do |e| %> 二つのエントリー情報をもとに、アソシエーションつたってIDとe-mailを取得 <% end %> <% if @messages.present? %> #messagesはルームにあるすべてのメッセージ <% @messages.each do |m| %> <%= m.content %> by メッセージはまだありません <% end %> <%= form_for @message do |f| %> # @messageはカラのメッセージ <%= f.text_field :content, :placeholder => "メッセージを入力して下さい" , :size => 70 %> <%= f.hidden_field :room_id, :value => @room.id %> <%= f.submit "投稿する" %> <% end %> <%= link_to "ユーザー一覧に戻る", users_path %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby技術者認定試験silverで中々覚えにくいやつメモ

表題の通りです。Rubysilver取得の学習中ですが、なんか覚えにくいなあというものをここに吐き出せばきっと覚えられるんじゃないか、と思ったので。 RExとかで9割超えてきたし、合格教本の問題も9割くらいできるようになったのでそろそろ受けようかなと思っているが、まあ最後の詰めです。 Stringオブジェクト strip 文字列の先頭と末尾の空白文字(\t\r\n\f\v)を取り除く。 chomp 末尾から改行コードを取り除く。 chop 末尾の文字を取り除きます。ただし、文字列の末尾が"\r\n"であれば、2文字とも取り除く。 上記について破壊的に実行したい場合は!をつける Arrayオブジェクト pop selfの末尾より1要素を取り出す(LIFO)。 push | selfの末尾に引数の値を追加します(LIFO)。 unshift | selfの先頭へ引数の値を追加します(FIFO)。引数が指定されていない場合は何もしない shift | selfの先頭より1要素を取り出します(FIFO)。 上記はすべて破壊的に実行される。 ファイル操作関連 File.openの2番目の引数について r 読み込み。 w 書き込み。既存ファイルの場合はファイルの内容が空になる a 追記。追加位置はファイルの末尾 r+ 読み書き。ファイルの読み書き位置は先頭 w+ 読み書き。既存ファイルの場合はファイルの内容が空になる a+ 読み書き。ファイルの読み込み位置は先頭、書き込み位置は末尾 ファイルポインタのseekメソッドの引数について IO::SEEK_SET ファイルの先頭からの位置を表す IO::SEEK_CUR 現在のファイルポインタからの位置を表す IO::SEEK_END ファイルの末尾からの位置を表す
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsでDM機能搭載 userコントローラー&ビュー

忘れないための備忘録です。 railsのDM機能 (参考:https://qiita.com/nojinoji/items/2b3f8309a31cc6d88d03) アソシエーションは ルームとユーザーが エントリーとメッセージに対して1:多の関係 まずユーザーコントローラーのShow def show @user=User.find(params[:id]) #詳細ページのメイン(DMしたい相手) @currentUserEntry=Entry.where(user_id: current_user.id) #ログインしている人が入っているルーム情報 @userEntry=Entry.where(user_id: @user.id) #詳細ページの人が入っているルーム情報 if @user.id == current_user.id #ログインしている人が自分のページを見ている else @currentUserEntry.each do |cu| #二人のルーム情報の中に @userEntry.each do |u| if cu.room_id == u.room_id then #二人のルームがあるなら @isRoom = true   @roomId = cu.room_id #この時にはcu,uともにお互いのルーム end end end if @isRoom #ないなら else @room = Room.new @entry = Entry.new end end end user/show.html.erb のビュー <% if @user.id == current_user.id %>  #自分が自分のマイページをみている <% else %> <% if @isRoom == true %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyのハッシュでno implicit conversion of symbol into integerがでたがintegerでなく自分のミスだった件について

はじめに 今回は初のRubyの記事を書きたいと思います。 Rubyは記事がたくさんあるイメージですが、この問題の原因はみつからなかったため記事にします。 いろいろな言語をやっていたりすると起きるミスなのかなとも思いました。 問題 以下のハッシュを定義して"サイン"を表示しようとしました。 def subject { math: { sin: "サイン", cos: "コサイン", tan: "タンジェント", }, science: { viology: "植物学", biology: "生物学", } } } # サインと表示したい select = subject[:math[:sin]] すると以下のエラーが発生します。 no implicit conversion of symbol into integer 文字列にしろとでているので、subject["math"["sin"]]など確かめたが表示されませんでした。 解決策 そもそもハッシュの表示の仕方を間違っていました。 エラー文が原因を表していると思っていましたが、それ以前の問題でした。 以下のコードで動きました。 select = sebject[:math][:sin] puts(select) # サインと表示される この原因によるエラーのほうが多いかも 先ほども述べましたが、このエラーが出た場合は多くは文字型で指定すると治るかと思います。 def subject { "math": { "sin": "サイン", "cos": "コサイン", "tan": "タンジェント", }, "science": { "viology": "植物学", "biology": "生物学", } } } # keyを文字型で定義している (ex "math"など) エラー通り文字型でハッシュを表示します。 シンボルでなく文字列で指定しなければならないので以下のようにする select = subject["math"]["sin"] さいごに エラーの内容に気を取られて、文字列にしたりシンボルにしたりを繰り返していましたが、まさかの表記の仕方自体が違っていたことにはなかなか気づきませんでした。リストやらハッシュやらわからなくなることがあります。エラー文を気にする前に表記が正しいか確認するのも大事です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

モデル単体テストコードの書き方

はじめに プログラミング初学者のため、自分の理解できている範囲内で言語化しています。 何か間違っている情報や改善点などありましたら、コメントいただけますと幸いです。?‍♂️ 手順 導入/設定 ・ RSpecの導入 ・ FactoryBotの導入 下準備 ・ テストコード記述ファイルの作成 FactoryBot ・ ファクトリーボットファイル記述 Spec ・ テストコード記述 実装 導入/設定 ポイントは:development,:testのところに書くこと Gemfile group :development, :test do gem 'factory_bot_rails' gem 'rspec-rails', '~> 4.0.0' end の後にbundle installを忘れない 次に、RSpecのインストール ターミナル % rails g rspec:install ここでインストールができないなどのエラーが出る場合はこの記事を参照 https://qiita.com/tech_hack_Rich/items/c8e12f5d9d3e9d677ed7 次に、テスト結果をターミナルで可視化するための設定をする .rspec --require spec_helper --format documentation ---追記する これで導入と設定は完了 下準備 テストコードファイルの作成 ターミナル % rails g rspec:model user これでテストコードを書くためのファイルを作成する、 同時にfactorybotのファイルもここで生成される 下準備OK FactoryBot こんな感じで記述 spec/factories/users.rb FactoryBot.define do factory :user do nickname {'test'} email {'test@example'} password {'000000'} password_confirmation {password} end end Spec spec/models/user_spec.rb require 'rails_helper' RSpec.describe User, type: :model do before do @user = FactoryBot.build(:user) end describe 'ユーザー新規登録' do it 'nicknameが空では登録できない' do @user.nickname = '' @user.valid? expect(@user.errors.full_messages).to include "Nickname can't be blank" end it 'emailが空では登録できない' do @user.email = '' @user.valid? expect(@user.errors.full_messages).to include "Email can't be blank" end end end 完成?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【RSpec】基本的な書き方

はじめに この記事は、使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」を参考(引用)してます。 describe / it / expect の役割 RSpecでよく使うdescribe, it, expectについて。 describe テストのグループ化 ・ describeはテストのグループ化を宣言する。 ・ describeの中では複数のexample(it end ... do)を記述できる。 it テストをexampleにまとめる ・ itはテストをexampleという単位にまとめる役割をする。 ・ it do ... endの中のエクスペクテーション(期待値と実際の値の比較)が全てパスすればそのexampleはパスしたことになる。 ・ it do ... endのなかには一つのエクスペクテーション(expect)を描く。(原則なので破っても良い) expect エクスペクテーション itの部分で述べたエクスペクテーションがexpectにあたり、 expect(X).to eq Yのような形で描く。 使えると便利なcontext / before context contextは条件でグループ化するときに使う。 RSpec.describe User do describe '#greet' do context '12歳以下の場合' do it 'ひらがなで答えること' do user = User.new(name: 'たろう', age: 12) expect(user.greet).to eq 'ぼくはたろうだよ。' end end context '13歳以上の場合' do it '漢字で答えること' do user = User.new(name: 'たろう', age: 13) expect(user.greet).to eq '僕はたろうです。' end end end end before ・ beforeは共通の前準備をする時に使える。 ・ before do ... endに記述したものは、  example(it do ... end)の実行前に呼ばれる。 ・ beforeはテスト実行前の共通処理やデータのセットアップ等を行うことが多い。 RSpec.describe User do describe '#greet' do before do @params = { name: 'たろう' } end context '12歳以下の場合' do it 'ひらがなで答えること' do user = User.new(@params.merge(age: 12)) expect(user.greet).to eq 'ぼくはたろうだよ。' end end context '13歳以上の場合' do it '漢字で答えること' do user = User.new(@params.merge(age: 13)) expect(user.greet).to eq '僕はたろうです。' end end end end beforeではインスタンス変数を使用している。 これはbeforeとitでは変数のスコープが異なるため。 ネストしたdescribe, contextの中でのbefore beforeはdescribeやcontextごとに用意できる。 呼ばれる順番は、親、子の順。 RSpec.describe User do describe '#greet' do before do @params = { name: 'たろう' } end context '12歳以下の場合' do before do @params.merge!(age: 12) end it 'ひらがなで答えること' do user = User.new(@params) expect(user.greet).to eq 'ぼくはたろうだよ。' end end context '13歳以上の場合' do before do @params.merge!(age: 13) end it '漢字で答えること' do user = User.new(@params) expect(user.greet).to eq '僕はたろうです。' end end end end letの役割 インスタンス変数の代わり これまではbeforeブロックに@paramsという形でインスタンス変数を利用してきました。これをletを使って次のように書き換えられる。 #これを before do @params = { name: "たろう" } end #このように let(:params) { { name: "たろう" } } ローカル変数の代わりにも インスタンス変数だけでなく、ローカル変数もletでかける。 #これを user = User.new(params) #このように let(:user) = User.new(params) letのメリット beforeがexpample(it do ... end)の前に呼ばれるのに対し、letは遅延評価される。letは必要になるまで呼ばれない。 RSpec.describe User do describe '#greet' do let(:user) { User.new(params) } let(:params) { { name: 'たろう', age: age } } context '12歳以下の場合' do let(:age) { 12 } it 'ひらがなで答えること' do expect(user.greet).to eq 'ぼくはたろうだよ。' end end context '13歳以上の場合' do let(:age) { 13 } it '漢字で答えること' do expect(user.greet).to eq '僕はたろうです。' end end end end ここでは、 1. expect(user.greet).to eqの部分でuserをさがす。 2. let(:user) { User.new(params) }が呼ばれ、   ここでparamsをさがす。 3. let(:params) { { name: 'たろう', age: age } }が呼ばれ、次はageをさがす。 4. let(:age) { 12 }がよばれる。 のようにしてletが呼び出される。 事前に実行されるlet! let!を使うとexample*の実行前にlet!で定義した値が作られる。 RSpec.describe Blog do #事前にblogが作られるためexpectでエラーにならない。 let!(:blog) { Blog.create(title: 'RSpec必勝法', content: 'あとで書く') } it 'ブログの取得ができること' do expect(Blog.first).to eq blog end end subjectの使い方 subjectを使えば同じエクスペクテーションを一つにまとめることができる。 #12歳以下のとき expect(user.greet).to eq 'ぼくはたろうだよ。' #13歳以上のとき expect(user.greet).to eq '僕はたろうです。' #上の二つを次のようにまとめる subject { user.greet } #subjectで宣言し、 is_expected.to eq 'ぼくはたろうだよ。' #is_expected.toでテスト さらに、itを省略して、次のようにかける。 RSpec.describe User do describe '#greet' do let(:user) { User.new(name: 'たろう', age: age) } subject { user.greet } context '12歳以下の場合' do let(:age) { 12 } it { is_expected.to eq 'ぼくはたろうだよ。' } end context '13歳以上の場合' do let(:age) { 13 } it { is_expected.to eq '僕はたろうです。' } end end end itのエイリアス specify, example itの代わりにspecifyとexampleが使える。 # それ(it)はユーザー名を返す it 'returns user name' do # ... end # 会社は社員を持つことを仕様として明記(specify)する specify 'Company has employees' do # ... end # fizz_buzzメソッドの実行例(example) example '#fizz_buzz' do # ... end RSpecの高度な機能 shared_example と it_behaves_like shared_example と it_behaves_likeを使えば、exampleの再利用ができる。  RSpec.describe User do describe '#greet' do #省略 shared_examples '大人のあいさつ' do it { is_expected.to eq '僕はたろうです。' } end context '13歳の場合' do let(:age) { 13 } it_behaves_like '大人のあいさつ' end context '100歳の場合' do let(:age) { 100 } it_behaves_like '大人のあいさつ' end end end shared_example "foo" do ... endで再利用したいexampleを定義し、it_behaves_like "foo"で呼び出す。 shared_context と include_context shared_exampleとit_behaves_likeのように、 contextの再利用には、shared_contextとinclude_contextが使える。 RSpec.describe User do let(:user) { User.new(name: 'たろう', age: age) }  #これらの記述でlet(:age)の繰り返しをなくす shared_context '12歳の場合' do let(:age) { 12 } end shared_context '13歳の場合' do let(:age) { 13 } end describe '#greet' do subject { user.greet } context '12歳以下の場合' do include_context '12歳の場合' it { is_expected.to eq 'ぼくはたろうだよ。' } end context '13歳以上の場合' do include_context '13歳の場合' it { is_expected.to eq '僕はたろうです。' } end end describe '#child?' do subject { user.child? } context '12歳以下の場合' do include_context '12歳の場合' it { is_expected.to eq true } end context '13歳以上の場合' do include_context '13歳の場合' it { is_expected.to eq false } end end end shared_context "foo" do ... endで再利用したいコンテキスト宣言し、contextの中でinclude_context "foo"を使って呼び出す。 スキップさせたいとき RSpecではテストを一旦置いときたいときにスキップできる。 xを使う itやdescribeの前にxと書きxit, xdescribeとすることで、そのテストをスキップできる。(pending扱いになる。) RSpec.describe '何らかの理由で実行したくないクラス' do #xexample, xspecify も使える。 xit '実行したくないテスト' do expect(foo).to eq bar end end #describeをスキップ xdescribe '四則演算' do end # contextをスキップ xcontext '管理者の場合' do end テストは後で書く、中身のない it it "foo" do ... endのdo ... endを省略することでpendingにすることができる。 RSpec.describe User do describe '#good_bye' do context '12歳以下の場合' do #do ... end を省略 it 'ひらがなでさよならすること' end end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]Basic認証について

はじめに こんにちは、docker導入によってテストコードが動かなくなるというエラーのために寝れなくなるほど悩んでいるとちです。息抜きに、ちょっと全然違うことについてまとめてみました。ぜひご覧ください Basic認証とは Basic認証とは、HTTP通信の規格に備え付けられている、ユーザー認証の仕組みのことです。閲覧できるユーザーを制限するために使用します。 少ない手間で認証を実装できるため便利ですが、安全性という観点から、完全に信頼できる認証方式ではありません。 HTTP通信で定義されている仕様上、ユーザー名とパスワードが通信経路上にそのまま送られるため、漏洩のリスクがあります。また、ログアウトの概念が定義されていないため、もし必要になる場合は自力で実装する必要があるほか、複数のサーバーをまたいだ認証が難しいといった特徴も問題になります。 実装について簡単にまとめ はい、これで完成なんですね、実は。 app/controllers/application_controller.rb class ApplicationController < ActionController::Base before_action :basic_auth private def basic_auth authenticate_or_request_with_http_basic do |username, password| username == ENV["BASIC_AUTH_USER"] && password == ENV["BASIC_AUTH_PASSWORD"] # 環境変数を読み込む記述に変更 end end end 一応、味気なさすぎるので解説します。 そんなに難しいわけではないですが、、鬼長い名前のメソッドがありますね!そう!authenticate_or_request_with_http_basicメソッドです。 RailsアプリケーションでBasic認証を実装するために使用する、Railsのメソッドで、ブロックを開き、ブロック内部でusernameとpasswordを設定することでBasic認証を利用できます。 ただし、ここで注意なのは、それぞれの値は環境変数で設定してください。でないと、github上にあげてしまうと誰でも認証できてしまうということになります! ちなみにローカル、AWS、herokuそれぞれで環境変数は設定する必要があります herokuの場合 herokuの場合は、heroku configで設定できます。以下のコマンドで設定しましょう % heroku config:set BASIC_AUTH_USER="admin" % heroku config:set BASIC_AUTH_PASSWORD="2222" ローカルの場合 macOSがCatalina以降の方は、以下のコマンドです。 % ~/.zshrc Mojave以前の方は以下のコマンドです。 % source ~/.bash_profile (以下、macOSがCatalina以降の方と仮定して説明、基本変わりないです) 以下のように環境変数を設定してやってパスを通しましょう export BASIC_AUTH_USER='admin' export BASIC_AUTH_PASSWORD='2222' % source ~/.zshrc とこんな感じです!! 参考 zsh 「zsh」はログインシェルと呼ばれるもので、プログラムを実行する時に、ユーザーの要求に一番最初に対応する役割を担います。隠しファイルなので、特別な設定なしではFinderなどには表示されていません。 環境変数を記載する場所は、設定ファイルである「.zshrc」の中です。 bash 「bash」とは、zsh同様、ログインシェルの1つです。zshとの違いは、OSがCatalina以降であれば「zsh」、Mojave以前であれば「bash」が自動で適用されます。 環境変数を記載する場所は、設定ファイルである「.bash_profile」の中です。 おわりに いい感じに息抜きできたので再度エラーと戦ってきます!行ってきます!笑
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】国一覧リストの実装(carmen-rails)

実装すること gem carmen-railsを使用して、ユーザープロフィールにて国一覧を選択・表示出来るようにする。 (私のPFが留学関係である為、世界各国で登録できるようにしたく、実装しました。) carmen-rails: https://github.com/carmen-ruby/carmen-rails https://github.com/jim/carmen-demo-app/ 完成形 デフォルトでは、日本が選択されているように設定。 居住地で国が一覧で表示され、選択が可能。 前提 下記の機能実装済み。 ・devise(今回は、memberモデル) schema.rb ActiveRecord::Schema.define(version: 2021_05_05_122222) do create_table "members", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "name", default: "", null: false t.text "introduction" t.string "image_id" t.string "country_code", default: "JP" t.string "experienced_country", default: "" t.boolean "is_deleted", default: false, null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["email"], name: "index_members_on_email", unique: true t.index ["reset_password_token"], name: "index_members_on_reset_password_token", unique: true end end 手順 ①gem"carmen-rails"の導入 ②country_codeカラムの追加 ③controllerへの記載 ④modelへの記載 ⑤viewへの記載 おまけ ⑥日本語表示にしたい場合 実装 1.carmen-railsのインストール Gemifileにcarmen-railsを追加します。 Gemfile gem 'carmen-rails', '~> 1.0.0' インストールします。 terminal $ bundle install 2.country_codeカラムの追加 carmen-railsをしようする為には、カラムを追加する必要があります。 terminal $ rails g migration AddCountryCodeToMembers マイグレーションファイルができたと思うので、下記のように記載します。 デフォルトで「日本」と表示したいのでdefaultも記載します。 db/add_country_code_to_members.rb class AddCountryCodeToMembers < ActiveRecord::Migration[5.2] def change add_column :members, :country_code, :string, default: "JP" end end 記載できたら、反映させましょう。 terminal $ rails db:migrate 無事できたかschemaファイルで確認しておきましょう。 3.controllerへの記載 dbに保存する為にも、ストロングパラメーターにcountry_code追記しておきましょう。 members_controller.rb class Public::MembersController < ApplicationController def show @member = Member.find(params[:id] end ----省略---- private def member_params params.require(:member).permit(:name, :email, :image, :introduction, :country_code, :experienced_country, :is_deleted) end 4.modelへの記載 現状でもviewで表示は可能ですが、 カラム名から分かる通り、そのまま表示すると国コード(2文字)が表示されます。 http://www.kc.tsukuba.ac.jp/ulismeta/metadata/standard/cntry_code.html (※ ただ、編集画面では、国名がしっかりと表示されます。) なので、memberモデルに国名で表示させるメソッドを記載します。 member.rb def country Carmen::Country.coded(country_code) end これによって、国コードの国名に変換してくれます。 5.viewへの記載 ①ユーザー詳細ページ ここで先ほど作成したメソッドを使用します。 members/show.html.erb <div>  <div> <p>住居</p> <h5><%= @member.country.name %></h5> </div> </div> ②ユーザー編集ページ form_withを使用しているので、 それに則って下記を記載します。 <td><%= f.country_select :country_code %></td> 下記のようになるかと思います。 members/edit.html.erb <%= form_with model: @member, url:member_path, method: :put, local: true do |f| %>   ---省略---- <div class="form-group"> <th><%= f.label :居住地 %></th> <td><%= f.country_select :country_code %></td> </div> <% end %> country_selectで、選択ができる使用になります。 :以降は、カラム名を記載します。 form_withの説明は省きます。詳しくはこちらでご確認を。 6.日本語表示にしたい場合 gemで'rails-i18n'というものがあります。 そちらをインストールすると日本語表記に変わりますのでお好み。 以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

mysql2をbundle installするとエラーになる(AWS cloud9 Ubuntu18.04)

railsのDBをdevelopment, production環境共にMySQLに変えるため Gemfileにmysql2を記述してbundle installしたらエラーが出たので色々調べてみた。 環境 ・Rails 6.0.3 ・Ubuntu 18.04 エラー内容 $ bundle install The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`. . . . Gem::Ext::BuildError: ERROR: Failed to build gem native extension. current directory: /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/mysql2-0.5.3/ext/mysql2 /home/ubuntu/.rvm/rubies/ruby-2.6.3/bin/ruby -I /home/ubuntu/.rvm/rubies/ruby-2.6.3/lib/ruby/2.6.0 -r ./siteconf20210512-4404-8ubcpu.rb extconf.rb checking for rb_absint_size()... yes checking for rb_absint_singlebit_p()... yes checking for rb_wait_for_single_fd()... yes checking for -lmysqlclient... no ----- mysql client is missing. You may need to 'sudo apt-get install libmariadb-dev', 'sudo apt-get install libmysqlclient-dev' or 'sudo yum install mysql-devel', and try again. ----- *** extconf.rb failed *** Could not create Makefile due to some reason, probably lack of necessary libraries and/or headers. Check the mkmf.log file for more details. You may need configuration options. Provided configuration options: --with-opt-dir --without-opt-dir --with-opt-include --without-opt-include=${opt-dir}/include --with-opt-lib --without-opt-lib=${opt-dir}/lib --with-make-prog --without-make-prog --srcdir=. --curdir --ruby=/home/ubuntu/.rvm/rubies/ruby-2.6.3/bin/$(RUBY_BASE_NAME) --with-mysql-dir --without-mysql-dir --with-mysql-include --without-mysql-include=${mysql-dir}/include --with-mysql-lib --without-mysql-lib=${mysql-dir}/lib --with-mysql-config --without-mysql-config --with-mysql-dir --without-mysql-dir --with-mysql-include --without-mysql-include=${mysql-dir}/include --with-mysql-lib --without-mysql-lib=${mysql-dir}/lib --with-mysqlclientlib --without-mysqlclientlib To see why this extension failed to compile, please check the mkmf.log which can be found here: /home/ubuntu/.rvm/gems/ruby-2.6.3/extensions/x86_64-linux/2.6.0/mysql2-0.5.3/mkmf.log extconf failed, exit code 1 Gem files will remain installed in /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/mysql2-0.5.3 for inspection. Results logged to /home/ubuntu/.rvm/gems/ruby-2.6.3/extensions/x86_64-linux/2.6.0/mysql2-0.5.3/gem_make.out An error occurred while installing mysql2 (0.5.3), and Bundler cannot continue. Make sure that `gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'` succeeds before bundling. In Gemfile: mysql2 bundle installをしてみると、上のようなエラーが出てきたので とりあえずググってみると参考になる記事がいくつかヒットしたので これらを参考にさせて頂き色々試してみた。 ・【Rails】MySQL2がbundle installできない時の対応方法 ・Cloud9でRailsプロジェクトを作成しよう ・【備忘録】ubuntuでbundle installするとmysqlでこける やってみたこと ①LDFLAGSやCPPFLAGSの設定を変えてみる 1つ目の記事を参考に下のコードをそのまま実行して、 その後bundle installをしてみた。 $ bundle config --local build.mysql2 "--with-cppflags=-I/usr/local/opt/openssl/include" $ bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl/lib" しかし、最初のbundle installと同じエラーメッセージが出てしまった。 おそらく1つ目の記事はmac OSの場合、有効な方法だったのかもしれない ②sudo yum install mysql-develを実行してみる 次に2つ目の記事を参考に こちらのコードを実行してみると $ sudo yum install mysql-devel sudo: yum: command not found yumコマンドが見つからないとなったので、 これについても色々と調べると それっぽいコマンドが見つかったが そもそもyumはubuntuでは通常、使えるようには構成されておらず apt-getを使うのが普通みたいなので apt-getで解決できる方法を探すことにした。 ③sudo apt-get install libmysqlclient-devを実行してみる 3つ目の記事では $ sudo apt install libmysqlclient-dev というコマンドが用いられていたが Ubuntuのバージョンが違かったので これを参考にしつつ最初のエラーメッセージにあったヒントを用いることにした。 用いたエラーメッセージを抜粋 ----- mysql client is missing. You may need to 'sudo apt-get install libmariadb-dev', 'sudo apt-get install libmysqlclient-dev' or 'sudo yum install mysql-devel', and try again. ----- このメッセージには3つのコマンドが書いてあり 3つ目は既に試してある。 残った2つを見てみると参考にした記事にあったコードと似たものがあった。 そこで 2つ目のコマンドを試してみることにした。 $ sudo apt-get install libmysqlclient-dev Reading package lists... Done Building dependency tree Reading state information... Done The following packages were automatically installed and are no longer required: debugedit libarchive13 librpm8 librpmbuild8 librpmio8 librpmsign8 python-libxml2 python-lzma python-pycurl python-rpm python-sqlite python-sqlitecachec python-urlgrabber rpm rpm-common rpm2cpio Use 'sudo apt autoremove' to remove them. The following additional packages will be installed: libmysqlclient20 The following NEW packages will be installed: libmysqlclient-dev libmysqlclient20 0 upgraded, 2 newly installed, 0 to remove and 11 not upgraded. Need to get 1677 kB of archives. After this operation, 10.0 MB of additional disk space will be used. Do you want to continue? [Y/n] y Get:1 http://ap-northeast-1.ec2.archive.ubuntu.com/ubuntu bionic-updates/main amd64 libmysqlclient20 amd64 5.7.33-0ubuntu0.18.04.1 [687 kB] Get:2 http://ap-northeast-1.ec2.archive.ubuntu.com/ubuntu bionic-updates/main amd64 libmysqlclient-dev amd64 5.7.33-0ubuntu0.18.04.1 [989 kB] Fetched 1677 kB in 0s (33.9 MB/s) debconf: unable to initialize frontend: Dialog debconf: (Dialog frontend requires a screen at least 13 lines tall and 31 columns wide.) debconf: falling back to frontend: Readline Selecting previously unselected package libmysqlclient20:amd64. (Reading database ... 132293 files and directories currently installed.) Preparing to unpack .../libmysqlclient20_5.7.33-0ubuntu0.18.04.1_amd64.deb ... Unpacking libmysqlclient20:amd64 (5.7.33-0ubuntu0.18.04.1) ... Selecting previously unselected package libmysqlclient-dev. Preparing to unpack .../libmysqlclient-dev_5.7.33-0ubuntu0.18.04.1_amd64.deb ... Unpacking libmysqlclient-dev (5.7.33-0ubuntu0.18.04.1) ... Setting up libmysqlclient20:amd64 (5.7.33-0ubuntu0.18.04.1) ... Setting up libmysqlclient-dev (5.7.33-0ubuntu0.18.04.1) ... Processing triggers for man-db (2.8.3-2ubuntu0.1) ... Processing triggers for libc-bin (2.27-3ubuntu1.4) ... 特にエラーメッセージは発生せず 「これは行けそう!」と思いながら そのままbundle installを実行 $ bundle install (色々コードが出てきて) . . . Bundle complete! 17 Gemfile dependencies, 76 gems now installed. Use `bundle info [gemname]` to see where a bundled gem is installed. 「いけたー!」 これにて無事にインストールができた。 結論 Ubuntu18.04の場合は $ sudo apt-get install libmysqlclient-dev を実行すればbundle installができるようになる。 とりあえず解決はできたが yumやapt-getの知識が全然ないので調べておく必要があるなと思った。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ECSから rails db:migrate:reset するときに発生するエラー(〇〇 is being accessed by other users)を解消する

注意 ポートフォリオ用に作成したサイトの本番環境を直接操作しています。 適切な対処法でないかもしれないので、とりあえずエラーを直したいという方はどうぞ。 背景 railsコンテナで rails db:migrate:reset ができない あるときseedファイルを変更したので、一度RDSの中をリセットしようと思いrailsコンテナから上記のコマンドを打つと、タイトルのエラーが発生しました。他のユーザー(というかコンテナ)が接続しており、実行できないとのことです。 使用技術 ・ECS:EC2タイプ ・RDS:PostgresQL(11.11) 上記のインスタンスの作成は割愛します。 やったこと EC2にpsqlコマンドをインストールしてRDSに接続 railsコンテナからRDSにアクセスしようとしたのですが、肝心のpsqlコマンドをインストールしていませんでした。 そこで応急処置として、EC2にpsqlコマンドを入れて接続しました。 以下、その手順です。 (現在、amazon-linux-extrasはpostgresql11までしかインストールできません。12,13をインストールする場合はこちらの記事が参考になります。) # ご使用のバージョンに合わせてインストールしてください $ sudo amazon-linux-extras postgresql11 # バージョンの確認 $ psql --version psql (PostgreSQL) 11.5 # DBアクセス用のuserがいない場合は作成してください $ sudo adduser <ユーザー名> $ su - <ユーザー名> # postgresサーバーに接続 $ psql \ > --host=<RDSのエンドポイント> \ > --username=<ユーザー名> > --password \ > --port=5432 \ > --dbname=<RDSのインスタンス名> これで接続は完了です。 (接続できないときは、RDSのセキュリティグループのインバウンドを確認してみてください) RDSにアクセスしているプロセスを削除 接続できれば、あとは接続中のプロセスをみつけて削除するだけです。 # すべてのプロセスを表示 SELECT pid FROM pg_stat_activity where pid <> pg_backend_pid(); # 表示例 1234 1235 # プロセスを削除 SELECT pg_terminate_backend(1234); SELECT pg_terminate_backend(1235); # psqlの終了 \q # コンテナに入ったのち、下記のコマンドを実行 $ bundle exec rake db:migrate:reset DISABLE_DATABASE_ENVIRONMENT_CHECK=1 以上になります。 まとめ 私は開発環境でPostgresQLをコンテナ化していたので、エラーが出た時はDBコンテナを壊して再構築していました。そのため、本番環境の対処がわからず戸惑いました。何も考えずに作業すると良くないですね勉強します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

完全数またはほぼ完全数 [ruby]

あるプログラミングテスト問題に出会い、とても参考になったので、その学びを納めておきたいと思います。自身の回答に関しては、繰り返し処理が2重で存在するなど、まだまだ稚拙な回答ですが、この問題自体はとても参考になりました。 問題  Xを2 以上の整数とし、X の約数のうち X 自身を除いたものの和を Y とします。  このとき X = Y となるような X を完全数 |X-Y| = 1 となるような X をほぼ完全数 (|X-Y| は X-Y の絶対値を意味する) と言うことにする。 たとえば、X = 28 のとき 28 の約数は 1, 2, 4, 7, 14, 28 なので、Y = 1+2+4+7+14 = 28 となります。 従って、28 は完全数です。 また、X = 16 のとき Y = 1+2+4+8 = 15 となるので、|16 - 15| = 1 従って、16 はほぼ完全数です。 入力された整数が完全数かほぼ完全数かそのいずれでもないかを判定するプログラムを作成する 入力される値 入力は以下のフォーマットで与えらる Q X_1 . . X_Q1 1行目のQには、判定したい整数の個数 続く Q 行には整数 X_1, ..., X_Q が入力されます。 期待する出力 各 X_i に対して X_i が完全数であれば "完全数" X_i がほぼ完全数であれば "ほぼ完全数" どちらでもなければ "どちらでもない"と 1 行に出力してください。 条件 すべてのテストケースで以下の条件を満たします。 1 ≦ Q ≦ 50 2 ≦ X_i ≦ 1000 (i=1, 2, ..., Q) 自分の回答 input_lines = readlines.map(&:to_i) count = input_lines[0] given_numbers = input_lines[1..count] given_numbers.each do |num| divide_numbers = [*1..num-1] # 解説1 sum = 0 divide_numbers.each do |divide| sum += divide if num % divide == 0 end if sum == num puts "完全数" elsif (sum - num).abs == 1 # 解説2 puts "ほぼ完全数" else puts "どちらでもない" end end 入力される数字の例 3 4 28 777 出力される例 ほぼ完全数 完全数 どちらでもない 解説 ちっぽけな解説ですが、一応載せておきます。 解説1 divide_numbers = [*1..num-1] これは数学の話ではありますが、この問題は約数が重要です。また今回はその中でも、その数字自体は約数に含めてはいけないので、上記の構文で、1から始まり自分の一つ下の数字までの配列が格納されます。この配列は、今回の条件で約数になりうる数字の配列です。 # 28の場合 [1, 2, 3, 4, 5, 6, .. , 26, 27] 解説2 (sum - num).abs == 1 absはrubyで絶対値を出力するメソッドです。 (-5).abs = 5 (5).abs = 5 解説は以上です。 余談 余談ですが、この問題を見て小川洋子さんの「博士の愛した数式」を思い出しました。 記憶が短時間しか続かない数学者の博士と主人公である家政婦のやりとりを基本とした人間ドラマです。 その中で、博士が主人公とコミュニーションを取る上で、「友愛数」という数学の面白さを基に家政婦とコミュニケーションを取るシーンがありました。博士らしい他者との距離の取り方で、とても印象的でした。 この完全数の考え方は「友愛数」の考え方に似ています。なので思い出しました。コーディングテストの練習中に、博士の愛した数式を思い出して、少し悲しくなる、という謎現象でした。 最後までお読み頂きありがとうございました!(笑
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

クッキーの仕組み

クッキーとは ユーザーの情報を保存しておくためのファイルのことです。 仕組み 初回アクセス時サーバーがユーザーにクッキーを発行します。 クッキーデータは一定期間ユーザーのブラウザに保存されて次回以降のアクセス時に利用されます。 どのように利用されるのか たとえば、多くのショッピングサイトではユーザーのIDやカートの情報をクッキーで保存しています。 これにより1回サイトを離れてしまったユーザーがまたサイトを訪れた際に、毎回IDを入力したり商品をカートに入れ直したりすることなくスムーズにショッピングの続きができます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails初学者によるRailsチュートリアル学習記録⑨ 第7章

目次 1. はじめに 2. 第7章の概要 3. 学習内容 4. 終わりに 1. はじめに この記事は、Rails初学者の工業大学三年生がRailsチュートリアルの学習記録をつけるための記事です。 筆者自体がRailsやWebについて知識が少ないので、内容の解釈などに間違いがある可能性があります。(その時はコメントで指摘してくださると助かります!) Railsチュートリアル内ではRailsの内容以外にも、gitでのバージョン管理やHerokuを使ったデプロイも学習しますが、gitに関しては既に私が学習済みのため学習記録には記述しません。 演習の記録も省略します。 2. 第7章の概要 この章では、ユーザー登録機能を実装して、ブラウザからユーザー登録をできるようにしていきます。 RESTアーキテクチャに基づいたユーザーのページも使用できるようにして、より実際のwebアプリケーションに 近づいていく1章です。 ユーザー登録の準備 RESTアーキテクチャによるルート デバッグ gravatarを使用する ビューにフォームを作成する フォームの処理の流れ Strong Parameters フォーム送信後の処理 flashでウェルカムメッセージを表示する 3. 学習内容 1. ユーザー登録の準備 1-1. RESTアーキテクチャによるルート RESTアーキテクチャは、GET / POST / PATCH / DELETE の4つのHTTPメソッドに対応したアクションやリソースをデフォルトで提供します。 提供されるアクションの一覧を以下の表に示します。 HTTPリクエスト URL アクション名 用途 GET /users index ユーザー一覧を表示するページ GET /users/1 show 特定のユーザーを表示するページ GET /users/new new ユーザーを新規作成するページ POST /users create ユーザーを作成するアクション GET /users/1/edit edit id=1 のユーザーを編集するページ PATCH /users/1 update ユーザーを更新するアクション DELETE /users/1 destroy ユーザーを削除するアクション これらのアクションを使用するにはroutesファイルにresources :usersという一行を追加する必要があります。 また上記の表の用途の列で、○○するページと○○するアクションという表記が混在していますが、 ○○するページと表記されたアクションは、それに対応するビューを作成するのに対し、 ○○するアクションと表記されたアクションはビューを必要とせず、処理を行った後に別のビューにリダイレクトします。 1-2. デバッグ この章で作成するプロフィルページはこのアプリケーション内での初めての動的なページです。 そのページではデータベースからユーザーのデータを取り出し、それを表示します。 ここでは、そのページが正しく動作しているかを確認できるようにしていきます。 デバッグ情報を表示させるには、ビューファイルにdebugメソッドとparams変数を使います。 この時、本番環境ではデバッグ情報を表示させたくないので、if Rails.env.development?を使うことで、 開発環境でのみデバッグ情報を表示させます。 以下のコードがデバッグ情報を挿入したビューファイルです。 app/views/layoouts/application.html.erb <!-- body部分のみを表示 --> <body> <%= render 'layouts/header' %> <div class="container"> <%= yield %> <%= render 'layouts/footer' %> <%= debug(params) if Rails.env.development? %> <!-- 開発環境でのみデバッグ情報を挿入する --> </div> </body> 1-3. gravatarを使用する gravatarとは、メールアドレスと画像を紐づけることで、ユーザーのアイコンとしてその画像を表示できる機能です。 Railsでgravatarを使用するには、gravatar_forというヘルパーメソッドを使用します。 このヘルパーメソッドはUsersコントローラのヘルパーファイルに定義して以下のように使用します。 app/helpers/users_helper.rb module UsersHelper # 引数で与えられたユーザーの Gravatar 画像を返す def gravatar_for(user) gravatar_id = Digest::MD5::hexdigest(user.email.downcase) gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}" image_tag(gravatar_url, alt: user.name, class: "gravatar") end end app/views/users/show.html.erb <% provide(:title, @user.name) %> <h1> <%= gravatar_for @user %> <!-- 第一引数にユーザーオブジェクトを渡す--> <%= @user.name %> </h1> 2. フォームを作成する 2-1. フォームの処理の流れ Railsでフォームを実装するにはform_withヘルパーメソッドを使用する。 フォームを作るユーザー登録ページはUsersコントローラのnewアクションに対応させて、 newアクションで@user = User.newを実行することでオブジェクトを生成します。 以下のコードが登録フォームのコードです。 app/views/users/new.html.erb # フォーム部分のみを抜粋 <%= form_with(model: @user, local: true) do |f| %> <%= f.label :name %> <%= f.text_field :name %> <%= f.label :email %> <%= f.email_field :email %> <%= f.label :password %> <%= f.password_field :password %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %> 上のコードのようにlabelと入力部分(input)をペアで作成し、 ペアは引数のシンボルで判断されます。 このシンボルはフォームに入力されたデータを扱うときにも使用します。 ボタンを押すとform_withの引数になっている@userが新しいユーザーであるとRailsが判断して、 POSTリクエストを/users、つまりcreateアクションに入力された値を送信します。 上記の処理の流れは <%= form_with(model: @user, local: true) do |f| %>というRubyのコードが <form action="/users" class="new_user" id="new_user" method="post">というHTMLを生成していることを意味します。 入力された値はcreateアクションにparamsハッシュとして渡されます。 paramsハッシュには各リクエストの情報が含まれていますがユーザー登録の場合は、 userハッシュが格納されています。つまり、ハッシュが入れ子になっている状態です。 そして、userハッシュの中にフォームに入力された値がフォームのシンボルとともに格納されています。 よって、createアクションで@user = User.new(params[:user])と書くことで、 フォームに入力された値を持ったユーザーが作成できます。 上記の@userにsaveメソッドを用いればユーザー登録ができるようになります。 しかし、上記のparamsを渡す方法ではユーザーが送信したデータをそのままUserオブジェクトに渡しており、 この方法はセキュリティ上の欠陥があります。 具体的な例としては、Userモデルにadmin属性という属性があり、サイトの管理者はその属性の値が1になるという運用をした場合、 admin='1'という値をprams[:user]に紛れ込ませて渡すことで、誰でもサイトの管理者権限を得ることができてしまいます。 このような欠陥を次節のStrongParametersで解消します。 2-2. Strong Parameters Strong Parametersはparamsハッシュで必須の値や許可された値を決めることができます。 よって、許可されていない値を受け取らないことで、先述した例のような欠陥を解消できます。 この機能を実装するにはuser_paramsというメソッドを使用するのが慣習です。 このメソッドはUsersコントローラに書き、外部ユーザーから隠すためにprivateキーワードを使用します。 app/views/users/new.html.erb # user_paramsメソッドの部分のみを抜粋 private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end <% end %> そして、createアクションでのオブジェクト生成にこのメソッドを使用します。 @user = User.new(user_params)このように記述して、paramsハッシュを安全に扱っています。 2-3. フォーム送信後の処理 ① 登録に成功したときの処理 ユーザー登録に成功した場合は、自分自身のプロフィールページにリダイレクトし、 そこでウェルカムメッセージを表示させます。 まず、リダイレクトはredirect_toメソッドで実装します。 @user.saveが成功したときにredirect_to @userというコードを実行すると、 railsが自動で判断してuser_url(@user)というプロフィールページにリダイレクトするコードに変換します。 ウェルカムメッセージはflashという特殊な変数で実装します。 登録に成功したときに、flash[:success] = "Welcome to the Sample App!"と書くことで、 flash変数にメッセージを代入します。 :successというキーを使用しているのはRailsの慣習で、成功時のメッセージを意味します。 メッセージが代入されたflash変数を、ビューで使用することでウェルカムメッセージを表示します。 app/views/layouts/application.html.erb <div class="container"> <% flash.each do |message_type, message| %> <div class="alert alert-<%= message_type %>"><%= message %></div> <% end %> <%= yield %> <%= render 'layouts/footer' %> <%= debug(params) if Rails.env.development? %> </div> flashの機能とは直接関係ないのですが、<div class="alert alert-<%= message_type %>"><%= message %></div> この行でのclassの設定にRubyが用いられています。 現状ではウェルカムメッセージしかflashで扱っていませんが、他のメッセージも使うことになった場合に メッセージの種類に合わせてcssを切り替えることができるようになります。 ② 登録に失敗したときの処理 ユーザー登録に失敗した場合は、エラーメッセージを表示してユーザー登録に失敗したことを ユーザーに分かりやすく伝える必要があります。 Railsはエラーの発生時に表示させたいメッセージを自動で生成してくれています。 それはuser.errors.full_messageというオブジェクトに配列として格納されています。 それをビューに表示できるようなパーシャルを作成しましょう app/views/layouts/application.html.erb # エラーがあるときのみパーシャルを表示 <div class="container"> <% flash.each do |message_type, message| %> <div class="alert alert-<%= message_type %>"><%= message %></div> <% end %> <%= yield %> <%= render 'layouts/footer' %> <%= debug(params) if Rails.env.development? %> </div> 4. 終わりに この章で実際のアプリケーションのようにブラウザからユーザー登録ができるようになったので、 Herokuに上げて開発環境でなく本番環境でアプリケーションを動かすことができました。 実装したユーザー登録ではユーザー自身の情報を扱うということもあり、Railsチュートリアルでも セキュリティに関する内容が増えてきました。 この記事では書いていませんが、本番環境でSSLを使用したり本番環境用のWebサーバーを使用したりなども行っています。 セキュリティに関する知識は資格試験の勉強などで勉強しましたが、実際に扱うことは初めてなのでセキュリティの内容は 注意深く実践していきたいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Railsチュートリアル】第12章 パスワードの再設定 演習と解答

Ruby on Railsチュートリアル 第12章の演習問題と解答をまとめました。 第12章 パスワードの再設定 - Railsチュートリアル 12.1 PasswordResetsリソース 12.1.1 PasswordResetsコントローラ 12.1.1 - 1 この時点で、テストスイートが green になっていることを確認してみましょう。 テストはgreen 12.1.1 - 2 表 12.1の名前付きルートでは、_pathではなく_urlを使うように記してあります。なぜでしょうか? 考えてみましょう。ヒント: アカウント有効化で行った演習(11.1.1.1)と同じ理由です。 パスワードリセットする際にユーザーにメールを送信する必要があり、 その内容に個別のトークンを含めたURLを記すため 12.1.2 新しいパスワードの設定 12.1.2 - 1 リスト 12.4のform_withメソッドで、@password_resetではなく:password_resetを使っている理由を考えてみましょう。 password_resetはUsersモデル(reset_digestとreset_sent_at)への操作を行うコントローラーであるため、 インスタンス変数を使うのであれば、@userと書く(@password_resetとはそもそも書けない) <!-- @userを使うなら引数はmodel --> <%= form_with(model: @user, local: true) %> Railsでは上のように書くだけで、「フォームのactionは/usersというURLへのPOSTである」と自動的に判定する。(第8章 8.1.2) password_resetではリソースのスコープとそれに対応するURLを具体的に指定する必要があり、form_withメソッドでは Rails <%= form_with(url: password_resets_path, scope: :password_reset, local: true) %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.submit "Submit", class: "btn btn-primary" %> <% end %> HTML <form action="/password_resets" accept-charset="UTF-8" method="post"> <!--省略--> <label for="password_reset_email">Email</label> <input class="form-control" type="email" name="password_reset[email]" id="password_reset_email"> <input type="submit" name="commit" value="Submit" class="btn btn-primary" data-disable-with="Submit"> </form> app/controlles/password_resets_controller.rb class PasswordResetsController < ApplicationController # 省略 def create   # スコープを明示することで password_reset[email]というパラメータを受け取ることができる @user = User.find_by(email: params[:password_reset][:email].downcase) # 省略 end # 省略 end 参考: form_with scope Railsでform_withでログインする 12.1.3 createアクションでパスワード再設定 12.1.3 - 1 試しに有効なメールアドレスをフォームから送信してみましょう(図 12.6)。どんなエラーメッセージが表示されたでしょうか? 指定する引数の数が間違っているというエラー 12.1.3 - 2 コンソールに移り、先ほどの演習課題で送信した結果、(エラーと表示されてはいるものの)該当するuserオブジェクトにはreset_digestとreset_sent_atがあることを確認してみましょう。また、それぞれの値はどのようになっていますか? ターミナル $ rails console >> user = User.first >> user.reset_digest => "$2a$12$3RIjqssNfDKjmYvRP6nphu4qJJVlJWOR2yFkgighgPYq3KZ53v2ra" >> user.reset_sent_at => Tue, 11 May 2021 07:25:10 UTC +00:00 12.2 パスワード再設定のメール送信 12.2.1 パスワード再設定のメールとテンプレート 12.2.1 - 1 ブラウザから、送信メールのプレビューをしてみましょう。「Date」の欄にはどんな情報が表示されているでしょうか? 送信時刻 12.2.1 - 2 パスワード再設定フォームから有効なメールアドレスを送信してみましょう。また、Railsサーバーのログを見て、生成された送信メールの内容を確認してみてください。 ターミナル ----==_mimepart_609a37b12e1cc_14fe2b165177ae742497c Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit To reset your password click the link below: https://ac648bab7dd147e29059466b0a50e263.vfs.cloud9.ap-northeast-1.amazonaws.com/password_resets/sr_gLDyeDWoRsVO-WHmG5w/edit?email=example%40railstutorial.org This link will expire in two hours. If you did not request your password to be reset, please ignore this email and your password will stay as it is. ----==_mimepart_609a37b12e1cc_14fe2b165177ae742497c Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 7bit <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <style> /* Email styles need to be inline */ </style> </head> <body> <h1>Password reset</h1> <p>To reset your password click the link below:</p> <a href="https://ac648bab7dd147e29059466b0a50e263.vfs.cloud9.ap-northeast-1.amazonaws.com/password_resets/sr_gLDyeDWoRsVO-WHmG5w/edit?email=example%40railstutorial.org">Reset password</a> <p>This link will expire in two hours.</p> <p> If you did not request your password to be reset, please ignore this email and your password will stay as it is. </p> </body> </html> ----==_mimepart_609a37b12e1cc_14fe2b165177ae742497c-- 12.2.1 - 3 コンソールに移り、先ほどの演習課題でパスワード再設定をしたUserオブジェクトを探してください。オブジェクトを見つけたら、そのオブジェクトが持つreset_digestとreset_sent_atの値を確認してみましょう。 ターミナル $ rails console >> user = User.first >> user.reset_digest => "$2a$12$o0TZNDeEmuugekuFpAevG.ynPjwRCi6oBMxgwRV/2BDkAvIA3Mu7u" >> user.reset_sent_at => Tue, 11 May 2021 07:52:17 UTC +00:00 12.2.2 送信メールのテスト 12.2.2 - 1 メイラーのテストだけを実行してみてください。このテストは green になっているでしょうか? ターミナル $ rails test test/mailers/user_mailer_test.rb Running via Spring preloader in process 21873 Started with run options --seed 41313 2/2: [========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.11354s 2 tests, 16 assertions, 0 failures, 0 errors, 0 skips リスト 12.12にある2つ目のCGI.escapeを削除すると、テストが red になることを確認してみましょう。 テストはred 12.3 パスワードを再設定する 12.3.1 editアクションで再設定 12.3.1 - 1 12.2.1.1で示した手順に従って、Railsサーバーのログから送信メールを探し出し、そこに記されているリンクを見つけてください。そのリンクをブラウザから表示してみて、図 12.11のように表示されるか確かめてみましょう。 12.3.1 - 2 先ほど表示したページから、実際に新しいパスワードを送信してみましょう。どのような結果になるでしょうか? updateアクションが見つからないというエラー 12.3.2 パスワードを更新する 12.3.2 - 1 12.2.1.1で得られたリンク(Railsサーバーのログから取得)をブラウザで表示し、passwordとconfirmationの文字列をわざと間違えて送信してみましょう。どんなエラーメッセージが表示されるでしょうか? 12.3.2 - 2 コンソールに移り、パスワード再設定を送信したユーザーオブジェクトを見つけてください。見つかったら、そのオブジェクトのpassword_digestの値を取得してみましょう。次に、パスワード再設定フォームから有効なパスワードを入力し、送信してみましょう(図 12.13)。パスワードの再設定は成功したら、再度password_digestの値を取得し、先ほど取得した値と異なっていることを確認してみましょう。ヒント: 新しい値はuser.reloadを通して取得する必要があります。 ターミナル $ rails c Running via Spring preloader in process 6480 Loading development environment (Rails 6.0.3) >> user = User.find_by(email: "example@railstutorial.org") (省略) >> user.password_digest => "$2a$12$yWNzFPqWC9.MF4PCrCHage/Q3WAKdBRbDRNxFvbBhcQqGqYg4BtoK" ターミナル >> user.reload.password_digest => "$2a$12$yKE3ZHKmy5JO58Hg/.oBzeknUpMTW3ZS7xC4mVSRX2hfWjlSuvtP2" 12.3.3 パスワードの再設定をテストする 12.3.3 - 1 リスト 12.6にあるcreate_reset_digestメソッドはupdate_attributeを2回呼び出していますが、これは各行で1回ずつデータベースへ問い合わせしていることになります。リスト 12.20に記したテンプレートを使って、update_attributeの呼び出しを1回のupdate_columns呼び出しにまとめてみましょう(これでデータベースへの問い合わせが1回で済むようになります)。また、変更後にテストを実行し、 green になることも確認してください。ちなみにリスト 12.20にあるコードには、前章の演習(リスト 11.39)の解答も含まれています。 app/models/user.rb class User < ApplicationRecord # 省略 # パスワード再設定の属性を設定する def create_reset_digest self.reset_token = User.new_token update_columns(reset_digest: User.digest(reset_token), reset_sent_at: Time.zone.now) end # 省略 テストはgreen 12.3.3 - 2 リスト 12.21のテンプレートを埋めて、期限切れのパスワード再設定で発生する分岐(リスト 12.16)を統合テストで網羅してみましょう(12.21 のコードにあるresponse.bodyは、そのページのHTML本文をすべて返すメソッドです)。期限切れをテストする方法はいくつかありますが、リスト 12.21でオススメした手法を使えば、レスポンスの本文に「expired」という語があるかどうかでチェックできます(なお、大文字と小文字は区別されません)。 test/integration/password_resets_test.rb require 'test_helper' class PasswordResetsTest < ActionDispatch::IntegrationTest def setup ActionMailer::Base.deliveries.clear @user = users(:michael) end # 省略 test "expired token" do get new_password_reset_path post password_resets_path, params: { password_reset: { email: @user.email } } @user = assigns(:user) @user.update_attribute(:reset_sent_at, 3.hours.ago) patch password_reset_path(@user.reset_token), params: { email: @user.email, user: { password: "foobar", password_confirmation: "foobar" } } assert_response :redirect follow_redirect! # / assert_match /expired/i, response.body end end 12.3.3 - 3 2時間経ったらパスワードを再設定できなくする方針は、セキュリティ的に好ましいやり方でしょう。しかし、もっと良くする方法はまだあります。例えば、公共の(または共有された)コンピューターでパスワード再設定が行われた場合を考えてみてください。仮にログアウトして離席したとしても、2時間以内であれば、そのコンピューターの履歴からパスワード再設定フォームを表示させ、パスワードを更新してしまうことができてしまいます(しかもそのままログイン機構まで突破されてしまいます!)。この問題を解決するために、リスト 12.22のコードを追加し、パスワードの再設定に成功したらダイジェストをnilになるように変更してみましょう5 。 app/controllers/passward_resets_controller.rb class PasswordResetsController < ApplicationController . . . def update if params[:user][:password].empty? @user.errors.add(:password, :blank) render 'edit' elsif @user.update(user_params) log_in @user # パスワードの再設定に成功したら、Userモデルのreset_digestをnilに変更する @user.update_attribute(:reset_digest, nil) flash[:success] = "Password has been reset." redirect_to @user else render 'edit' end end . . . end 12.3.3 - 4 リスト 12.18に1行追加し、1つ前の演習課題に対するテストを書いてみましょう。ヒント: リスト 9.25のassert_nilメソッドとリスト 11.33のuser.reloadメソッドを組み合わせて、reset_digest属性を直接テストしてみましょう。 test/integraion/password_resets_test.rb require 'test_helper' class PasswordResetsTest < ActionDispatch::IntegrationTest # 省略 test "password resets" do . . . # 一番下に追加する   assert_nil user.reload.reset_digest end # 省略 12.4 本番環境でのメール送信(再掲) 12.4 - 1 production環境でユーザー登録を試してみましょう。ユーザー登録時に入力したメールアドレスにメールは届きましたか? メールを受信できたら、実際にメールをクリックしてアカウントを有効化してみましょう。また、Heroku上のログを調べてみて、有効化に関するログがどうなっているのか調べてみてください。ヒント: ターミナルからheroku logsコマンドを実行してみましょう。 ターミナル $ heroku logs (省略) 2021-05-12T03:19:59.142893+00:00 app[web.1]: I, [2021-05-12T03:19:59.142779 #8] INFO -- : [ace20863-3884-4dc3-b5b0-08ba43eccc49] Started GET "//account_activations/5IAV2iDazfcOFeidztgegQ/edit?email=email_address%40domain.com" for 133.209.118.35 at 2021-05-12 03:19:59 +0000 2021-05-12T03:19:59.144903+00:00 app[web.1]: I, [2021-05-12T03:19:59.144834 #8] INFO -- : [ace20863-3884-4dc3-b5b0-08ba43eccc49] Processing by AccountActivationsController#edit as HTML 2021-05-12T03:19:59.145119+00:00 app[web.1]: I, [2021-05-12T03:19:59.144933 #8] INFO -- : [ace20863-3884-4dc3-b5b0-08ba43eccc49] Parameters: {"email"=>"email_address@domain.com", "id"=>"5IAV2iDazfcOFeidztgegQ"} 2021-05-12T03:19:59.193387+00:00 app[web.1]: D, [2021-05-12T03:19:59.193267 #8] DEBUG -- : [ace20863-3884-4dc3-b5b0-08ba43eccc49] User Load (1.7ms) SELECT "users".* FROM "users" WHERE "users"."email" = $1 LIMIT $2 [["email", "email_address@domain.com"], ["LIMIT", 1]] 2021-05-12T03:19:59.494796+00:00 heroku[router]: at=info method=GET path="//account_activations/5IAV2iDazfcOFeidztgegQ/edit?email=email_address%40dmain.com" host=fast-refuge-97890.herokuapp.com request_id=ace20863-3884-4dc3-b5b0-08ba43eccc49 fwd="133.209.118.35" dyno=web.1 connect=1ms service=357ms status=302 bytes=1252 protocol=https 2021-05-12T03:19:59.495048+00:00 app[web.1]: D, [2021-05-12T03:19:59.494907 #8] DEBUG -- : [ace20863-3884-4dc3-b5b0-08ba43eccc49] User Update (3.0ms) UPDATE "users" SET "activated" = $1, "activated_at" = $2 WHERE "users"."id" = $3 [["activated", true], ["activated_at", "2021-05-12 03:19:59.490686"], ["id", 1]] 2021-05-12T03:19:59.495655+00:00 app[web.1]: I, [2021-05-12T03:19:59.495587 #8] INFO -- : [ace20863-3884-4dc3-b5b0-08ba43eccc49] Redirected to https://fast-refuge-97890.herokuapp.com/users/1 2021-05-12T03:19:59.495906+00:00 app[web.1]: I, [2021-05-12T03:19:59.495834 #8] INFO -- : [ace20863-3884-4dc3-b5b0-08ba43eccc49] Completed 302 Found in 351ms (ActiveRecord: 21.8ms | Allocations: 2742) 2021-05-12T03:19:59.792252+00:00 app[web.1]: I, [2021-05-12T03:19:59.792137 #8] INFO -- : [36d4c7b3-9d8e-4cec-8290-9471069524bd] Started GET "/users/1" for 133.209.118.35 at 2021-05-12 03:19:59 +0000 2021-05-12T03:19:59.793096+00:00 app[web.1]: I, [2021-05-12T03:19:59.793018 #8] INFO -- : [36d4c7b3-9d8e-4cec-8290-9471069524bd] Processing by UsersController#show as HTML 2021-05-12T03:19:59.793150+00:00 app[web.1]: I, [2021-05-12T03:19:59.793105 #8] INFO -- : [36d4c7b3-9d8e-4cec-8290-9471069524bd] Parameters: {"id"=>"1"} 2021-05-12T03:19:59.799188+00:00 app[web.1]: D, [2021-05-12T03:19:59.799103 #8] DEBUG -- : [36d4c7b3-9d8e-4cec-8290-9471069524bd] User Load (1.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]] 2021-05-12T03:19:59.800098+00:00 app[web.1]: I, [2021-05-12T03:19:59.800039 #8] INFO -- : [36d4c7b3-9d8e-4cec-8290-9471069524bd] Rendering users/show.html.erb within layouts/application 2021-05-12T03:19:59.800515+00:00 app[web.1]: I, [2021-05-12T03:19:59.800399 #8] INFO -- : [36d4c7b3-9d8e-4cec-8290-9471069524bd] Rendered users/show.html.erb within layouts/application (Duration: 0.2ms | Allocations: 47) 2021-05-12T03:19:59.801581+00:00 app[web.1]: I, [2021-05-12T03:19:59.801496 #8] INFO -- : [36d4c7b3-9d8e-4cec-8290-9471069524bd] Rendered layouts/_rails_default.html.erb (Duration: 0.8ms | Allocations: 213) 2021-05-12T03:19:59.801864+00:00 app[web.1]: I, [2021-05-12T03:19:59.801793 #8] INFO -- : [36d4c7b3-9d8e-4cec-8290-9471069524bd] Rendered layouts/_shim.html.erb (Duration: 0.1ms | Allocations: 5) 2021-05-12T03:19:59.804309+00:00 app[web.1]: D, [2021-05-12T03:19:59.804249 #8] DEBUG -- : [36d4c7b3-9d8e-4cec-8290-9471069524bd] User Load (1.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]] 2021-05-12T03:19:59.804686+00:00 heroku[router]: at=info method=GET path="/users/1" host=fast-refuge-97890.herokuapp.com request_id=36d4c7b3-9d8e-4cec-8290-9471069524bd fwd="133.209.118.35" dyno=web.1 connect=1ms service=17ms status=200 bytes=3378 protocol=https 2021-05-12T03:19:59.805142+00:00 app[web.1]: I, [2021-05-12T03:19:59.805050 #8] INFO -- : [36d4c7b3-9d8e-4cec-8290-9471069524bd] Rendered layouts/_header.html.erb (Duration: 3.0ms | Allocations: 350) 2021-05-12T03:19:59.805699+00:00 app[web.1]: I, [2021-05-12T03:19:59.805627 #8] INFO -- : [36d4c7b3-9d8e-4cec-8290-9471069524bd] Rendered layouts/_footer.html.erb (Duration: 0.2ms | Allocations: 49) 2021-05-12T03:19:59.806113+00:00 app[web.1]: I, [2021-05-12T03:19:59.806036 #8] INFO -- : [36d4c7b3-9d8e-4cec-8290-9471069524bd] Completed 200 OK in 13ms (Views: 4.6ms | ActiveRecord: 3.2ms | Allocations: 1601) アカウントを有効化できたら、今度はパスワードの再設定を試してみましょう。正しくパスワードの再設定ができたでしょうか? 受信したメールの「Reset password」リンクをクリックするとパスワードが再設定される 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS S3に写真をアップロード設定を行なっていた時のエラー

storage.yml local: service: Disk root: <%= Rails.root.join("storage") %> amazon: service: S3 region: ap-northeast-1 bucket: tast123456 access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %> secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %> インデントを揃えないとエラーが出る設定になっています。 上記だとservice: Diskと同じ並びになるのでlocal:の続きと読まれてしまう。 storage.yml local: service: Disk root: <%= Rails.root.join("storage") %> amazon: service: S3 region: ap-northeast-1 bucket: tast123456 access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %> secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails6.0 + jQuery + Bootstrap] d-flexが有効だと style="display: none;" が効かない。

課題 Bootstrapのd-flexが有効な場合 $element.fadeOut() が効かなかった。 **子要素の列が消えてくれない。 原因 どうやらこいつだ。 結論 d-flex を外すと効いた。 jquery $element.removeClass('d-flex') $element.fadeOut(300)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

時間に関わるspec

目的 特定の時間になるかを確認するためにspecを書きたい どのように書けば良いか分からず調べた travel_toとは ActiveSupport :: Testing :: TimeHelpersのメソッドの一つです。 変更したい日時を渡すと、Time.now、Date.today、DateTime.nowで返される 日時を変更することができます。 コードイメージ spec/models/○○○_spec.rb Spec.describe Task, type: :model do it 'is whether now time' do travel_to('2019-05-26 23:00'.to_time) do expect(Time.zone.now).to eq(Time.new(2019, 5, 26, 23, 0, 0)) end after do travel_back end end end 指定した時間に、時間を止められます it 'puts date and time' do travel_to Time.zone.local(2020, 03, 16) do pp Date.today # -> Mon, 16 Mar 2020 pp Time.now # -> 2020-03-15 15:00:00 +0000 pp DateTime.now # -> Mon, 16 Mar 2020 00:00:00 +0900 end end 時間を止めてしまうとそれ以降のspecの時間も止まってしまうため、下記の記述が必要になります。 after do travel_back end 参考URL https://qiita.com/mightysosuke/items/72f764fc237422b423b0 https://fuqda.hatenablog.com/entry/2019/03/04/223012
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

spring-watcher-listen

spring-watcher-listen springのファイルシステム検知方法をポーリング方式からlistenを使用した方法に変更する。 ポーリングとは、主となるシステムが他のシステムに対して一定間隔で順番に変更がないか確認する制御方式で、いつ起こるかわからないイベントを監視する際に用いられる。 したこと gemfileからspring-watcher-listenをコメントアウト、bundle update
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rspecのrspecに学ぶ、ネストの深いrspecを書かない方法

ネストの深いrspec rspecはcontextのネストが深くなることがある。 BDDのテストケースは下記の形式をとるので、これをcontextで表現しようとすると難しいことがある。 Given:最初の文脈(前提)があって、 When:イベントが発生した場合、 Then:なんらかのアウトプットを保証する。 例えば、fizz_buzz問題でfizzを出力する振る舞いを確認する場合はこうなる。事前条件は必要ないので書いていない。シンプルで、contextとitをつなげてみたときにわかりやすい英文として記述できる。 describe "#fizz_buzz" do subject { fizz_buzz(input) } context "when input is multiple of 3" do let!(:input) { 3 } it { is_expected to eq "fizz" } end end ここで、3の倍数に対する入力がfizzであることを確認するのに、3だけを確認するのは心細いことに気づく。 上述のフォーマットを崩さないように、itの中身を変えないことを意識するとこうなる。 describe "#fizz_buzz" do subject { fizz_buzz(input) } context "when input is multiple of 3" do let!(:input) { 3 } it { is_expected to eq "fizz" } end context "when input is multiple of 3" do let!(:input) { 12 } it { is_expected to eq "fizz" } end context "when input is multiple of 3" do let!(:input) { 303 } it { is_expected to eq "fizz" } end end 同じcontextが横並びしてしまった。5の倍数や3と5の倍数のパターンも記述することを考えると、3の倍数のパターンはひとくくりにしたほうがわかりやすそうだ。 describe "#fizz_buzz" do subject { fizz_buzz(input) } context "when input is multiple of 3" do context "inputting 3" do let!(:input) { 3 } it { is_expected to eq "fizz" } end context "inputting 12" do let!(:input) { 12 } it { is_expected to eq "fizz" } end context "inputting 303" do let!(:input) { 303 } it { is_expected to eq "fizz" } end end end ネストが深くなってしまった。context "inputting 3" doというのは、letで与えている数字と重複しているし、そもそも3を選んだことにはあまり意味はない。3の倍数であれば何でも良かったのだが、contextにまで顔を出してしまっている。 愚直にこうしてはいけないのだろうか?is_expectedのようなおしゃれな構文を手放した代わりに、ネストが浅く、単純で理解しやすいexpectが並んでいる。 describe "#fizz_buzz" do context "when input is multiple of 3" do it "returns fizz" do expected = "fizz" expect(fizz_buzz(3)).to eq expected expect(fizz_buzz(12)).to eq expected expect(fizz_buzz(303)).to eq expected end end end rspecの書き方に関する議論 どうも、Qiitaなどで見かける記事では、subjectを使おうとか、contextを細かく分けていたり、またそのcontextの中にちょっとずつbeforeが入っていてなんかしている事が多い。[要出典] 私は異端者なのだろうか。 なんか腑に落ちないので、rspecのrspecを読んでみた。 rspecのrspecの流儀 rpec-coreのrspecを読んだところ、指針らしきものが見えてきた。結論から言うと、rspecのrspecではとても愚直にrspecを記述している。 contextにbefore、letが無くてもいい contextのbeforeやletは無理して使わない。 itの中で事前条件のためのセットアップをすることが多い itの中身は一行でなくてもいい 事前条件のセットアップを含めて、そこそこ長い記述をすることもある ただし、これが許されるのはあくまで1つの振る舞いを確認している場合。複数の振る舞いを1つのitで確認することは無い itの中身に事前条件のセットアップを含んでもいい というかほとんどそうしている is_expectedは無理に使わない ほぼ見かけなかった itに複数のexpectを書いていい 1つの振る舞いに関することであれば1つのitに複数のexpectを書く 1つの振る舞いを観測するために複数の検証が必要なことは普通にある itは振る舞いで分割する expectを複数書くのを許容しているが、何でもかんでも書いているわけではない。 外から見た動きとして特徴づけられることごとにitを分ける context内でメソッド定義してitの中身を短くする subjectやletは無理に使わず普通にメソッド定義する ちなみに、context内でのメソッド定義はcontextローカルなヘルパーとして登録される。 rspecに学ぶrspecの書き方のミソ どうやら、rspecを書く上で重要なのは下記二点のよう。subjectとかis_expectedとかはこれらを満たした上で使えたら使おう。 1つの振る舞いにつき1つのit(振る舞いの単位は観測者によって変わる) contextとitに書いてあることが英文として読みやすいように構成する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails6】deviseで独自カラムを追加して使用する方法 ④

これまで これまでに、deviseのインストール・Userモデルの作成・Viewの作成・Controllerの作成・Modelのカスタマイズ・Routesの編集を行いました。 今回は、Deviseの設定 ( 独自カラムでログインできるようにする ) を行っていきたいと思います。 ▽前回の記事はこちら▽ 【Rails6】deviseで独自カラムを追加して使用する方法 ① 【Rails6】deviseで独自カラムを追加して使用する方法 ② 【Rails6】deviseで独自カラムを追加して使用する方法 ③ ① Devise の設定 /config/initializers/devise.rb ファイルを編集していきます。 今回編集する内容は、ログインの認証するキーを email から user_code にするようにします。 コメントアウトされているので、#を外します。 devise.rb_編集前 # ==> Configuration for any authentication mechanism # Configure which keys are used when authenticating a user. The default is # just :email. You can configure it to use [:username, :subdomain], so for # authenticating a user, both parameters are required. Remember that those # parameters are used only when authenticating and not when retrieving from # session. If you need permissions, you should implement that in a before filter. # You can also supply a hash where the value is a boolean determining whether # or not authentication should be aborted when the value is not present. # config.authentication_keys = [:email] devise.rb_編集後 # ==> Configuration for any authentication mechanism # Configure which keys are used when authenticating a user. The default is # just :email. You can configure it to use [:username, :subdomain], so for # authenticating a user, both parameters are required. Remember that those # parameters are used only when authenticating and not when retrieving from # session. If you need permissions, you should implement that in a before filter. # You can also supply a hash where the value is a boolean determining whether # or not authentication should be aborted when the value is not present. config.authentication_keys = [:user_code] 上記に編集すると、ログイン認証のキーが変更されます。 前回まで、ファイルをまるまる載せていましたが、長くなるため編集する部分のみを記載します。 ② ストロングパラメータ 追加 今回の肝と思われるのは、ここの部分です。 今までのカスタマイズ・設定で、画面の表示は正常に行われるのですが、 このままでは、独自カラムが画面に入力した値が正常にコントローラへ渡せません。 Deviseでは、ストロングパラメータを追加する必要があります。 ストロングパラメータの追加は、 /app/controllers/application_controller.rbを編集します。 application_controller.rb_編集前 class ApplicationController < ActionController::Base end application_controller.rb_編集後 class ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? # before_action でメゾット呼び出し protected # protected内にメゾットを記述 def configure_permitted_parameters # メゾット devise_parameter_sanitizer.permit(:sign_up, keys: [:user_code, :user_last_name, :user_first_name] ) devise_parameter_sanitizer.permit(:sign_in, keys: [:user_code]) devise_parameter_sanitizer.permit(:account_update, keys: [:user_code, :user_last_name, :user_first_name]) end end No 種類 説明 1 sing_up 登録時 2 sing_in ログイン時 3 account_update ユーザ情報更新時 上記のパターンの説明を表にしてみました。 3種類の処理時にストロングパラメータが通るように、追加します。 devise_parameter_sanitizer.permit(:処理種類, keys: [:カラム名, :カラム名]) ここまで行うと、独自カラムを追加してdeviseを使用することができるようになります。 あとは、ログインページ等のHTMLとCSSを作り、実用性のある画面を作成すると完成です。 最後に deviseの使用方法などはたくさんインターネットに情報がありますが、今回は「独自カラムの追加とログイン時に独自のカラムで行えるようにする」というテーマで、一からご説明してきました。 どちらかというと、Rails初心者の方向けに近かったのかなと思いますが、deviseのカスタマイズをされたい方も参考になるかなと思いますので、ぜひ参考にしてみてください。 記事を複数回分けましたが以上、deviseで独自カラムを追加して使用する方法のご紹介でした。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【rails】PV数の導入(impressionist)

実装すること gem impressionistを使用して、投稿ページにPV(ページビュー)を設置します。 同じユーザーがPV数を稼ぐ、増やせないように同じセッションはカウントしないようにします。 impressionist: https://github.com/charlotte-ruby/impressionist こちらのgithubを参考に実装していきます。 完成形 目のアイコンがPV数を表示しており、 投稿詳細に行くと数字がカウントされています。 前提 下記の機能実装済み。 ・devise(今回は、memberモデル) ・投稿機能(今回は、questionモデル) schema.rb ActiveRecord::Schema.define(version: 2021_05_05_122222) do create_table "members", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.datetime "remember_created_at" t.string "name", default: "", null: false t.text "introduction" t.string "image_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["email"], name: "index_members_on_email", unique: true"index_members_on_reset_password_token", unique: true end create_table "questions", force: :cascade do |t| t.integer "member_id", null: false t.string "title", default: "", null: false t.text "content", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["member_id"], name: "index_questions_on_member_id" end end 手順 ①gem"impressionist"の導入 ②modelへの記載 ③controllerへの記載 ④viewへの記載 実装 1. impressionistのインストール railsのバージョンに注意。(6と5以下で異なる) 私は5なので、下記を記載。 Gemfile #rails 5 or lower gem 'impressionist', '~>1.6.1' インストールします。 terminal bundle install impressionistのマイグレーションファイルを作成します。 terminal rails g impressionist 下記が自動的に作成されます。 *user_idとありますが、ここは特にいじる必要はありません。  私はuserではなく、memberを使用していたのでmember_idに修正したところエラーが出ました。   私自身が作成したmemberモデルを表すidではなかったです。 create_impressions_table.rb class CreateImpressionsTable < ActiveRecord::Migration[5.2] def self.up create_table :impressions, :force => true do |t| t.string :impressionable_type t.integer :impressionable_id t.integer :user_id t.string :controller_name t.string :action_name t.string :view_name t.string :request_hash t.string :ip_address t.string :session_hash t.text :message t.text :referrer t.text :params t.timestamps end add_index :impressions, [:impressionable_type, :message, :impressionable_id], :name => "impressionable_type_message_index", :unique => false, :length => {:message => 255 } add_index :impressions, [:impressionable_type, :impressionable_id, :request_hash], :name => "poly_request_index", :unique => false add_index :impressions, [:impressionable_type, :impressionable_id, :ip_address], :name => "poly_ip_index", :unique => false add_index :impressions, [:impressionable_type, :impressionable_id, :session_hash], :name => "poly_session_index", :unique => false add_index :impressions, [:controller_name,:action_name,:request_hash], :name => "controlleraction_request_index", :unique => false add_index :impressions, [:controller_name,:action_name,:ip_address], :name => "controlleraction_ip_index", :unique => false add_index :impressions, [:controller_name,:action_name,:session_hash], :name => "controlleraction_session_index", :unique => false add_index :impressions, [:impressionable_type, :impressionable_id, :params], :name => "poly_params_request_index", :unique => false, :length => {:params => 255 } add_index :impressions, :user_id end def self.down drop_table :impressions end end dbに反映させましょう。 terminal rails db:migrate 2. modelへの記載 models/question.rb class Question < ApplicationRecord is_impressionable belongs_to :member validates :title, presence: true validates :title, length: { minimum: 5, maximum: 50 }, allow_blank: true validates :content, presence: true validates :content, length: { minimum: 20 }, allow_blank: true end 下記を一番上に追加しています。 is_impressionable or is_impressionable :counter_cache => true これを追加することで、questionモデルでimpresionistを使う事ができます。 3. controllerへの記載 questions_controller.rb class Public::QuestionsController < ApplicationController impressionist :actions => [:show] def show @member = current_member @question = Question.find(params[:id]) impressionist(@question, nil, unique: [:session_hash.to_s]) end それぞれ説明します。 ① impressionist :actions => [:show] ⇨ PV数を計測したいので、アクションを指定します。  ・投稿詳細(questions#show)で計測したい。  ・[ ]内は、計測したいアクションを記載。 ② impressionist(@question, nil, unique: [:session_hash.to_s]) ⇨ showアクション内にも記載します。  ・@questionを計測する対象として設定します。   今回は、投稿詳細なので@question。showにある変数を入れましょう。  ・uniqueでは、どの値で計測するかを書きましょう。今回は、sessionで計測したいので、   先ほど作成した、マイグレーションファイルのカラム名を書きましょう。(参照: t.string :session_hash) 4.viewへの記載 *わかりやすくするために余計な箇所は省いてあります。 questions/show.html.erb <div class="container"> <div class= "row"> <div class="col-md-3 pt-5">    <i class="fas fa-eye">     <%= @question.impressionist_count(:filter=>:session_hash) %>     </i> </div> </div> </div> ここで計測(いわゆるcount)したいので、下記を記載します。 <%= @question.impressionist_count(:filter=>:session_hash) %> ・ impressionist_countで計測。 ・ (:filter=>:session_hash)でフィルターをかけ、どれで計測するか設定。   今回は、sessionで計測なので、記載の通り。 これで問題なく計測できるかと思います。 もし、エラーや上手く動かない。などあればコメント頂けたらと思います。 以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【devise】アカウント情報変更機能実装

こんばんは。 現在オートバイのレビューサイトを作成中のプログラミング初学者です。 マイページに、アカウント編集機能を実装するにあたって、いくつか壁にぶち当たったので、実装までの流れをまとめたいと考え、投稿します。 目的 deviseを用いて、マイページ内にアカウント情報編集機能を実装する 環境 Ruby on rails ver.6.1.3.1 Ruby ver.2.6.5 Docker ver.20.10.5 Docker compose ver.1.28.5 前提条件 ・gem Devise導入済み 公式Github ・マイページ実装済み(参考:私はusersコントローラーを自作し、showアクションにてマイページを実装しました。) 実装 ①ルーティング設定 devise_for :users, controllers: { registrations: 'users/registrations' } devise導入で、すでにdevise_for :usersまでは記載済みかと思いますが、 controllersオプションを追記し、deviseで標準装備されているregistrationsコントローラーを用いてアカウント情報編集機能を実装していきます。 rails routesでPrefixパスの確認などをしておいてもいいかと思います。 ②application_controller.rb編集 class ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? private def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname]) devise_parameter_sanitizer.permit(:account_update, keys: [:nickname]) end end ストロングパラメーターを実装します。 deviseを導入済みであれば、アカウント新規登録時に用いるストロングパラメーターconfigure_permitted_parametersメソッド(メソッド名は任意、慣習的に使用。)を実装済みかと思いますが、アカウント情報編集のためにdevise_parameter_sanitizer.permit(:account_update, keys: [:nickname])を追記します。 ※アカウント情報にemail、password以外のカラムを用意している場合。私はnicknameカラムを追加しています。 ③ビューファイルにアカウント情報変更画面への遷移パスを設置 <div class="user-edit"> <% if user_signed_in? && @user == current_user %> <%= link_to "アカウント情報編集", edit_user_registration_path, class: "user-edit-btn" %> <% end %> </div> classなどは自由ですが、if user_signed_in? && @user == current_userでユーザーとしてサインイン済みであること、遷移しようとするアカウント情報変更画面は今ログインしているユーザーであることを前提にリンクを設置します。 ※インスタンス変数userは事前にusersコントローラーのshowメソッド内で、@user = User.find(params[:id])などと定義しておきましょう。 ④アカウント情報変更画面の実装 <h2>Edit <%= resource_name.to_s.humanize %></h2> <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> <%= render "devise/shared/error_messages", resource: resource %> <div class="field"> <%= f.label :email %><br /> <%= f.email_field :email, autofocus: true, autocomplete: "email" %> </div> <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> <div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div> <% end %> <div class="field"> <%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br /> <%= f.password_field :password, autocomplete: "new-password" %> <% if @minimum_password_length %> <br /> <em><%= @minimum_password_length %> characters minimum</em> <% end %> </div> <div class="field"> <%= f.label :password_confirmation %><br /> <%= f.password_field :password_confirmation, autocomplete: "new-password" %> </div> <div class="field"> <%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br /> <%= f.password_field :current_password, autocomplete: "current-password" %> </div> <div class="actions"> <%= f.submit "Update" %> </div> <% end %> <h3>Cancel my account</h3> <p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %></p> <%= link_to "Back", :back %> ビューファイルはviews/devise/registrations/edit.html.erbを使います。 初期は上記のように必要十分の装備と、味気ない見た目となりますので、これを編集していきます。 <div class="user-edit-contents"> <h1>プロフィール編集</h1> <div class="user-edit-form"> <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> <%= render "devise/shared/error_messages", resource: resource %> <div class="field"> <%= f.label :email %> <%= f.email_field :email, autofocus: true, autocomplete: "email" %> </div> <div class="field"> <%= f.label :nickname %> <%= f.text_field :nickname, autofocus: true, autocomplete: "nickname" %> </div> <div class="field"> <%= f.label :password %> <%= f.password_field :password, autocomplete: "new-password" %> </div> <div class="field"> <%= f.label :password_confirmation %> <%= f.password_field :password_confirmation, autocomplete: "new-password" %> </div> <div class="actions"> <%= f.submit "保存", class:"user-update-btn" %> </div> <% end %> <%= link_to "戻る", user_path(current_user.id), class:"redirect-btn" %> </div> </div> 編集後がこちらになります。昼下がりの料理番組のようですね。 ポイントは下記の通りです。 ①メール認証部分を削除 ②nickname変更フォームを追加 ③パスワード最小文字表示部分の削除 ④:current_passwordフォーム削除(後ほど解説します) ⑤アカウント削除ボタン削除 このままでは、正しくフォームを入力しても更新されませんし、エラーが出ることがあります。 ⑤registrations_controller.rb編集 deviseで標準装備されているコントローラーcontrollers/users/registrations_controller.rbに必要なメソッドを定義していきます。 ※標準ではコメントだらけのコントローラーファイルになっているかと思います。 protected #コメントアウト外す def update_resource(resource, params) resource.update_without_current_password(params) #独自のメソッド。解説は下記にて。 end #更新後のパスを指定。マイページに戻るように設定。 def after_update_path_for(resource) user_path(@user.id) end update_without_current_passwordメソッドについて deviseでアカウント情報を変更する際に、現在のパスワードが必須となります。 毎回パスワードを入力しないといけないのは面倒なので、省くメソッドを作ります。 deviseに標準であるメソッドはupdate_without_passwordであり、アカウント情報変更時にパスワードそのものを必要としなくすることは可能ですが、 今回は現在のパスワードは不要でも、パスワードの変更は可能とするように実装していきます。 ⑥Userモデルにupdate_without_current_passswordメソッドの定義 def update_without_current_password(params, *options) params.delete(:current_password) if params[:password].blank? && params[:password_confirmation].blank? params.delete(:password) params.delete(:password_confirmation) end result = update(params, *options) clean_up_passwords result end これにて現在のパスワードはパラメーター内で不要となり、かつパスワードとパスワードの確認を空とした場合、パスワードの更新はなされなくなります。 参考にした記事の多くはupdate_attributesメソッドを使っていましたが、おそらくrailsの6.1以降はこのエイリアスの使用はできずNoMethodErrorを返しますので、同義のupdateメソッドを採用します。 これで実装完了です。お疲れ様でした。 ※上記例はdeviseの日本語化をしています。CSSはご自由に。 補足 正規表現のバリデーションを設けている場合 userモデルでPASSWORD_REGEXのようなバリデーションを設けている場合、これが反応してどれだけパスワードを入力しても、バリデーションに引っかかるエラーが出現するかと思います。(実際私はこれにかなり苦戦しました。) user.rb PASSWORD_REGEX = /\A(?=.*?[a-z])(?=.*?\d)[a-z\d]+\z/i.freeze validates_format_of :password, with: PASSWORD_REGEX, message: 'には英字と数字の両方を含めて設定してください' この場合、このバリデーションに下記1点メソッドを追記してください。 user.rb PASSWORD_REGEX = /\A(?=.*?[a-z])(?=.*?\d)[a-z\d]+\z/i.freeze validates_format_of :password, with: PASSWORD_REGEX, message: 'には英字と数字の両方を含めて設定してください', if: :password_required? deviseの標準メソッドのうちの1つです。 参考:varidatavle.rb これによりpasswordの有無を判定します。その上で、正規表現バリデーションを適応させます。 ゲストログイン機能を実装している場合 ポートフォリオでよくあるゲストログイン。ワンタップでログインでき、アプリの機能を試せるのは魅力的ですよね。 今回アカウント情報編集機能の実装ができましたが、ゲストユーザーに限っては誰でもログインできる特性上、簡単に編集できるのは望ましくありません。 コントローラーに壁を張ります。 registrations_controller.rb before_action :ensure_normal_user, only: [:edit, :update] #メソッド名は任意 protected #ゲストユーザーはアカウント情報を編集・更新できない def ensure_normal_user if resource.email == 'guest@example.com' redirect_to root_path, alert: 'ゲストユーザーは編集できません。' end end ゲストユーザーではないことを確認するメソッドを追加しました。 ゲストログインに使用されるメールアドレス(今回はguest@example.com)でeditアクション、updateアクションを実行しようとした場合、ルートパスにリダイレクトさせ、アラートを表示させるようにしました。 これで、ゲストユーザーはそもそもアカウント情報編集画面にすら遷移できなくなります。 まとめ ここまでご覧いただきありがとうございました。 何か間違いなどがあれば遠慮なくご指摘いただけますと幸いです。 参考記事 公式Github [Rails] deviseの使い方(rails5版) devise ユーザーのプロフィール画面作成と編集(デフォルトをカスタマイズ) deviseで現在のパスワード無しでuserを更新する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む