- 投稿日:2020-03-15T22:34:17+09:00
マイクロポストにタグ付けする
はじめに
ご訪問いただきありがとうございます。レベル的にはrailsチュートリアルを終えたくらいのレベルです。もし間違えているところ等あればアドバイスいただけると幸いです。
作るもの
今回はマイクロポストに自由にタグ付けと編集の機能を追加します。
(題材は自分のポートフォリオサイトです。尚、すでにUserテーブルとMicropostテーブルは作成済みです。)対象読者
railsチュートリアルに機能を追加したい等自分と同じ位のレベルの人を対象としています。
作成の流れ
1.TagモデルとTag_relationshipモデルの作成
2.モデルの関連付けとバリテーション
3.対応するビューの作成
4.コントローラーのアクション作成
5.タグ編集機能の作成1.TagモデルとTag_relationshipモデルの作成
今回はタグ付機能を作ります。マイクロポストから見ると一つのポストに一つ以上のタグをつけることができる。タグから見ると一つのタグは一つ以上のマイクロポストに関連づくということになります。つまり多対多の関連付けとなり、MicropostもでるとTagモデルの中間にTagrelationテーブルを設ける方法で今回は進めます。それぞれの中身はこんな感じです。
Tag_relationship
カラム 型 id integer micropost_id integer tag_id integer Tag
カラム 型 id integer name string モデルを作成します。
rails g model tag name:stringrails g model tag_relationship micropost:references tag:references作成されたマイグレーションファイルを確認します。
db/migrateclass CreateTags < ActiveRecord::Migration[5.1] def change create_table :tags do |t| t.string :name, null: false t.timestamps end end endnameを空で保存されるのを防ぐためにnull:falseを追記してます。
db/migrateclass CreateTagRelationships < class CreateTagRelationships < ActiveRecord::Migration[5.1] def change create_table :tag_relationships do |t| t.references :micropost, foreign_key: true t.references :tag, foreign_key: true t.timestamps end add_index :tag_relationships, [:micropost_id,:tag_id],unique: true end endこちらはreferencesを付けて作成したので自動的にreferencesが設定されています。また、後々nameで検索する可能性を踏まえindexをnameに追記しました。unipue:tureは同じ名前のタグを保存できないようにしています。
ちなみにadd_index :テーブル名, :カラム名で指定します。
※referencesはbelongs_toのエイリアスです。(tag_idは一つのTag.idと紐づく)
migrateを行います。rails db:migrate2モデルの関連付けとバリレーション
各モデルの関連付けは以下のようになります。
app/model/tag.rbclass Tag < ApplicationRecord has_many :tag_relationships, dependent: :destroy has_many :microposts, through: :tag_relationships validates :name, uniqueness: true endruby/app/model/tag_relationship.rbclass TagRelationship < ApplicationRecord belongs_to :micropost belongs_to :tag endapp/model/micropost.rbhas_many :tag_relationships, dependent: :destroy has_many :tags, through: :tag_relationshipsここで「has_many:microposts, through: :tag_relationships」
とすることによって、tag_relationshipモデルを通してタグに紐づくマイクロポストを取得することができるようになります。(マイクロポスト側も同じ)
注意点として、スルーの関連付けの前にrelationshipsの中間テーブルの関連付けを記載しないとうまく動作しません。(先にhas_many:tags~とすると中間テーブルが関連づいてないので中間テーブルがありませんとエラーが出ます。)
validates :name, uniqueness: trueでタグの名前が重複して保存されるのを防ぎます。3対応するビューの作成
今回はマイクロポストを作成する際にタグをつけるような設定とします。
app/views/microposts/new<%= form_with model:@micropost, local: true do |f| %> ※タグの部分のみ抜粋 <%= f.text_field :tag_ids, class: "form-control", id:'tag_ids',\ placeholder: "タグをつける。複数つけるには','で区切ってください。" %> <% end %>タグを複数つけるときは(,)で区切ってもらうようにします。
「f.text_field :tag_ids」と記載することでマイクロポストを作成するタイミングでタグの情報も一緒に送ります。
※tag_idsはマイクロポストで「has_many :tags, through: :tag_relationships」の関連付けをすることよってマイクロポストオブジェクトに使用できるようになります。例 Micropost.first.tag_ids => [2, 3, 4, 5]タグはマイクロポストの中に表示するので、マイクロポストのビューの中にタグの表示を追記します。
app/views/microposts/_micropost.html.erb※タグを表示する部分のみ抜粋 <% micropost.tags.each do |tag| %> <%= tag.name %> <% end %>4コントローラのアクション作成
マイクロポストの情報と一緒にタグの情報も送られてくるようになったのでマイクロポストのcreateアクションでタグも保存されるようにします。
app/controllers/microposts_controller.rbdef create @micropost = current_user.microposts.build(micropost_params) tag_list = params[:micropost][:tag_ids].split(',') if @micropost.save @micropost.save_tags(tag_list) flash[:success] = '投稿しました!' redirect_to root_url else render 'new' end end「tag_list」で送られてくるタグの値を取得します。複数のタグ付を行うときにsplit(',')で区切るようにします。
「@micropost.save_tags(tag_list)」でマイクロポストにタグを関連付けます。save_tagsメゾットの中身はこんな感じです。app/model/micropost.rbdef save_tags(savemicropost_tags) savemicropost_tags.each do |new_name| micropost_tag = Tag.find_or_create_by(name: new_name) self.tags << micropost_tag end「find_or_create_by」メゾットはカラムの中から同じ値がないか探して、あればそのままfindの動き、なければcreateの動きで新たにカラムに保存します。
これでタグ付機能は作成完了です。タグ編集機能の作成
ここからはタグの編集機能を付けていきます。
タグの編集はマイクロポストの編集ページで行います。(すでにマイクロポスト編集ページ作成済みの前提で行います)※タグの部分のみ抜粋(form_withを使用しているのでrenderでマイクロポストのフォームページと共有できます。)
app/micropost/edit<%= f.text_field :tag_ids,value: @tag_list,\ placeholder: "タグをつける。複数つけるには','で区切ってください。" %>ここで「@tag_list」で既存の値を表示しています。
それを踏まえたコントローラのアクションがこちらです。app/controllers/microposts_controller.rbdef edit @micropost =Micropost.find(params[:id]) @tag_list =@micropost.tags.pluck(:name).join(",") end def update @micropost =Micropost.find(params[:id]) tag_list = params[:micropost][:tag_ids].split(',') if @micropost.update_attributes(micropost_params) @micropost.save_tags(tag_list) flash[:success] = '投稿を編集しました‼' redirect_to @micropost else render 'edit' end endまずeditアクションには既存のタグを取得すために「@tag_list」を追記します。
tagsでマイクロポストに関連するタグを取得
pluck(:name)でタグのnameの配列を取得
join(",")で配列を,で区切った文字列として取得します。「save_tags」を更新用に編集します。
※マイクロポスト編集ページへの「link_to」で行き先を「edit_micropost_path」としたときにマイクロポストshowページ以外からリンクを踏んでアクセスするとタグフォームに既存の値が入らずうまくタグの更新ができなかったので「/microposts/#{micropost.id}/editでリンクを設定してます。
app/model/micropost.rbdef save_tags(savemicropost_tags) current_tags = self.tags.pluck(:name) unless self.tags.nil? old_tags = current_tags - savemicropost_tags new_tags = savemicropost_tags - current_tags old_tags.each do |old_name| self.tags.delete Tag.find_by(name: old_name) end new_tags.each do |new_name| micropost_tag = Tag.find_or_create_by(name: new_name) self.tags << micropost_tag end end動作は既存のタグを残すものと消すものに分けて、新しいタグを既存の残すタグに追加していくといった形です。
「current_tags」既存のタグを取得
「old_tags」消すタグを取得。
「new_tags」新たに追加するタグを取得。
old_tagsとnew_tagsにある「ー」は引き算です。
それぞれ引き算して残ったタグを繰り返し処理して完了です。
例
current_tags - savemicropost_tags =old_tags
「test, test2, test3」 - [test, test3] =[test2]以上でタグ付機能は完成となります。最後までお読みいただきありがとうございました。
アドバイス等いただけるととても喜びます。次は今回作ったタグを利用した検索機能を作ろうと思います。
ありがとうございました。
- 投稿日:2020-03-15T22:34:17+09:00
railsタグ付機能(rails チュートリアル後の機能追加)
はじめに
ご訪問いただきありがとうございます。レベル的にはrailsチュートリアルを終えたくらいのレベルです。もし間違えているところ等あればアドバイスいただけると幸いです。
作るもの
今回はマイクロポストに自由にタグ付けと編集の機能を追加します。
(題材は自分のポートフォリオサイトです。尚、すでにUserテーブルとMicropostテーブルは作成済みです。)対象読者
railsチュートリアルに機能を追加したい等自分と同じ位のレベルの人を対象としています。
作成の流れ
1.TagモデルとTag_relationshipモデルの作成
2.モデルの関連付けとバリテーション
3.対応するビューの作成
4.コントローラーのアクション作成
5.タグ編集機能の作成1.TagモデルとTag_relationshipモデルの作成
今回はタグ付機能を作ります。マイクロポストから見ると一つのポストに一つ以上のタグをつけることができる。タグから見ると一つのタグは一つ以上のマイクロポストに関連づくということになります。つまり多対多の関連付けとなり、MicropostもでるとTagモデルの中間にTagrelationテーブルを設ける方法で今回は進めます。それぞれの中身はこんな感じです。
Tag_relationship
カラム 型 id integer micropost_id integer tag_id integer Tag
カラム 型 id integer name string モデルを作成します。
rails g model tag name:stringrails g model tag_relationship micropost:references tag:references作成されたマイグレーションファイルを確認します。
db/migrateclass CreateTags < ActiveRecord::Migration[5.1] def change create_table :tags do |t| t.string :name, null: false t.timestamps end end endnameを空で保存されるのを防ぐためにnull:falseを追記してます。
db/migrateclass CreateTagRelationships < class CreateTagRelationships < ActiveRecord::Migration[5.1] def change create_table :tag_relationships do |t| t.references :micropost, foreign_key: true t.references :tag, foreign_key: true t.timestamps end add_index :tag_relationships, [:micropost_id,:tag_id],unique: true end endこちらはreferencesを付けて作成したので自動的にreferencesが設定されています。また、後々nameで検索する可能性を踏まえindexをnameに追記しました。unipue:tureは同じ名前のタグを保存できないようにしています。
ちなみにadd_index :テーブル名, :カラム名で指定します。
※referencesはbelongs_toのエイリアスです。(tag_idは一つのTag.idと紐づく)
migrateを行います。rails db:migrate2モデルの関連付けとバリレーション
各モデルの関連付けは以下のようになります。
app/model/tag.rbclass Tag < ApplicationRecord has_many :tag_relationships, dependent: :destroy has_many :microposts, through: :tag_relationships validates :name, uniqueness: true endruby/app/model/tag_relationship.rbclass TagRelationship < ApplicationRecord belongs_to :micropost belongs_to :tag endapp/model/micropost.rbhas_many :tag_relationships, dependent: :destroy has_many :tags, through: :tag_relationshipsここで「has_many:microposts, through: :tag_relationships」
とすることによって、tag_relationshipモデルを通してタグに紐づくマイクロポストを取得することができるようになります。(マイクロポスト側も同じ)
注意点として、スルーの関連付けの前にrelationshipsの中間テーブルの関連付けを記載しないとうまく動作しません。(先にhas_many:tags~とすると中間テーブルが関連づいてないので中間テーブルがありませんとエラーが出ます。)
validates :name, uniqueness: trueでタグの名前が重複して保存されるのを防ぎます。3対応するビューの作成
今回はマイクロポストを作成する際にタグをつけるような設定とします。
app/views/microposts/new<%= form_with model:@micropost, local: true do |f| %> ※タグの部分のみ抜粋 <%= f.text_field :tag_ids, class: "form-control", id:'tag_ids',\ placeholder: "タグをつける。複数つけるには','で区切ってください。" %> <% end %>タグを複数つけるときは(,)で区切ってもらうようにします。
「f.text_field :tag_ids」と記載することでマイクロポストを作成するタイミングでタグの情報も一緒に送ります。
※tag_idsはマイクロポストで「has_many :tags, through: :tag_relationships」の関連付けをすることよってマイクロポストオブジェクトに使用できるようになります。例 Micropost.first.tag_ids => [2, 3, 4, 5]タグはマイクロポストの中に表示するので、マイクロポストのビューの中にタグの表示を追記します。
app/views/microposts/_micropost.html.erb※タグを表示する部分のみ抜粋 <% micropost.tags.each do |tag| %> <%= tag.name %> <% end %>4コントローラのアクション作成
マイクロポストの情報と一緒にタグの情報も送られてくるようになったのでマイクロポストのcreateアクションでタグも保存されるようにします。
app/controllers/microposts_controller.rbdef create @micropost = current_user.microposts.build(micropost_params) tag_list = params[:micropost][:tag_ids].split(',') if @micropost.save @micropost.save_tags(tag_list) flash[:success] = '投稿しました!' redirect_to root_url else render 'new' end end「tag_list」で送られてくるタグの値を取得します。複数のタグ付を行うときにsplit(',')で区切るようにします。
「@micropost.save_tags(tag_list)」でマイクロポストにタグを関連付けます。save_tagsメゾットの中身はこんな感じです。app/model/micropost.rbdef save_tags(savemicropost_tags) savemicropost_tags.each do |new_name| micropost_tag = Tag.find_or_create_by(name: new_name) self.tags << micropost_tag end「find_or_create_by」メゾットはカラムの中から同じ値がないか探して、あればそのままfindの動き、なければcreateの動きで新たにカラムに保存します。
これでタグ付機能は作成完了です。タグ編集機能の作成
ここからはタグの編集機能を付けていきます。
タグの編集はマイクロポストの編集ページで行います。(すでにマイクロポスト編集ページ作成済みの前提で行います)※タグの部分のみ抜粋(form_withを使用しているのでrenderでマイクロポストのフォームページと共有できます。)
app/micropost/edit<%= f.text_field :tag_ids,value: @tag_list,\ placeholder: "タグをつける。複数つけるには','で区切ってください。" %>ここで「@tag_list」で既存の値を表示しています。
それを踏まえたコントローラのアクションがこちらです。app/controllers/microposts_controller.rbdef edit @micropost =Micropost.find(params[:id]) @tag_list =@micropost.tags.pluck(:name).join(",") end def update @micropost =Micropost.find(params[:id]) tag_list = params[:micropost][:tag_ids].split(',') if @micropost.update_attributes(micropost_params) @micropost.save_tags(tag_list) flash[:success] = '投稿を編集しました‼' redirect_to @micropost else render 'edit' end endまずeditアクションには既存のタグを取得すために「@tag_list」を追記します。
tagsでマイクロポストに関連するタグを取得
pluck(:name)でタグのnameの配列を取得
join(",")で配列を,で区切った文字列として取得します。「save_tags」を更新用に編集します。
※マイクロポスト編集ページへの「link_to」で行き先を「edit_micropost_path」としたときにマイクロポストshowページ以外からリンクを踏んでアクセスするとタグフォームに既存の値が入らずうまくタグの更新ができなかったので「/microposts/#{micropost.id}/editでリンクを設定してます。
app/model/micropost.rbdef save_tags(savemicropost_tags) current_tags = self.tags.pluck(:name) unless self.tags.nil? old_tags = current_tags - savemicropost_tags new_tags = savemicropost_tags - current_tags old_tags.each do |old_name| self.tags.delete Tag.find_by(name: old_name) end new_tags.each do |new_name| micropost_tag = Tag.find_or_create_by(name: new_name) self.tags << micropost_tag end end動作は既存のタグを残すものと消すものに分けて、新しいタグを既存の残すタグに追加していくといった形です。
「current_tags」既存のタグを取得
「old_tags」消すタグを取得。
「new_tags」新たに追加するタグを取得。
old_tagsとnew_tagsにある「ー」は引き算です。
それぞれ引き算して残ったタグを繰り返し処理して完了です。
例
current_tags - savemicropost_tags =old_tags
「test, test2, test3」 - [test, test3] =[test2]以上でタグ付機能は完成となります。最後までお読みいただきありがとうございました。
アドバイス等いただけるととても喜びます。次は今回作ったタグを利用した検索機能を作ろうと思います。
ありがとうございました。
- 投稿日:2020-03-15T21:49:33+09:00
【Rails6・楽天API】商品検索機能の実装手順
はじめに
Railsで楽天APIを使用して商品(本)の検索、商品情報の取得・表示をする方法をまとめました。
今回は、本を題名で検索し、ヒットした本の情報を一覧にして下に表示する形です。バージョン
・Ruby 2.7.0
・Rails 6.0.2.1実装手順
アプリケーションIDの取得
楽天API情報を使用するには、Rakuten Developersより
「新規アプリ登録」を行いアプリケーションIDを取得する必要があります。
簡単な情報を入力してすぐに取得することができます。
AmazonAPIは申請を行い審査を受ける(落ちることも多いようです)ので、この点が違いますね。gemをインストール
Ruby SDK(公式)に従って進めます。
Gemfileに下記追記して、bundle
でインストール。Gemfilegem 'rakuten_web_service'アプリケーションIDを設定
取得したアプリケーションIDを設定します。
config/initializers
フォルダの中に下記ファイルを作成。rakuten.rbRakutenWebService.configure do |c| # (必須) アプリケーションID c.application_id = '*******************' # (任意) 楽天アフィリエイトID c.affiliate_id = '*******************' endこちらも、Ruby SDK(公式)を参照してください。
タイミングによっては書き方が微妙に異なるようです。ビューの作成
search.html.slim.search-box = form_tag(books_search_path, method: :get) .form-group = text_field_tag :keyword, '', id: "book_search", class: "form-control", name: "keyword", placeholder: "題名を入力してください" button title='検索' class="form-control" type='submit' | 本の題名を検索 - if @books.present? = render 'books/book'_book.html.slim- @books.each do |book| = book.title = book.author = book.item_caption = link_to (image_tag(book.medium_image_url)), book.item_url※フォーマットはかなり省いているので、適宜調整してください。
コントローラーの作成
books_controller.rbclass BooksController < ApplicationController def search if params[:keyword] @books = RakutenWebService::Books::Book.search(title: params[:keyword]) end end endルーティングの設定
routes.rbget 'books/search'題名検索→商品情報表示の流れ
上記コードで最低限の設定はOKのはずです。
本の題名を入力してから商品情報が表示されるまでの流れは下記のようになります。①
text_field_tag
に本の題名を入力して検索ボタンをおす
= form_tag(books_search_path, method: :get)
により、
books
コントローラーのsearch
アクションにいきます。
その際、入力した内容はparams[:keyword]
に格納されています。②楽天APIを使用した検索が行われる
入力した値params[:keyword]
を引数としてsearch
メソッドで検索をかけ、
ヒットしたものを@books
に格納します。
今回は本の検索で絞りたかったため、RakutenWebService::Books::Book
としてます。③再度ビューに展開する
検索結果が@books
に格納されたことにより、
ビューの- if @books.present?
がtrue
となり、パーシャルが表示されます。
パーシャルでは、上から本の「題名」「著者」「説明文」「画像(商品リンク)」を表示しております。
使用できるプロパティはまだまだたくさんあり、楽天ブックス書籍検索API に一覧があります。まとめ
検索結果から商品情報をビューの上でひっぱりだすところで苦戦しましたが、
公式に記載のパラメーターを落ち着いて確認することで対応ができました。
ご参考になりましたら幸いです。参考
●【Rails基礎】楽天APIで商品検索アプリを作ってみた
●Ruby on Rails で楽天商品ページを取得してみた
- 投稿日:2020-03-15T21:14:58+09:00
ruby-dnnとディープラーニングでリンゴをオレンジに変換してみた(Cycle-GAN)
はじめに
今回はruby-dnnでCycle-GANを動かしてリンゴをオレンジに変換してみたいと思います。
RubyとCPUでCycle-GANという時点でもう無理ゲー感半端ないです?コード全文はhttps://github.com/unagiootoro/apple2orange-cycleganにあります。
使用ライブラリ/バージョンなど
Ruby ... v2.6.5
ruby-dnn ... v1.1.4
Numo::NArray ... v0.9.1.5
Numo::Linalg ... v0.1.4
rubyzip ... v2.2.0Cycle-GANざっくり解説
Cycle-GANとは、二つのドメインの異なる画像間でそれぞれ相互に変換ができるようにするモデルです。
画像の変換というと、前回やったPix2pixも画像変換ですが、Pix2pixは入力画像に対する出力画像が一対一でなければならないのに対して、Cycle-GANでは、一対一のペアがなくても変換元と変換先の画像さえあれば変換できるのが特徴です。Cycle-GANでは、「DCGAN-A」「DCGAN-B」「Cycle Consistency Loss」の3つによって変換が行われます。イメージとしては、以下の図のような感じです。
(画像はペイントで作った手抜きです?)
DCGAN-Aでは、リンゴがオレンジになることを学習させます。
逆にDCGAN-Bでは、オレンジがリンゴになることを学習させます。
しかし、これだけだと、リンゴの画像をオレンジに変換したとき、元のリンゴの画像の形状を保つことができないので、Cycle Consistency Lossが必要になります。Cycle Consistency Loss
Cycle Consistency Lossは、DCGAN-Aでリンゴをオレンジに変換した画像にDCGAN-Bを適用すると元のリンゴに戻ることを学習させるために使用します。同じように、DCGAN-Bでオレンジをリンゴに変換した画像にDCGAN-Aを適用すると元のオレンジに戻ることも学習させます。
これによって、元のリンゴの画像の形状を保ったまま必要な箇所だけオレンジに変換することができるようになります。
ruby-dnnでCycle-GAN
モデル定義
Cycle-GANのモデル定義です。ほぼ前回やったPix2pixのモデルを使いまわしています。 1
各クラスの役割は、以下の通りです。
Generator: 入力画像から変換先の画像を生成します。
Discriminator: Generatorが生成した画像を受け取り、それが本物か生成された画像かを判断できるように学習させるためのモデルです。
DCGAN: Discriminatorを騙せるような画像を生成できるようにGenerator-Aを学習させるためのモデルです。また、Generator-Aが生成した画像にGenerator-Bを適用したとき、元の画像に戻るように学習させます。
長いので折りたたんでいます
class Generator < Model def initialize(input_shape, base_num_filters) super() @input_shape = input_shape @cv1 = Conv2D.new(base_num_filters, 4, padding: true) @cv2 = Conv2D.new(base_num_filters, 4, strides: 2, padding: true) @cv3 = Conv2D.new(base_num_filters * 2, 4, padding: true) @cv4 = Conv2D.new(base_num_filters * 2, 4, strides: 2, padding: true) @cv5 = Conv2D.new(base_num_filters * 2, 4, padding: true) @cv6 = Conv2D.new(base_num_filters, 4, padding: true) @cv7 = Conv2D.new(base_num_filters, 4, padding: true) @cv8 = Conv2D.new(3, 4, padding: true) @cvt1 = Conv2DTranspose.new(base_num_filters * 2, 4, strides: 2, padding: true) @cvt2 = Conv2DTranspose.new(base_num_filters, 4, strides: 2, padding: true) @bn1 = BatchNormalization.new @bn2 = BatchNormalization.new @bn3 = BatchNormalization.new @bn4 = BatchNormalization.new @bn5 = BatchNormalization.new @bn6 = BatchNormalization.new @bn7 = BatchNormalization.new @bn8 = BatchNormalization.new end def forward(x) input = InputLayer.new(@input_shape).(x) x = @cv1.(input) x = @bn1.(x) h1 = LeakyReLU.(x, 0.2) x = @cv2.(h1) x = @bn2.(x) x = LeakyReLU.(x, 0.2) x = @cv3.(x) x = @bn3.(x) h2 = LeakyReLU.(x, 0.2) x = @cv4.(h2) x = @bn4.(x) x = LeakyReLU.(x, 0.2) x = @cv5.(x) x = @bn5.(x) x = LeakyReLU.(x, 0.2) x = @cvt1.(x) x = @bn6.(x) x = LeakyReLU.(x, 0.2) x = Concatenate.(x, h2, axis: 3) x = @cv6.(x) x = @bn7.(x) x = LeakyReLU.(x, 0.2) x = @cvt2.(x) x = @bn8.(x) x = LeakyReLU.(x, 0.2) x = Concatenate.(x, h1, axis: 3) x = @cv7.(x) x = LeakyReLU.(x, 0.2) x = @cv8.(x) x = Tanh.(x) x end end class Discriminator < Model def initialize(input_shape, base_num_filters) super() @input_shape = input_shape @cv1 = Conv2D.new(base_num_filters, 4, padding: true) @cv2 = Conv2D.new(base_num_filters, 4, strides: 2, padding: true) @cv3 = Conv2D.new(base_num_filters * 2, 4, padding: true) @cv4 = Conv2D.new(base_num_filters * 2, 4, strides: 2, padding: true) @d1 = Dense.new(1024) @d2 = Dense.new(1) @bn1 = BatchNormalization.new @bn2 = BatchNormalization.new @bn3 = BatchNormalization.new @bn4 = BatchNormalization.new end def forward(x) x = InputLayer.new(@input_shape).(x) x = @cv1.(x) x = @bn1.(x) x = LeakyReLU.(x, 0.2) x = @cv2.(x) x = @bn2.(x) x = LeakyReLU.(x, 0.2) x = @cv3.(x) x = @bn3.(x) x = LeakyReLU.(x, 0.2) x = @cv4.(x) x = @bn4.(x) x = LeakyReLU.(x, 0.2) x = Flatten.(x) x = @d1.(x) x = LeakyReLU.(x, 0.2) x = @d2.(x) x end # Discriminatorの学習を許可する。 def enable_training trainable_layers.each do |layer| layer.trainable = true end end # Discriminatorの学習を禁止する。 def disable_training trainable_layers.each do |layer| layer.trainable = false end end end class DCGAN < Model attr_reader :gen1 attr_reader :gen2 attr_reader :dis def initialize(gen1, gen2, dis) super() @gen1 = gen1 @gen2 = gen2 @dis = dis end def forward(input) images = @gen1.(input) @dis.disable_training # 変換した画像に対するDiscriminatorの出力結果。 out = @dis.(images) # 変換した画像を元の画像に復元した画像。 cycle_image = @gen2.(images) [cycle_image, out] end end # 学習したモデルを保存するためのモデル class CycleGANModel < Model attr_accessor :dcgan_A attr_accessor :dcgan_B def initialize(dcgan_A, dcgan_B) super() @dcgan_A = dcgan_A @dcgan_B = dcgan_B end endモデルの作成
gen_A = Generator.new([64, 64, 3], 64) gen_B = Generator.new([64, 64, 3], 64) dis_A = Discriminator.new([64, 64, 3], 64) dis_B = Discriminator.new([64, 64, 3], 64) # リンゴからオレンジに変換するDCGAN-A dcgan_A = DCGAN.new(gen_A, gen_B, dis_A) # オレンジからリンゴに変換するDCGAN-B dcgan_B = DCGAN.new(gen_B, gen_A, dis_B) dis_A.setup(Adam.new(alpha: 0.00001, beta1: 0.1), SigmoidCrossEntropy.new) dis_B.setup(Adam.new(alpha: 0.00001, beta1: 0.1), SigmoidCrossEntropy.new) dcgan_A.setup(Adam.new(alpha: 0.0001, beta1: 0.5), [MeanAbsoluteError.new, SigmoidCrossEntropy.new], loss_weights: [10, 1]) dcgan_B.setup(Adam.new(alpha: 0.0001, beta1: 0.5), [MeanAbsoluteError.new, SigmoidCrossEntropy.new], loss_weights: [10, 1]) cycle_gan_model = CycleGANModel.new(dcgan_A, dcgan_B)イテレータ
Cycle-GANでは、変換元画像と変換先画像がペアになっている必要はありません。
むしろペアになっていることで、過学習してしまう可能性があります。
そのため、ペアにならない変換元画像と変換先画像のミニバッチを返すイテレータを作成します。class DNN::CycleGANIterator < DNN::Iterator def initialize(x_datas, y_datas, random: true, last_round_down: false) @x_datas = x_datas @y_datas = y_datas @random = random @last_round_down = last_round_down num_datas1 = x_datas.is_a?(Array) ? x_datas[0].shape[0] : x_datas.shape[0] num_datas2 = y_datas.is_a?(Array) ? y_datas[0].shape[0] : y_datas.shape[0] if num_datas1 < num_datas2 @num_datas = num_datas1 else @num_datas = num_datas2 end reset end def next_batch(batch_size) raise DNN::DNNError, "This iterator has not next batch. Please call reset." unless has_next? if @indexes1.length <= batch_size batch_indexes1 = @indexes1 batch_indexes2 = @indexes2 @has_next = false else batch_indexes1 = @indexes1.shift(batch_size) batch_indexes2 = @indexes2.shift(batch_size) end x_batch, _ = get_batch(batch_indexes1) _, y_batch = get_batch(batch_indexes2) # ランダムにサンプリングした変換元画像と変換先画像を返す。 [x_batch, y_batch] end def reset @has_next = true @indexes1 = @num_datas.times.to_a @indexes2 = @num_datas.times.to_a if @random @indexes1.shuffle! @indexes2.shuffle! end end end学習部分
モデルの学習を行う部分です。学習させている内容をざっくりとまとめるとこんな感じです。
リンゴ => オレンジの変換の学習
①Discriminator-Aの学習: Generator-Aが出力したオレンジの画像と本物のオレンジの画像を見分けられるように学習する。
②Generator-Aの学習: Discriminator-Aを騙せるようなオレンジの画像を出力できるように学習する。
③Generator-AとGenerator-Bの学習: Generato-Aが出力したオレンジの画像をGenerator-Bに渡したとき、出力される画像が、元のリンゴの画像に戻ることを学習させる。
※オレンジ => リンゴの場合についても、上記と同様に学習させる。iter1 = DNN::CycleGANIterator.new(x, y) iter2 = DNN::CycleGANIterator.new(x, y) iter3 = DNN::CycleGANIterator.new(x, y) iter4 = DNN::CycleGANIterator.new(x, y) num_batchs = iter1.num_datas / batch_size real = Numo::SFloat.ones(batch_size, 1) fake = Numo::SFloat.zeros(batch_size, 1) (initial_epoch..epochs).each do |epoch| num_batchs.times do |index| x_batch, y_batch = iter1.next_batch(batch_size) x_batch2, y_batch2 = iter2.next_batch(batch_size) x_batch3, y_batch3 = iter3.next_batch(batch_size) x_batch4, y_batch4 = iter4.next_batch(batch_size) # DCGAN-Aの学習 images_A = gen_A.predict(x_batch) dis_A.enable_training dis_loss = dis_A.train_on_batch(y_batch, real) dis_loss += dis_A.train_on_batch(images_A, fake) dcgan_loss = dcgan_A.train_on_batch(x_batch2, [x_batch2, real]) puts "A epoch: #{epoch}, index: #{index}, dis_loss: #{dis_loss}, dcgan_loss: #{dcgan_loss}" # DCGAN-Bの学習 images_B = gen_B.predict(y_batch3) dis_B.enable_training dis_loss = dis_B.train_on_batch(x_batch3, real) dis_loss += dis_B.train_on_batch(images_B, fake) dcgan_loss = dcgan_B.train_on_batch(y_batch4, [y_batch4, real]) puts "B epoch: #{epoch}, index: #{index}, dis_loss: #{dis_loss}, dcgan_loss: #{dcgan_loss}" end if epoch % 5 == 0 cycle_gan_model.save("trained/cycle_gan_model_epoch#{epoch}.marshal") end iter1.reset iter2.reset iter3.reset iter4.reset endモデルの実行
今回も学習済みモデルのGeneratorを用意しています。
Githubからリポジトリをクローンして、「imgen.rb」を実行することで、画像を生成することができます。$ ruby imgen.rb実行結果
生成した画像のうち、比較的うまくいったものを貼っておきます。
inputが入力した画像で、outputが変換した画像になります。
上2行が、リンゴをオレンジに変換したもので、下2行は、オレンジをリンゴに変換したものです。オレンジ色のリンゴと赤色のオレンジができただけなので、Cycle-GANに成功したとは言えそうにないですが、CPUでやったにしてはうまくいったんじゃないでしょうか。
おわりに
Pix2pixが動いたので、もしかしたらCycle-GANもいけるんじゃね?っていう軽い気持ちでやってみましたが、思ってたよりはうまく動いてくれました。
いつかは、ruby-dnnをGPUに対応させて、ウマをシマウマに変換させられるようになりたいですね!
Pix2pixモデルを使いまわしているので、InstanceNormalizationを使っていないなど、実際のCycle-GANとはいくつか異なる点があります。 ↩
- 投稿日:2020-03-15T20:41:01+09:00
GraphQLのクエリを自動的にテストする
こんにちは。株式会社ビットジャーニーでエンジニアをしてる@pockeです。
ビットジャーニーではKibelaというWebアプリケーションを開発しています。KibelaではPublicなWeb APIをGraphQLを使用して提供しています。
https://github.com/kibela/kibela-api-v1-document
また、このAPIはKibelaのWebアプリケーションの内部でも同じものが使用されています。先日、このGraphQL APIを自動的に(ある程度)網羅的にテストするためのgem graphql-autotestをリリースしました。
https://github.com/bitjourney/graphql-autotest
この記事ではこのgraphql-autotestを紹介します。なおgraphql-autotestはRubyで書かれていますが、任意の言語で実装されたGraphQLサーバーをテスト可能です(Rubyのコードを多少書く必要はありますが)。
「網羅的なテスト」とは
graphql-autotestの目的は、GraphQLのクエリをある程度網羅的にテストすることです。
私達は普段GraphQL APIを開発する際にユニットテストを書いています。
理想論を言えばすべてのフィールドにユニットテストを書きたいところですが、しばしば漏れが生じます。graphql-autotestはそのような漏れを拾い上げるようなテストです。
まず、スキーマ定義から可能な限りすべてのフィールドを取得するようなクエリを自動的に書き出します。
そしてそのクエリを実行し、エラーが出ないことを確認します。ポイントは、クエリを自動的に書き出す点です。
クエリの生成を自動化することで、新規に追加したフィールドにユニットテストを書き忘れていたとしても、graphql-autotestでそのフィールドをテストできます。ただし、デフォルトではgraphql-autotestはクエリの実行がエラーを出さないことのみをチェックします。
「GraphQLサーバーの実装にtypoがあってNoMethodErrorが出ていた」と言ったケースは検出できますが、「1が返るべきところで2が返っていた」と言ったバグは検出できません。
ただし、クエリを実行したレスポンスに対して、それを検証する適当なコードを書いてやればある程度はテストできるでしょう。なお、クエリの実行はgraphql gemを使っている場合にのみ可能です。Ruby以外の言語でGraphQLサーバーが実装されている場合は、生成されたクエリを実行する部分は別に実装する必要があります。
使い方
では、使い方を見ていきましょう。
Installation
まずこのgemをインストールします。
$ gem install graphql-autotestもしくはBundlerを使っている場合、次のコードを
Gemfile
に足してください。Gemfilegem 'graphql-autotest'設定例
graphql gemを使っている場合
まずは、graphql gemを使っている場合の一番シンプルなコードを紹介します。
require 'graphql/autotest' class YourSchema < GraphQL::Schema end runner = GraphQL::Autotest::Runner.new( schema: YourSchema, context: { current_user: User.first }, ) runner.report!まず、
YourSchema
としてスキーマ定義をしています。
これは自身のスキーマ定義に置き換えてください。そして、
GraphQL::Autotest::Runner
のインスタンスを生成し、report!
でクエリを生成、実行しています。
Runner.new
の引数には定義したYourSchema
と、context
を渡しています。このcontext
はYourSchema.execute
を呼ぶ際に渡されます。これだけでスキーマ定義からクエリを自動生成して、実行し、エラーがないかをテストできます。
ただし、実用上ではデフォルトの設定では充分な結果が得られないことがあるでしょう。そのため後述するarguments_fetcher
などの設定が必要です。graphql gemを使っていない場合
冒頭でも説明したとおり、このgemはgraphql gemを使ってサーバーを実装している場合以外でもクエリの自動生成ができます(クエリの実行はできません)。
クエリを自動生成するには、 https://github.com/kibela/kibela-api-v1-document/blob/master/schema.graphql のようなスキーマ定義が必要です。
このスキーマ定義がpath/to/schema.graphql
に保存されている場合、次のように書くとクエリを生成できます。require 'graphql/autotest' fields = GraphQL::Autotest::QueryGenerator.from_file(path: 'path/to/schema.graphql') # 生成したクエリがすべて表示される fields.each do |field| puts field.to_query end生成したクエリは適応なGraphQLクライアントに渡して実行すると良いでしょう。
設定方法
ここまでで、デフォルトの設定でクエリを生成、実行する方法を解説しました。
ですが前述したとおり、デフォルトの設定では不十分なケースもあります(Kibelaの場合はそうです)。
そのためgraphql-autotestではいくつかの設定項目を用意しています。以下にその設定項目を解説します。
arguments_fetcher
arguments_fetcher
では、フィールドが引数を受け取る場合にどのような引数を渡すかを定義します。
arguments_fetcher
はProcを指定します。指定したProcはフィールドの情報を引数として呼ばれ、Hashを返せばそれが引数として使われます。デフォルトではgraphql-autotestでは必須の引数がない(== 引数なしで呼べる)フィールドのみをクエリとして書き出します。
そのため、必須の引数があるフィールドを取得するためにはarguments_fetcher
を使用して、どのような引数を渡したら良いかを定義します。
また必須でない引数でも、必要があれば指定できます。たとえば、KibelaではRelay Connectionに対する
first
かlast
引数が必須となっています。
このfirst
を埋めるようなfetch_argumentsの定義は次のようになります。fill_first = proc do |field| field.arguments.any? { |arg| arg.name == 'first' } && { first: 5 } end fields = GraphQL::Autotest::QueryGenerator.from_file( path: 'path/to/schema.graphql', arguments_fetcher: GraphQL::Autotest::ArgumentsFetcher.combine( fill_first, GraphQL::Autotest::ArgumentsFetcher::DEFAULT, ), )この
arguments_fetcher
をより設定することで、フィールドの網羅性を上げることができます。
skip_if
skip_if
では、取得したくないフィールドをスキップできます。
どうしても自動的なクエリの生成ではエラーが出てしまうようなフィールドを無視するのに使えるでしょう。
skip_if
もProcを指定します。このProcはフィールドの情報を引数に呼ばれ、truthyな値を返せばそのフィールドはスキップされます。skip_if = proc do |field| field.name == 'sensitiveField' end fields = GraphQL::Autotest::QueryGenerator.from_file( path: 'path/to/schema.graphql', skip_if: skip_if, )
max_depth
max_depth
には、クエリの最大の深さを指定します。graphql-autotestではフィールドの循環参照があった場合に無限ループにならないよう制御しているため、理論上は
max_depth
を指定しなくてもクエリの実行は終了します。
とはいえクエリが深すぎる場合、クエリの生成や実行に時間がかかってしまいます。
その場合はmax_depth
オプションを使用して、深さを減らしてください。デフォルトの
max_depth
は10です。この値は適当です。fields = GraphQL::Autotest::QueryGenerator.from_file( path: 'path/to/schema.graphql', max_depth: 5, )まとめ
GraphQLの網羅的なテストを実行するためのGemであるgraphql-autotestを紹介しました。
実際にKibelaではこのgemを実装する過程で複数のバグを発見しました。
バグがあったのはいずれもKibelaのWebアプリケーションでは使っていないフィールドでした。ですが、ユーザーに公開されているAPIでも取得されうるフィールドであることを考えると早めにバグを発見できたのは良かったと思います。graphql-autotestを使って、GraphQLサーバーのテストを実施していただければ幸いです。
- 投稿日:2020-03-15T20:26:55+09:00
【1日目】今日の学び fontawesomeをeach文の中で繰り返して表示させる
【1日目】プログラミング初学者の今日の学び
プログラミング初学者が、ポートフォリオを作っている中で得た気づきを書いていきます。Fontawesomeをhtml.erbのループ内で使う
Microposts一覧表示ページにおいて、各micropostを削除するボタンを、Fontawesomeのゴミ箱アイコンで表現したかった。
埋め込みrubyの繰り返し内でhtmlタグを使いたい時は、ブロックを受け渡せば良いらしい。app/views/microposts/_micropost.html.erb<% @microposts.each do |micropost| %> <div> <%= link_to micropost.name, "#" %> <%= link_to micropost_path(micropost), :method => :delete do %> <i class="far fa-trash-alt"></i> <% end %> </div> <% end %>
- 投稿日:2020-03-15T20:26:55+09:00
【1日目】初学者の今日の学び ストロングパラメータ, fontawesomeについて
【1日目】プログラミング初学者の今日の学び
プログラミング初学者が、ポートフォリオを作っている中で得た気づきを書いていきます。Fontawesomeをhtml.erbのループ内で使う
Microposts一覧表示ページにおいて、各micropostを削除するボタンを、Fontawesomeのゴミ箱アイコンで表現したかった。
埋め込みrubyの繰り返し内でhtmlタグを使いたい時は、ブロックを受け渡せば良いらしい。app/views/microposts/_micropost.html.erb<% @microposts.each do |micropost| %> <div> <%= link_to micropost.name, "#" %> <%= link_to micropost_path(micropost), :method => :delete do %> <i class="far fa-trash-alt"></i> <% end %> </div> <% end %>
- 投稿日:2020-03-15T19:47:12+09:00
Progate無料版をやってみる【Ruby on Rails5 I】
前回に引き続き無料レッスンをこなしていきたいと思います。
Rubyとどう違うのか?
→Rubyのフレームワークのことを指す、またはRubyのフレームワークを使って開発することだと思います。
→Webアプリケーション開発のためのフレームワークらしいです。
Ruby on RailsとフレームワークRuby on Railsの全レッスンをクリアすると「TweetApp」というTwitterに似せたWebアプリを作れるらしいです。
ちょっと興味あるので、課金しようかな・・・。というか経験者の場合、課金しないとどのレッスンも有益な知識は得られないかもですね・・・。
無料だから仕方ないですね!Ruby on Rails5 I
Ruby on Railsを始めよう
・まずRailsをインストール
gem instrall railsターミナルで、そのディレクトリまで移動し以下を入力実行。
rails new tweet_app勝手にいろいろなディレクトリやファイルが作られました!
tweet_appディレクトリがTOPにできますので、自身でディレクトリを用意する必要はなかったですね・・・。・tweet_appのディレクトリに移動して、
rails serverbundleってなにw
参考にさせていただきました。
→https://pikawaka.com/rails/bundler
bundle install
を行うと、Gemfileに記載されたgemがインストールされるとのこと。
Gemfileにsqlite3があった。
これのバージョンが1.4だとだめらしい。
以下を参考にさせていただきました。
Windows10でRuby on Railsの環境構築1.3.13に変更しました。
一度bundle update
した後
あれwまだエラー・・・。
以下の「3.sqlite3のダウンロード」の手順を実行
Windows10でRuby on Railsの環境構築再度
rails server
→ まったく同じエラー。以下を参考にさせていただきました。
Windows10で「rails server」コマンドを実行したときに「cannot load such file -- sqlite3/sqlite3_native」とエラーが出ることへの対処上記対応を行い、改めて
rails server
以下のエラー(もうなんなの・・・)
rails webpacker:install
を実行する必要があるとのこと。
ただ、それに加えてNode.jsとYarnも付随してインストールが必要であった・・・。
参考にさせていただきました。
→https://qiita.com/libertyu/items/1eb74adc817ab8971100あ、なんかいい感じ。
http://localhost:3000をブラウザで表示してみると
なんだこれは・・・
メッセージを見ると、sqlite3が1.4だけど1.3.13が指定されているよー的な感じ。ちょっと心当たりがありました。
少し上の対応で
Windows10で「rails server」コマンドを実行したときに「cannot load such file -- sqlite3/sqlite3_native」とエラーが出ることへの対処
sqlite3_native.so
のコピーの時にC:\Ruby26-x64\lib\ruby\gems\2.6.0\gems\sqlite3-1.4.2\lib\sqlite3\sqlite3_native.so
をコピーしており(というか1.13.3のディレクトリがなかったので)、Gemfileの指定(1.4)とは異なると違和感を感じていました。よってGemfileのsqlite3のバージョンを1.13.3 → 1.4にもどしたところ
やっと正常に表示できました。
結局1.13.3に戻した意味がなかった・・・。トップページを作ってみよう
・以下コマンドでTOPがページが作成される
rails generate controller home top
http://localhost:3000/home/top
をブラウザで開くと
ビューを学ぼう・ビューを理解しよう
・先ほどのTOPページは
app/views/home/top.html.erb
に作成されているらしい。
このページをリクエスト時にクライアント側に返しているコントローラを理解しよう
・先ほどの
rails generate controller home top
でapp/controllers/home_controller.rb
のファイルにtopというメソッドが作られる
・「コントローラと同じ名前のビューフォルダから、アクションと同じ名前のHTMLファイルを探してブラウザに返します」とのこと。
例:URLで~home/top
と指定すると、home
の下のtop.html.erb
を返す。ルーティングを学ぼう
・ルーティングとは
・config/routes.rb
に
home/top
の記載がありました。
ここの記述を変更することで、指定したURLと開くページの対応関係を変更できる。サービス紹介ページを追加しよう
・
config/routes.rb
やapp/controllers/home_controller.rb
に追記することで、新たにルーティングとメソッドを追加できる。
また、そのリクエストの帰り値となるhtmlをapp/views/home
ディレクトリに新しく作成する。レイアウトを整えよう
・CSSファイルは
app/assets/stylesheets
に配置されている。
すべてのhtmlに対して適用される模様。画像を表示しよう
・publicに画像を入れておくだけで、「<img src="/画像名" >」や「background-image: url("/画像名");」のように、画像名を指定するだけで、簡単に画像を表示することができる模様。
トップページのURLを変更しよう
・ルーティングにて
get "/" => "home#top"
とするリンクを作ろう
・aタグのhrefにルーティングで設定したURLをそのまま入れることで対応するhtmlを表示できる。
感想
他言語(私が知っている数少ない言語)に比べて簡単にWebアプリが作成できるような仕組み、枠組みという印象を受けました。
→どれくらい簡単に凝った画面を作れるかも試してみたくなりました。環境構築のところはかなり手こずってしまい、課題となりました。
次回はRuby on Rails5 IIをやっていきたいと思います。
無料はIIまでのようです。
- 投稿日:2020-03-15T18:42:57+09:00
rbenvでプロダクト毎にrubyのバージョン管理をする
rbenv
rubyをプロダクト毎にバージョンを管理する必要性は以下の2つにあると考えています。
- ローカルを汚さない
- 複数のプロダクトを落とす毎に毎度毎度バージョンを切り替えるのは大変
gemsetを併用して使うと尚、いいと推測します。下記のリンクはrbenv-gemsetの使い方です。
rbenv-gemsetを使ってプロジェクト毎のgemを管理する
rbenvをbrewでインストールする
$ brew installl rbenvrbenvの基本コマンド群
インストールできるrubyのパッケージを確認 $ rbenv install -l インストールしてあるrubyのバージョンを管理できる $ rbenv versions rbenvの初期化 $ rbenv init rubyをインストールする $ rbenv install バージョン インストールしたrubyのバージョンを反映させる $ rbenv rehash システム全体のrubyバージョンを設定する $ rbenv global rubyバージョン プロダクト毎にrubyバージョンを設定する $ rbenv local rubyバージョン基本的なコマンドはざっと上記の通りです。
プロダクト毎にrubyバージョンを管理する
必要なrubyバージョンを手に入れる
$ rbenv install rubyバージョン $ rbenv rehash初期化する
$ rbenv init # Load rbenv automatically by appending # the following to ~/.zshrc: eval "$(rbenv init -)"initして出てきた内容を.zshrcに書き込み、反映させる
$ echo 'eval "$(rbenv init -)"' >> .zshrc $ source ~/.zshrc管理するプロダクトへ移動し、localへセットする
$ cd プロダクト名 $ rbenv local rubyバージョン 下記のようなファイルができているはずです $ cat .ruby-version 2.5.4rubyのバージョンが変わったことを確認する
プロダクト内部では下記のバージョンに $ ruby -v ruby 2.5.4p155 (2019-03-13 revision 67245) [x86_64-darwin19] $ rbenv versions system * 2.5.4 (set by /Users/naoya.ohsaki/gx_community_admin/.ruby-version) 2.7.0 $ cd プロダクトの外部では下記のバージョンに $ ruby -v ruby 2.6.3p62 (2019-04-16 revision 67580) [universal.x86_64-darwin19] $ rbenv versions * system (set by /Users/naoya.ohsaki/.rbenv/version) 2.5.4 2.7.0上記のように切り替わっていたら完了です。お疲れ様でした。
- 投稿日:2020-03-15T17:52:05+09:00
Rails~パスワードの取り扱いについて~
パスワードの取り扱い
パスワード機能がついているSNSアプリやサービスにはデータベースにパスワードが保存されている。このままだとPCを覗き見されたりハッキングでデータを盗まれた場合そのパスワードを使って他人のアカウントに不正にログインできてしまう。そのため万が一パスワードを覗き見されたりデータを盗まれてもパスワードの内容がわからない状態を目標とする。
gemファイルを使用しパスワードを暗号化する。
gemとは
gemとはRubyやRailsでプログラミングをする際に「よく使う機能」をパッケージ化したもの。「検索機能を作るgem」や「暗号化するためのgem」など様々なgemが存在し、Railsにインストールすることで使用することができる。
今回使用するgem= bcrypt(暗号化)するためのgemgemfiles
Railsにはインストールしたいgemを記述するGemfileというファイルが存在する。
「gem 'rails','5.0.3'」等と記載する。「rails new」コマンドで生成されたgemfileにはすでにいくつかのgemが書かれてインストールされている。数字の部分はgemのバージョンを意味する。バージョンを指定しない場合は最新のgemがインストールされる。bcryptのインストール
Gemfile
gem 'bcrypt'ターミナル
bundle installGemfileへインストールしたいgemを入力→ターミナルにてbundle installと入力する。
bcryptをインストールすると「has_secure_password」というメソッドが使える用になるパスワードを扱うモデルにhas_secure_passwordを追加する。これによりユーザーを保存する際に自動的にパスワードを暗号化してくれる。password_digestカラムを追加する。
上記のメソッドは暗号化したパスワードをpassword_digestカラムに保存する。そのためpasswordカラムは不要となるので削除を行う。
マイグレーションファイルの作成
ターミナル
rails g migration change_users_columns rails db:migrateマイグレーションファイル
def change add_column :users,:password_digest,:string remove_column:users, :password, :string endauthenticateメソッド
has_secure_passwordメソッドを有効にするとauthenticateメソッドを使える様になる。このメソッドは渡された引数を暗号化し、password_digestの値と一致するかどうかを判定してくれる。authenticateメソッドを使用し「送信されたメールアドレスと一致するユーザー」のパスワードと送信されたパスワードが一致するかどうかログイン処理を行う。
controller
def login @user = User.find_by(email:params[:email]) if @usergets && @user.authenticate(params[:password]) && @user.authenticate(params[:password]) end end
- 投稿日:2020-03-15T16:51:46+09:00
Rails ~いいね機能を作る~
テーブルの作成
モデルとマイグレーションファイルの作成を準備する。今回作成するテーブルにはuser_idとpost_idの2つのデータをもたせるように処理を行う。
STEP1 マイグレーションファイルを作成
STEP2 マイグレーションファイルの内容をデータベースに反映させるターミナル
マイグレーションファイルの作成 rails g model Like user_id:integer post_id:integer データベースに変更を反映。データ名:integerでデータ作成 rails db:migrateバリデーションの追加
class LIKE<ApplicationRecord validates:user_id,{presence:true} validates:post_id,{presence:true} どちらも存在しないと不完全なデータとなってしまう。 endコンソールにて仮作成
rails console > like=Like.new(user_id:1,post_id:2) IDが「1」のユーザーがIDが「2」の投稿をいいねした、というデータを作成する。 > like.saveいいねした投稿かどうかを表示する
ログインしているユーザーがその投稿にいいね舌データが存在するという条件のために、user_idとpost_idが合致するデータがlikesテーブルに存在するかどうか、find_byを用いてチェックする(find_by には該当する条件が存在しなかったときにNILを返す性質がある。)
view
<% if Like.find_by(user_id:@current_user.id,post_id:@post.id)%> いいね済み <%else%> いいね!していません <%end%>いいねボタンの準備
いままでのコントローラーは「rails g controller」コマンドで自動生成してきた。コマンドを用いるとviewファイルも自動生成されるが今回はそれらのファイルが必要ないため手動でのコントローラ作成を行う。[likes_controller]を作成する。
createアクションを用意
作成したlikesコントローラに新たにLikeデータを作成するためにcreateアクションを用意する。
routes
Rails.applicaion.routes.draw do post"likes/:post_id/create"=>"likes#create" endcontroller
def create @like = Like.new( user_id:@current_user.id, post_id:params[:post_id] ) @like.save redirect_to("/posts/#{params[:post_id]}") endview
<%Like.find_by(...)%> <%= link_to("いいね!済み","/likes/#{@post/id}/destroy",)%> <% else %> <%=link_to("いいね!","/likes/#{@post.id}/create",{method:"post"})%> <% end %>いいね!を削除する
destroyアクションの用意
「いいね!」を取り消す機能を作るために、まずはlikesコントローラにdestroyアクションを作成する。destroyアクション内では、受け取った@current_user.idとparams[:post_id]をもとに削除すべきlikeデータを取得しdestroyメソッドを用いて削除する。
controller
def destroy @like = Like.find_by( user_id:@current_user.id, post_id:params[:post_id] ) @like.destroy redirect_to("/posts/#{params[:post_id]}") endroutes
Rails.application.routes.draw do post"likes/:post_id/destroy"=>"likes#destroy" endcountメソッド
likesテーブルからデータの件数を取得するにはcountメソッドを用いる。
countメソッドは配列の要素数を取得するメソッドですがテーブルのデータ数を取得するためにも利用することができる。likesアクションを用意する
routes
get"users/:id/likes"=>"users#likes"controller
def likes @user = User.find_by(id:params[:id]) @likes = Likes.where(user_id: @user.id) endview
<% @likes.each do |like|%> <% post = Post.find_by(id:like.post_id)%> <% end %>
- 投稿日:2020-03-15T15:59:37+09:00
初心者向け、メソッドの用語まとめ(Ruby)
はじめに
- 本記事はRubyでプログラミングを初めてメソッドでいろんな用語が出てきて混乱しているという方が調べる際のヒントになればいいなと思い書きました。
- まずい表現や間違ったことが書いてある場合はコメント等で教えていただけると幸いです。
メソッドってなんだ?
引数、仮引数ってなんだ?
戻り値ってなんだ?、、、
となった人向けに書きました。私はなりました。
メソッドがプログラミングを始めて、初めのつまづきポイントなのではないかなと思っています。
メソッドのおさらい
まずメソッドのおさらいです。
実際のコードを見てみましょう。
例として、税込価格を計算して返してくれるメソッドを書いてみました。####################メソッドを定義############################ #税込価格を計算するメソッドを定義 def calculate_tax_included_price(price) #税込価格 tax_included_priceを変数定義 #税込価格の計算結果を代入 tax_included_price = price * 1.1 #returnで戻り値を返す(省略可能) return tax_included_price end ####################メソッドを呼び出す###################### #支払い額 paymentを変数定義 #calculate_tax_included_price 引数:500 でメソッドを呼び出す。 payment = calculate_tax_included_price(500) #putsメソッドでpaymentをターミナルに出力 puts payment ####################出力結果(コンソール)#################### 550.0
- def ~ end のかたまりがメソッドの定義です。 defは定義 definitionの略
- メソッドは呼び出す場所より上で定義する。(Rubyでは上から読み込んでいくため、メソッドは使用される前に定義する必要があるため。)
- returnの一文は省略が可能
メソッドの用語
メソッドに入ってから用語が増えるので、用語を覚えるまでは混乱することが多いと思います。
ここで復習しましょう。・ メソッド、関数
メソッドと関数という言葉がほとんど同じ意味なのに2通りの呼び方があることが混乱の一因だと思います。
メソッドと関数は厳密には違う意味をもつらしいのですが区別しなくても、あまり困ることはないかなと思います。
Rubyではメソッドの概念しか登場しないそうなのでRubyでは全部メソッドと呼んでおけばOKだと思います。・ メソッドの定義
def ~ endの中に処理を書いて、呼び出して使えるようにすることです。
・ メソッドを呼び出す、使う
メソッド名を記述する、メソッドに仮引数が定義されている場合は引数を渡すとメソッド内の処理が実行されます。
・ (実)引数、仮引数
どっちがどっちかわからなくなる、引数と仮引数ですがここで復習しましょう。
メソッドを呼び出すところで、渡すのが(実)引数
メソッドを定義したときに書いているのが仮引数です。ちなみに、実引数と仮引数の変数名違う名前でもOKです。ここは混乱するポイントだと思いますが、
実引数が仮引数に改名されると考えるのではなく、
仮引数はメソッドが呼ばれたときに新しく定義される変数で、実引数の値が仮引数にコピーして格納されるとイメージしましょう。・ 戻り値、返り値
これも2通りの呼び方がある戻り値、返り値ですが、全く同じものです。好きな方で呼びましょう
returnの後ろに書いたものが戻り値、返り値として呼び出し元に返されます。
calculate_tax_included_price(500)が550.0という値が格納された変数tax_included_priceに置き換わると考えましょう。Rubyではreturnを省略するとメソッドの最後に記述した式が戻り値として返されます。(今回の例ではreturnを省略しても挙動に変化なし)
・ レシーバ
"こんにちは".size #=> 5メソッドの実行主体のこと、「 . 」の左側
なぜレシーバという名前かというと、、、
オブジェクト指向では処理をメッセージのやり取りによって行うという考え方があり、メソッドの実行側がこのメッセージの受け取り側と捉えることができます。そのためレシーバと呼ばれます。
だそうです。・ (カーネル)メソッド、組み込み関数
標準で組み込まれているメソッド・関数
レシーバが不要なメソッド
よく使うputsメソッドなどが含まれます。・ 公開範囲
Railsprivate def set_user @user = User.find(params[:id]) endpublic, protected, privateがある。(protectedはあまり使われない)
・public どこからでもアクセス可能、デフォルト設定でされる。
・private 外部からアクセス不可、隠蔽したいメソッドに利用する。・ 継承
子クラスは継承親のメソッドを受け継ぎます。
########## 親クラス runメソッドを定義################################ class Car #runメソッド def run puts '走る' end end ######### 子クラス(Carクラスを継承している) メソッドは何も定義していない###### class SuperCar < Car end ####################実行部分################################### #SuperCarクラスのインスタンス化を行う。 supercar = SuperCar.new #SuperCarではrunメソッドを定義していないがCarクラスのrunメソッドを使用できる。 supercar.run ####################出力結果(コンソール)########################## 走る・ オーバーライド
子クラスで親クラスで定義されたものと同じ名前を使ってメソッドを新しく定義しなおすことです。
########## 親クラス runメソッドを定義################################ class Car #runメソッド def run puts '走る' end end #########子クラス(Carクラスを継承している) runメソッドをオーバーライドしている#### class SuperCar < Car def run puts '超速く走る' end end ####################実行部分################################### #SuperCarクラスのインスタンス化を行う。 supercar = SuperCar.new #SuperCarのオーバーライドされたrunメソッドを使用する。 supercar.run ####################出力結果(コンソール)########################## 超速く走る・ オーバーロード、多重定義
Rubyにはない概念ですが、オーバーライドと字が似ているので誰しもが最新は混乱すると思います。
どういうものかというとメソッドの名前は同じで引数のデータ型や引数の個数を変えたものを複数定義するというものです 。
同じメソッド名が使えるのでメソッドを使うときに楽になる、ポリモーフィズムが実現できると言ったメリットがあります。
- 投稿日:2020-03-15T15:17:36+09:00
RuboCopのAbcSizeチェックの対応
Rubyの静的コード解析をやってくれるRuboCopをRails開発に導入しました。そこで発生したAbcSizeチェックについての作業メモ。
AbcSizeとは
実装したメソッドに対してRuboCop君が計算してくれるスコア。
以下の3つのカウントが評価対象。
- Assignment : 代入
- Branch : メソッド呼び出し
- Condition : 条件
デフォルトで15を超えると警告が出て、もっとスコア下げてねと言われる。
警告が出たメソッド
ユーザーのサインイン機能を担う、こちらの
SessionsController.create
。sessions_controller.rbdef create @user = User.find_by(email: params[:session][:email].downcase) if @user&.authenticate(params[:session][:password]) # ユーザーログイン後にユーザ情報のページにリダイレクトする log_in @user params[:session][:remember_me] == '1' ? remember(@user) : forget(@user) recdirect_back_or @user else # エラーメッセージを作成する flash.now[:danger] = 'Invalid email/password combination' render 'new' end end出た警告はこちら。
Assignment Branch Condition size for create is too high. <2, 19, 4> 19.52/15",
どうやらスコアは19.52らしく、評価項目毎だとこうなってる。
- Assignment => 2
- Branch => 19
- Condition => 4
対策
...とはいえ現状可読性は問題ない(主観)し、メソッド抽出が必要なほどロジックは複雑ではないので、AbcSizeチェックの基準値を調整しておく。
.rubocop.ymlMetrics/AbcSize: Max: 20おわりに
デフォルトの15を下回れるような解決方法があれば後ほど更新したい。
ちなみにRuboCop自体の
.rubocop_todo.yml
では基準値が18になっているという...
- 投稿日:2020-03-15T15:15:07+09:00
【今日のエラー】クラスとインスタンス
railsの勉強の際、あまりにもインスタンスの理解が浅かったのでRubyの復習をしてきました。
クラスとインスタンス
簡単にまとめると、クラスという設計図をまず作ることによってその設計図からいくらでもインスタンスを生成することができる。
イメージとしては設計図から大量の実物が生成されるイメージ。
例えばruby_test.rbclass Tanaka attr_accessor :name, :age def initialize self.name = '田中' self.age = 10 end def introduce(personal_name) puts "#{self.name}が#{personal_name}を紹介して#{self.age}歳という年齢を教えた" end end tanaka_A = Tanaka.new tanaka_A.introduce('佐藤') p tanaka_A結果
田中が佐藤を紹介して10歳という年齢を教えた
日本語的には非常に怪しいですが笑
田中というクラスから名前や年齢などのインスタンスが生成されそれを
tanaka_A.introduce()
とすることでintroduceメソッドを呼び出したりすることもできる。
かなり直感的に理解しやすかったです。またこの
self.
の部分は下で書いたtanaka_A
のインスタンスを指すということを理解しておくとなおよい。間違ってたら指摘お願いします笑
参考文献
- 投稿日:2020-03-15T14:24:17+09:00
Rails勉強中 EC_siteカート機能 同一商品追加時のcreate
はじめに
現在Rails勉強中の人が備忘録として書いてます。
こうした方がいい、あーした方がいいといった点があれば教えてくださいコード
内容
viewは比較的スムーズに実装できると思うので割愛
コード見れば分かると思うが、自身が誤っていないかの確認
↓
始めに、isExistとIdのデフォルト値の定義
each end内で追加した商品IDとカートに追加されている商品IDが同じかif文で記す
if isExistではeach内の変数を持っていけないので、前に定義したIDを持ってくるsumで新たに追加したい値と既にカートに保存されている値を計算
updateするのは追加する商品の個数だけなので(ここではcount)、
update_attributesでカラム指定して実行
※update_attributeもあるが、これはvalidationチェックされずにupdateが実行される
個数をupdateしたいが、仮に文字が入ったとしてもupdateされてしまう。最後に
modelとかhelperを使えばもう少しコントロールの記述量が減るかもしれないが、
勉強不足のため日々精進していきたい
- 投稿日:2020-03-15T13:48:44+09:00
FullcalendarをRailsに導入
Fullcalendarについて
カレンダー機能付きのアプリを作りたく調べてみました。
こんな感じのが出来ます。
FullCalendarはjQueryのプラグインでrailsに導入する際は、gemで導入しました。
今回はScaffoldを使って簡単なEventモデルを作り、カレンダーに反映出来るところまでやります。では、
1.Gemの追加
Gemfile.gem 'jquery-rails', '4.3.3' gem 'fullcalendar-rails' gem 'momentjs-rails'そして、
bundle install
2.application.jsに記述
app/assets/javascript/application.js//= require jquery //= require moment //= require fullcalendar $(function () { // 画面遷移を検知 $(document).on('turbolinks:load', function () { // lengthを呼び出すことで、#calendarが存在していた場合はtrueの処理がされ、無い場合はnillを返す if ($('#calendar').length) { function eventCalendar() { return $('#calendar').fullCalendar({ }); }; function clearCalendar() { $('#calendar').html(''); }; $(document).on('turbolinks:load', function () { eventCalendar(); }); $(document).on('turbolinks:before-cache', clearCalendar); $('#calendar').fullCalendar({ events: '/events.json' }); } }); });一つ目の関数ではFullCalendarの設定を読み込み、二つ目の関数ではFullCalendarを削除します。
その下に呼び出すコードを2行書き最後にイベントを表示させるためのコードを書きます。3.application.cssに記述
app/assets/stylesheets/application.css... *= require_tree . *= require_self *= require fullcalendar */これで、あとはviewのerbファイルに
<div id="calendar"></div>
と書き込むとカレンダーが表示されます。4.イベントを表示する
Fullcalendarにイベントの情報を表示するには、JSONファイルを使ってあげます。
イベントにはタイトル、説明、開始日、終了日が必要です。今回はscaffoldで作りました。
rails generate scaffold Event title:string description:text start_date:datetime end_date:datetime
そして、
rails db:migrate
JSONを渡すために、Railsのjbuilderというものを使っていきます。scaffoldによってjson.jbuilderファイルが自動で作られています。
以下のコードを追加app\views\events\index.json.jbuilderjson.array!(@events) do |event| json.extract! event, :id, :title, :description json.start event.start_date json.end event.end_date json.url event_url(event, format: :html) endこうすることで、{"id":"1", "title":"タイトル", "description":"説明", "start":"日付1", "end":"日付2", "url":"some_address"}のようにjsonファイルが作られ、カレンダーが読み込めるデータになります。
url項目があることで、カレンダーの予定にurlが埋め込まれ、クリックすると予定の詳細に飛ぶことができます。現状、ルーティングはこのようになっています。
routes.rbRails.application.routes.draw do resources :events # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html root 'events#index' end一旦、これで表示は出来るようになりました。
あとは、カレンダーのオプションで色々触ってみて下さい。$('#calendar').fullCalendar({ });の{ }内にオプションを加えることで、カレンダーのタイトルをYYYY年MM月のようにしたり、レイアウト、時間の表示を設定できます。
公式ドキュメントには色々な使い方、機能が載っていますので確認してみて下さい。
FullCalendarの使い方
- 投稿日:2020-03-15T13:48:44+09:00
FullCalendarをRailsに導入
FullCalendarについて
カレンダー機能付きのアプリを作りたく調べてみました。
こんな感じのが出来ます。
FullCalendarはjQueryのプラグインでrailsに導入する際は、gemで導入しました。
今回はScaffoldを使って簡単なEventモデルを作り、カレンダーに反映出来るところまでやります。では、
1.Gemの追加
Gemfile.gem 'jquery-rails', '4.3.3' gem 'fullcalendar-rails' gem 'momentjs-rails'そして、
bundle install
2.application.jsに記述
app/assets/javascript/application.js//= require jquery //= require moment //= require fullcalendar $(function () { // 画面遷移を検知 $(document).on('turbolinks:load', function () { // lengthを呼び出すことで、#calendarが存在していた場合はtrueの処理がされ、無い場合はnillを返す if ($('#calendar').length) { function eventCalendar() { return $('#calendar').fullCalendar({ }); }; function clearCalendar() { $('#calendar').html(''); }; $(document).on('turbolinks:load', function () { eventCalendar(); }); $(document).on('turbolinks:before-cache', clearCalendar); $('#calendar').fullCalendar({ events: '/events.json' }); } }); });一つ目の関数ではFullCalendarの設定を読み込み、二つ目の関数ではFullCalendarを削除します。
その下に呼び出すコードを2行書き最後にイベントを表示させるためのコードを書きます。
その他、説明は少しはしょります。3.application.cssに記述
app/assets/stylesheets/application.css... *= require_tree . *= require_self *= require fullcalendar */これで、あとはviewのerbファイルに
<div id="calendar"></div>
と書き込むとカレンダーが表示されます。4.イベントを表示する
Fullcalendarにイベントの情報を表示するには、JSONファイルを使ってあげます。
イベントにはタイトル、説明、開始日、終了日が必要です。今回はscaffoldで作りました。
rails generate scaffold Event title:string description:text start_date:datetime end_date:datetime
そして、
rails db:migrate
JSONを渡すために、Railsのjbuilderというものを使っていきます。scaffoldによってjson.jbuilderファイルが自動で作られています。
以下のコードを追加app\views\events\index.json.jbuilderjson.array!(@events) do |event| json.extract! event, :id, :title, :description json.start event.start_date json.end event.end_date json.url event_url(event, format: :html) endこうすることで、{"id":"1", "title":"タイトル", "description":"説明", "start":"日付1", "end":"日付2", "url":"some_address"}のようにjsonファイルが作られ、カレンダーが読み込めるデータになります。
url項目があることで、カレンダーの予定にurlが埋め込まれ、クリックすると予定の詳細に飛ぶことができます。現状、ルーティングはこのようになっています。
routes.rbRails.application.routes.draw do resources :events # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html root 'events#index' end一旦、これで表示は出来るようになりました。
あとは、カレンダーのオプションで色々触ってみて下さい。$('#calendar').fullCalendar({ });の{ }内にオプションを加えることで、カレンダーのタイトルをYYYY年MM月のようにしたり、レイアウト、時間の表示を設定できます。
公式ドキュメントには色々な使い方、機能が載っていますので確認してみて下さい。
FullCalendarの使い方
- 投稿日:2020-03-15T13:48:36+09:00
誰でもわかる!オブジェクト指向(Rubyで解説)
オブジェクト指向とは?
オブジェクト指向とは「物」に注目した考え方のことです。
「物」には「属性(どんなもの)」と「操作(どのように動く)」という性質があります。
「物」がどんなもので、どのように動くかをあれこれ考えるのがオブジェクト指向です。これだけではわかりづらいと思いますので、例を上げて説明します。
車で例えてみましょう。
車の「属性」
- フェラーリ
- 赤い
- タイヤがある
- ライトがある
- ハンドルがある
- バックミラーがある
- エンジンがある
車の「操作」
- 走る
- 止まる
- バックする
- 曲がる
- ドアが開く
- ライトがつく
- クラクションがなる
- ウインカーが点滅する
など様々な「属性」や「操作」があります。
このような「物」を作るのには必ず「属性」と「操作」を組み合わせる必要があります。プログラミングでは、このような「物」に注目して
クラス(設計図)やインスタンス(実体)を用いて作っていきます。オブジェクトとは?
オブジェクトとは「物」です。
オブジェクト指向の説明でわかっているとは思いますが、「物」です。
プログラミングではクラス(設計図)とインスタンス(実体)を使って作っていきます。
クラスもインスタンスもオブジェクトという「物」です。
クラス(設計図)という「物」だし、
インスタンス(実体)という「物」です。クラスとは?
# Carという名前のクラス(設計図) class Car # 設計図の内容を記述していく endクラスとは「設計図」のことです。
何か物を作る時は最初に設計図を作ってから物を作り始めますよね。
〇〇クラスという枠の中にどんな物を作るかを書いていきます。
クラス名はこれから記述するコードの内容にあった名前がベストです。
クラス名の頭文字は必ず大文字で書きましょう。
クラスはあくまで設計図なので、このままではクラス内の事をWeb上に表示させたり、データベースに保存することはできません。インスタンスとは?
# Carというインスタンスを生成している Car.newインスタンスとは「実体」のことです。
クラス名.newとすることで初めてクラスという「設計図」からインスタンスという「実体」を作り出す事ができます。
「実体」を作り出す事ができれば、それを表示させたり、データベースに保存する事ができます。メンバ変数(クラス変数)とは?
# クラス変数nameに文字列のフェラーリを代入している class Car @@name = "フェラーリ" endメンバ変数(クラス変数)とはオブジェクト指向における「属性」を定義したもの
オブジェクト指向では、「物」には「属性」と「操作」という性質があることを説明しました。
その中で「属性」(どんなもの)を定義するものがメンバ変数です。
JavaやC#にはメンバ変数があります。
しかしRubyにはメンバ変数がありませんが、似た変数としてクラス変数があります。
メンバ変数とクラス変数には細かい違いがいくつかありますが、ここでは割愛させていただきます。
Rubyのクラス変数は@@変数名で表します。メソッドとは?
# Carクラスの中でrunメソッドを定義している class Car def run puts '走るぜ!' end endメソッドとはオブジェクト指向における「操作」を定義したもの
オブジェクト指向では、「物」には「属性」と「操作」という性質があることを説明しました。
その中で「操作」(どのように動く)を定義するものがメソッドです。
Rubyではdef メソッド名で定義します。実際にコードを書いてみよう
class Car @@name = "フェラーリ" def run puts "#{@@name}が走る!!" end end car = Car.new puts car.runとコードを書くと
フェラーリが走る!!と表示されます。
まとめ
オブジェクト指向とは「物」に注目した考え方。
オブジェクトとは「物」。
オブジェクトには「属性」と「操作」という性質がある。
クラスとは「設計図」。
インスタンスとは「実体」。
クラスもインスタンスもオブジェクト。
メンバ変数(クラス変数)とはオブジェクト指向における「属性」を定義したもの。
メソッドとはオブジェクト指向における「操作」を定義したもの。以上で終わりです。
曖昧なところや間違った考えをしている可能性もありますので、気になる事があれば是非コメントいただけたら幸いです。
- 投稿日:2020-03-15T12:39:05+09:00
Your Ruby version is 2.6.3, but your Gemfile specified 2.5.3の対処法
状況
ローカル開発環境でbundle installを実行しようとしたら以下のようなエラーが出ました。
terminalYour Ruby version is 2.6.3, but your Gemfile specified 2.5.3どうやら、開発環境で使われているrubyのバージョンと、Gemfileで指定しているrubyのバージョンが違うようです。
対処法
terminal$ source ~/.bash_profileこのコマンドを実行すると、
ruby -v ruby 2.5.3p105rubyのバージョンが切り替わりました。
rubyのバージョンを切り替えた後に、bundle installを実行すると、うまく通りました。
最後に
開発中に、いつの間にかrubyのバージョンが切り替わっていて、原因がわからないのですが、また何かわかったら記事にまとめます。
- 投稿日:2020-03-15T11:58:20+09:00
AWS EC2デプロイで困ったときに見たところ
- 投稿日:2020-03-15T11:39:26+09:00
【feature】Rspecで作成したテストデータに引数を渡す方法 let! (){create()}
基本的なRspecの書き方についてはこちらに全て記載してあるので書きません。
https://qiita.com/jnchito/items/607f956263c38a5fec24順序
・テストデータhoge_product、hoge_caetgoryを作成する
・hoge.categoryを持っているページにアクセスする
・カテゴリと紐づいた商品が表示されるテストを行うコードに落とし込む
test_spec.rbrequire 'rails_helper' RSpec.feature "Categories", type: :feature do let!(:hoge_caetgory) { create(:caetgory) } let!(:hoge_product) { create(:product ,caetgory: [caetgory]) } #caetgoryと言う引数(属性)を持ったproductを作成し、代入 before do visit hoge_category_path(category) end it カテゴリと紐づいた商品が表示される do expect(page).to have_content hoge_product.category #caetgoryと言う引数(属性)を持ったhoge_productが表示されるテスト end書き方のみの説明記事のため、コントローラについては割愛。
間違いがありましたらコメントください。
関連記事はこちら。
https://qiita.com/kojiro3/items/b0957813695ca61fa3aa
- 投稿日:2020-03-15T11:39:26+09:00
【feature】Rspecで作成したテストデータに引数を渡す方法 let! ( ) {create( )}
基本的なRspecの書き方についてはこちらに全て記載してあるので書きません。
https://qiita.com/jnchito/items/607f956263c38a5fec24順序
・テストデータhoge_product、hoge_caetgoryを作成する
・hoge.categoryを持っているページにアクセスする
・カテゴリと紐づいた商品が表示されるテストを行うコードに落とし込む
test_spec.rbrequire 'rails_helper' RSpec.feature "Categories", type: :feature do let!(:hoge_caetgory) { create(:caetgory) } let!(:hoge_product) { create(:product ,caetgory: [caetgory]) } #caetgoryと言う引数(属性)を持ったproductを作成し、代入 before do visit hoge_category_path(category) end it カテゴリと紐づいた商品が表示される do expect(page).to have_content hoge_product.category #caetgoryと言う引数(属性)を持ったhoge_productが表示されるテスト end
let!(:hoge_product) { create(:product ,caetgory: [caetgory1]) }
だったら、
categoryがcategory1であるproductを作成してくれます。
let!(:変数) { create(:データ ,引数) }
のイメージです。書き方のみの説明記事のため、コントローラについては割愛。
間違いがありましたらコメントください。
関連記事はこちら。
https://qiita.com/kojiro3/items/b0957813695ca61fa3aa
- 投稿日:2020-03-15T09:39:57+09:00
【Vim】コメントを入力した時、カーソルが勝手に文頭に移動してしまう
問題
コメントを入力しようとしたら、勝手にカーソルが移動してインデントが消えてしまう問題です。
自分の場合はRubyファイルでしたが、
controller/items_controller.rbif @row.save redirect_to title_path(title_id), notice: "ITEM SAVED" else redirect_to title_path(title_id), notice: "SAVE FAILED" endこういう場合、if文内にそれぞれ
# 保存成功
と# 保存失敗
を入れたくても、#を入力した瞬間にカーソルが移動してしまい、controller/items_controller.rbif @row.save # 保存成功 redirect_to title_path(title_id), notice: "ITEM SAVED" else redirect_to title_path(title_id), notice: "SAVE FAILED" endとズレてしまいました。
ああああああああああ!!!!!!!!!!!!!!!原因
Vimの設定で
filetype plugin indent off
になっていたからでした。確認方法
Vimファイルを開いて
:filetype
で確認出来ます。filetype detection:ON plugin:ON indent:ON
↑
この3つすべてがONが良いみたいですね。自分の場合は、3つ目のindentがOFFになっていました。
設定方法
読み込んでいる.vimrcで、(最後あたりに書く?)
.vimrcfiletype plugin indent onこれで解決しました。
雑記
最初はvim74/syntax/ruby.vimのあたりに原因があると思って、コメントを判別する行を消したりしてみました。
ただこのシンタックスは「このファイル形式だったら、こういうコードはこういう意味ですよ」という判別をしてくれてるファイルみたいですね。問題はそのシンタックスの定義にもちゃんとインデント幅があり、それをちゃんと認識してなかったという事でした。
「vim ruby indent コメント」とかで検索しても、「コメント行の次の行までコメントアウトされてしまう!」問題ばかりがヒットして地味に見つけるのが大変だったのでまとめておきます。
以上です
- 投稿日:2020-03-15T09:17:33+09:00
駆け出す前に一歩立ち止まって、ユーザー登録機能におけるDB設計を考えよう!
記事内容
メルカリクローンサイトのユーザー登録機能を実装しました。
前回の記事でDB設計について記述しましたが、ユーザー機能の実装中にDB設計との変更点がありましたので、変更内容と変更理由を記載します。
駆け出しエンジニアは「とりあえず進みながら修正していこう!」とういうマインド」で開発がスタートすると思います。自分たちのチームもそうでした。
読んでくれた方がBD設計で手戻りがないようになればと思います。DB設計の修正が結構やっかいだったので...
前回の記事はこちらこの記事DB設計をした時点で考えていたこと。
ユーザー・アイテムモデルに大量のカラムができるから、括れるものは別テーブルを作成し1体1の関係でリレーションする。(テーブルにカラムが多すぎると不便になりそう・・・)
DB設計変更箇所
今回変更した箇所は以下の通りです。
変更しなければ実装できないというわけではないと思いますが、実装難易度(検索してヒットするか)、分かりやすさを考慮しました。
ユーザー/プロフィールテーブルのカラムを変更
生年月日のカラムを3分割(年・月・日)から1つに変更
ユーザー/プロフィールテーブルのカラムを変更
実際に修正したのはプロフィールテーブルに入れる予定だった、氏名、氏名(カナ)、生年月日をユーザーテーブルに移動。
今回ユーザー登録機能をウィザード形式で作成しました。(ウィザード形式について)
ウィザード形式ではページを遷移する時sessionを用いて保存しますが、一つのページで登録するカラムが2つのテーブに別れていると登録できなかったため、1ページ目で入力したいカラムは1つのテーブル(users)にまとめました。
複数のテーブルへ保存する方法はあるようで、チャレンジしてみたのですが、deviseを使用するとできない??実装方法分かる方いましたら教えてください
([Rails]ウィザードフォーム実装でfields_forを使って複数のテーブルに保存する)変更前
users table
Column Type Options nickname string null:false password string null:false string null:false, unique: true, index:true
profiles table
Column Type Options introduction text avatar string user references null: false, foreign_key: true first_name string null:false family_name string null:false first_name_kana string null:false family_name_kana string null:false birth_year date null:false birth_month date null:false birth_day date null:false 変更後
users table
Column Type Options nickname string null:false password string null:false string null:false, unique: true, index:true first_name string null:false family_name string null:false first_name_kana string null:false family_name_kana string null:false birth_year date null:false birth_month date null:false birth_day date null:false
profiles table
Column Type Options introduction text avatar string user references null: false, foreign_key: true 生年月日のカラムを3分割(年・月・日)から1つに変更
続いてはuserモデルに移動した生年月日を登録する際、カラムを3つにわけていたものを1つにまとめたことです。これについては実装内容どうこうの話ではなく、単純に実装が早そうだったからです。
(【Rails】date_selectタグの使い方メモ)【結論】考えることと駆け出すことのバランスを!
走り始める前に「なぜそうするのか」理由を考える
考えた理由を元に走り始める
以上を繰り返して、知識を蓄積することが大切です。修正することも勉強になります!
- 投稿日:2020-03-15T02:49:53+09:00
【自分用】チョコプラのコント"カレー屋"の「入っちゃってる」かどうかを確かめるプログラムと、それを作って学んだこと
元ネタ
https://youtu.be/aVYQC9oDz88?t=173
コード
puts "長田「小辛は唐辛子マークでいうと何個分なの?」" puts "松尾「" mild_hot_min = gets puts "から" mild_hot_max = gets puts "になります。」" mild_hot_level = [*(mild_hot_min.to_i..mild_hot_max.to_i)] puts "長田「じゃあ中辛は?」" puts "松尾「" normal_min = gets puts "から" normal_max = gets puts "になります。」" normal_hot_level = (normal_min.to_i..normal_max.to_i).to_a haicchateru_yatsu = mild_hot_level & normal_hot_level if haicchateru_yatsu.empty? puts "長田「・・・いや入っちゃってないんかい!」" else puts "長田「いや#{(mild_hot_level & normal_hot_level).join('と')}が小辛にも中辛にも入っちゃってるじゃん。」" puts "松尾「・・・”入っちゃってる”というのは?(・。・) キョトン…」" end書いて学んだこと
RangeオブジェクトをArrayオブジェクトに変換する方法は、何個かある
mild_hot_level = [*(mild_hot_min.to_i..mild_hot_max.to_i)]とか
normal_hot_level = (normal_min.to_i..normal_max.to_i).to_a配列同士の積集合(どっちにもある要素)を求めたいなら、&でつなぐ
haicchateru_yatsu = mild_hot_level & normal_hot_level他にも和集合や、もっと複雑なのも同じように求められます。
参考:https://qiita.com/namitop/items/be11c007da456ea95735
- 投稿日:2020-03-15T02:03:49+09:00
Rails 6 Grapeを利用したAPI作成、Swaggerでの確認
RoRでのAPI作成メモ
ruby 2.7.0 & Rails 6
Rails 5.x 系の資源を利用する例です。
https://qiita.com/katafuchix/items/8a96e2fa9ddc8bc83545プロジェクト作成
$ mkdir api_sample $ cd api_sample $ bundle initGemfile編集
# frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem 'rails'インストール
*MySQLを利用する例$ bundle install --path vendor/bundle $ bundle exec rails new . --skip-action-mailer --skip-active-storage --skip-action-cable -d mysqlGemfileに追加
# API gem 'grape' gem 'hashie-forbidden_attributes' gem 'grape-jbuilder' gem 'grape_on_rails_routes' gem 'swagger-ui_rails' gem 'grape-swagger' gem 'grape-swagger-rails'もう一度 bundle install を実行してエラーがないことを確認する
モデルクラス準備、データ投入
$ bin/rails g model Task name:string description:stringdb/seed.rb
seed.rbTask.create(name: 'タスク1', description: 'サンプルタスク1') Task.create(name: 'タスク2', description: 'サンプルタスク2') Task.create(name: 'タスク3', description: 'サンプルタスク3')$ bundle exec rake db:create $ bundle exec rake db:migrate $ bundle exec rake db:seedGrapeによるAPI作成
ルーティング
http://localhost:3000/api/v1/tasks/displays とかでアクセスしたいので各種設定
RailsかGrapeの仕様で「app/api」というディレトクリ以下のパス、ファイル名、クラス名が一致しなければならない。
それまでのRailsで「api/versions/v1」という作りをしている場合は、「api/api/versions/v1」というパスが二重になったフォルダ構成となってしまう。
ここでは以前のシステムを最小限度の手順で最新のRailsで組み直すため、ここはこのままで進めます。config/routes.rbRails.application.routes.draw do mount Versions::V1::Api => '/' mount GrapeSwaggerRails::Engine => '/api/swagger' endAPIディレクトリの作成
プロジェクト直下に api フォルダを作成してそこにAPIのプログラムを配置するために for Grape 以下の記述を追加
config/application.rbrequire_relative 'boot' require 'rails/all' # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) module ApiSample class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 6.0 # Settings in config/environments/* take precedence over those specified here. # Application configuration can go into files in config/initializers # -- all .rb files in that directory are automatically loaded after loading # the framework and any gems in your application. # For Grape config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb') config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')] config.middleware.use(Rack::Config) do |env| env['api.tilt.root'] = Rails.root.join 'app', 'views', 'api' end end end$ mkdir app/api $ mkdir app/api/api $ mkdir app/api/api/versions $ mkdir app/api/api/versions/v1API作成
大元にこういうのを作成してその中でエンドポイント単位のモジュールをincludeするのが管理しやすい
/api/vi/〜のパスにするために内部でこんな風に記述
テンプレートはJbuilderを指定app/api/api/versions/v1/api.rbmodule Versions module V1 class Api < Grape::API version 'v1', using: :path format :json formatter :json, Grape::Formatter::Jbuilder prefix :api include ::Versions::V1::TaskDisplays # :nocov: if Rails.env.development? add_swagger_documentation add_version: true end # :nocov: end end end各APIのコードはこんな感じ。 命名規則に注意。
app/api/api/versions/v1/task_displays.rbmodule Versions module V1 module TaskDisplays extend ActiveSupport::Concern included do namespace :tasks do namespace :displays do desc 'タスク一覧を取得する' get '', jbuilder: 'v1/task_displays/index' do @tasks = Task.all end end end end end end endテンプレードはこんな感じ
app/views/api/v1/task_displays/index.jbuilderjson.tasks @tasks do |task| json.(task, :id, :name, :description) endswagger設定ファイル
config/initializers/grape_swagger_rails.rbunless Rails.env.production? GrapeSwaggerRails.options.app_name = 'Grape API Sample' GrapeSwaggerRails.options.app_url = '/' GrapeSwaggerRails.options.url = 'api/v1/swagger_doc.json' endswagger用にjs, cssをimportするために2行追加
app/assets/config/manifest.js//= link_tree ../images //= link_directory ../stylesheets .css // 下2行追加 //= link grape_swagger_rails/application.css //= link grape_swagger_rails/application.js確認
railsを起動してブラウザで確認
bin/rails sAPI
http://localhost:3000/api/v1/tasks/displays
swagger
http://localhost:3000/api/swagger
あとは ここと同じように・・・
Rails 5.2 Grapeを利用したAPI作成、Swaggerでの確認 続編
- 投稿日:2020-03-15T00:29:34+09:00
【随時更新】Railsでたまに見返したいメモ集
個人的に見返したい資料のメモ書きです。
HTTPメソッド一覧
HTTPメソッド 使用するリクエスト GET ページを表示する操作のみを行う時 POST データを登録する操作をする時 PUT データを変更する操作をする時 DELETE データを削除する操作を行う時 アクション名一覧
アクション名 対応するリクエスト index 一覧表示ページを表示 new 新規投稿ページを表示 create データの投稿 show 個別詳細ページを表示 update データの編集 destroy データの削除 createメソッド
メソッド 用途 all テーブルの全てのデータを取得する find テーブルのレコードの内、ある1つのデータを取得する create テーブルにレコードを追加する 例:
controller.rbdef create Post.create(content: params[:content]) endcontent: テーブルのカラム名
params: paramsとして送られてきたデータ
- 投稿日:2020-03-15T00:27:53+09:00
Railsチュートリアルメモ - 第11章
(メモの目次記事はこちら)[https://qiita.com/yokohama4580/items/dedfd5510080273dc2a0]
(公式Railsチュートリアル第11章へのリンク)[https://railstutorial.jp/chapters/account_activation?version=5.1#cha-account_activation]
サマリ
- メールの送信とアカウント有効化機能の実装
- メタプログラミング
- SendGridを利用した本番環境でのメール送信
ポイント
rails generate mailer
でメーラーの雛形を作成できる
- e.g.
rails generate mailer UserMailer account_activation password_reset
- 生成されるHTML/textメーラーのレイアウトは
app/views/layouts
で定義されている- コントローラー同様、メーラーの中で作成したインスタンス変数はテンプレートの中で使用することができる
- 送信先やメールタイトルなどは
- e.g.
mail to:hoge@sample.com
deliver_now
メソッドを呼び出すことでメールを送信する
- e.g.
UserMailer.account_activation(@user).deliver_now
- 名前付きroot
- 名前付きrootの第一引数は:idになりBase64でエンコードされてURLが生成される
- 第二引数としてハッシュを渡すとURLパラメーターにしてURLを生成してくれる。その際にエスケープも行ってくれる。
- 名前付きrootの引数として指定されたものはコントローラー内で
params[:引数]
でデコードされた状態で取り出すことができる- 以下を行うとメールのプレビューが有効になる
config/environments/development.rb
に以下を追記するtest/mailers/previews/user_mailer_preview.rb
を修正する- プレビューURLは
https://localhost:3000/rails/mailers/user_mailer/
config.action_mailer.raise_delivery_errors = true config.action_mailer.delivery_method = :test host = 'localhost:3000' config.action_mailer.default_url_options = { host: host, protocol: 'https' }
assert_match
で正規表現を利用してassertできる
send
メソッドは引数を評価してから実行するので、メタプログラミング(プログラムによるコードの生成)を行う際に利用される
e.g.attribute = "foobar" user.send("#{attribute}_digest") # => user.foobar_digestが実行される
テストの中でのみ
assigns
メソッドを使うと対応するアクション内のインスタンス変数にアクセスできるようになる
- e.g.
user = assigns(:user)
=>@userにアクセス可能になる
heroku addons:create sendgrid:starter
でherokuにsendgridを導入できる感想、詰まった箇所など
config/environments/development.rb
を書き換えた後にサーバーの再起動を行わなかったせいでMissing host to link to! Please provide the :host parameter,...
というエラーが出てしまい少し詰まった。- herokuにSendGridを追加した後、画面からユーザー登録を行うとメールが飛ばずに
We're sorry, but something went wrong.
というエラー画面が表示された。ログを確認したところNet::SMTPAuthenticationError (535 Authentication failed: account disabled
となっていた。- 原因分からず解消しなかったため、herokuの画面からSendGridを「Delete Add-on」した後、再度SendGrid追加しようとしたところ
Error Provisioning User - User status - banned
と表示されてしまった。- herokuアプリ自体を再作成してからsendgridを再追加することで解消した。