- 投稿日:2020-09-28T23:27:42+09:00
【Rails】ビューファイルは使用できない、パラメータIDも存在しない。そんな中でアソシエーションのデータを取得する。【APIモードで使用するやつ】
はじめに
備忘録です。
ここでは、ホーム画面("/")で
「データ全件」
「1つのデータに関連付けされているデータの総数」
「1つのデータに関連付けされているハッシュのデータ」
を表示させたいとして、indexアクションにて上記のデータを取得する方法について見ていきます。
例えばQiitaのようなアプリケーションを作成するとするならば、
「記事全件」
「1つの記事に関連付けされているコメントの総数」
「1つの記事に関連付けされているタグのハッシュデータ」
を取得していくようなイメージです。
全てのデータはハッシュとして取得します。
環境
Ruby: 2.6
Rails: 5.2モデルの例(作成の過程は省略)
コード
1.まずは記事全件を取得する
def index @all_article = Article.all end2.1つの記事に関連付けされているコメントの総数を取得する
def index @all_article = Article.all # [ { 1: "コメント数" }, { 2: "コメント数" }, ... ]という形のデータを作成する。 # article.commentsでループ処理した記事データからコメントを呼び出している。 @article_count = @all_article.map{ |article| [article.id, article.comments.count.to_s] #もしコメント数を表示させるならto_sで文字列化する。 }.to_h # 上記のコメントデータのidと記事のidが一致した場合に、コメント数をcount属性に格納する @article_count.map do |key, value| @all_article.map do |article| if article.id === key article["count"] = value end end end # => [ { "id": 1, "count": "コメント数" }, { "id": 2, "コメント数" }, ... ]のように得られる end3.1つの記事に関連付けされているタグのハッシュデータを取得する(上と大体一緒)
def index # 記事全件 @all_article = Article.all # コメント数 @article_count = @all_article.map{ |article| [article.id, article.comments.count.to_s] }.to_h @article_count.map do |key, value| @all_article.map do |article| if article.id === key article["count"] = value end end end # 1つの記事が所有するタグをハッシュで取得 @all_article.map{ |article| [article.id, article.article_tags.all] }.to_h # => { # "1": [ # { "id": 1, "tag_name": "Rails" }, # { "id": 2, "tag_name": "React" }, # ... , # ], # "2": [ # { "id": 1, "tag_name": "JavaScript" }, # { "id": 2, "tag_name": "Qiita" }, # ... , # ] # } end以上ですが、上のままだとコントローラがごちゃごちゃしてしまうので、モデルファイルにインスタンスメソッドとして抽出するなどすると良いかと思います。
最後に
バックエンドとフロントエンドを切り離してSPAアプリケーションを作成する際にはビューファイルは使わないので、ルートのURLを持つホーム画面でアソシエーションされているデータを取得する方法を見つけるのにとても苦労しました。
で、結果このようにする結論に辿り着いたのですが、間違いやもっといい方法があるなどあれば、是非教えて頂けるととても助かります!?♂️
- 投稿日:2020-09-28T23:27:42+09:00
【Rails】ビューファイルは使わず、パラメータIDも存在しない中で、アソシエーションのデータを取得する。【APIモードで使う】
はじめに
備忘録です。
ここでは、ホーム画面("/")で
「データ全件」
「1つのデータに関連付けされているデータの総数」
「1つのデータに関連付けされているハッシュのデータ」
を表示させたいとして、indexアクションにて上記のデータを取得する方法について見ていきます。
例えばQiitaのようなアプリケーションを作成するとするならば、
「記事全件」
「1つの記事に関連付けされているコメントの総数」
「1つの記事に関連付けされているタグのハッシュデータ」
を取得していくようなイメージです。
全てのデータはハッシュとして取得します。
環境
Ruby: 2.6
Rails: 5.2モデルの例(作成の過程は省略)
コード
1.まずは記事全件を取得する
def index @all_article = Article.all end2.1つの記事に関連付けされているコメントの総数を取得する
def index @all_article = Article.all # [ { 1: "コメント数" }, { 2: "コメント数" }, ... ]という形のデータを作成する。 # article.commentsでループ処理した記事データからコメントを呼び出している。 @article_count = @all_article.map{ |article| [article.id, article.comments.count.to_s] #もしコメント数を表示させるならto_sで文字列化する。 }.to_h # 上記のコメントデータのidと記事のidが一致した場合に、コメント数をcount属性に格納する @article_count.map do |key, value| @all_article.map do |article| if article.id === key article["count"] = value end end end # => [ { "id": 1, "count": "コメント数" }, { "id": 2, "コメント数" }, ... ]のように得られる end3.1つの記事に関連付けされているタグのハッシュデータを取得する(上と大体一緒)
def index # 記事全件 @all_article = Article.all # コメント数 @article_count = @all_article.map{ |article| [article.id, article.comments.count.to_s] }.to_h @article_count.map do |key, value| @all_article.map do |article| if article.id === key article["count"] = value end end end # 1つの記事が所有するタグをハッシュで取得 @all_article.map{ |article| [article.id, article.article_tags.all] }.to_h # => { # "1": [ # { "id": 1, "tag_name": "Rails" }, # { "id": 2, "tag_name": "React" }, # ... , # ], # "2": [ # { "id": 1, "tag_name": "JavaScript" }, # { "id": 2, "tag_name": "Qiita" }, # ... , # ] # } end以上ですが、上のままだとコントローラがごちゃごちゃしてしまうので、モデルファイルにインスタンスメソッドとして抽出するなどすると良いかと思います。
最後に
バックエンドとフロントエンドを切り離してSPAアプリケーションを作成する際にはビューファイルは使わないので、ルートのURLを持つホーム画面でアソシエーションされているデータを取得する方法を見つけるのにとても苦労しました。
で、結果このようにする結論に辿り着いたのですが、間違いやもっといい方法があるなどあれば、是非教えて頂けるととても助かります!?♂️
- 投稿日:2020-09-28T23:13:00+09:00
RailsアプリにElasticsearchを組み込む
Elasticsearchとは
ElasticsearchとはElastic社が開発しているOSSの全文検索エンジンです。
大量のドキュメントから目的の単語を含むドキュメントを高速で抽出することができます。RailsアプリでElasticsearchを扱う考え方
1.全文検索エンジンに検索対象のデータが入っている
2.アプリケーション側で検索すると、検索エンジンに対してクエリを発行し、結果が返却される
3.アプリケーション側で検索対象のデータが更新されると、連携して検索エンジンのデータも更新されるRailsアプリでElasticsearchを扱う
インデックスを作成する
Elasticsearchでは、データの保存場所としてインデックスを作成します。
リレーショナル・データベースでいうところのテーブルのようなものです。まずは、RailsのモデルをElasticsearchでも扱うために専用のgemをインストールします。
以下をGemfileに記述し、bundle installします。gem 'elasticsearch-model', github: 'elastic/elasticsearch-rails' gem 'elasticsearch-rails', github: 'elastic/elasticsearch-rails'bundle installが終わったら、Elasticsearchにインデックスを作成します。
検索対象としたいモデルの中で、Elasticsearch::Modelをincludeします。class Article < ActiveRecord::Base include Elasticsearch::Model endこれでモデルでElasticsearchを扱う準備ができました。
インデックスは以下のようなコードで作成できます。Article.__elasticsearch__.create_index! force:trueインデックスにドキュメントを入れる
Elasticsearchでは、インデックスに入っているデータのことをドキュメントと呼びます。
インデックスには検索対象にしたいデータを入れます。以下のコードでElasticsearchにドキュメントをインポートします。
Article.importこれでElasticsearchのインデックスの中に、ドキュメントが登録されます。
ドキュメントを検索する
ドキュメントを検索するには、Elasticsearchにクエリを投げます。
以下のように書くことで、RailsからElasticsearchにクエリを投げることができます。response = Article.search 'hoge'引数で検索文字列を指定することで、ドキュメントを検索することができます。
フロントからパラメータを受け取って検索すると以下のように書けます。
def index @articles = Article.search(params) endRails側で検索対象のレコードが更新されると、それに伴ってElasticsearchのドキュメントも更新する
実際にサービスを運用するとなると、Rails側でレコードが更新されるとElasticsearchのドキュメントを更新する必要があります。
まず単にElasticsearchのドキュメントを更新するには、以下のように実装します。
Article.first.__elasticsearch__.update_document他にも、delete_documentというメソッドがあるので、これを使えばドキュメントの削除もできます。
上記のように明示的に書かなくても、レコードを更新した際に自動的にドキュメントを更新することもできます。
gemであるelasticsearch-modelでは、Elasticsearch::Model::CallbacksをModelにincludeしておくと、レコードを更新した際にElasticsearchのドキュメントを更新するクエリを投げてくれます。class Article include Elasticsearch::Model include Elasticsearch::Model::Callbacks endRailsアプリにElasticsearchを組み込む
実際にArticleモデルの検索周りの処理を作ります。
article-m/app/models/concerns/article/searchable.rbrequire 'active_support/concern' module Article::Searchable extend ActiveSupport::Concern included do include Elasticsearch::Model index_name "article" settings index: { number_of_shards: 1, number_of_replicas: 0 } do mapping _source: { enabled: true } do indexes :id, type: 'integer', index: 'not_analyzed' indexes :title, type: 'string' indexes :content, type: 'text' end end end module ClassMethods def create_index!(options={}) client = __elasticsearch__.client client.indices.delete index: "article" rescue nil if options[:force] client.indices.create index: "article", body: { settings: settings.to_hash, mappings: mappings.to_hash } end end endモジュールの中で、include Elasticsearch::Modelして便利なメソッド群を使えるようにします。
index_nameはインデックス名、settingsにはインデックスの設定を書きます。
number_of_shardsやnumber_of_replicasはシャードやレプリカの設定で、耐障害性や性能に関連します。mappingはインデックスをどのように定義するか決めます。RDBでいうテーブルスキーマのようなものです。
create_index!は実際にインデックスを作成するヘルパーです。
elasticsearch.clientでElasticsearchのクライアントのオブジェクトが取れるので、
このクライアント経由でいろいろ操作できます。作ったモジュールをモデルにincludeします。
article-m/app/models/article.rbclass Article < ActiveRecord::Base include Article::Searchable def self.search_message(keyword) if keyword.present? query = { "query": { "match": { "message": keyword } } } Article.__elasticsearch__.search(query) else Article.none end end endもらったキーワードから検索のクエリを組み立てて、Article.elasticsearch.searchに渡します。
Articleモデルに対してelasticsearch.searchを呼び出すことで、elasticsearch-railsとelasticsearch-modelがクエリを投げてくれます。コントローラーは以下のようになります。
article-m/app/controllers/articles_controller.rbclass ArticlesController < ApplicationController def search @keyword = params[:keyword] @articles = Article.search_message(@keyword).paginate(page: params[:page]) end以上がRailsアプリにElasticsearchを組み込む一例です。
参考
elastisearch-railsを使ってRailsでElasticsearchを動かす【初心者向け】
Railsアプリの検索処理にElasticsearchを組み込むのにやったことまとめ
- 投稿日:2020-09-28T21:34:50+09:00
[rails6.0.0]ウィザード形式でActiveStorageを使用して画像を保存する方法
概要
ユーザー登録とプロフィールをさせる時に、ウィザード形式でフォームを作成。
プロフィールに画像を保存したかったが上手くいかなかったため備忘録として記載。
※検索しても同じ状況の方がいなかったので役に立てばと思います。ウィザード形式とは何か?という方はこちらを参考にしてください
内容
開発環境
MacOS Catalina 10.15.6
Rails 6.0.0
Ruby 2.6.5テーブル構成
deviseを使ってユーザー登録をさせようとしています。
userテーブルはnicknameのみ追加。
profileテーブルにはtwitterのリンクなど記載。
profileテーブルにactive_strageで画像を保存したい。ウィザード形式を実装
ここらへんの機能の実装に関しては
こちらの記事がかなり近いので参考にしました。問題点
ウィザード形式で、画像以外はしっかり保存できたけど、画像は保存されない。
registrations_controllerclass Users::RegistrationsController < Devise::RegistrationsController def create @user = User.new(sign_up_params) unless @user.valid? render :new and return end session["devise.regist_data"] = {user: @user.attributes} session["devise.regist_data"][:user]["password"] = params[:user][:password] @profile = @user.build_profile render :new_profile end def create_profile @user = User.new(session["devise.regist_data"]["user"]) @profile = Profile.new(profile_params) unless @profile.valid? render :new_profile end @user.build_profile(@profile.attributes) @user.save session["devise.regist_data"]["user"].clear sign_in(:user, @user) redirect_to root_path end private def profile_params params.require(:profile).permit(:avatar, :favorite_beer, :twitter_link, :info) endこの中の以下の記述が悪さをしていた様子。
registrations_controller@user.build_profile(@profile.attributes) @user.saveここでは、@userと@profileをbuildで関連付けさせて@user.saveでまとめて保存をしているのですが、ここでの保存が原因で上手く行ってない様子。
この記事を見つけて原因
ActiveStorageに実際にファイルが保存されるタイミングはmodelをsaveして処理がコミットされた時なので、保存される前のmodelから画像をattachしてもファイルが未完成の状態になるっぽいです。どうもActiveStrageはモデルをちゃんとsaveしないと行けなさそうなので以下のように記述を変更したら解決しました。
registrations_controller# それぞれのモデルで保存させた # @user.build_profile(@profile.attributes) @user.save @profile.user_id = @user.id @profile.saveまとめ
正直buildを使っての保存の仕組みがよくわかってなかったのに使用してしまったのがエラーの原因かもしれないです。
少しでも参考になれば幸いです。
- 投稿日:2020-09-28T21:23:36+09:00
Markdown記法について
今回がQiita初投稿になります。よろしくおねがいします。
「プログラミング知識をMarkdown記法で書いて共有しよう」
と、Qiitaの投稿画面の一番初めにもデフォルトである通りQiitaにしろREADMEにしろGitHubのプルリクエストにしろプログラミング業界ではMarkdown記法というのがちょいちょい出てくるんですね。
自分も初めの頃は参考のサイトを見て少しやろうと思いましたが、案外やり方が多くて面倒になり別にまだ覚えなくてもいいかと思っていたのですが最近になり少しずつやり方が分かってきたので備忘録的な意味と自分の練習もかねて投稿しようと思いました。まず初めにMarkdownについてですが
Markdownとは
Markdown(マークダウン)は文章を記述するための記法(マークアップ言語)の1つです。
Markdownとは、メールを記述する時のように書きやすくて読みやすいプレーンテキストをある程度見栄えのするHTML文書へ変換出来るフォーマットとしてジョン・グルーバーによって開発されました。
以下の特徴があります。
- 簡単で覚えやすい記述
- 文章の構造を明示出来る
- Markdownそのままでも理解出来る
- 対応アプリを使うことでより快適に読み書き出来る
- 拡張子は「.md」
とのことです。
例として以下にRailsのアプリケーションのREADMEの一例を載せます。
README
users テーブル
Column Type Options user_name string null: false string null: false password string null: false Association
- has_many :friends
- has_many :comments
というようにアプリケーションにはREADMEが必須でありその中の記述はMarkdown記法のためMarkdown記法を最低限は覚える必要があるということですね。
では自分がよく使うものを抜粋して紹介します。
「#」を先頭に入れて半角スペース1つ分空けると見出しの意味になり大文字になり強調されます。(h1タグと同じ意味)
「#」が一番大きくて以下1つずつ増やすごとに大文字がだんだんと小さくなり「######」最大6つまで連続で記述出来ます。「-」を先頭に入れて半角スペース1つ分空けると「・」が付与されます。
「---」と「-」を3つ以上連続で入れると水平線が出来ます。
と上記が自分がよく使用している記述でした。上記の記述方法は本当にほんの一部なので興味のある人はちゃんとしたサイトから学習頂ければと思います。(笑)
と言ってもQiitaにいる人達はみんなMarkdown記法で記事を投稿しているのだから恐らくQiitaに投稿した人の中で自分が一番Markdown記法を知らない(使いこなしていない)と思いますのでこれから精進して参りたいと思います。ここまでお付き合い頂きありがとうございました。注:Markdown記法の「#」や「-」などは全て半角で記述しないと正しく反映されません。さらには半角スペースも1つ分空けてください。全角では反映されませんので注意!
今回参考にさせて頂いたサイト
- 投稿日:2020-09-28T20:14:50+09:00
データベースのカラムなどを修正したい場合
db:migrateしたデータベースを修正したい場合
プログラミングスクールでのチーム開発学習中にデータベースの修正を行いたかったのですが、
細かい部分の知識が抜け落ちていたので、備忘録用にまとめてます。データベースの操作に慣れていない方や、これからデータベースを学習する方の参考にもなれば幸いです。
開発環境
DB: MySQL
Rails: 5.2.4.3まず自分が修正したいデータベース(テーブル)の確認を行いましょう
ターミナル% rails db:migrate:statusすると、テーブルがこの様に出てくると思います(出てくるテーブルの数や名前はそれぞれ違ってきます)
ターミナルStatus Migration ID Migration Name -------------------------------------------------- up 20200823051138 Devise create ----s up 20200824122031 Create -------s up 20200824122659 Add ancestry to ------s up 20200824123715 Create -----s up 20200829083145 Create -----s up 20200906141656 Create -----s 今回はこのテーブルを修正したい up 20200907114227 Create -----s down 20200927061950 Create -----s down 20200927065357 Create -----s ※----は自分で作成したテーブル名ここでupとdownに注目です。
マイグレーションの修正を行うには、statusをdownの状態にしておく必要があります。
次に自分が修正したいデータベース(テーブル)をdownにしましょう
downの状態にするにはターミナルでこの様なコマンドを実行しましょう
ターミナル% rails db:rollbackもう一度statusを確認してみましょう
ターミナル% rails db:migrate:statusターミナルStatus Migration ID Migration Name -------------------------------------------------- up 20200823051138 Devise create ----s up 20200824122031 Create -------s up 20200824122659 Add ancestry to ------s up 20200824123715 Create -----s up 20200829083145 Create -----s up 20200906141656 Create -----s 今回はこのテーブルを修正したい down 20200907114227 Create -----s down 20200927061950 Create -----s down 20200927065357 Create -----s ※----は自分で作成したテーブル名あれ?
一つ下しかdownに変わってません。
というのもrollbackコマンドは一つずつしかdownに変えられないのです。
なので、もう一度やってみましょう。
ターミナル% rails db:rollbackもう一度statusを確認してみましょう
ターミナル% rails db:migrate:statusターミナルStatus Migration ID Migration Name -------------------------------------------------- up 20200823051138 Devise create ----s up 20200824122031 Create -------s up 20200824122659 Add ancestry to ------s up 20200824123715 Create -----s up 20200829083145 Create -----s down 20200906141656 Create -----s 今回はこのテーブルを修正したい down 20200907114227 Create -----s down 20200927061950 Create -----s down 20200927065357 Create -----s ※----は自分で作成したテーブル名今度は無事に目的のテーブルをdownに出来ました。
修正が終わった後
今回はカラム名の修正を行いたかったので、この後にマイグレーションファイルのカラム名の変更を行いました。
最後に
ターミナル% rails db:migrateもう一度statusを確認しておきましょう
ターミナル% rails db:migrate:statusターミナルStatus Migration ID Migration Name -------------------------------------------------- up 20200823051138 Devise create ----s up 20200824122031 Create -------s up 20200824122659 Add ancestry to ------s up 20200824123715 Create -----s up 20200829083145 Create -----s up 20200906141656 Create -----s 修正したテーブル up 20200907114227 Create -----s up 20200927061950 Create -----s up 20200927065357 Create -----s ※----は自分で作成したテーブル名rails db:migrateコマンドの場合は、downのテーブルを全てupに変更します。
db:migrateは1度で全てupにするけど
db:rollbackは1つずつしかdownに出来ないんですね。一度にrollbackをまとめて行いたい場合
今回の様に複数回rollbackを行わないといけない場合にまとめて行える方法も紹介します
ターミナル% rails db:rollback STEP=2※STEP=2を入力する事でrollbackを2回分まとめて実行してくれます。
rollbackコマンドに慣れてきたら、STEPオプションも積極的に使って、作業性をあげていきましょう。
- 投稿日:2020-09-28T20:14:41+09:00
form_objectで親子関係のあるフォームを作成する(テストも書いてます)
背景
@shop
に紐づく@comment
、@employee
など、様々な子要素があるテーブルがあります。初回情報登録時には、@shop
、@comment
、@employee
など、親要素と一緒に全ての子要素も一緒に保存できるようにしていたのですが、下記の記事のような形で、
accepts_nested_attributes_for
を用いてこれらを実現していたものの、▼こんなふうに実現していました
fields_forで子テーブルのデータを一気に作成する(テストも書いてます)[Rails][Rspec]そのうち、
@shop
だけの編集フォームや、@comment
、@employee
などの投稿・編集フォームも必要になってきたため、だんだんmodel
が様々な記述で肥大化してきました。form_objectとは?
↑上記のような状態の時に、特定のフォームに関するバリデーションやデフォルト値の設定などを一箇所に集め、モデルの記述を簡素化できるのが、
form_object
です。個人的には、導入にかなりつまづいてしまったので、記事を書いて記録を残しておこうと思います。なお、実行環境は下記の通りです。
- Rails 5.2.4.2
- rspec-rails 4.0.1
導入方法
form_object
,controller
,view
の基本の書き方は下記の通りです。なお、今回は@shop
の初回登録時に@comment
も1件登録できるようなフォームを例にしたいと思います。実装にあたって、一番参考にさせていただいたのは、こちらの記事です。
accepts_nested_attributes_forを使わず、複数の子レコードを保存する
DB構造
shops name string category integer ↑ categoryはショップ種別。
enum
のカラム。
comments content text shop_id integer 作成したファイル
form_object
/forms/shop_entry_form.rbclass ShopEntryForm include ActiveModel::Model # @shopに関する記述 ----------------------------- concerning :ShopBuilder do def initialize(params = {}) super(params) @category = params[:category] end def facility @shop ||= Shop.new end end attr_accessor :name, :category validates :name, presence: true validates :category, presence: true # @commentに関する記述 ----------------------------- concerning :CommentBuilder do attr_reader :comments_attributes def comments @comments_attributes ||= Comment.new end def comments_attributes=(attributes) @comments_attributes = Comment.new(attributes) end end attr_accessor :content # 実装のロジック ------------------------------------ def save # バリデーションエラーならfalseを返して以下の処理は行わない return false if invalid? shop.assign_attributes(shop_params) build_asscociation shop.save ? true : false end private def shop_params { name: name, category: @category, } end def build_asscociations # shopの子要素にcommentを追加する。ただし、中身が空なら追加しない。 shop.comments << comments if comments[:content].present? end endこれだけでつまづきどころがかなりありました。。。。
まず、concerning :ShopBuilder do ... end
の部分ですが、以下のような意味を持ちます。# この記述は... concern :ShopBuilder do ... end # 下記と同じ module ShopBuilder extend ActiveSupport::Concern ... end詳しくは、実装にあたって参考にした、こちらの記事をご覧ください。
次に、
initialize(params = {}) ... end
の部分なのですが、以下のような意味を持ちます。def initialize(params = {}) # @shopのparamsにアクセスできるようにする super(params) # DBでデフォルト値が設定されているカラム用の記述 @category = params[:category] endまず、
super(params)
については、こちらも実装にあたって大変参考にさせていただいた記事である以下の記事によると
super(params)
でパラメーターを格納する記述で、以下の記述と同じ意味を持ちます。@attributes = self.class._default_attributes.deep_dup assign_attributes(params)また、db側でデフォルト値が設定されているカラムは、以下のように明示的にparamsにアクセスすることを書かないとparamsにアクセスできず、値を入力してもDBのデフォルト値になってしまいました...。
@category = params[:category]この謎は解けず。今後の課題としたいです。。。
enumを使ったカラムにdb側でデフォルト値が必要な理由は、こちらの記事をご覧ください。そして
def comments_attributes=(attributes) ... end
の部分なのですが、def comments_attributes=(attributes) @comments_attributes = Comment.new(attributes) endこちらはRailsばかりやっているとなかなか目にしない、
セッターメソッド
という書き方で、=でおわるメソッド(引数)
の形で、引数によって@のつく要素を変更することができます。
個人的には、こんなことをやっているイメージに近いのではないかなと思いました。def comments_attributes=(attributes) # ... 以下略 # こんなイメージ comments_attributes = attributes # なので、こんな感じに呼び出せる self.comments_attributes # => attributesの中身Rubyのゲッターとセッターを正しく理解していなかったせいですね。。。。トホホ。。。頑張ります。。。
なお、=でおわるメソッド
については、『プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで』のp215
を15回ぐらい読み直しました。controller
次は、コントローラーの記述です。コントローラーはこのような形になりました。
app/controllers/shops_controller.rbclass ShopsController < ApplicationController def new @shop = ShopEntryForm.new end def create @shop = ShopEntryForm.new(shop_entry_params) if @shop.save # 成功したときの処理 else # 失敗したときの処理 end end private def shop_entry_params params.require(:shop_entry_form).permit(:caregory, :name, comments_attributes: [:content]) end endこちらは、意外に記述が減らなかった印象があります。当初
shop_entry_params
がcontrollerから減ってくれればいいなーと期待したものの、結局controllerからは消せず。アソシエーションを作るメソッドだけはcontrollerから削除することができました。なお、Modelに関しては、バリデーションとデフォルト値設定のメソッド、アソシエーションなども全て消すことができました!増えた記述は、なし!!やはり、
form_object
はモデルをスリム化するために便利な書き方なのですね!!View
最後に、Viewはこのようになっています。
app/views/shops/new.html.haml= form_with model: @shop, url: shops_path, local: true do |f| = f.text_field :name = f.fields_for :shop_comments, local: true do |comment_form| = comment_form.text_field = f.submit "送信"
fields_for
を使うあたりは、accept_nested_attributes_for
を使った実装と変わらないのですね^^テスト
テストも至ってシンプルでした!
spec/forms/shop_entry_form_spec.rbrequire 'rails_helper' RSpec.describe ShopEntryForm, type: :model do before do @shop_form = ShopEntryForm.new(category: "category1", name: "テストのお店") end describe "バリデーションのテスト" do it "名前とカテゴリーがあればバリデーションを通過すること" do @shop_form.valid? expect(@shop_form).to be_valid end # 以下略 end endファイルの置き場所と、
RSpec.describe ShopEntryForm ...
の部分, テスト用のインスタンス生成時の記述に注意すれば良いだけでした^^これは、少し古いのですがこちらの記事を参考に作成しました。
感想・参考資料など
さて、、、、本当に長い時間が実装にかかりました。実際のフォームはネストした子要素が3種類もあったり、形もかなり複雑だったのもあるのですが、何よりも素のRubyの書き方に慣れていなかったのが大きかったと思います。。。落ち着いたら、またRubyを復習したいです。
今回、参考にした記事や資料まとめです。
▼全体的な書き方
accepts_nested_attributes_forを使わず、複数の子レコードを保存する▼paramsへのアクセス方法
フォームクラスを使う
『プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで』(p.215)▼Concerningについて
Bite-sized separation of concerns▼テストの書き方
フォームオブジェクトのテストをRSpecで書く
この後、editとupdateのフォームも残っているので、次はそちらを取り組みたいです^^
- 投稿日:2020-09-28T18:46:17+09:00
【Rails】 binding.pryの活用方法
この記事では、binding.pryの使い方を解説しています。
binding.pryを活用することで、
・一次ソースに触れる機会が増え、学習効率が上がる
・binding.pryを複数設置して、paramsの流れが理解しやすくなるなど、たくさんメリットがあります。
自分みたいな、初学者方の参考になればと思い、記事にしてみました。前提
チャットアプリを題材にbinding,pryの使い方を学びます。
(注意:この記事ではチャットアプリは完成しません!チャットアプリ作成の記事ではありません)開発環境
・ruby 2.6.5
・Rails 6.0.3.3完成イメージ
ER図
必要なテーブル
・usersテーブル
・roomsテーブル
・entriesテーブル (中間テーブルです!)流れ
①user, room, entryモデル、テーブルを作成(下準備)
②アソシエーションを書く(下準備)
③Gemfileにpry-railsを追加してbundle installを実行(下準備)
④ビューにform_withを用意
⑤roomsコントローラーにcreateアクションを書く
①user, room, entryモデル、テーブルを作成(下準備)
userモデルのマイグレーションを編集
userモデルは、deviseを使って作成しているものとします!db/migrate/xxxx_devise_create_users.rbclass DeviseCreateUsers < ActiveRecord::Migration[6.0] def change create_table :users do |t| t.string :nickname, null: false t.string :email, null: false, default: "" #省略 end #省略 end endusersテーブルには、nicknameとemailのカラムを用意しています。
roomモデルのマイグレーションを編集
db/migrate/xxxx_create_rooms.rbclass CreateRooms < ActiveRecord::Migration[6.0] def change create_table :rooms do |t| t.string :name, null: false t.timestamps end end endroomsテーブルには、nameのカラムを用意しています。
entryモデルのマイグレーションを編集(下準備)
db/migrate/xxxx_create_entries.rbclass CreateEntries < ActiveRecord::Migration[6.0] def change create_table :entries do |t| t.references :room, foreign_key: true t.references :user, foreign_key: true t.timestamps end end endentriesテーブルは、usersテーブルとroomsテーブルを繋ぐ中間テーブルなので、
user, roomそれぞれを外部キーとして、references型で保存するようにしています。②アソシエーションを書く(下準備)
一人のuserは複数のroomに入れて、
一つのroomは複数人のuserが入るので、
usersテーブルとroomsテーブルは「多対多」の関係になります。
以下のように、アソシエーションを記述します。userモデル
app/models/user.rbclass User < ApplicationRecord #省略 has_many :entries has_many :rooms, through: :entries endroomモデル
app/models/room.rbclass Room < ApplicationRecord has_many :entries has_many :users, through: :entries endentryモデル
app/models/entry.rbclass Entry < ApplicationRecord belongs_to :room belongs_to :user end③gem 'pry-rails' をインストール(下準備)
pry-railsをインストールできるようGemfileに記述して、bundle installを実行します。
参考 : rweng/pry-rails: Rails >= 3 pry initializer - GitHubGemfilegem 'pry-rails'ターミナル% bundle install④ビューにform_withを用意
新しくルームを作成するために、roomsコントローラーにnewアクションを定義します。
app/controllers/rooms_controller.rbclass RoomsController < ApplicationController def new @room = Room.new #newメソッドでインスタンスを作成 @users = User.all #全ユーザーのレコードを取得 end end
ビューには、フォームを設置します。
チャットルーム名(name)を入力し、チャットしたい相手(user_ids)を選べるようにします。app/views/rooms/new.html.erb<%= form_with model: @room, local: true do |f| %> <%= f.label :チャットルーム名%> <%= f.text_field :name%> <%# 入力したチャットルーム名を取得 %> <label>チャットしたい相手</label> <select name="room[user_ids][]"> <%# 選択したユーザーを取得 %> <option value="">未選択</option> <% @users.each do |user| %> <option value=<%= user.id %>><%= user %></option> <% end %> </select> <%= f.submit %> <% end %>ここで、
user_ids
と複数形になっているのは、自分と相手の2人分保存するからです!
userモデルにて、has_many
を定義したことで、_ids
メソッドが使えるようになりました!
参考 : Active Record の関連付け - Railsガイド実際にフォームへ入力してみます。
すると下記のように、ユーザーが誰が誰だか分からないではありませんか!では、何が原因でこの出力が得られたのか推測します。
考えやすくするために、ユーザー選択の記述を、rubyの文法で書き直してみます。
@users.each do |user| user end
@users
は、roomsコントローラーのnewアクションで定義しているインスタンス変数で、
@users = User.all
と定義しています。
ビューの中では、each文による繰り返し処理によって、@users
から一人ずつ取り出しています。では、
binding.pry
を使って、出力される値を確認してみましょう。app/views/rooms/new.html.erb<%= form_with model: @room, local: true do |f| %> <%= f.label :チャットルーム名%> <%= f.text_field :name%> <%# 入力したチャットルーム名を取得 %> <label>チャットしたい相手</label> <select name="room[user_ids][]"> <%# 選択したユーザーを取得 %> <option value="">未選択</option> <% @users.each do |user| %> <% binding.pry %> <%# ?each文の中にbinding.pryを設置!! %> <option value=<%= user.id %>><%= user %></option> <% end %> </select> <%= f.submit %> <% end %>
ブラウザをリロードすると、ターミナルに以下のような出力が表示されます。ターミナル3: <%= f.text_field :name%> 4: <p><label>チャットしたい相手</label></p> 5: <select name="room[user_ids][]"> 6: <option value="">未選択</option> 7: <% @users.each do |user| %> => 8: <% binding.pry %> 9: <option value=<%=user.id%>><%= user %></option> 10: <% end %> 11: </select> 13: <p><%= f.submit%></p> [1] pry(#<#<Class:xxxx>>)>
=>
で、ビューの8行目で処理を止めてるよ!とターミナルが教えてくれています。
7〜10行間は、each文で繰り返し処理していることから、
繰り返し処理の1回目
で、一時的に処理を止めてくれています。したがって、
user
には一人目のデータ
が格納されていると考えられます。
では、実際にuser
の値を確認してみましょう。ターミナル[1] pry(#<#<Class:xxxx>>)> user => #<User id: 1, nickname: "user_1", email: "test@1"> [2] pry(#<#<Class:xxxx>>)> user.nickname => "user_1"
[1]pry>
の後に、式
を入力することで、
=>
後に、値
を出力してくれます。
user
には、idが1であるユーザーのレコード
が格納されていることが確認できました。
今回はユーザー名を一覧表示させたいので、nickname
の値だけを取り出すことにします。
2回目のpryで、user.nickname
と記述すると、ユーザー名を取り出せると確認できました。
したがって、ビューファイルを下記のように書き換えます。app/views/rooms/new.html.erb<%= form_with model: @room, local: true do |f| %> <%= f.label :チャットルーム名%> <%= f.text_field :name%> <%# 入力したチャットルーム名を取得 %> <label>チャットしたい相手</label> <select name="room[user_ids][]"> <%# 選択したユーザーを取得 %> <option value="">未選択</option> <% @users.each do |user| %> <option value=<%= user.id %>><%= user.nickname %></option> <%# ?user.nicknameを表示するように変更!! %> <% end %> </select> <%= f.submit %> <% end %>無事にユーザー名を一覧表示することができました!
しかし、この一覧表示には、一つだけ問題があります。
それは、自分自身も表示されている
ことです。
このままでは、自分しかいない孤独なチャットルームが作成されてしまいます...。
このような事態を防ぐために、自分以外のユーザーを一覧表示
するようにします。どんな式が必要か、
binding.pry
を活用して探していきます。ターミナル3: <%= f.text_field :name%> 4: <p><label>チャットしたい相手</label></p> 5: <select name="room[user_ids][]"> 6: <option value="">未選択</option> 7: <% @users.each do |user| %> => 8: <% binding.pry %> 9: <option value=<%= user.id %>><%= user.nickname %></option> 10: <% end %> 11: </select> 12: <p><%= f.submit%></p> [1] pry(#<#<Class:xxxx>>)>まず初めに、
@users
の中身を確認しましょう。ターミナル[1] pry(#<#<Class:xxxx>>)> @users => [#<User id: 1, nickname: "user_1", email: "test@1">, #<User id: 2, nickname: "user_2", email: "test@2">, #<User id: 3, nickname: "user_3", email: "test@3">, #<User id: 4, nickname: "user_4", email: "test@4">, #<User id: 5, nickname: "user_5", email: "test@5">] # ?現在のユーザー(current_user)
@users
には、全ユーザーのデータ
が、一人ずつ配列で格納されていることが確認できます。
ではそもそも、インスタンス変数@users
とは何と定義していたかというと、
User.all
と等しいよ!と定義していましたね。
では、User.all
の内容を確認しましょう。ターミナル[2] pry(#<#<Class:xxxx>>)> User.all => [#<User id: 1, nickname: "user_1", email: "test@1">, #<User id: 2, nickname: "user_2", email: "test@2">, #<User id: 3, nickname: "user_3", email: "test@3">, #<User id: 4, nickname: "user_4", email: "test@4">, #<User id: 5, nickname: "user_5", email: "test@5">]
@users
と全く同じデータが出力されることが確認できました。続いて、
現在のユーザーのデータ
を取り出せないか試してみます。ターミナル[3] pry(#<#<Class:xxxx>>)> User.all.where(id: current_user) => [#<User id: 5, nickname: "user_5", email: "test@5">]
.where
はActive Recordのメソッドの一つで、条件に該当するレコードを配列に格納して出力してくれます。
超便利なのでどんどん使っていきましょう。
参考 : Active Record の基礎 - Railsガイドさて、現在のユーザーのデータを取り出すことができました。
ということは、現在のユーザー以外のデータも取り出せるのでは?と思いつきます。
.where.not
メソッドを使うと良さそうです。
.where.not
は、条件に該当しないレコードを配列に格納して出力してくれる、.where
と対をなすメソッドです。ターミナル[4] pry(#<#<Class:xxxx>>)> User.all.where.not(id: current_user) => [#<User id: 1, nickname: "user_1", email: "test@1">, #<User id: 2, nickname: "user_2", email: "test@2">, #<User id: 3, nickname: "user_3", email: "test@3">, #<User id: 4, nickname: "user_4", email: "test@4">]現在のユーザーは
user_5
なので、現在のユーザー以外のデータ
が出力されています。
これで、現在のユーザー以外を一覧表示させる式を見つけることができました!
したがって、@users
の定義を変更しましょう。app/controllers/rooms_controller.rbclass RoomsController < ApplicationController def new @room = Room.new #newメソッドでインスタンスを作成 @users = User.all.where.not(id: current_user) #現在のユーザー以外のレコードを取得 end end
これで自分だけのチャットルームを作らないよう設定できました。
めでたしめでたし...と言いたいところですが、
roomを保存できるか確認してみましょう。⑤roomsコントローラーにcreateアクションを書く
roomsコントローラーにcreateアクションを定義します。
app/controllers/rooms_controller.rbclass RoomsController < ApplicationController def new @room = Room.new @users = User.all.where.not(id: current_user) end def create #createアクションを定義 binding.pry end endフォームで入力した情報(リクエスト)を確認したいので、
この段階では、createアクションには何も処理は定義せず、
binding.pryを設置しておきます。
こうすることで、
「フォームで入力された情報が届いたよー!」
と、ルーティングを介して、roomsコントローラーのcreateアクションを実行する瞬間に、
処理を止めることができます。
では、フォームにルーム名room1
、チャットしたい相手user_2
と選択して送信します。
すると下記のようにターミナル上で、createアクション内で定義したbinding.pry
で処理を止めてるよと教えてくれます。ターミナル7: def create => 8: binding.pry 9: end [1] pry(#<RoomsController>)>
では、リクエストのパラメータを確認してみましょう。ターミナル[1] pry(#<RoomsController>)> params => <ActionController::Parameters {"authenticity_token"=>"xxxxxxx==", "room"=>{"name"=>"room1", "user_ids"=>["2"]}, "commit"=>"Create Room", "controller"=>"rooms", "action"=>"create"} permitted: false>
params
(パラムス)はパラメーターズの略です。
authenticity_token
は、セキュリティのために生成されるトークンなので、今回は無視します。
room
の中に、form_withで入力したパラメータが、配列としてハッシュで管理されています。
このroom
とは、form_withで用意した、model: @room
と対応しています。では、paramsの中の、
room
の情報だけ見てみます。ターミナル[2] pry(#<RoomsController>)> params[:room] => <ActionController::Parameters {"name"=>"room1", "user_ids"=>["2"]} permitted: false>
params[:xxxx]
とすることで、見たいパラメータxxxx
だけを確認できます。
上記の結果から、チャットルームroom1
にuser_idが2
のユーザーが入ったことが確認できます。
ちゃんとルームに人を呼べていることが確認できましたね。
めでたしめでたし.....
って、自分自身がルームに入ってないじゃん!!!
ここから、自分もルームに入れるよう、ビューを書き換えきます!app/views/rooms/new.html.erb<%= form_with model: @room, local: true do |f| %> <%= f.label :チャットルーム名%> <%= f.text_field :name%> <%# 入力したチャットルーム名を取得 %> <label>チャットしたい相手</label> <select name="room[user_ids][]"> <%# 選択したユーザーを取得 %> <option value="">未選択</option> <% @users.each do |user| %> <option value=<%= user.id %>><%= user.nickname %></option> <% end %> </select> <input name="room[user_ids][]" type="hidden" value=<%=current_user.id%>> <%# ?現在のユーザーもroomに追加するように変更!! %> <%= f.submit %> <% end %>
input
は、formにおけるテキストフィールドの種類を指定します。
hidden
属性を指定することで、ブラウザには表示せずにパラメータとしてデータを受け渡すことができます。
この記述では、user_ids
にcurrent_user(現在のユーザー)
も含まれるよう記述しています!
ではもう一度、フォームにルーム名room1
、チャットしたい相手user_2
と選択して送信します。ターミナル[1] pry(#<RoomsController>)> params => <ActionController::Parameters {"authenticity_token"=>"xxxxxxx==", "room"=>{"name"=>"room1", "user_ids"=>["2", "5"]}, "commit"=>"Create Room", "controller"=>"rooms", "action"=>"create"} permitted: false> [2] pry(#<RoomsController>)> params[:room] => <ActionController::Parameters {"name"=>"room1", "user_ids"=>["2", "5"]} permitted: false>無事に
現在のユーザー(user_5)
がuser_ids
に含まれていることが確認できました!
続いて、createアクションを定義し直して、テーブルにデータが保存できるようにしましょう。app/controllers/rooms_controller.rbclass RoomsController < ApplicationController #省略 def create @room = Room.new(room_strong_params) if @room.save redirect_to root_path else render :new end end private def room_strong_params params.require(:room).permit(:name, user_ids: []) end endストロングパラメータは、
roomモデル
の、name
とuser_ids
のパラメータだけ許可するとしています。
createアクションの内部で、どのようにパラメータの受け渡しがされているか、binding.pry
を使って確認しましょう。app/controllers/rooms_controller.rb#省略 def create @room = Room.new(room_strong_params) binding.pry # ?binding.pryを設置!! if @room.save binding.pry # ?binding.pryを設置!! redirect_to root_path else render :new end endインスタンス変数
@room
が、保存される前、後でパラメータをそれぞれ確認してみます。
では、フォームにルーム名room1
、チャットしたい相手user_2
と選択して送信します。ターミナル7: def create 8: @room = Room.new(room_strong_params) => 9: binding.pry 10: if @room.save 11: binding.pry 12: redirect_to root_path 13: else 14: render :new 15: end 16: end [1] pry(#<RoomsController>)>
@room
保存前の各パラメータを確認します。ターミナル[1] pry(#<RoomsController>)> params[:room] => <ActionController::Parameters {"name"=>"room1", "user_ids"=>["2", "5"]} permitted: false> [2] pry(#<RoomsController>)> @room => #<Room:xxxx id: nil, name: "room1", created_at: nil, updated_at: nil> [3] pry(#<RoomsController>)> room_strong_params => <ActionController::Parameters {"name"=>"room1", "user_ids"=>["2", "5"]} permitted: true>2回目のpryに注目してください。
@room
は、id: nil
であることから、この時点では、レコードは作成されていないと分かります。
3回目のpryでは、ストロングパラメータを確認していますが、name
とuser_ids
に値が正しく格納されていることが確認できます。
リクエストしたパラメータが、コントローラーのcreateアクションに正しく受け渡されているのに、まだレコードが作成されていない理由は、
.new
メソッドでインスタンスを作成する場合、
.save
メソッドを実行して初めてデータベースにレコードとしてコミットされるからです。では、
@room.save
後を確認してみます。ターミナル7: def create 8: @room = Room.new(room_strong_params) 9: binding.pry 10: if @room.save => 11: binding.pry 12: redirect_to root_path 13: else 14: render :new 15: end 16: end [1] pry(#<RoomsController>)> params[:room] => <ActionController::Parameters {"name"=>"room1", "user_ids"=>["2", "5"]} permitted: false> [2] pry(#<RoomsController>)> @room => #<Room:xxxx id: 1, name: "room1"> [3] pry(#<RoomsController>)> room_strong_params => <ActionController::Parameters {"name"=>"room1", "user_ids"=>["2", "5"]} permitted: true>2回目のpryに注目してください。
@room
は、id: 1
であることから、レコードは正常に保存されました!今回は無事に保存できたのですが、
レコードを保存できなかった時に使える便利なメソッドも、合わせて紹介します!ターミナル[4] pry(#<RoomsController>)> @room.valid? => true [5] pry(#<RoomsController>)> @room.errors => #<ActiveModel::Errors:xxxx @base=#<Room:xxxx id: 1, name: "room1">, @details={}, @messages={}>[4] pryの
@room.valid?
では、「@room
のバリデーションはOK?」みたいな感じで、
バリデーションを実行してエラーがあるかを判別します。
エラーが無ければtrue
を,
エラーが有ればfalse
を返します。[5] pryの
@room.errors
では、@room.valid?でfalseが返された時に、エラーメッセージを出力してくれます。
今回はエラーはないので、エラーメッセージは出力されていません。
エラーがある時はmessages{}
の中にエラーメッセージが格納されます。最後に、保存されたデータをコンソールで確認してみましょう。
ターミナル% rails c [1] pry(main)> Room.all => [#<Room:xxxx id: 1, name: "room1">] [2] pry(main)> Entry.all => [#<Entry:xxxx id: 1, room_id: 1, user_id: 2>, #<Entry:xxxx id: 2, room_id: 1, user_id: 5>]コンソールでも、Active Recordのメソッドを使うことができます。
Room.all
で全てのルームを表示させると、
room1が保存されていることが確認できます。
Entry.all
で全てのレコードを表示させると、
2つのレコードが保存されていることが確認できます。
「ルーム1にユーザー2と5がいるよーっ!」と教えてくれています。コンソールで確認した内容は、以下の表と同じ内容です!
roomsテーブル
id name 1 room1 entriesテーブル
id room_id user_id 1 1 2 2 1 5 最後までお付き合いいただきありがとうございました!
参考資料
- 投稿日:2020-09-28T14:53:22+09:00
【Rails】検索フォームで、ひらがな・カタカナ・漢字の区別なく検索(精度は100%ではないよ)
自分がPFとして作っているアプリに検索フォームを実装しました。
ただ、【rails 検索 フォーム】 とかで検索すると、部分一致や完全一致が多く出てきます。
なんとかして漢字の部分をひらがなやカタカナで検索できないかな、と思いやってみました。流れとしては、動画のタイトルを保存する時に、そのタイトルをローマ字に変換して専用のカラムに保存し、検索するときも検索ワードをローマ字に変換して専用のカラムと参照する、という感じです。
最初は検索する時にタイトルを全部変換しようかと思いましたが、動画が増えると時間がかかりそうだな・・・と思ったので上記の方法にしました。如何せん初学者なので、そんな冗長なことしなくてもみたいな部分はあるのと思いますが、忘備録の意味合いも込めて書くので大目に見てやって下さい。
参考にしたサイト
- ひらがな-カタカナ-漢字-ローマ字を変換するgemつくったよ
- Web上のコンテンツや入力情報などが、英語か日本語か判別したいときのメモ
- Rubyの正規表現の使い方をマスターしよう!match/gsub
検索フォームを作る
検索フォーム自体は色々と記事があるので簡単に作れると思います。
自分の場合は動画の投稿サイトです。検索で、検索ワードが動画のタイトルに一致する、という検索フォームを作ります。/application.html.erb<div id="search-box"> <%= form_tag(search_path, :method => 'get') do %> <div class="input-tag"> <%= text_field_tag :search, '', placeholder: '検索', value: params[:title] %> </div> <div class="submit tag"> <%= button_tag type: 'submit', class: 'btn btn-default' do %> <i class="fas fa-search"></i> <% end %> </div> <% end %> </div>ルーティングを、videosコントローラーのサーチアクションに飛ばします。
routes.rb# 検索機能 get "search" => "videos#search"コントローラーに追記していく
gemを導入します。
今回、 miyabi というgemを使いました。ひらがな〜カタカナ〜ローマ字に変換したり判定したりできるgemです。
今回使ったメソッド.to_roman #文字列をローマ字に変換 .to_kanhira #漢字が含まれた文字列をひらがなに変換 .is_hira? #文字列がひらがなか判定 .is_kana? #文字列がカタカナか判定gemを導入したらコントローラーのcreateとsearchを書いていきます。
Videoというモデルには
user_id title introduction
のカラムがあり、そこにタイトルをローマ字に変換した物を保存する conversion_title というカラムを追加しました。schema.rbcreate_table "videos", force: :cascade do |t| t.integer "user_id" t.string "title" t.text "introduction" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "conversion_title" endvideos.controllerのcreateを作るのですが、問題が発生しました。
gem 'miyabi' では、【漢字が含まれている文字列を変換】はできるのですが、【漢字が文字列に含まれているか】は判定することができません。
rubyの持つ、正規表現で漢字が含まれているかどうかを判断します。
今回は漢字がタイトルに含まれているかどうかを調べたいので、ピンポイントで漢字だけを判定させます。@video.title.match(/[一-龠々]/)これで漢字が含まれているかどうか判定できます。
createで投稿された動画を保存します。
/videos_controller.rbdef create @video = Video.new(video_params) @video.user_id = current_user.id if @video.title.match(/[一-龠々]/) @video.conversion_title = @video.title.to_kanhira.to_roman elsif @video.title.is_hira? || @video.title.is_kana? @video.conversion_title = @video.title.to_roman else @video.conversion_title = @video.title end if @video.save redirect_to video_path(@video) else render :new end end private def video_params params.require(:video).permit(:title, :introduction, :video) end上から順に、まず、タイトルに漢字が含まれるか判定します。
含まれていれば、タイトルをひらがなに変換した後さらにローマ字に変換して保存します。漢字が含まれておらず、全てひらがな、カタカナの場合はローマ字に変換し保存します。
どちらにも当てはまらない場合は、ローマ字で投稿されていると判断してそのまま保存します。
searchアクションも同様に書いていきます。
/videos_controller.rbdef search word = params[:search] unless word.blank? if word.match(/[一-龠々]/) conversion_word = word.to_kanhira.to_roman elsif word.is_hira? || word.is_kana? conversion_word = word.to_roman else conversion_word = word end end @search_video = Video.search(conversion_word) endフォームで検索されたワードを、wordに代入して、wordが入っていればローマ字に変換します。
createと同様に、上から順番に条件にあった変換をします。
もし検索ワードが何も無しで検索された場合は動画を全て返しています。検索結果のviewはこんな感じ。
/videos/search.html.erb<h2>検索結果</h2> <% unless @search_video.blank? %> <div class="row"> <% @search_video.each do |video| %> ===== 省略 ===== <% end %> </div> <% else %> <p>検索結果はありません</p> <% end %> </div>検索したワードに一致するものがなければ、その旨を表示するようにしてあります。
これで一通りできました。完成!
実際にやってみます。
ひらがなで "うみがめ" と入力
"umigame" という conversion_title を持っている動画を返してくれました。
(海亀のタイトルを持っている動画がたくさんありますが、これは conversion_title を追加する前の動画です。ご愛敬。)ローマ字でも検索してみます。
表示されました。
その他
テストをしながら、ブラウザバック等が入るとパラメーターの動きが変わるのか、全ての動画が読まれたりというとが発生します。
多分キャッシュとかなんだろうな...JSも勉強しないとなぁ...と思うところであります。漢字に関しては、タイトルにもありますが100%完璧に変換してくれる訳ではないようです。
(実際、 "最強" という文字が "saikiu" と変換されていました)
ちょっとした検索を作りたい時などに利用できるかと思います。もっといい方法があれば、ぜひお願いします。
- 投稿日:2020-09-28T14:37:45+09:00
active recordで特定カラムを空で保存できるように修正
解決策
下記のようにallow_blankを追加したら解決できました
validates :something, allow_blank: true参考記事
Validate attribute only if it present (only if user fill in it)
- 投稿日:2020-09-28T13:40:15+09:00
【ActiveAdmin】コピペ新規作成するcloneアクションを追加する
はじめに
rails_adminの方ではclone機能をgemで追加して実装出来たのですが……
→ https://qiita.com/MATO/items/116bda1f3629ece0812cactive_adminの方ではgemではなく設定ファイルをいじって実装出来たのでメモです。
かなり汎用性あるコード書けたと思います。ソースコード
関係ない所もありますが、見返す用に全部載せておきます。
app/admin/items.rbActiveAdmin.register Item do permit_params :kind_id, :name, :memo # 一覧ページで検索フィルター要らないので消しておく config.filters = false # 一覧ページでのデフォルトソート config.sort_order = 'updated_at_desc' # 間違って削除しないように処理自体を消しておく actions :all, :except => [:destroy] # カスタムアクション、clone member_action :clone, method: :get do from_item = Item.find(params[:id]) @item = Item.new # ここでコピペしておきたいカラムをもう入れてしまう @item[:kind_id] = from_item.kind_id @item[:name] = from_item.name render :new, layout: false end # 詳細ページにもCloneボタンを追加 # indexページでエラーが出る、、、のでIF文入れておく action_item :only => :show do if params[:id].present? link_to "Clone Item", clone_admin_item_path(id: params[:id]) end end index do # selectable_column # id_column column :kind column :name column :memo actions defaults: false do |item| item 'View', admin_item_path(item), class: 'view_link member_link' item 'Edit', edit_admin_item_path(item), class: 'edit_link member_link' item 'Clone', clone_admin_item_path(id: item.id), class: 'clone_link member_link' end end form do |f| inputs do input :name input :kind, collection: Kind.all.order(:name) input :memo end actions end endこれで一覧ページでレコードごとにCloneリンクも追加されて、機能します。
/adminから/manageとかに名前を変更した場合でも、rails routes
で表示されるパスリンク?を使えばいけます。カスタムアクションとして
今回は「新規作成ページ」というnewと同じ処理する時にちょっとデータを入れただけ、ですね。
ただこれを元にしたら、そのレコードに対しての処理を好き放題にControllerに書くように書けたので、かなり楽しくカスタムできそうです。参考ページ
GitHubにドンピシャの質問あったのですが、色々と方法ありすぎたり、2013年とちと古かったり、英語だし……でちょっと違いますね。
https://github.com/activeadmin/activeadmin/issues/972俺のコードの方がキレイ?
終わり
- 投稿日:2020-09-28T12:13:52+09:00
クラスやメソッド、変数における名前の単語の区切り方
疑問
クラスやメソッド、変数の名前をつける時に、単語の区切り方の違いが分からない結論
クラスにはアッパーキャメルケースを使い、メソッドや変数にはスネークケースを用いる。説明
(ex)
「 user tweet creator」という名前を付けたい時を例にする・クラスに命名する時
UserTweetCreator
というように命名する・メソッドや変数に命名する時
user_tweet_creator
というように命名するつまり、
アッパーキャメルケース
初めの文字を大文字にし、単語の区切りを大文字で表すスネークケース
単語の区切りを_で表すとなります。メソッドや変数はスネークケースを使用かつ、全て小文字となります。
まとめ
命名規則を守ることで、チームの方達との、コードの意味の伝達が円滑になると思いますので、
参考にして、いただけると幸いです。*ちなみにカラム名もスネークケースを用います。
- 投稿日:2020-09-28T10:53:13+09:00
とりあえずのdocker-compose upから入って、Web server(Nginx)の基礎設計を学びながら、Dockerを学ぶ①
①環境構築〜最もシンプルなWeb server構築編です
分かりづらい点、不正確な点はコメントいただけましたらモチベーションに繋がります
今回こちらに先立ってZennというサービスで記事を書いて見ました
元記事
とりあえずのdocker-compose upから入って、Web serverの基礎設計を学びながらDockerを学ぶ① | Zennはじめに
以下のような方とって有益な内容になればと思っています
- これから初めてのwebアプリを作成する
- webアプリを作成し、これからデプロイする
- 初めてデプロイまで到達したが、Nginxが何をしているかとか、設定内容はコピペでよくわかっていない
私は独学プログラミング5ヶ月目で3の状態に近いかと思います
個人的なメッセージとしては是非1.の状態の方により多く、この記事の内容が届くといいなと思っています
Dockerがなんとなくわかる、便利な気がする!、webアプリが動く仕組みに関心を広げる、そんな気づきを共有できたらいいなと思っていますこの記事と同じ内容は、AWS上のEC2を利用したり、契約したVPSを利用するよることで再現可能ですが、dockerを利用すれば完全無料で挑戦可能です!より手軽で、予定外の課金に怯える必要はありません
この記事で知ることができる内容
ネットワークに視野を広げる
- webアプリが最低限動作するために必要な構成を知る
- Nginx(エンジンエックスと読みます)の3つの重要な役割, webサーバー, ロードバランサー, リバースプロキシの役割に触れる
- Nginxの基本的な設定を知る
Dockerの基本を知る
- 既存の開発環境を簡単に再現できることを知る
- Docker上で開発を行うために最低限必要なコマンドを試すことができる
- docker-compose.ymlに記述された内容や、volumeの仕組みを手を動かして知ることができる
- 複数のdocker-compose.ymlを用意して、異なる環境をシミュレートする (development -> production)
webアプリの開発環境 - 本番環境での違いを知る
- 本番環境でアセットコンパイルが必要な理由を知る
- 開発環境でアセットコンパイルが必要でない理由を知る
よって、この記事の最後ではDocker上で、仮想の本番環境で開発環境との違いに触れながら、アプリをデプロイしてみます
Appendix
appendixは補足的内容となっています
その項で知ることのできる内容を初めに書いておきましたので、
改めて知る必要のない内容でしたら読み飛ばして頂いて結構です
もし知らない内容でしたら、実際に手を動かして頭の片隅に留めておくことで、後々役に立つ物があるかもしれません
必要なもの、スキル
エディタ(VS Codeで検証しています)
Visual Studio Code - Code Editing. RedefinedLinuxの基礎コマンドの知識(cd, ls, vi...くらい、なくてもコピペでなんとかなります)
Docker desktop
Docker Desktop for Mac and Windows | DockerDockerって何?っていう方は以下がおすすめです
【連載】世界一わかりみが深いコンテナ & Docker入門 〜 その1:コンテナってなに? 〜 | SIOS Tech. Lab
概念をさらっと理解していただき、ここでは手を動かしてみるというのがおすすめですアプリの部分はFW(フレームワーク)にRailsを使用しておりますが、
Railsの知識はなくても大丈夫です(私自身Rails以外の開発経験がないため、他のFWにおいて不適切な内容があるかもしれません)
検証環境
macOS Catalina
docker desktop: 2.3.05
(docker engin: 19.03.12, docker-compose: 1.27.2)
Nginx: 1.18
Rails: 6.03
PostgreSQL: 11.0アーキテクチャ(設計)概要
これからDockerで構築する環境では
Nginxはリバースプロキシとして機能していて、静的コンテンツをapp: Rails
に代わって代理(=プロキシ)配信しており、動的コンテンツへのリクエストのみapp: Rails
に転送するようになっていますというのを少しずつ理解していきたいと思います
よく見る構成です(Databaseほか一部省略)
docker上でweb(Nginx), app(rails)というサービスがそれぞれ独立したコンテナで動いていて
docker-composeによってそれぞれの依存関係等が定義されているような理解です
目標5分、DockerでRailsの環境構築
Nginx - Railsの環境を構築します
以下の素晴らしい記事を参考にします(笑)
Nginx, Rails 6, PostgreSQL環境(おまけにBootstrapまで)がすぐに構築できます!
少しづつ改善していますので、改善コメントもお待ちしております。上記をベースに今回の記事のために用意したソースコード
https://github.com/naokit-dev/try_nginx_on_docker.gitソースコードをgit clone
#アプリを配置するディレクトリを作成(アプリケーションルート) mkdir try_nginx_on_docker #アプリケーションルートへ移動 cd $_ #ソースコード取得 git clone https://github.com/naokit-dev/try_nginx_on_docker.git #アプリケーションルートにソースコードを移動 cp -a try_nginx_on_docker/. . rm -rf try_nginx_on_docker以下のような構成になるかと思います
.(try_nginx_on_docker) ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── README.md ├── docker │ └── nginx │ ├── default.conf │ ├── load_balancer.conf │ └── static.conf ├── docker-compose.prod.yml ├── docker-compose.yml ├── entrypoint.sh ├── setup.sh └── temp_files ├── copy_application.html.erb ├── copy_database.yml └── copy_environment.jsソースコードの一部
docker-compose.yml
4つのコンテナが定義されていますversion: "3.8" services: web: image: nginx:1.18 ports: - "80:80" volumes: - ./docker/nginx/static.conf:/etc/nginx/conf.d/default.conf - public:/myapp/public - log:/var/log/nginx - /var/www/html depends_on: - app db: image: postgres:11.0-alpine volumes: - postgres:/var/lib/postgresql/data:cached ports: - "5432:5432" environment: PGDATA: /var/lib/postgresql/data/pgdata POSTGRES_USER: ${POSTGRES_USER:-postgres} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password} POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --locale=ja_JP.UTF-8" TZ: Asia/Tokyo app: build: context: . image: rails_app tty: true stdin_open: true command: bash -c "rm -f tmp/pids/server.pid && ./bin/rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp:cached - rails_cache:/myapp/tmp/cache:cached - node_modules:/myapp/node_modules:cached - yarn_cache:/usr/local/share/.cache/yarn/v6:cached - bundle:/bundle:cached - public:/myapp/public - log:/myapp/log - /myapp/tmp/pids tmpfs: - /tmp ports: - "3000-3001:3000" environment: RAILS_ENV: ${RAILS_ENV:-development} NODE_ENV: ${NODE_ENV:-development} DATABASE_HOST: db DATABASE_PORT: 5432 DATABASE_USER: ${POSTGRES_USER} DATABASE_PASSWORD: ${POSTGRES_PASSWORD} WEBPACKER_DEV_SERVER_HOST: webpacker depends_on: - db - webpacker webpacker: image: rails_app command: ./bin/webpack-dev-server volumes: - .:/myapp:cached - public:/myapp/public - node_modules:/myapp/node_modules:cached environment: RAILS_ENV: ${RAILS_ENV:-development} NODE_ENV: ${NODE_ENV:-development} WEBPACKER_DEV_SERVER_HOST: 0.0.0.0 tty: false stdin_open: false ports: - "3035:3035" volumes: rails_cache: node_modules: yarn_cache: postgres: bundle: public: log: html:環境構築
source setup.sh
正常にセットアップが終われば
アプリケーションルートディレクトリで以下のコマンドでコンテナ立ち上げた後docker-compose up # バックグラウンドで起動させる場合 -dオプション docker-compose up -d
ブラウザから
localhost
もしくはlocalhost:80
へアクセスすると
Yay! You’re on Rails!
が確認できるかと思います
誰でも簡単に開発環境を構築できます!Dockerのメリット1つ目ここで起動しているコンテナを確認してみます
(-d
オプションを付けずにdocker-compose up
した場合には新しくターミナルを開きます。VS Codeならcontrol
+@
*mac環境)docker psweb (Nginx), app(Rails), webpacker(webpack-dev-server), db(PostgreSQL)の4つのコンテナが起動していることだけ確認してください
確認できたら一旦コンテナを終了させておきます
docker-compose down
Nginxで静的コンテンツを配信してみる
まだRailsアプリは使用しません
ここでは以下に挑戦します
Nginxの最小の設定を確認する
Docker-composeを利用しつつ、コンテナを単独 (nginxのみ) で起動してみる
Nginx単独で単純な静的コンテンツ(HTML)を配信してみる
最もシンプルなNginxの設定
Nginxの設定を変更するため
docker-compose.yml
を編集しますservices: web: image: nginx:1.18 ports: - "80:80" volumes: #ここを書き換える./docker/nginx/default.conf... -> ./docker/nginx/static.conf... - ./docker/nginx/static.conf:/etc/nginx/conf.d/default.conf - public:/myapp/public - log:/var/log/nginx depends_on: - app ...Dockerのvolumeについて少し
volumes:
の./docker/nginx/static.conf:/etc/nginx/conf.d/default.conf
は<host側path>:<container側path>
になっていて
これによってホスト(ローカル)側のstatic.conf
をボリュームとしてマウントし、コンテナ内のdefault.conf
として扱えるようにしています
ここでは、ホスト側とコンテナ内では、ストレージが独立して存在するように振る舞われるため、このようなvolumeマウントが必要ということだけ心に留めてください
docker/nginx/static.conf
にはNginxの設定が記述されており、中身は以下のようになっていますserver { #ココから listen 80; # must server_name _; # must root /var/www/html; # must index index.html; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; } #ココまで、一つのserverブロック = Nginxが扱う一つの仮想サーバーの仕様server: "{}"で囲われた内容(serverブロック)をもとに仮想サーバーを定義します
ここでは以下3項目が設定必須です
listen: 待ち受けるIP, portを指定(xxx.xxx.xxx.xxx:80, localhost:80, 80)
server_name: 仮想サーバに割り当てる名前。Nginxはリクエストに含まれるホスト名(example.com)やIP(xxx.xxx.xxx.xxx)に一致する仮想サーバを検索します。("_"はすべての条件で一致させるの意味です。その他、ワイルドカードや正規表現が利用可能)
root: ドキュメントルート、コンテンツが配置されたディレクトリを指定しますちなみにlogについては、
etc/nginx/nginx.conf
というファイルで上記と同じパスが定義されているので、
ここで記述がなくても、error_logおよびaccess_logともに/var/log/nginx/
以下に記録されるはずです
例えばaccess_log /var/log/nginx/static.access.log;
とすることで、当該の仮想サーバ(serverブロック)固有のログを記録することもできるようですNginxコンテナ単独で起動
先程の
docker-compose up
ではnginx, rails, webpack-dev-server, dbのすべてのコンテナが起動していますが、docker-composeのオプションを使用することで特定のコンテナだけを起動することも可能です
--no-deps
: コンテナ間の依存関係を無視して起動 (ここではweb: nginxのみ)
-d
: バックグラウンドでコンテナを起動、シェルは入力を継続できます
-p
: ポートマッピング<host>:<container>
web
: composeで定義されたnginxコンテナです以下のコマンドでNginxコンテナを起動します
docker-compose run --no-deps -d -p 80:80 web(ポートマッピングについてはcomposeでも指定しているのですが、改めて指定する必要があり、ホスト側のport 80をwebコンテナのport 80にマッピングしています)
docker-composeをオプション無しで実行したときとの違いを確認します
docker ps先ほどと異なり、nginxのコンテナのみが起動していると思います
HTMLコンテンツを作成
コンテナの中でシェルを呼び出します
docker-compose run --no-deps web bash
以下webコンテナ内での作業です
# index.htmlを作成 touch /var/www/html/index.html # index.htmlの中身を追加 echo "<h1>I am Nginx</h1>" > /var/www/html/index.html # index.htmlを確認 cat /var/www/html/index.html <h1>I am Nginx</h1>これでコンテナ内のドキュメントルート直下に
index.html
が作成できたのでexit
でシェルを閉じましょう動作確認
ブラウザからlocalhostにアクセスすると、
以下のようにHTMLとして配信されているのが確認できていると思いますここでのNginxはリクエストに一致するコンテンツをドキュメントルートから探して、一致するものを返すというシンプルな挙動をしています
確認できたら一旦コンテナを終了させておきます
docker-compose downAppendix - リクエストに一致する仮想サーバがない場合のNginxの挙動
- Nginxのデフォルトサーバーの概念を知る
Nginxはクライアントからのリクエストに含まれるHostフィールドの情報をもとに、どの仮想サーバーにルーティングするかを定義しています
では、いずれの仮想サーバもリクエストと一致しない場合はどのような挙動をするのでしょうか?
設計を考える上で重要そうだったので、ここではそれを確認してみます先の設定ファイルのserverブロックで、いずれのリクエストに対しても該当するように
server name
を定義しましたが、これをリクエストと一致しないデタラメな名前に書き換えてみますserver_name undefined_server;再びNginxコンテナを起動します
docker-compose run --no-deps -p 80:80 webブラウザからlocalhostにアクセスすると、リクエストに一致する仮想サーバが存在しないにもかかわらず
予想に反して先ほどと同じ"I am Nginx"が表示されると思いますdefault server
Nginxはリクエストがいずれの仮想サーバにも該当しなかった場合、default serverで処理する使用になっており、一番最初、一番上に記述された仮想サーバをdefault serverとして扱う仕様になっています
In the configuration above, the default server is the first one — which is nginx’s standard default behaviour. It can also be set explicitly which server should be default, with the default_server parameter in the listen directive:
How nginx processes a requestまたはlistenディレクティブに明示的に
default_server
を指定することも可能ですlisten 80 default_server;今回の実験では"undefined_server"はリクエストに一致しないが、他に一致するものがないので
default serverとしてルーティングされたと考えられますいずれの仮想サーバもリクエストと一致しない場合 => default serverにルーティングされる
うまくバックエンドのサーバーに接続されない場合など、エラーを切り分けるのに役立つ気がします
一旦コンテナも終了させておきましょう
docker-compose downappendix - Dockerのvolumeを少し理解する
- コンテナの独立性について知る
- コンテナ - コンテナ間でストレージを共有する(永続化して共有する)仕組みとしてvolume、ここでは特にnamed volume, anonymous volumeの違いについて知る
そもそもvolumeが必要(= 永続化が必要)な意義について
Dockerではコンテナ内のデータを永続化するためにvolumeを作成し管理しますよくわからないので確認してみます
webコンテナの中でシェルを呼び出します
docker-compose run --no-deps web bash
以下webコンテナ内での作業です
# 検証用のディレクトリを作成 mkdir /var/www/test # 検証用のファイルを作成します touch /var/www/test/index.html # 存在確認 ls /var/www/test/これで
/var/www/test
はdocker-compose.yml
の中でボリュームとして管理されていないパスであることがポイントです一旦
exit
でシェルを閉じましょう(コンテナも終了します)再度webコンテナを起動しシェルを呼び出します
docker-compose run --no-deps web bash
先程のファイルを探してみます
cat /var/www/test/index.html
ls /var/www
いかがでしょうか、
ディレクトリ/var/www/test
、ファイル/var/www/test/index.html
ともに見つからないと思いますコンテナを終了すると、コンテナ内のデータは保持されないこれが原則であり
ボリュームはこの仕組を回避するために利用可能です
exit
でターミナルを閉じますすべてのコンテナを停止します
docker-compose downvolumeの種類
Dockerにおけるボリュームには以下のタイプがありますが、コンテナ内のデータを永続化するという点では同じです
- host volume ?(ちょっと名前がわからないです)
- anonymous volume (匿名ボリューム?anonymous volumeで通っている気がします)
- named volume (名前付きボリューム)
docker-compose.ymlを見みてみます
version: "3.8" services: web: image: nginx:1.18 ports: - "80:80" volumes: - ./docker/nginx/static.conf:/etc/nginx/conf.d/default.conf #host volume - public:/myapp/public # named volume - log:/var/log/nginx # named volume - html:/var/www/html # named volume ... volumes: # ここで異なるコンテナ間での共有を定義 public: log: html:host volume
nginxの設定のパートで触れました
./docker/nginx/static.conf:/etc/nginx/conf.d/default.conf
の部分でホスト側のパスをボリュームとしてマウントします
ホスト内のファイルをコンテナ側にコピーしているイメージですnamed volume
html:/var/www/html
の部分
"html"という名前をつけてボリュームをマウントしています
さらに、"services"ブロックと同列の"volumes"ブロックでこの名前をもって定義することで
複数のコンテナ間でボリュームをシェアすることを可能にしていますそして、このボリュームはホスト側からは独立して永続化されます
最後にanonymous volume
公式docではnamed volumeとの違いは名前があるかないかのみとありますが
実際に名前がないというより、named volumeの名前に相当する部分がコンテナごとにハッシュで与えられているそうです
ちょっとわかりにくいですが、ホスト側をマウントする必要がないが、永続化の必要がある、かつ複数のコンテナでの共有を想定しない場合に利用するケースが考えられます
(まだイメージし難いですが、この後のコンテンツでanonymous volumeでないといけない場面に遭遇します)ここでは少し理解を深めるために検証してみます
もともとnamed volumeとして定義している/var/www/html
をanonymous volumeに変更して
本項で実施したHTMLファイル作成の手順を繰り返してみます
docker-compose.yml
version: "3.8" services: web: image: nginx:1.18 ports: - "80:80" volumes: - ./docker/nginx/static.conf:/etc/nginx/conf.d/default.conf - public:/myapp/public - log:/var/log/nginx - /var/www/html # コンテナ側のpathのみ指定しanonymous volumeに変更 ... volumes: public: log: # html: ここをコメントアウトNginxをweb serverとして起動
docker-compose run --no-deps -d -p 80:80 webシェルを呼び出します
docker-compose run --no-deps web bash
ここが重要なのですが、別のターミナルでいま起動しているコンテナを確認すると
docker ps2つのコンテナが起動しており、シェルが動いているコンテナは、80:80でポートマッピングしているコンテナとは別であることがわかります
このままコンテナ内でHTMLを作成
# index.htmlを作成 touch /var/www/html/index.html # index.htmlの存在を確認 ls /var/www/htmlさきほどと同様にブラウザから
localhost
にアクセスしてみましょうするとブラウザは403エラーを示し
Nginxのエラーログを確認するとtail -f 20 /var/log/nginx/error.log...directory index of "/var/www/html/" is forbidden...
ディレクトリを見つけられないとエラーが記録されています
named volume -> anonymous volumeに変更したことで
2つのコンテナ間で/var/www/html/
以下の内容が共有されなくなり
ローカルからport 80でリクエストを受けたコンテナからはindex.html
を参照することができなくなったことで
このようなエラーが生じていると考えられます永続化はするが、他のコンテナとボリュームを共有しない、その特性に触れることができたかと思います
確認できたら
exit
でシェルを閉じ毎度ですがコンテナを終了させておきましょう
docker-compose down(変更したdocker-compose.ymlの内容はこのままでも構いません)
...
appendixの内容に思ったよりも熱が入ってしまい長くなったので、(私のモチベーション維持のために)一旦ここで区切ります
②に続く
- 投稿日:2020-09-28T10:42:30+09:00
最後に任意の文字があるかのメソッドを作る
【概要】
1.結論
2.どのようにコーディングするか
3.開発環境
1.結論
downcaseメソッド、lengthメソッドと、sliceメソッドの3種類を使う!
2.どのようにコーディングするか
def str_discrimination(str_a, str_b) a_down_str = str_a.downcase #---❶ b_down_str = str_b.downcase a_len_str = a_down_str.length #---❷ b_len_str = b_down_str.length if b_down_str.slice(-(a_len_str)..- 1) == a_down_str || a_down_str.slice(-(b_len_str)..- 1) == b_len_str #---❸ puts "True" else puts "False" end end❶:大文字小文字で区別しないためです。任意の2種類の文字列(str_a,str_b)をdowncaseメソッドで全てを小文字にしました。
変数(文字列).downcase❷:❶で小文字に変換した変数をlengthメソッドで文字数を返しています。これは❸で使うためにコーディングしています。
❸:ここで最後に任意の文字があるかを判別しています。str_aやstr_bのどちらに探されたい文字列、探したい文字が来てもいいようにOR条件にしています。またsliceメソッドを使用し、❷で使用したlengthメソッドで代入された変数を入れることで最後から任意の文字を探しています。(-(a_len_str)..- 1)は、最後から(-1)から-(a_len_str)の文字数までを示しています。
参考にしたURL:
はじめてのRuby!文字列を大文字⇔小文字に変換する方法まとめ
length、size、count メソッドの違いまとめ【Ruby】3.開発環境
Ruby 2.6.5
Rails 6.0.3.3
Visual Studio Code
- 投稿日:2020-09-28T09:20:07+09:00
[Rails]データベースに保存された情報をviewに表示する方法
投稿の内容
今回はデータベースに保存された情報をviewに表示する方法について投稿します。
なお、既にデータベースに情報は保存されている、として投稿します。実装の流れは以下の通り。
① コントローラーアクションの記述(showアクション)
② 表示したい情報が文字列の場合
③ 表示したい情報が複数枚画像の場合環境
Rails 5.2.4.3
ruby 2.5.1
mysql 14.14
viewはhamlで実装① コントローラーアクションの記述(showアクション)
今回は
商品(item)詳細ページ
に情報を表示したいので、itemsコントローラーのshowアクション
を使います。items.controller.rbclass ItemsController < ApplicationController #省略 def show @item = Item.find(params[:id]) end #省略解説) まず
Itemモデルのpathのidをfindメソッドで検索し、該当するidに属した商品(item)のインスタンスを作成します。
← 分かりにくければすいません...。ここで作成したインスタンスをもとに、データベースから情報を引き出します。
② 表示したい情報がテキストの場合
データベースに保存されている情報が上記画像のような場合、= @item.カラム名
と指定してあげるとviewに表示できます。例えば、nameカラムの情報を表示したい場合は以下のようにすればOKです。
show.html.haml%h2.item-show-page__item-name = @item.name③ 表示したい情報が複数枚画像の場合
今回はitemsテーブルとアソシエーションを組んでいるitem_imagesテーブルに保存されている複数枚画像を表示したいとします。
show.html.haml- @item.item_images.each do |image| = image_tag image.image.url解説) まず、itemsテーブル(@item)とアソシエーションを組んでいるitem_imagesテーブルをブロック変数imageに変換します。次に、
image_tag
を使い、先ほど変換したブロック変数image(item_imagesテーブル)のimageを呼び出し、.url
を付けます。この
.url
はデータベースから情報を選択し、viewに表示させる場合必須となります。.url
がなければviewにはデータベースに保存されている画像URLが表示されてしまします。あとは
each文
で繰り返し処理ですね。最後に
今回はデータベースに保存された情報をviewに表示する方法について投稿しました。
Railsでアプリケーションを開発する際に、必ず用いる方法と言っても過言ではないと思いますので、是非参考にしていただければと思います。最後まで目を通していただきありがとうございました!
- 投稿日:2020-09-28T03:21:44+09:00
Rails6 OmniAuth activestorage ユーザー画像を取得する
永遠の初心者による自分のためのメモ。
Rails学習開始3ヶ月。
とりあえずこれで動きました程度に考えていただければ幸いです。
こうした方がええやんと言うご意見がある方は優しいコメント頂けると嬉しいです。
この記事の目的
active storageにユーザーのプロフィール画像を保存する。
ググってみてもcarrierwaveばかりでactive storageの記事をあまり見かけなかったので、同じ境遇の人がいればと思い投稿ました。環境
Ruby 2.7.1p83
Rails 6.0.3.3
前提条件
deviseによるログイン機能実装済み。
OmniAuthによるTwitter、google、facebookなどのログイン認証機能実装済み。
私は、以下の記事を参考にさせて頂きました
・手続き関連
https://qiita.com/kazuooooo/items/47e7d426cbb33355590e
・OmniAuth導入
https://qiita.com/LuckHackMahiro/items/9dfca6e67777a2161240参考
・active storageに画像URLを保存する方法
https://qiita.com/gomasio1010/items/09c6ee58ed4c95f109ffとても助かりました!ありがとうございます。
active storageに画像urlを保存する
機能実装できている前提で、
app/models/user.rb
require "open-uri" #ここ class User < ApplicationRecord #省略 def self.from_omniauth(auth) where(provider: auth.provider, uid: auth.uid).first_or_create do |user| user.provider = auth.provider user.uid = auth.uid user.name = auth.info.name user.password = Devise.friendly_token[0, 20] user.email = auth.info.email user.email = User.dummy_email(auth) if user.provider == "twitter" avatar = open("#{auth.info.image}") #ここ user.image.attach(io: avatar, filename: "user_avatar.jpg") #ここ end end #省略 end記事を参考に画像を取得する事ができました。
active storageでの記事はあまり見かけなかったので、とても助かりました。画像が小さくて、荒くなるとの噂でしたので、サイズが大きくなりそうな感じにしてみました。
色々見ているとモデル側で指定している方もいらっしゃいました。config/initializers/devise.rb
config.omniauth :facebook, ENV['FACEBOOK_ID'], ENV['FACEBOOK_SECRET_KEY'], :image_size => 'large'#これ config.omniauth :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'] config.omniauth :twitter, ENV['TWITTER_API_KEY'], ENV['TWITTER_API_SECRET_KEY'], callback_url: "http://localhost:3000/users/auth/twitter/callback", :image_size => 'original'#これログイン認証関連の実装は、時間帯によってうまくいかなかったり、ブラウザにcookieが残っていると正しく動かない時がありましたので気長にやると良さそうです。
- 投稿日:2020-09-28T02:44:24+09:00
Rails6 OmniAuth twitter認証 emailの条件分岐
永遠の初心者による自分のためのメモ。
Rails学習開始3ヶ月。
とりあえずこれで動きました程度に考えていただければ幸いです。
こうした方がええやんと言うご意見がある方は優しいコメント頂けると嬉しいです。
この記事の目的
twitter認証はメールアドレスを取得しないので、ランダムメールアドレスを生成する。
google、facebook認証は、正規のメールアドレスを取得する。
twitterはランダムアドレス。他は、正規のアドレスで条件分岐させる。環境
Ruby 2.7.1p83
Rails 6.0.3.3
前提条件
deviseによるログイン機能実装済み。
OmniAuthによるTwitter、google、facebookなどのログイン認証機能実装済み。
私は、以下の記事を参考にさせて頂きました
・手続き関連
https://qiita.com/kazuooooo/items/47e7d426cbb33355590e
・OmniAuth導入
https://qiita.com/LuckHackMahiro/items/9dfca6e67777a2161240条件分岐 : TwitterかTwitter以外
機能実装できている前提で、
app/models/user.rb
def self.from_omniauth(auth) where(provider: auth.provider, uid: auth.uid).first_or_create do |user| user.provider = auth.provider user.uid = auth.uid user.name = auth.info.name user.password = Devise.friendly_token[0, 20] user.email = auth.info.email #google,facebookの時 user.email = User.dummy_email(auth) if user.provider == "twitter" #twitterの時 avatar = open("#{auth.info.image}") user.image.attach(io: avatar, filename: "user_avatar.jpg") end endとしたら、twitterではランダムアドレス。
google、facebookでは正規のアドレスが取得する事ができました。条件分岐がうまくいって嬉しかったです。