- 投稿日:2020-05-23T23:50:48+09:00
rails で特定のカラムの確認の仕方
最近オリジナルアプリを作る中で、モデル内のカラムを効率よく検索する方法を探していました。
#コンソールを開く $ rails c#調べたいモデル User.columnsこっちの方が、わかりやすい。
User.column_namesこれから沢山お世話になりそう。
- 投稿日:2020-05-23T23:03:48+09:00
ActsAsTaggableOn 日本語訳
このプラグインは、もともとJonathan VineyによるActs as Taggable on Steroidsに基づいていました。その時点から大幅に進化していますが、多くの人々が使用していた最初のタグ付け機能はすべて彼の功績です。
たとえば、ソーシャルネットワークでは、スキル、興味、スポーツなどと呼ばれるタグがユーザーにある場合があります。タグを区別する実際の方法はないため、このタイプの実装は、ステロイドでタグ付け可能として機能することはできません。
「タグ付け可能として機能」を入力します。特定のキーワード(つまりtags)に機能を結び付けるのではなく、タグ付け可能として機能し、ステロイドが使用されたのと同じ方法でローカルまたは組み合わせて使用できる任意の数のタグ「コンテキスト」を指定できます。
取り付け
これを使用するには、Gemfileに追加します。gem 'acts-as-taggable-on', '~> 6.0'
and bundle:bundle
インストール後
移行をインストールする#最新バージョンの場合:
rake acts_as_taggable_on_engine:install:migrations
生成された移行を確認して、移行します。rake db:migrate
MySqlユーザーの場合
初期化ファイルを設定することにより、特殊文字の問題623の問題をいつでも回避できます。ActsAsTaggableOn.force_binary_collation = true
または、このrakeタスクを実行して:
rake acts_as_taggable_on_engine:tag_names:collate_bin
詳細については、「構成」セクションと、古いバージョンのGemに有効な一般的な注意事項を参照してください。使用法
セットアップclass User < ActiveRecord::Base
acts_as_taggable_on :tags
acts_as_taggable_on :skills, :interests #モデルごとに複数のタグタイプを設定することもできますclass UsersController < ApplicationController
def user_params
params.require(:user).permit(:name, :tag_list) ## Rails 4の強力なパラメータの使用
end@user = User.new(:name => "Bobby")
単一のタグを追加および削除する
@user.tag_list.add("awesome") # add a single tag. alias for <<
@user.tag_list.remove("awesome") # remove a single tag
@user.save # save to persist tag_list配列内の複数のタグを追加および削除する
@user.tag_list.add("awesome", "slick")
@user.tag_list.remove("awesome", "slick")
@user.save文字列の形式でタグを追加および削除することもできます。これは、文字列内のタグ入力パラメータを処理する場合などに便利です。
この場合、オプションとしてparse:trueを追加する必要があることに注意してください。
文字列内の区切り文字を確認することもできます。デフォルトはカンマなので、ここでは何もする必要はありません。ただし、区切り文字の設定を変更した場合は、文字列が一致することを確認してください。区切り文字について詳しくは、構成を参照してください。
@user.tag_list.add("awesome, slick", parse: true)
@user.tag_list.remove("awesome, slick", parse: true)また、直接割り当ててタグを追加および削除することもできます。これにより既存のタグが削除されるため、注意して使用してください。
@user.tag_list = "awesome, slick, hefty"
@user.save
@user.reload
@user.tags
=> [#,
#,
#]モデルでコンテキストを定義すると、コンテキスト内のタグを管理および表示するための複数の新しいメソッドを自由に使用できます。たとえば、:skillコンテキストでは、次のメソッドがモデルに追加されます:skill_list(およびskill_list.add、skill_list.remove skill_list =)、skills(plural)、skill_counts。
@user.skill_list = "joking, clowning, boxing"
@user.save
@user.reload
@user.skills
=> [#,
#,
#]@user.skill_list.add("coding")
@user.skill_list
=> ["joking", "clowning", "boxing", "coding"]
@another_user = User.new(:name => "Alice")
@another_user.skill_list.add("clowning")
@another_user.saveUser.skill_counts
=> [#,
#,
#]タグが作成される順序を維持するには、acts_as_ordered_taggableを使用します。
class User < ActiveRecord::Base
# Alias for acts_as_ordered_taggable_on :tags
acts_as_ordered_taggable
acts_as_ordered_taggable_on :skills, :interests
end@user = User.new(:name => "Bobby")
@user.tag_list = "east, south"
@user.save@user.tag_list = "north, east, south, west"
@user.save@user.reload
@user.tag_list # => ["north", "east", "south", "west"]使用頻度の高いまたは最も少ないタグを見つける
以下を使用して、最も使用されているタグまたは最も使用されていないタグを見つけることができます。ActsAsTaggableOn::Tag.most_used
ActsAsTaggableOn::Tag.least_usedメソッドに制限を渡すことで結果をフィルタリングすることもできますが、デフォルトの制限は20です。
ActsAsTaggableOn::Tag.most_used(10)
ActsAsTaggableOn::Tag.least_used(10)タグ付きオブジェクトの検索
タグ付け可能として機能は、スコープを使用してタグの関連付けを作成します。このようにして、結果をフィルタリングして組み合わせることができます。class User < ActiveRecord::Base
acts_as_taggable_on :tags, :skills
scope :by_join_date, order("created_at DESC")
endUser.tagged_with("awesome").by_join_date
User.tagged_with("awesome").by_join_date.paginate(:page => params[:page], :per_page => 20)#指定されたすべてのタグに一致するユーザーを検索します。
#注:これは、指定されたタグの正確なセットを持つユーザーにのみ一致します。ユーザーが追加のタグを持っている場合、それらは返されません。
User.tagged_with(["awesome", "cool"], :match_all => true)#指定されたタグのいずれかを持つユーザーを検索します。
User.tagged_with(["awesome", "cool"], :any => true)#awesomeまたはcoolでタグ付けされていないユーザーを検索します。
User.tagged_with(["awesome", "cool"], :exclude => true)#コンテキストに基づいて任意のタグを持つユーザーを検索します。
User.tagged_with(['awesome', 'cool'], :on => :tags, :any => true).tagged_with(['smart', 'shy'], :on => :skills, :any => true):wild => trueオプションを:anyまたは:excludeオプションとともに使用することもできます。 SQLで%awesome%と%cool%を探します。
ヒント:User.tagged_with([])またはUser.tagged_with( '')は、空のレコードセットである[]を返します。
関係
特定のコンテキストの類似したタグに基づいて、同じタイプのオブジェクトを見つけることができます。また、オブジェクトは、一致したタグの総数に基づいて降順で返されます。@bobby = User.find_by_name("Bobby")
@bobby.skill_list # => ["jogging", "diving"]@frankie = User.find_by_name("Frankie")
@frankie.skill_list # => ["hacking"]@tom = User.find_by_name("Tom")
@tom.skill_list # => ["hacking", "jogging", "diving"]@tom.find_related_skills # => [, ]
@bobby.find_related_skills # => []
@frankie.find_related_skills # => []
ダイナミックタグコンテキスト
定義で生成されたタグコンテキストに加えて、動的タグコンテキストを許可することもできます(これはユーザー生成のタグコンテキストである可能性があります!)@user = User.new(:name => "Bobby" )
@user 。set_tag_list_on(:customs 、 "same、as、tag、list" )
@user 。tag_list_on(:customs ) #=> ["same"、 "as"、 "tag"、 "list"]
@user.save
ます。tags_on(:customs ) #=> [、...]
@user 。tag_counts_on(:customs )
User.tagged_with("same", :on => :customs) # => [@user]タグパーサー
タグの解析方法を変更したい場合は、独自の実装を定義できます。class MyParser < ActsAsTaggableOn::GenericParser
def parse
ActsAsTaggableOn::TagList.new.tap do |tag_list|
tag_list.add @tag_list.split('|')
end
end
end
これで、このパーサーを使用して、パラメーターとして渡すことができます。@user = User.new(:name => "Bobby")
@user.tag_list = "east, south"
@user.tag_list.add("north|west", parser: MyParser)
@user.tag_list # => ["north", "east", "south", "west"]または
@user.tag_list.parser = MyParser
@user.tag_list.add("north|west")
@user.tag_list # => ["north", "east", "south", "west"]
または、グローバルに変更します。ActsAsTaggableOn.default_parser = MyParser
@user = User.new(:name => "Bobby")
@user.tag_list = "east|south"
@user.tag_list # => ["east", "south"]タグの所有権
タグは所有者を持つことができます:class User < ActiveRecord::Base
acts_as_tagger
endclass Photo < ActiveRecord::Base
acts_as_taggable_on :locations
end@some_user.tag(@some_photo, :with => "paris, normandy", :on => :locations)
@some_user.owned_taggings
@some_user.owned_tags
Photo.tagged_with("paris", :on => :locations, :owned_by => @some_user)
@some_photo.locations_from(@some_user) # => ["paris", "normandy"]
@some_photo.owner_tags_on(@some_user, :locations) # => [#...]
@some_photo.owner_tags_on(nil, :locations) # => Ownerships equivalent to saying @some_photo.locations
@some_user.tag(@some_photo, :with => "paris, normandy", :on => :locations, :skip_save => true) #won't save @some_photo objectオブジェクトを保存しない
所有されているタグの操作
tag_listタグ付けに所有者がいないタグのみを返すことに注意してください。上記の例の続き:@some_photo 。tag_list #=> []
オブジェクトのすべてのタグを取得するには(所有権に関係なく)、または1人の所有者だけがオブジェクトにタグ付けできる場合は、を使用しますall_tags_list。所有タグを追加する
所有タグは、文字列内のコンマ区切りタグの形式で一度に追加されることに注意してください。また、所有タグを再度追加しようとすると、以前の所有タグのセットが上書きされるだけです。そのため、既存の所有タグリストにタグを追加するには、次のようにします。def add_owned_tag
@some_item = Item.find(params[:id])
owned_tag_list = @some_item.all_tags_list - @some_item.tag_list
owned_tag_list += [(params[:tag])]
@tag_owner.tag(@some_item, :with => stringify(owned_tag_list), :on => :tags)
@some_item.save
enddef stringify(tag_list)
tag_list.inject('') { |memo, tag| memo += (tag + ',') }[0..-1]
end所有するタグを削除する
上記と同様に、削除は次のようになります。def remove_owned_tag
@some_item = Item.find(params[:id])
owned_tag_list = @some_item.all_tags_list - @some_item.tag_list
owned_tag_list -= [(params[:tag])]
@tag_owner.tag(@some_item, :with => stringify(owned_tag_list), :on => :tags)
@some_item.save
end汚れたオブジェクト
@bobby = User.find_by_name("Bobby")
@bobby.skill_list # => ["jogging", "diving"]@bobby.skill_list_changed? #=> false
@bobby.changes #=> {}@bobby.skill_list = "swimming"
@bobby.changes.should == {"skill_list"=>["jogging, diving", ["swimming"]]}
@bobby.skill_list_changed? #=> true@bobby.skill_list_change.should == ["jogging, diving", ["swimming"]]
タグクラウドの計算
タグクラウドを構築するには、各タグの頻度を計算する必要があります。クラスで指定acts_as_taggable_onしたためUser、を使用してすべてのタグ数の計算を取得できますUser.tag_counts_on(:customs)。しかし、1人のユーザーの投稿のタグ数が必要な場合はどうでしょうか。これを実現するには、関連付けでtag_countsを呼び出します。User.find(:first).posts.tag_counts_on(:tags)
タグクラウドの生成を支援するヘルパーが含まれています。
タグクラウドを生成する例を次に示します。
ヘルパー:
module PostsHelper
include ActsAsTaggableOn::TagsHelper
endコントローラ:
class PostController < ApplicationController
def tag_cloud
@tags = Post.tag_counts_on(:tags)
end
end見る:
<% tag_cloud(@tags、%w(css1 css2 css3 css4))do | tag、css_class | %>
<%= link_to tag.name、{:action =>:tag、:id => tag.name}、:class => css_class %>
<% end %>
CSS:。css1 { font-size:1.0 em ; }
。css2 { font-size:1.2 em ; }
。css3 { font-size:1.4 em ; }
。css4 { font-size:1.6 em ; }
構成
タグ付けを削除した後で未使用のタグオブジェクトを削除する場合は、次を追加します。ActsAsTaggableOn 。remove_unused_tags = true
強制的にタグを小文字で保存したい場合:ActsAsTaggableOn 。force_lowercase = true
タグをパラメータ化して保存したい場合(to_paramも再定義できます):ActsAsTaggableOn 。force_parameterize = true
タグで大文字と小文字を区別し、作成にLIKEクエリを使用しない場合:ActsAsTaggableOn 。strict_case_match = true
MySqlで特殊文字をカバーする完全一致が必要な場合:ActsAsTaggableOn 。force_binary_collation = true
テーブル名を指定したい場合:ActsAsTaggableOn 。tags_table = 'aato_tags'
ActsAsTaggableOn 。taggings_table = 'aato_taggings'
デフォルトの区切り文字を変更する場合(デフォルトは「、」)。(['、'、 '|'])などの区切り文字の配列を渡すこともできます。ActsAsTaggableOn 。区切り文字 = '、'
- 投稿日:2020-05-23T22:24:56+09:00
ReactとRailsを同一サーバーで動かす方法
経緯
Railsの学習を始めるに当たって、 ERBを利用したVIEWでフロント側を構築していたが、
最近はSPAというものが流行っていると小耳に挟んだ。とりあえずチャレンジ!
ということでReactの学習を並行して進めていたが、、、
これどうやってデプロイするんだ。。。となってしまいました笑
とりあえず別サーバーで動かすことにしてCORS制約などもなんとなくクリアして無事にRailsとReactのやりとりが可能になった。
しかし、ReactとRailsを利用したアプリ関係の記事を漁っていると、同一サーバーで動かしているものを多数発見!!!
何回かチャレンジしたものの上手く行かず断念。。。しばらく時間が過ぎ、この土日に絶対動かしてやる!!! と執念で参考記事を探しまくった結果、
Heroku公式のBlogにわかりやすい記事があり、やっと構築ができました笑同じような人がいたら参考になれば良いなと思い投稿するに至りました!
前提条件
- Heroku blogの記事から必要最小限の部分だけ引用したものになります
- Ruby、Rails、Node、npm OR yarnなどの基本的なインストールが済んでいること
- Railsアプリを作成したことがある
- Reactアプリをcreate-react-appで作成したことがある
- Herokuでホスティングをしたことがある(gitでデプロイ)
参考記事:A Rock Solid, Modern Web Stack—Rails 5 API + ActiveAdmin + Create React App on Heroku
Railsアプリの作成
rails new コマンドでrails アプリを作成します。
--apiコマンドでapiモードにするのをお忘れなく
rails new rails_app --api
作成したアプリが起動するか確かめましょう。
cd rails_app rails s
Reactアプリの作成
Railsアプリのルートディレクトリ上でclientという名前でReactアプリを構築します。
npm create-react-app client
Railsと同じように、Reactが動くか確かめましょう。
cd client npm start
このままだと開発中にReactとRailsで別ポートとなるので
package.jsonに設定を追加します。package.json{ "name": "client", "version": "0.1.0", "private": true, "proxy": "http://localhost:3001", #ここを追加 ... }この設定によりRails側には、Reactアプリがポート3001で起動しているように認識させます。
React - Rails ローカル編
ローカル環境立ち上げ用のタスクを作成します。
ProcfileにReactとRailsを立ち上げるコマンドを書き、Railsのタスクで呼び出す方法にします。
Procfileはheroku上で利用されるファイルで、プロセスのタイプ:コマンドで記述できます。
<process type>: <command>今回は、ルートディレクトリにProcfile.devというファイル名で、RailsアプリとReactアプリの起動コマンドを記述します。
Procfile.devweb: cd client && PORT=3000 npm start api: PORT=3001 && bundle exec rails s呼び出しコマンドを簡潔にしたいため、taskにします。
start.rakenamespace :start do desc 'Start development server' task :dev do exec 'heroku local -f Procfile.dev' end endtaskでprocfileの起動コマンドをキックします。
rake start:dev
ReactとRailsがどちらも立ち上がったと思います。
ターミナル上のログでわかりづらい場合はRails側で何かしらJsonを返すようにしておき、React側からfetchできるようにしておくとわかりやすいと思います。
React - Rails Heroku編
メインテーマであるHeroku上へのデプロイです。
まずはルートディレクトリにpackage.jsonファイルを作成します。
RailsのpublicディレクトリにReactの静的ファイルを作成するコマンドを記述します。
package.json{ "name": "list-of-ingredients", "license": "MIT", "engines": { "node": "10.15.3", "yarn": "1.15.2" }, "scripts": { "build": "yarn --cwd client install && yarn --cwd client build", "deploy": "cp -a client/build/. public/", "heroku-postbuild": "yarn build && yarn deploy" } }production用のProcfileを作成し、railsの起動コマンドを記述します。
必要に応じてrelease後のmigrateコマンドも記述します。
Procfileweb: bundle exec rails s release: bin/rake db:migrate
CLIを利用してheroku上のdynoの作成とビルドパックの設定を行います。アプリの作成heroku apps:create
ビルドパックの設定heroku buildpacks:add heroku/nodejs --index 1 heroku buildpacks:add heroku/ruby --index 2
heroku上の準備ができたのでgitでpushします。
git add . git commit -m "React - Rails" git push heroku masterアプリを開きます。
heroku openあとがき
みなさん無事に動きましたでしょうか?
もっと良い方法があれば是非教えていただけるとありがたいです。
もし動かない場合は下記の参考元の記事を読んでみてください!
参考記事:A Rock Solid, Modern Web Stack—Rails 5 API + ActiveAdmin + Create React App on Heroku
- 投稿日:2020-05-23T21:44:57+09:00
100日後に1人前になる新人エンジニア(3日目)
100日後に1人前になる新人エンジニア(3日目)
どうもこんばんは。
土曜日でもめげずに学び更新していきます。今日は本屋にいって書籍を買ってきました。
「Ruby on Rails 速習実践ガイド」 です
コツコツとやっていきたいと思います。本日のお題は
・ REST
・Ruby製のテンプレートエンジンslim以上の二つです。全然違うテーマだけど自分がよく分かっていないと
思っていたところなので書いていきます。REST
Railsをやるようになってから「この設計はRESTfulだとか」,「RESTらしい」
という言葉を書籍等でよく見るようになった。
でもよく考えたらRESTについてしっかり分かっていないなと思ったので
土曜日を使って学んでみた。RESTとは
次の6つを組み合わせたWebのアーキテクチャスタイルのこと
- クライアント/サーバ
- クライアントがサーバーにリクエストを送り、サーバーはそれに対してレスポンスを返す
- ステートレスサーバ
- クライアントのアプリケーションの状態をサーバーで管理しない
- キャッシュ
- 一度取得したリソースをクライアント側で使い回す。
- 統一インターフェース -リソースに対する操作を統一したインターフェースで行う(GET POST PUT DELETE HEAD OPTIONS TRACE CONNECT の8つ)
- 階層化システム
- システムがいくつかの階層に分離すること
- コードオンデマンド
- プログラムコードをサーバからダウンロードし、クライアント側で実行するスタイル。JavaScriptとかがこれに当てはまる
これら6つを合わせたアーキテクチャスタイルをRESTって言うんだって。
ちなみにステートレスサーバに関してだけどCookieとかは
RESTの観点で言うとHTTPの間違った拡張の方法みたいです。
状態を保存しているし、ステートレスではないもんね。でもそれは必ずしも間違いではないから
あくまでRESTを意識して必要最低限の拡張でアプリケーションを作っていくことが大事なんだって。Railsの本を読んでてRESTに関して言っていたのは,
統一インターフェースの事だったのね。適切なインターフェースで処理ができているのか。と言うところ。それぞれのインターフェースには役割があるからそれをしっかり理解して、使いましょうって事でした。RESTに関しては以上!!(結構長くなっちゃった)
アプリケーションを作りながら意識するともっと分かってくると思った。slim
slimとは, Ruby製のテンプレートエンジンのこと、
htmlをより簡潔でスマートに書くことができるんだって。erbのファイル形式しか知らなかったからまとめてみます。
slimの特徴
・<>がいらない
・<%= %> → =
・<% %> → -
・コメント → /
・id指定 → #
・class指定 → .|以降にある文字は全てテキストと認識される
p | テキストテキストテキスト テキストテキストテキスト p | テキストテキストテキスト テキストテキストテキスト条件式なども省略できる
- if cuser.nil? li 新規登録 - else li ログイン基本的な部分だけまとめてみた。
調べて分かったけど覚えることは意外と少ないみたい
はじめて見た時はなんじゃこりゃって感じだったけど、
慣れたら大丈夫になってきそうな気がしてきた。では今日はこの辺で。
1人前のエンジニアになるまであと97日
- 投稿日:2020-05-23T20:47:32+09:00
ElementClickInterceptedErrorについて
前提
Selenium利用時のトラブルシューティングのごく一部について書きます。
本題
フラッシュメッセージのテストを行うとエラーが発生
エラー内容
1) Dishes 料理詳細ページ 料理の削除 削除成功のフラッシュが表示されること Failure/Error: click_on '削除' Selenium::WebDriver::Error::ElementClickInterceptedError: element click intercepted: Element <a class="delete-dish" data-confirm="本当に...しますか?" rel="nofollow" data-method="delete" href="/dishes/1232">削除</a> is not clickable at point (393, 91). Other element would receive the click: <div class="container">...</div> (Session info: headless chrome=81.0.4044.138) [Screenshot]: tmp/screenshots/failures_r_spec_example_groups_dishes_nested_2_nested_2_削除成功のフラッシュが表示されること_759.png # 0 chromedriver 0x000000010f4e9269 chromedriver + 4674153 # 1 chromedriver 0x000000010f483593 chromedriver + 4257171 # 2 chromedriver 0x000000010f12cfcf chromedriver + 757711 # 3 chromedriver 0x000000010f09df9c chromedriver + 171932 # 4 chromedriver 0x000000010f09c9c8 chromedriver + 166344 # 5 chromedriver 0x000000010f09ac73 chromedriver + 158835 # 6 chromedriver 0x000000010f099ed4 chromedriver + 155348 # 7 chromedriver 0x000000010f0916f6 chromedriver + 120566 # 8 chromedriver 0x000000010f0b54b2 chromedriver + 267442 # 9 chromedriver 0x000000010f091476 chromedriver + 119926 # 10 chromedriver 0x000000010f0b572e chromedriver + 268078 # 11 chromedriver 0x000000010f0c2288 chromedriver + 320136 # 12 chromedriver 0x000000010f0b56d3 chromedriver + 267987 # 13 chromedriver 0x000000010f08f1dd chromedriver + 111069 # 14 chromedriver 0x000000010f090185 chromedriver + 115077 # 15 chromedriver 0x000000010f4aafff chromedriver + 4419583 # 16 chromedriver 0x000000010f4b87ba chromedriver + 4474810 # 17 chromedriver 0x000000010f4b8557 chromedriver + 4474199 # 18 chromedriver 0x000000010f48f149 chromedriver + 4305225 # 19 chromedriver 0x000000010f4b9067 chromedriver + 4477031 # 20 chromedriver 0x000000010f4a0d87 chromedriver + 4377991 # 21 chromedriver 0x000000010f4cf2d4 chromedriver + 4567764 # 22 chromedriver 0x000000010f4ef1f7 chromedriver + 4698615 # 23 libsystem_pthread.dylib 0x00007fff684e52eb _pthread_body + 126 # 24 libsystem_pthread.dylib 0x00007fff684e8249 _pthread_start + 66 # 25 libsystem_pthread.dylib 0x00007fff684e440d thread_start + 13 # ./spec/system/dishes_spec.rb:88:in `block (5 levels) in <top (required)>' # ./spec/system/dishes_spec.rb:87:in `block (4 levels) in <top (required)>' # -e:1:in `<main>'原因
1、クリックしようとしている要素が表示されれいる領域に表示されていない
2、クリックしようとしている要素のSeleniumがクリックするポイントが他の要素に隠れている解決
自分は2が原因でしたので被らないようにHTMLを修正すると解決しました
- 投稿日:2020-05-23T20:27:00+09:00
Railsでとにかくログイン機能を作る
とにかくログイン機能を作りましょう
Railsを用いたWEBアプリケーションでログイン機能を作るためだけの記事です。
これを体に覚え込ませて爆速で作れるようになっちゃいましょう!アプリの雛形を作る
これを作らないと何も始まりません!ターミナルから以下のコマンドを実行しましょう。
% rails _5.2.3_ new アプリ名 -d mysql上記のコマンドによりアプリの雛形ができたらアプリのディレクトリに移動しましょう。
% cd アプリ名deviseのインストール
エディタでGemfileを開き、deviseを追加します。
deviseとは、ユーザー認証に必要な機能を簡単に作れるようになるgemのことです。
また、今回は私の好みでerbではなくhamlを使うのでerbファイルをhamlに変換するgemもインストールします。gem 'devise' gem "haml-rails"Gemfileにdeviseを追記したらインストールします!
% bundle installインストールが終了したらerbファイルをhamlに変換しておきます。
% rails haml:erb2haml上記の変換作業が終わったらdeviseの設定ファイルを作成します。
% rails g devise:installで、以下の設定ファイルが出来ます。
config/initializers/devise.rb
config/locales/devise.en.ymlデータベース周りの設定
続いてユーザーモデルの作成するため以下のコマンドを実行します。
% rails g devise userこれでルーティングファイルにもdeviseのルーティングが記載されます。
Rails.application.routes.draw do devise_for :users endこのアプリで使用するデータベースが未作成の状態なので作りましょう。
% rails db:createモデルを作成した時点でマイグレーションファイルが作成されていると思うので、マイグレーションしましょう
xxxxxxxxxxxxxx_devise_create_users.rb% rails db:migrateこれでデータベース周りの設定は完了です。
トップページを表示させる
続いて、トップページのビューファイルを作ります。
ここにログイン、新規登録、ログアウトへの導線を作っておきしょう。
今回は何らかの投稿をユーザーで投稿できるアプリ、と仮定しapp/views/postsにindex.html.hamlを作ります。
deviseをインストールすると、user_signed_inというヘルパーメソッドが使えるようになるので、これで未ログイン時とログイン時で表示を分けましょう。%header - if user_signed_in? %div = link_to "ログアウト", destroy_user_session_path, method: :delete - else %div = link_to "ログイン", new_user_session_path = link_to "新規登録", new_user_registration_pathトップページができたらroutes.rbにルーティングを追記します。
Rails.application.routes.draw do devise_for :users root to: 'posts#index' end現時点でトップページを開こうとすると下記のようなエラーになると思うので、コントローラを作ります。
% rails g controller postsこれでコントローラができました!
作成されたコントローラにindexアクションを追加すればトップページが開けるはずです。class PostsController < ApplicationController def index end end何とも味気ない画面ですが、うまくいっていれば下記のような画面になるはずです。
ログインと新規登録
続いて、ログインと新規登録画面の作成を行います。
下記のコマンドを実行すると、新規登録とログイン用のビューが作成されます。% rails g devise:views.新規登録画面
app/views/devise/registrations/new.html.erb・ログイン画面
app/views/devise/sessions/new.html.erb今回はhamlで作成するので、下記のコマンドを再度実行してerbをhamlに変換しておきましょう。
% rails haml:erb2hamlここまで出来れば新規登録、ログイン、ログアウトができるはずなので早速実践してみます。
・新規登録をクリック
・メールアドレスとパスワードを入力して"sign up"をクリック
・トップページに遷移し、ログアウトが表示されるのでログアウトをクリック
スタイルが当たってないので見た目はしょぼいですが、とりあえずサーバーサイドの実装は完了です!
今回の記事を参考にして、ぜひ爆速実装を目指してみてください!
- 投稿日:2020-05-23T18:32:31+09:00
【備忘録】Devise + cancancan の使い方
はじめに
deviseを使用してUserモデル(認証)を作成後して
cancancanを追加して認可をできるようにしました。その過程を備忘録としてログします。
deviseのgemを追記します。
・ ・ ・ gem 'devise'バンドルインストールします。
$bundle installデバイスをインストールします。
$rails generate devise:install設定があるか確認します。
config/environments/development.rb・ ・ config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }page#indexを追記します。
config/routes.rbRails.application.routes.draw do root to: "page#index" ・ ・ end以下を追記します。
app/views/layouts/application.html.erb<body> <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p> ・ ・ </body>Viewをインストールします。
$ rails g devise:views以下が作られます。
app/views/devise/unlocks/new.html.erb
app/views/devise/shared/links.html.erb
app/views/devise/shared/_error_messages.html.erb
app/views/devise/sessions/new.html.erb
app/views/devise/registrations/new.html.erb
app/views/devise/registrations/edit.html.erb
app/views/devise/passwords/new.html.erb
app/views/devise/passwords/edit.html.erb
app/views/devise/mailer/unlockinstructions.html.erb
app/views/devise/mailer/reset_password_instructions.html.erb
app/views/devise/mailer/password_change.html.erb
app/views/devise/mailer/email_changed.html.erb
app/views/devise/mailer/confirmation_instructions.html.erb
app/views/devise/confirmations/new.html.erbモデルを作成します。
今回はUserモデルを作ります。$ rails g devise user以下モデルが作られます。
app/models/user.rbclass User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable end以下が同時に作られます。
config/application.rbconfig.i18n.default_locale = :ja以下も作られます。
config/routes.rbails.application.routes.draw do devise_for :users ・ ・ endモデル作成後に、DBに反映します。
rails db:migrateまだpage#indexのViewがないので作ります。
$ rails g controller Pages index余談ですが、routes.rbにスコープを作ることでPathを変更できます。
routes.rbdevise_scope :user do get 'login', to: 'devise/sessions#new' post 'login', to: 'devise/sessions/#create' delete 'logout', to: 'devise/sessions#destroy' endこれにより、
http://localhost:3000/login
にアクセスしたときに、ログインページが表示されます。もちろんデフォルトの/users/sign_inでもログインページが表示されるので必要であれば削除しておきましょう。
routes.rbdevise_for :users, skip: [:sessions]デバイスの設定は完了です。
cancancanのインストール
次にcancancanをインストールしていきます。
これは認可と言って、ユーザによって、
アクセス権を付与するという意味になります。
システム管理者と一般ユーザの違いです。gem 'cancancan'バンドルインストールします。
$bundle installAbilityクラスが作成されます。
rails g cancan:abilityAbilityクラスのデフォルトです。
class Ability include CanCan::Ability def initialize(user) # Define abilities for the passed in user here. For example: # # user ||= User.new # guest user (not logged in) # if user.admin? # can :manage, :all # else # can :read, :all # end # # The first argument to `can` is the action you are giving the user # permission to do. # If you pass :manage it will apply to every action. Other common actions # here are :read, :create, :update and :destroy. # # The second argument is the resource the user can perform the action on. # If you pass :all it will apply to every resource. Otherwise pass a Ruby # class of the resource. # # The third argument is an optional hash of conditions to further filter the # objects. # For example, here the user can only update published articles. # # can :update, Article, :published => true # # See the wiki for details: # https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities end end以下に変更します。
# frozen_string_literal: true class Ability include CanCan::Ability def initialize(user) user ||= User.new can :read, :all if user.admin? can :manage, :all end # Define abilities for the passed in user here. For example: # # user ||= User.new # guest user (not logged in) # if user.admin? # can :manage, :all # else # can :read, :all # end # # The first argument to `can` is the action you are giving the user # permission to do. # If you pass :manage it will apply to every action. Other common actions # here are :read, :create, :update and :destroy. # # The second argument is the resource the user can perform the action on. # If you pass :all it will apply to every resource. Otherwise pass a Ruby # class of the resource. # # The third argument is an optional hash of conditions to further filter the # objects. # For example, here the user can only update published articles. # # can :update, Article, :published => true # # See the wiki for details: # https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities end enduser ||= User.new
上記がないとゲストユーザで操作しているときに、エラーになりました。if user.admin? ←adminって何?と聞かれました。
def initialize(user)
user ||= User.new
can :read, :all
Abilityモデルが呼ばれたときに
初期値としてログインしていれば、そのユーザを変数に入れ、ログインしていなければゲストユーザが変数に代入されます。
そして、ユーザ変数に対して、read権限を付与するという意味となる。全てのユーザがread権限があるということです。read権限というのは、CRUDに紐づいている訳ではないので注意しましょう。
ページが読めるということであり、getができるという意味ではないし、createメソッドとかも別にプログラムすればできます。if user.admin? can :manage, :allこちらはUserモデルに作成したadminカラムにtrueが入っていれば実行するプログラムです。
つまりは管理者権限を持つユーザということになります。Page#indexのビューページに以下を追加します。
app/views/pages/index.html.erb<h1>Pages#index</h1> <p>Find me in app/views/pages/index.html.erb</p> <%= link_to "削除", login_path, method: :delete %> <% if can? :update, current_user %> <h1>update</h1> <% end %> <% if can? :read, current_user %> <h1>read</h1> <% end %><% if can? :update, current_user %>
今ログインしているユーザがupdateの権限を持っていれば中身を表示します。<% if can? :read, current_user %>
もしログインしているユーザがreadの権限を持っていれば中身を表示します。という意味になります。
db/schema.rbcreate_table "users", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.boolean "admin", default: false t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end上記でadminってUserモデルにないけどどうするの?ってなるかと思うが、
追加することとなります。$rails g migration AddAdminToUser admin:booleanデフォルトで以下が作成されるので
default: "false"を追加します。ビューページでは、adminを選択するビューは作成せず、デフォルトでユーザを作成したらadmin=falseがつくようにします。
ここでは、adminは管理側で意図的に作成することとします。db/migrate/20200522114428_add_admin_to_users.rbclass AddAdminToUsers < ActiveRecord::Migration[6.0] def change add_column :users, :admin, :boolean, default: "false" end endマイグレートします。
$rails g migrateDBのスキーマの確認をします。
db/schema.rbcreate_table "users", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.boolean "admin", default: false t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true endhttp://localhost:3000/
で一般ユーザを作成しておきます。adminユーザは、
以下の通り作成します。$user =User.new(id: xx, email: "xxx@yyy", password: "xxxx", admin: true)これで手続きは完了なので、
実際に一般ユーザとAdminユーザそれぞれで、
Page#indexにアクセスします。一般ユーザはread権限のみなのでreadを表示可能です。
Adminユーザは,manage権限(全ての権限を持つ)なのでread,updateそれぞれ表示可能です。
- 投稿日:2020-05-23T18:32:31+09:00
【備忘録】Rails6 Devise + cancancan の使い方
はじめに
deviseを使用してUserモデル(認証)を作成後して
cancancanを追加して認可をできるようにしました。その過程を備忘録としてログします。
deviseのgemを追記します。
・ ・ ・ gem 'devise'バンドルインストールします。
$bundle installデバイスをインストールします。
$rails generate devise:install設定があるか確認します。
config/environments/development.rb・ ・ config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }page#indexを追記します。
config/routes.rbRails.application.routes.draw do root to: "page#index" ・ ・ end以下を追記します。
app/views/layouts/application.html.erb<body> <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p> ・ ・ </body>Viewをインストールします。
$ rails g devise:views以下が作られます。
app/views/devise/unlocks/new.html.erb
app/views/devise/shared/links.html.erb
app/views/devise/shared/_error_messages.html.erb
app/views/devise/sessions/new.html.erb
app/views/devise/registrations/new.html.erb
app/views/devise/registrations/edit.html.erb
app/views/devise/passwords/new.html.erb
app/views/devise/passwords/edit.html.erb
app/views/devise/mailer/unlockinstructions.html.erb
app/views/devise/mailer/reset_password_instructions.html.erb
app/views/devise/mailer/password_change.html.erb
app/views/devise/mailer/email_changed.html.erb
app/views/devise/mailer/confirmation_instructions.html.erb
app/views/devise/confirmations/new.html.erbモデルを作成します。
今回はUserモデルを作ります。$ rails g devise user以下モデルが作られます。
app/models/user.rbclass User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable end以下が同時に作られます。
config/application.rbconfig.i18n.default_locale = :ja以下も作られます。
config/routes.rbails.application.routes.draw do devise_for :users ・ ・ endモデル作成後に、DBに反映します。
rails db:migrateまだpage#indexのViewがないので作ります。
$ rails g controller Pages index余談ですが、routes.rbにスコープを作ることでPathを変更できます。
routes.rbdevise_scope :user do get 'login', to: 'devise/sessions#new' post 'login', to: 'devise/sessions/#create' delete 'logout', to: 'devise/sessions#destroy' endこれにより、
http://localhost:3000/login
にアクセスしたときに、ログインページが表示されます。もちろんデフォルトの/users/sign_inでもログインページが表示されるので必要であれば削除しておきましょう。
routes.rbdevise_for :users, skip: [:sessions]デバイスの設定は完了です。
cancancanのインストール
次にcancancanをインストールしていきます。
これは認可と言って、ユーザによって、
アクセス権を付与するという意味になります。
システム管理者と一般ユーザの違いです。gem 'cancancan'バンドルインストールします。
$bundle installAbilityクラスが作成されます。
rails g cancan:abilityAbilityクラスのデフォルトです。
class Ability include CanCan::Ability def initialize(user) # Define abilities for the passed in user here. For example: # # user ||= User.new # guest user (not logged in) # if user.admin? # can :manage, :all # else # can :read, :all # end # # The first argument to `can` is the action you are giving the user # permission to do. # If you pass :manage it will apply to every action. Other common actions # here are :read, :create, :update and :destroy. # # The second argument is the resource the user can perform the action on. # If you pass :all it will apply to every resource. Otherwise pass a Ruby # class of the resource. # # The third argument is an optional hash of conditions to further filter the # objects. # For example, here the user can only update published articles. # # can :update, Article, :published => true # # See the wiki for details: # https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities end end以下に変更します。
# frozen_string_literal: true class Ability include CanCan::Ability def initialize(user) user ||= User.new can :read, :all if user.admin? can :manage, :all end # Define abilities for the passed in user here. For example: # # user ||= User.new # guest user (not logged in) # if user.admin? # can :manage, :all # else # can :read, :all # end # # The first argument to `can` is the action you are giving the user # permission to do. # If you pass :manage it will apply to every action. Other common actions # here are :read, :create, :update and :destroy. # # The second argument is the resource the user can perform the action on. # If you pass :all it will apply to every resource. Otherwise pass a Ruby # class of the resource. # # The third argument is an optional hash of conditions to further filter the # objects. # For example, here the user can only update published articles. # # can :update, Article, :published => true # # See the wiki for details: # https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities end enduser ||= User.new
上記がないとゲストユーザで操作しているときに、エラーになりました。if user.admin? ←adminって何?と聞かれました。
def initialize(user)
user ||= User.new
can :read, :all
Abilityモデルが呼ばれたときに
初期値としてログインしていれば、そのユーザを変数に入れ、ログインしていなければゲストユーザが変数に代入されます。
そして、ユーザ変数に対して、read権限を付与するという意味となる。全てのユーザがread権限があるということです。read権限というのは、CRUDに紐づいている訳ではないので注意しましょう。
ページが読めるということであり、getができるという意味ではないし、createメソッドとかも別にプログラムすればできます。if user.admin? can :manage, :allこちらはUserモデルに作成したadminカラムにtrueが入っていれば実行するプログラムです。
つまりは管理者権限を持つユーザということになります。Page#indexのビューページに以下を追加します。
app/views/pages/index.html.erb<h1>Pages#index</h1> <p>Find me in app/views/pages/index.html.erb</p> <%= link_to "削除", logout_path, method: :delete %> <% if can? :update, current_user %> <h1>update</h1> <% end %> <% if can? :read, current_user %> <h1>read</h1> <% end %><% if can? :update, current_user %>
今ログインしているユーザがupdateの権限を持っていれば中身を表示します。<% if can? :read, current_user %>
もしログインしているユーザがreadの権限を持っていれば中身を表示します。という意味になります。
db/schema.rbcreate_table "users", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.boolean "admin", default: false t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end上記でadminってUserモデルにないけどどうするの?ってなるかと思うが、
追加することとなります。$rails g migration AddAdminToUser admin:booleanデフォルトで以下が作成されるので
default: "false"を追加します。ビューページでは、adminを選択するビューは作成せず、デフォルトでユーザを作成したらadmin=falseがつくようにします。
ここでは、adminは管理側で意図的に作成することとします。db/migrate/20200522114428_add_admin_to_users.rbclass AddAdminToUsers < ActiveRecord::Migration[6.0] def change add_column :users, :admin, :boolean, default: "false" end endマイグレートします。
$rails g migrateDBのスキーマの確認をします。
db/schema.rbcreate_table "users", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.boolean "admin", default: false t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true endhttp://localhost:3000/
で一般ユーザを作成しておきます。adminユーザは、
以下の通り作成します。$user =User.new(id: xx, email: "xxx@yyy", password: "xxxx", admin: true)これで手続きは完了なので、
実際に一般ユーザとAdminユーザそれぞれで、
Page#indexにアクセスします。一般ユーザはread権限のみなのでreadを表示可能です。
Adminユーザは,manage権限(全ての権限を持つ)なのでread,updateそれぞれ表示可能です。
- 投稿日:2020-05-23T17:28:41+09:00
Google Books APIから情報を格納するモデルを作り、直感的な扱いとテストを可能にする
対象読者
- 本に関するRailsポートフォリオを作る方
- Google Books APIを用いたRailsポートフォリオを作る方
本記事の到達物
Google Books APIのIDから情報を取得し、クラスに格納する
pry(main)> @google_book = GoogleBook.new_from_id('c1L4IzmUzicC') pry(main)> @google_book.title => "Practical Rails Plugins" pry(main)> @google_book.authors => ["Nick Plante", "David Berube"] pry(main)> @google_book.image => "http://books.google.com/books/content?id=c1L4IzmUzicC&printsec=frontcover&img=1&zoom=5&edge=curl&imgtk=AFLRE73ENsMYFOfY27vluLqgI1cO-b80lA7enoeZzzcDGEhA5NWIj3djHvd6gvP1zlKoMoC4V0_7fKVuIjWQDYVs4FrDjHvxoqtRUcxHZ9L7isRtsHc2Cs5iS6DPAQQcTT20Oseo9gq_&source=gbs_api"キーワードから検索し、複数のオブジェクトを返す
pry(main)> @google_books = GoogleBook.search('Rails') pry(main)> @google_books[0].title => "実践Rails" pry(main)> @google_books.last.authors => ["Sam Ruby", "Dave Thomas", "David Heinemeier Hansson"] pry(main)> @google_books.map { |google_book| google_book.title } => ["実践Rails", "独習Ruby on Rails", "Railsレシピ", "Ajax on Rails", "Ruby on Rails 4アプリケーションプログラミング", "Ruby on Rails 5の上手な使い方 現場のエンジニアが教えるRailsアプリケーション開発の実践手法", "Ruby on Rails 5 超入門", "RailsによるアジャイルWebアプリケーション開発第3版", "JRuby on Rails 実践開発ガイド", "RailsによるアジャイルWebアプリケーション開発第4版"] pry(main)> @google_books.class => Array格納された情報を、複数のテーブルに保存できる
pry(main)> @google_book = GoogleBook.new_from_id('wlNHDwAAQBAJ') pry(main)> @google_book.title => "Ruby on Rails 5の上手な使い方 現場のエンジニアが教えるRailsアプリケーション開発の実践手法" pry(main)> @google_book.authors => ["太田 智彬", "寺下 翔太", "手塚 亮", "宗像 亜由美", "株式会社リクルートテクノロジーズ"] pry(main)> @google_book.save => true pry(main)> @book = Book.last pry(main)> @book.title => "Ruby on Rails 5の上手な使い方 現場のエンジニアが教えるRailsアプリケーション開発の実践手法" pry(main)> @book.authors[0].name => "太田 智彬" pry(main)> @book.authors.size => 5 pry(main)> @book.authors.class => Author::ActiveRecord_Associations_CollectionProxy pry(main)> @author = Author.last pry(main)> @author.name => "株式会社リクルートテクノロジーズ"本記事で書くこと
Google Books APIからの情報を格納する、GoogleBookモデルを作成します。
大きなメリットとしては、以下の2つがあります。
- Google Books APIから受け取った情報をControllerやViewで直感的に扱える。
- 情報を取得する / 情報を格納して整理する / 複数テーブルに跨いで保存をする、などのロジックを分離して書くことができ、それぞれテストを行える。
本記事ではControllerでの使用例、テストについても記載します。
DB設計
データベースには、本の以下の情報を保存します。
- 「タイトル」
- 「著者」
- 「画像URL」
- 「出版日」
- 「Google Books APIのID」
もちろん、Google Books APIにある情報であれば、これ以外も取得し保存することができます。
本記事では説明のために、取得する情報としては少なめにしてあります。ER図
BooksテーブルとAuthorsテーブルを用意します。
一つの本に対し、著者は複数人いることがあるので、book has_many authorsの関係にします。ただし、「本を一覧で表示する」ようなページで、著者全員でなく代表著者を表示したい場合があると思います。
そのため、authorsテーブルにはis_representative
カラムを用意しておきます。また、「Google Books APIから情報を取得するなら、自前のデータベースに情報を持つ必要は無いのでは?」と思われるかもしれません。
その設計を行い、失敗した話を以下に載せておきます。
GoogleBooksAPIだけで本リソースの取得をする設計を行い、失敗した話要約すると、本の情報は自前のデータベースでも情報を持っておいた方が良い、という結論になります。
マイグレーションファイル
マイグレーションファイルを作るなら、以下のようになるかと思います。
Booksテーブル
db/migrate/20202020202020_create_booksclass CreateBooks < ActiveRecord::Migration[5.2] def change create_table :books do |t| t.string :google_books_api_id, null: false t.string :title, null: false t.string :image t.date :published_at t.timestamps end add_index :books, :googlebooksapi_id, unique: true end endGoogle Books APIのIDは必ず必要なため、
null: false
を指定しておきます。
また、Google Books APIのIDが重複することは考えられないため、ユニークキーも付与します。タイトルが存在しない本は、存在しないと考えられるため、
null: false
を指定しておきます。逆に、他の情報に
null: false
を指定するのには注意が必要です。
外部APIからの情報なわけで、本によってはその情報が無いことがあり、「DBに登録できない」という事態を発生させうるからです。Authorsテーブル
db/migrate/20202020202021_create_authorsclass CreateAuthors < ActiveRecord::Migration[5.2] def change create_table :authors do |t| t.references :book, foreign_key: true t.string :name, null: false t.boolean :is_representative, null: false t.timestamps end end endGoogle Books APIを叩くメソッド
まずはAPIを叩くモジュールを
app/lib/
配下に追加します。app/lib/google_books_api.rbmodule GoogleBooksApi def url_of_creating_from_id(googlebooksapi_id) "https://www.googleapis.com/books/v1/volumes/#{googlebooksapi_id}" end # Google Books APIのIDから、APIのURLを取得する def url_of_searching_from_keyword(keyword) "https://www.googleapis.com/books/v1/volumes?q=#{keyword}&country=JP" end # キーワードから、検索するAPIのURLを取得する def get_json_from_url(url) JSON.parse(Net::HTTP.get(URI.parse(Addressable::URI.encode(url)))) end # URLから、JSON文字列を取得し、JSONオブジェクトを構築する end本記事で使用するGoogle Books APIは2種類です。
- IDから一つの本の情報を返してくれる
https://www.googleapis.com/books/v1/volumes/:ID
というURLによって取得できます。
以下のURLがその例です。
https://www.googleapis.com/books/v1/volumes/aB4B13xGEv4CGoogle Books APIから取得できる情報がどんなものか知らない場合、上のURLを見て確認してみて下さい。
タイトル、出版日、購入リンク、ISBN、など色々取得できることが分かると思います。
- キーワードから検索結果群を返してくれる
https://www.googleapis.com/books/v1/volumes?q=search?:キーワード
というURLによって取得できます。
以下のURLがその例です。
https://www.googleapis.com/books/v1/volumes?q=search?RailsGoogle Books APIの他仕様について知りたい方は、公式ドキュメントをご参照ください。
Getting StartedURLをエスケープするために、
addressable
というgemを使っています。
以下gemをGemfileに追記しbundle install
してください。Gemfilegem 'addressable'ちなみに、Railsでは、
app/**/**.rb
を自動で読み込んでくれます。
よって使いたいクラス内でinclude GoogleBooksApi
とinclude
さえすれば、以上3つのメソッドをクラス内で使用できます。情報を格納するモデル
Google Books APIからの情報を、オブジェクトとして格納するモデルを以下のように作ります。
app/models/google_book.rbclass GoogleBook include ActiveModel::Model include ActiveModel::Attributes include ActiveModel::Validations attribute :googlebooksapi_id, :string attribute :authors attribute :image, :string attribute :published_at, :date attribute :title, :string validates :googlebooksapi_id, presence: true validates :title, presence: true class << self include GoogleBooksApi def new_from_item(item) @item = item @volume_info = @item['volumeInfo'] new( googlebooksapi_id: @item['id'], authors: @volume_info['authors'], image: image_url, published_at: @volume_info['publishedDate'], title: @volume_info['title'], ) end def new_from_id(googlebooksapi_id) url = url_of_creating_from_id(googlebooksapi_id) item = get_json_from_url(url) new_from_item(item) end def search(keyword) url = url_of_searching_from_keyword(keyword) json = get_json_from_url(url) items = json['items'] return [] unless items items.map do |item| GoogleBook.new_from_item(item) end end private def image_url @volume_info['imageLinks']['smallThumbnail'] if @volume_info['imageLinks'].present? end end def save return false unless valid? book = build_book return false unless book.valid? ActiveRecord::Base.transaction do book.remote_image_url = image if image.present? book.save authors.each.with_index do |author, index| author = book.authors.build(name: author) author.is_representation = index.zero? author.save end end true end def find_book_or_save if Book.find_by(googlebooksapi_id: googlebooksapi_id) || save Book.find_by(googlebooksapi_id: googlebooksapi_id) else false end end private def build_book Book.new( googlebooksapi_id: googlebooksapi_id, published_at: published_at, title: title, ) end end長くなりました笑
一つ一つ解説していきます。ActiveModel
include ActiveModel::Model include ActiveModel::Attributes include ActiveModel::ValidationsActiveModelとは「データベースと連携しないActiveRecord」みたいなものです。
ActiveModelのattribute
attribute :googlebooksapi_id, :string attribute :authors (以下略)
ActiveModel::Attributes
をinclude
したので使えます。
authors
には配列を入れることを想定していますが、配列に対応するattributeの型は無いようなので、この書き方にしています。ActiveModel::Attributes が最高すぎるんだよな。
ActiveModelのvalidates
validates :googlebooksapi_id, presence: true validates :title, presence: trueGoogle Books APIのIDが無い本、タイトルの存在しない本は存在しない(と思われる)ので、
validetes
を入れておきます。
仮にGoogle Books APIのIDが無い本がオブジェクトとして格納された場合、valid?
メソッドを使ったときにfalse
を返すことができます。(後述しますが、save
メソッドにて使用します)クラスメソッドの定義方法
class << self
new_from_id
はGoogleBook.new_from_id('c1L4IzmUzicC')
のようにクラスメソッドとして使いたいメソッドです。クラスメソッドを定義する方法は他にもありますが、class << slef
によるやり方が推奨なようです。Rubyのクラスメソッドをclass << selfで定義している理由(翻訳)
IDからインスタンスを生成する
def new_from_id(googlebooksapi_id) url = url_of_creating_from_id(googlebooksapi_id) item = get_json_from_url(url) new_from_item(item) end順番がちょっと前後しますが、
new_from_item
より先にnew_from_id
を説明します。前述の
GoogleBooksApi
モジュールのurl_of_creating_from_id
およびget_json_from_url
を使用することで、一つの本の情報(JSON)をitem
として取得します。
そのitemをnew_from_item
に渡します。
item
からインスタンスを生成するdef new_from_item(item) @item = item @volume_info = @item['volumeInfo'] new( googlebooksapi_id: @item['id'], authors: @volume_info['authors'], image: image_url, published_at: @volume_info['publishedDate'], title: @volume_info['title'], ) endGoogle Books APIの中身を観察すると分かりますが、多くの情報は
item['volumeInfo']
に入っています。
取り出したい情報を上記のように適宜定義し、new()
を使ってインスタンスを生成する、というロジックにします。
image_url
だけ下記のやり方で実装しています。
image_url
の実装private def image_url @volume_info['imageLinks']['smallThumbnail'] if @volume_info['imageLinks'].present? end本によっては
volume_info['imageLinks']
が入っていない本が存在するため、そのままvolume_info['imageLinks']['smallThumbnail']
だけで使おうとするとundefind
のエラーが出てしまうことがあります。
undefind
を出さないようにするため、上記のように実装します。また、この
image_url
メソッドはクラス外で使用することは考えられないので、private
下で定義します。検索結果をインスタンス群として返すクラスメソッド
def search(keyword) url = url_of_searching_from_keyword(keyword) json = get_json_from_url(url) items = json['items'] return [] unless items items.map do |item| GoogleBook.new_from_item(item) end endGoogle Books APIのうち、検索結果群を返してくれるAPIを使った場合、返ってくるのは複数の
items
となります。
items
は配列として[item1, item2, item3, ...]
という形になっています。
map
メソッド内で一つ一つnew_from_item(item)
をすることで、[googlebook1, googlebook2, googlebook3, ...]
という配列の形で返すことができます。不適切なキーワードだと検索結果群を返してくれるAPIで
items
が返ってこないため、その場合もundefind
のエラーが発生してしまいます。
そこでreturn [] unless items
の一行を入れることで、items
が無い場合は空の配列を返すようにします。まだ
save
メソッド、find_book_or_save
メソッドの説明が残っていますが、先にControllerでの使い方を見るほうが分かりやすいと思うので、Controllerの説明に移りたいと思います。Controllerでの使用例
GoogleBookクラスを使うのは、「検索画面」と「リソース登録(create)」の2つかと思います。
ルーティング
config/routes.rbresources :books, only: %i[create show], shallow: true do collection do get :search end endあらかじめ、上記のようなルーティングは設定しておきます。
本筋とズレてしまうので説明は省きます。検索画面のController
app/controllers/books_controller.rbclass BooksController < ApplicationController def search @search_form = SearchBooksForm.new(search_books_params) books = GoogleBook.search(@search_form.keyword) @books = Kaminari.paginate_array(books).page(params[:page]) end private def search_books_params params.fetch(:q, keyword: '').permit(:keyword) end end弊記事で恐縮ですが、検索フォームは以下の記事のやり方で実装します。
【Ruby on Rails】フォームオブジェクトを使って検索キーワードをcontrollerに送る受け取ることのできた検索キーワード(
@search_form.keyword
)をGoogleBook.search
に渡して、検索結果をGoogleBook
クラスから生成したインスタンス群の配列として受け取ります。以降は任意ですが、私は検索結果をkaminariのページネーションで表示したかったため、
Kaminari.paginate_array
に渡して使用しました。リソース登録のController ver.1(今までのメソッドのみで書くNGな例)
やりたいこととしては以下の通りです。
GoogleBook
モデルに持っていた情報をBook
モデルやAuthor
モデルに格納し直す- ヴァリデーションをかける
- ヴァリデーション成功時にはDBに保存する
app/models/google_book.rbclass BooksController < ApplicationController def create google_book = GoogleBook.new_from_id(create_book_params[:googlebooksapi_id]) @book = Book.build( googlebooksapi_id: google_book.googlebooksapi_id, published_at: google_book.published_at, title: google_book.title, ) if @book.valid? @book.remote_image_url = google_book.image if google_book.image.present? @book.save google_book.authors.each.with_index do |author, index| @author = @book.authors.build(name: author) @author.is_representation = index.zero? @author.save end redirect_to @book else redirect_to search_books_path, danger: 'ページの表示に失敗しました' end end private def create_book_params params.permit(:googlebooksapi_id) end endしかし、上記の実装には色々な問題が発生しています。
- Fat Controllerになってしまっている
- 重複した本を弾けていない
- 複数リソースへのデータ登録なのに、
ActiveRecord::Base.transaction
を使えていない特にFat Controllerの問題はなんとかしなくてはいけないです。
そもそも、このDBへの保存に関する処理はGoogleBook
モデルが責務を持つ処理かと思います。
よって、保存に関するロジックはGoogleBook
のインスタンスメソッドとして定義することにします。複数テーブルへの保存を実現する、
save
メソッドapp/models/google_book.rbdef save return false unless valid? book = build_book ActiveRecord::Base.transaction do book.remote_image_url = image if image.present? book.save if authors.present? authors.each.with_index do |author, index| author = book.authors.build(name: author) author.is_representative = index.zero? author.save end end end true end private def build_book Book.new( googlebooksapi_id: googlebooksapi_id, published_at: published_at, title: title, ) end
ActiveRecord
のsave
メソッド風に仕上げるため、保存に成功すればtrue
、失敗すればfalse
を返すようにしたいです。
そこで、return false unless valid?
の一行で、valid?
で失敗したときにはfalse
を返すようにしました。
また、成功時の最後にtrue
の一行を入れることで、成功時にはtrue
を返すことができます。
ActiveRecord::Base.transaction
で囲うことで、複数リソース登録時で途中で失敗したときにロールバックを走らせることができます。
この場合では、何かの不具合で後半のauthor.save
で失敗したとしても、前半のbook.save
を取り止めにすることができます。
book.remote_image_url = image if image.present?
はCarrierwaveで画像のアップロードを行うロジックになります。本筋とズレるので、今回は説明は省きます。
author.is_representative = index.zero?
は、authors
配列のうち、最初のインデックスにある著者を「代表著者」とするための一行です。
each.with_index
を使って配列を回してるのもこれが理由です。リソース登録のController ver.2(重複の登録をしてしまう)
app/controllers/books_controller.rbclass BooksController < ApplicationController def create google_book = GoogleBook.new_from_id(create_book_params[:googlebooksapi_id]) if google_book.save @book = Book.find_by(googlebooksapi_id: google_book.googlebooksapi_id) redirect_to @book else redirect_to search_books_path, danger: 'ページの表示に失敗しました' end end private def create_book_params params.permit(:googlebooksapi_id) end endかなりスッキリしましたが、まだ問題が残っています。
Google Books APIのIDが重複した、すなわち同じ本を登録してしまう可能性があります。
find_or_create_by
風味な実装実現したいのは以下のことです。
- すでにbooksテーブルにその本があるなら、そのレコードに対応したモデルを返す
- なければ、
save
メソッドを実行して、新しく作ったレコードに対応したモデルを返す- booksテーブルにその本が無く、
save
メソッドが失敗するなら、false
を返す
ActiveRecord
で言えば、find_or_create_by
に近い挙動を実現したいことになります。
GoogleBook
のインスタンスメソッドとして、以下のようにfind_book_or_save
メソッドとして実装します。app/models/google_book.rbdef find_book_or_save if Book.find_by(googlebooksapi_id: googlebooksapi_id) || save Book.find_by(googlebooksapi_id: googlebooksapi_id) else false end endリソース登録のController ver.3(完成)
app/controllers/books_controller.rbclass BooksController < ApplicationController def create google_book = GoogleBook.new_from_id(create_book_params[:googlebooksapi_id]) if (@book = google_book.find_book_or_save) redirect_to @book else redirect_to search_books_path, danger: 'ページの表示に失敗しました' end end private def create_book_params params.permit(:googlebooksapi_id) end endRSpecでテストを書く
今回扱うテストは3種類とします。
- Google Books APIを叩くメソッドのテスト
GoogleBook
のモデルテスト- 本の登録時のリクエストスペック
テストの書き方については若干自信が無いため、ご指摘お待ちしております笑
Google Books APIを叩くメソッドのテスト
spec/lib/google_books_api_spec.rbrequire 'rails_helper' describe GoogleBooksApi do let(:test_class) { Struct.new(:google_books_api) { include GoogleBooksApi } } let(:google_books_api) { test_class.new } it '検索するAPIを叩き、複数のデータを返すkindが取得できること' do url = google_books_api.url_of_searching_from_keyword('Rails') expect(google_books_api.get_json_from_url(url)['kind']).to eq 'books#volumes' end it 'IDから本の情報を取得するAPIを叩き、特定データを返すkindが取得できること' do GOOGLE_BOOKS_API_ID_SAMPLE = 'aB4B13xGEv4C'.freeze url = google_books_api.url_of_creating_from_id(GOOGLE_BOOKS_API_ID_SAMPLE) expect(google_books_api.get_json_from_url(url)['kind']).to eq 'books#volume' expect(google_books_api.get_json_from_url(url)['id']).to eq GOOGLE_BOOKS_API_ID_SAMPLE end endモジュールのテストをするやり方に関しては以下サイトを参考にさせて頂きました。
【Ruby on Rails】初心者でもメソッドの単体テストがしたい!!
GoogleBook
のモデルテストまずはFactoryBotを定義しておきます。
spec/factories/google_book.rbFactoryBot.define do factory :google_book do googlebooksapi_id { 'wlNHDwAAQBAJ' } authors do [ '太田 智彬', '寺下 翔太', '手塚 亮', '宗像 亜由美', '株式会社リクルートテクノロジーズ' ] end image { 'http://books.google.com/books/content?id=wlNHDwAAQBAJ&printsec=frontcover&img=1&zoom=5&edge=curl&imgtk=AFLRE70j5lrdzOYN-iUu8w-G_JJKpEhnpUGAgqyZd7rj4jHu59NcAU48eQ75T4fkdyyZD6dMlwjjw0sAdQSKY_HiEdNBMMeyDn4DUmOcY-oLHFRAnxPXocc_T_PA7NYdSlZdwKckhCMy&source=gbs_api' } published_at { '2018-01-24' } title { 'Ruby on Rails 5の上手な使い方 現場のエンジニアが教えるRailsアプリケーション開発の実践手法' } end end現場Railsです。
次にモデルテストの本体です。app/models/google_book_spec.rbrequire 'rails_helper' RSpec.describe GoogleBook, type: :model do it '有効なファクトリを持つこと' do google_book = build(:google_book) expect(google_book).to be_valid end it 'Google Books APIのIDが存在しないときに無効なこと' do google_book = build(:google_book, googlebooksapi_id: nil) google_book.valid? expect(google_book.errors.messages[:googlebooksapi_id]).to include('を入力してください') end it 'タイトルが存在しないときに無効なこと' do google_book = build(:google_book, title: nil) google_book.valid? expect(google_book.errors.messages[:title]).to include('を入力してください') end it 'Google Books APIのIDから目的のインスタンスを生成できること' do googlebooksapi_id = 'YEfUBgAAQBAJ' google_book = GoogleBook.new_from_id(googlebooksapi_id) expect(google_book.title).to eq 'SpriteKitではじめる2Dゲームプログラミング Swift対応' expect(google_book.googlebooksapi_id).to eq googlebooksapi_id expect(google_book.authors).to eq %w[山下佳隆 村田知常 原知愛 近藤秀彦] expect(google_book.author).to eq '山下佳隆' end it '適切なキーワードから複数の検索結果を返し、そのタイトルにキーワードが含まれていること' do keyword = 'Ruby' keyword_count = 0 google_books = GoogleBook.search(keyword) expect(google_books.size).to be >= 5 # 検索結果を5個以上は返せる google_books.each do |google_book| if google_book.title.include?(keyword) keyword_count += 1 end end expect(keyword_count).to be >= 5 # キーワードのRubyを含むタイトルが5個以上は返せる end it '不適切なキーワードからは検索結果を返さないこと' do keyword = 'bbvjnaovnaov' # 適当 google_books = GoogleBook.search(keyword) expect(google_books.size).to be 0 end describe '保存時に' do context '不適切な情報しか持たないときは' do let(:google_book) { build(:google_book, googlebooksapi_id: nil) } it '保存に失敗すること' do expect { google_book.save }.to change { Book.count }.by(0).and change { Author.count }.by(0) end it 'falseを返すこと' do expect(google_book.save).not_to be_truthy end end context '適切な情報を持っているときは' do let(:google_book) { build(:google_book, authors: [ '太田 智彬', '寺下 翔太', '手塚 亮', '宗像 亜由美', '株式会社リクルートテクノロジーズ' ]) } it '保存できること' do expect { google_book.save }.to change { Book.count }.by(1).and change { Author.count }.by(5) end it 'trueを返すこと' do expect(google_book.save).to be_truthy end end context '著者の情報だけを持っていないときにも' do let(:google_book) { build(:google_book, authors: nil) } it '保存できること' do expect { google_book.save }.to change { Book.count }.by(1).and change { Author.count }.by(0) end it 'trueを返すこと' do expect(google_book.save).to be_truthy end end end endヴァリデーションと、それぞれのメソッドをテストしただけなので、あまり説明は要らないかと思います。
本の登録時のリクエストスペック
spec/requests/books_spec.rbrequire 'rails_helper' RSpec.describe 'Books', type: :request do it '適切なGoogle Books APIのIDである本が登録できること' do expect { post '/books', params: { googlebooksapi_id: 'xPbRxgEACAAJ' } }.to change { Book.count }.by(1) expect(response).to redirect_to book_path(Book.last.id) end it '既に登録されている本の登録に失敗し、その詳細画面に遷移すること' do same_google_books_api_id = 'xPbRxgEACAAJ' create(:book, googlebooksapi_id: same_google_books_api_id) expect { post '/books', params: { googlebooksapi_id: same_google_books_api_id } }.to change { Book.count }.by(0) expect(response).to redirect_to book_path(Book.find_by(googlebooksapi_id: same_google_books_api_id)) end end対応するControllerは「リソース登録のController ver.3(完成)」のものです。
テストは説明することが少なくて楽で良いですね以上になります。
改善していければと思っておりますので、間違ってる箇所や改善すべき箇所があればご指摘いただけると嬉しいです。
- 投稿日:2020-05-23T16:46:31+09:00
[Ruby]getとpostの違い
Rubyのルーティングの中にgetとpostがあります。
その違いは何なのかを説明していきます。get:
データベースを変更しないアクションpost:
データベースを変更するアクション非常にシンプルです
- 投稿日:2020-05-23T16:08:15+09:00
Rails+Webスクレイピング+DockerのアプリをEC2で立てようとしたら、デフォルト設定だとt2.small(2GB)とかが必要になりそう
※情弱なのでデフォルトのメモリ設定をしてしまってますが、もしかしたらDockerのメモリ割り当てをうまくやればもっと小さいサイズでいけるかもしれません。
概要
dockerでWebサイト(Rails + Webスクレイピング)をAWSのEC2で立てたいと思っていたんですが、自分しか使わないような遊びサイトだったので、できるだけ安い金額で立てられたらと思っていたんですが、見事に撃沈した話です。
結論
デフォルト設定(Docker、Rails)だとt2.smallを使わないと、
bundle install
のnokogiri
を入れるときにメモリエラーになります。※この記事のようにDockerを動かすだけならt2.nanoでも動きます。
※t2.smallならスクレイピング(docker-selenium)も動きました詳細
どんな環境?
Docker: 19.03.6-ce
docker-compose: 1.25.5
ruby:2.5.3 (Dockerhubのruby:2.5.3
のイメージを使用)
Rails: 5.2.3AWSにDockerを入れるのにはこちらを参考にさせてもらいました。
どんなことが起きたの?
t2.nanoとt2.microのdocker環境内で
bundle install
をしたら以下エラーが起きました。
(どうでもいいですが、ローカル環境で動いていたDockerfileでbundle install
したときに以下エラーが起き、まだイメージが出来上がる前だったので、わざわざDockerfileのbundle install
やdocker-compose.ymlのコマンドからbundle install
やそれに依存したrails s
をコメントアウトしてdocker-compose.ymlでtail -f Gemfile
をしてコンテナ延命した状態で以下のように独立してbundle install
を実行しました。docker-composeに慣れてdocker run
が打てない人間の悲しい性です)# bundle install The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`. Fetching gem metadata from http://rubygems.org/............ Fetching rake 12.3.3 Installing rake 12.3.3 Fetching concurrent-ruby 1.1.5 Installing concurrent-ruby 1.1.5 Fetching i18n 1.6.0 Installing i18n 1.6.0 Fetching minitest 5.11.3 Installing minitest 5.11.3 Fetching thread_safe 0.3.6 Installing thread_safe 0.3.6 Fetching tzinfo 1.2.5 Installing tzinfo 1.2.5 Fetching activesupport 5.2.3 Installing activesupport 5.2.3 Fetching builder 3.2.3 Installing builder 3.2.3 Fetching erubi 1.8.0 Installing erubi 1.8.0 Using mini_portile2 2.4.0 Fetching nokogiri 1.10.3 Installing nokogiri 1.10.3 with native extensions Gem::Ext::BuildError: ERROR: Failed to build gem native extension. current directory: /usr/local/bundle/gems/nokogiri-1.10.3/ext/nokogiri /usr/local/bin/ruby -I /usr/local/lib/ruby/site_ruby/2.5.0 -r ./siteconf20200523-12-t3a56p.rb extconf.rb checking if the C compiler accepts ... yes Building nokogiri using packaged libraries. Using mini_portile version 2.4.0 checking for gzdopen() in -lz... yes checking for iconv... yes ************************************************************************ IMPORTANT NOTICE: Building Nokogiri with a packaged version of libxml2-2.9.9 with the following patches applied: - 0001-Revert-Do-not-URI-escape-in-server-side-includes.patch - 0002-Remove-script-macro-support.patch - 0003-Update-entities-to-remove-handling-of-ssi.patch Team Nokogiri will keep on doing their best to provide security updates in a timely manner, but if this is a concern for you and want to use the system library instead; abort this installation process and reinstall nokogiri as follows: gem install nokogiri -- --use-system-libraries [--with-xml2-config=/path/to/xml2-config] [--with-xslt-config=/path/to/xslt-config] If you are using Bundler, tell it to use the option: bundle config build.nokogiri --use-system-libraries bundle install Note, however, that nokogiri is not fully compatible with arbitrary versions of libxml2 provided by OS/package vendors. ************************************************************************ Extracting libxml2-2.9.9.tar.gz into tmp/x86_64-pc-linux-gnu/ports/libxml2/2.9.9... OK Running git apply with /usr/local/bundle/gems/nokogiri-1.10.3/patches/libxml2/0001-Revert-Do-not-URI-escape-in-server-side-includes.patch... OK Running git apply with /usr/local/bundle/gems/nokogiri-1.10.3/patches/libxml2/0002-Remove-script-macro-support.patch... OK Running git apply with /usr/local/bundle/gems/nokogiri-1.10.3/patches/libxml2/0003-Update-entities-to-remove-handling-of-ssi.patch... OK Running 'configure' for libxml2 2.9.9... OK Running 'compile' for libxml2 2.9.9... OK Running 'install' for libxml2 2.9.9... OK Activating libxml2 2.9.9 (from /usr/local/bundle/gems/nokogiri-1.10.3/ports/x86_64-pc-linux-gnu/libxml2/2.9.9)... ************************************************************************ IMPORTANT NOTICE: Building Nokogiri with a packaged version of libxslt-1.1.33 with the following patches applied: - 0001-Fix-security-framework-bypass.patch Team Nokogiri will keep on doing their best to provide security updates in a timely manner, but if this is a concern for you and want to use the system library instead; abort this installation process and reinstall nokogiri as follows: gem install nokogiri -- --use-system-libraries [--with-xml2-config=/path/to/xml2-config] [--with-xslt-config=/path/to/xslt-config] If you are using Bundler, tell it to use the option: bundle config build.nokogiri --use-system-libraries bundle install ************************************************************************ Extracting libxslt-1.1.33.tar.gz into tmp/x86_64-pc-linux-gnu/ports/libxslt/1.1.33... OK Running git apply with /usr/local/bundle/gems/nokogiri-1.10.3/patches/libxslt/0001-Fix-security-framework-bypass.patch... OK Running 'configure' for libxslt 1.1.33... OK Running 'compile' for libxslt 1.1.33... OK Running 'install' for libxslt 1.1.33... OK Activating libxslt 1.1.33 (from /usr/local/bundle/gems/nokogiri-1.10.3/ports/x86_64-pc-linux-gnu/libxslt/1.1.33)... checking for -llzma... yes checking for xmlParseDoc() in libxml/parser.h... yes checking for xsltParseStylesheetDoc() in libxslt/xslt.h... yes checking for exsltFuncRegister() in libexslt/exslt.h... yes checking for xmlHasFeature()... yes checking for xmlFirstElementChild()... yes checking for xmlRelaxNGSetParserStructuredErrors()... yes checking for xmlRelaxNGSetParserStructuredErrors()... yes checking for xmlRelaxNGSetValidStructuredErrors()... yes checking for xmlSchemaSetValidStructuredErrors()... yes checking for xmlSchemaSetParserStructuredErrors()... yes creating Makefile current directory: /usr/local/bundle/gems/nokogiri-1.10.3/ext/nokogiri make "DESTDIR=" clean current directory: /usr/local/bundle/gems/nokogiri-1.10.3/ext/nokogiri make "DESTDIR=" make failedCannot allocate memory - make "DESTDIR=" Gem files will remain installed in /usr/local/bundle/gems/nokogiri-1.10.3 for inspection. Results logged to /usr/local/bundle/extensions/x86_64-linux/2.5.0/nokogiri-1.10.3/gem_make.out An error occurred while installing nokogiri (1.10.3), and Bundler cannot continue. Make sure that `gem install nokogiri -v '1.10.3' --source 'http://rubygems.org/'` succeeds before bundling. In Gemfile: rails was resolved to 5.2.3, which depends on actioncable was resolved to 5.2.3, which depends on actionpack was resolved to 5.2.3, which depends on actionview was resolved to 5.2.3, which depends on rails-dom-testing was resolved to 2.0.3, which depends on nokogiri長すぎて何がエラーかわからないので、見ろと言われたログを見てみます
# cat /usr/local/bundle/extensions/x86_64-linux/2.5.0/nokogiri-1.10.3/gem_make.out current directory: /usr/local/bundle/gems/nokogiri-1.10.3/ext/nokogiri /usr/local/bin/ruby -I /usr/local/lib/ruby/site_ruby/2.5.0 -r ./siteconf20200523-53-7krva6.rb extconf.rb extconf failedCannot allocate memory - /usr/local/bin/rubyここでメモリ不足だとわかりましたorz
そりゃ500MBでdockerだもんなまとめ
月2000円は痛いんですが、仕方ないかぁといった感じです。
誰か同じ状況の人やAWSに関しての意思決定で同じ問題意識を持った人の一助になれば嬉しいです。
- 投稿日:2020-05-23T15:08:56+09:00
Ruby on rails を初心者向けに解説⑤ ~データベースの編集と削除~
はじめに
今回は以前の記事の続きになります。
よろしければ、以前の記事も御覧ください。
Ruby on rails を初心者向けに解説② ~リンクの作成~
Ruby on rails を初心者向けに解説③ ~データベースの作成~
Ruby on rails を初心者向けに解説④ ~命名規則とform_Tagの使い方について~
データベースのデータ一覧の表示
以下のコードでusersテーブルのデータを取得して表示しましょう。
index.html.erb<% @users.each do |user| %> <p>name</p> <div class="ruby-name"><%= user.name %></div> <% end %>インスタンス変数@usersを使用するために、コントローラ内で渡しています。
users_controller.rbdef index @users = User.all end以下のような画面が表示されます。
また、以下のようにcssを指定しています。
users.scss.ruby-name { color: purple; display: inline-block; font-weight: bold; }データベースの削除
それではデータベースの削除を実装してみましょう。
以下のようにコードを書き換えてください。
index.html.erb<% @users.each do |user| %> <p>name</p> <div class="ruby-name"> <%= user.name %> <%= link_to("削除", "/users/#{user.id}/destroy", {method: "post"})%> </div> <% end %>削除ボタンをクリックすると、要素を削除することができました。
具体的な処理を見ていきましょう。index.html.erbに、以下のコードが追記されています。
<%= link_to("削除", "/users/#{user.id}/destroy", {method: "post"})%>これにより、削除をクリックすると、第二引数の処理が実行されます。
第二引数の"/users/#{user.id}/destroy"は、usersコントローラーのdestroyアクションへのルーティングに、データベースのidを含めたものになっています。このようにURLを指定することで、コントローラーは削除したいデータベースのidを受け取ることができます。
第三引数は、これがgetリクエストではなく、postリクエストであることを指定しています。
以下のようにルーティングしてください。
routes.rbpost "users/:id/destroy" => "users#destroy":id の部分は、任意の数字を受け取ることができます。受け取った数字は、usersコントローラーにおいて、
params[:id]
の中に格納されます。以下のようにコントローラーをコーディングしてください。
users_controller.rbdef destroy user = User.find_by(id: params[:id]) user.destroy redirect_to("/users/index") end
user = User.find_by(id: params[:id])
の部分で、データベースからモデルを使ってデータを抜き出します。index.html.erbから送られてきたidと同じidのデータをデータベースから抜き出し、userに格納します。
user.destroy
の部分でそのデータを削除しています。
redirect_to("/users/index")
の部分で、index.html.erbにリダイレクトしています。今回は、index.html.erbから削除リンクをクリックしたときの動作であるので、リロードになります。ここまでで、データベースからデータを削除することができました。
データベースの編集
次はデータベースの編集を行っていきましょう。
次のように
index.html.erb
を編集してください。index.html.erb<% @users.each do |user| %> <p>name</p> <div class="ruby-name"> <%= user.name %> <%= link_to("削除", "/users/#{user.id}/destroy", {method: "post"})%> <%= link_to("編集", "/users/#{user.id}/edit") %> </div> <% end %>今回新たに追加されたのは
<%= link_to("編集", "/users/#{user.id}/edit") %>
の部分です。これにより、新たに
users/edit
というviewファイルへ移動します。そのときに、そのviewファイルに編集したいデータベースのidを渡すことになります。
次のようにルーティングしてください。
routes.rbget "users/:id/edit" => "users#edit"edit.html.erb<%= form_tag("/users/#{@user.id}/update") do %> <input type="text" value="<%=@user.name%>" name="new_name"> <input type="submit" value="送信"> <% end %>試しに以下のindex.html.erbファイルの編集と書いてある部分をクリックしましょう。
ここで、edit.html.erbファイルについての解説です。
form_tag("/users/#{@user.id}/update")
の部分で、どのコントローラーのどのアクションを使うかを指定しています。今回は、usersコントローラーのupdateアクションを使用します。また、データベースの編集を行うため、編集したいデータベースのidも送ります。form_tagはpostリクエストになるのでしたね。
<input type="text" value="<%=@user.name%>" name="new_name">
の部分で、inputタグの初期値と名前を設定しています。@user
はusersコントローラーのeditアクションから送られてきたものです。users_controller.rbdef edit @user = User.find_by(id: params[:id]) end
@user
には、データベースからidに応じて探してきた値が格納されています。それでは、下のように値を変更して送信してみましょう。
そうすると、以下のルーティングにより、usersコントローラーのupdateアクションが実行されます。
routes.rbpost "users/:id/update" => "users#update"users_controller.rbdef update user = User.find_by(id: params[:id]) user.name = params[:new_name] user.save redirect_to("/users/index") endupdateアクション内では、送られてきたidに応じてデータベースからデータを探索し、ローカル変数userに格納した後、nameカラムのデータを送られてきた新しい名前に書き換えて、
user.save
でセーブした後に、/users/index
にリダイレクトしています。そのため、以下のように変化します。
終わりに
ここまでで今回の記事は終了です。
お付き合い頂きありがとうございました。
- 投稿日:2020-05-23T14:15:31+09:00
Fontawsomeのアイコンが表示されなかった件
はじめに
railsでアプリケーションを作成していました。
アイコンが欲しいと思いFontawsomeを使用することにしました。
ですが表示されないということが起きました。準備
まずはFontawsomeのアイコンが使えるようにする為に準備しました。
①Gemfileにgem 'font-awesome-sass', '~> 5.12.0'を記載し、bundle installしサーバーを一度きり、再起動させました。
②application.scssに@import "font-awesome-sprockets"; @import "font-awesome";を記載しました。
③該当のHamlファイルに
= icon('fas','star')と記載してスターのアイコンを表示させました。
chromeを更新してみると、、、、表示がされていませんでした。やったこと
①GitHubの公式サイトを見て最新バージョンの確認。
→最新バージョンでした。
②gemfile.lockを確認してインストールされているか確認。
→ちゃんと記載がありました。
③準備の②の記載する順番があっているかの確認。
→公式サイトを見たが合っている。
④hamlの記載方法が間違っていたのかと思い、確認。
→複数サイトを巡って確認したが合っている。
⑤検証ツールにて確認。
→0×0となって画面上にあることが確認できた。
→CSSでfont-sizeやwidthやheightを付けてみた。
→検証ツールのelement上ではCSSがかかっていることになっているが、画面上では表示されない。
⑥他の要素が上に被って表示されないのでは?
→他の要素と被らない全く関係ないところにhamlを記載していたが、表示されず。
⑦fontawsomeのサイトで調べてはいるがが、実はstarというアイコンはないのでは?
→他のアイコンも表示されなかったです。解決方法
メンターさんに質問して解決した方法は、
stylesheetsディレクトリのapplication.cssを消したら見事表示されました!
ありがとうございます!!考察
https://www.amazon.co.jp/%E7%8F%BE%E5%A0%B4%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B-Ruby-Rails-5%E9%80%9F%E7%BF%92%E5%AE%9F%E8%B7%B5%E3%82%AC%E3%82%A4%E3%83%89-
「Ruby on rails 速習実践ガイド」(上記url)を夜な夜な学習しています。
こちらの本のbootstrapのcssの読み込まれる順番に、
①application.scssにてbootstrapのcssを読み込む定義をする
②application.scssを通じて作成されるapplication.cssでbootstrapのcssが読み込まれる。
③共通レイアウトであるapplication.html.slimでapplication.cssを読み込む。
④各画面がapplication.cssを読み込む。
という感じで記載されています。要約が間違っていたら申し訳ありません。上記の②でapplication.scssを通じて作成されるはずのapplication.cssを読み込むはずが、私はapplication.cssを削除していなかったので、こちらを読みこまれてしまった。
しかし削除していないapplication.cssにはfontawsomeが記載されていないので、画面上で表示されなかったのではないかと考えました。まとめ
作成している途中で他のcssは読み込めているので、問題ないからapplication.cssは最後に消せば良いやと考えていましたが、最初に消さないと影響を受けてしまう場合があると学びました。
考察はあくまで私が考えた考察なので合ってるとは限りません。
なので正解を知っている方がいらっしゃればご教授いただけると有難いです。
- 投稿日:2020-05-23T13:09:31+09:00
Passing Variable to Partial - undefined local variable or method error 2つの解決法
1: render partial 'my_partial' と書いているか、render 'my_partial' と書いているか確認する
呼び出し方に違いがあります
<%= render 'my_partial', :locals => {:class_Name => "Science", :y => 36} %> <% render partial: 'my_partial', :locals => {:class_Name => 'Science', :y => 36 } %>2: 定義されているか確認する
# Rails4以降 <% if defined?(local_var) do %> # Rails3 <% if local_assigns[:local_var].present? do %>自分の場合、partialに書いた変数を使わない場合があり、
if defined?(local_var)
を書いたら解決した。defined?で確認して、local_varをそのまま使わないようにした。参考
自分はqiitaの記事のおかげで解決しました
https://qiita.com/shinichinomura/items/1921027fc28279ce54e0
https://stackoverflow.com/questions/16229470/passing-variable-to-partial-undefined-local-variable-or-method-error/21651848#21651848
- 投稿日:2020-05-23T09:56:42+09:00
Rails DM機能と非同期でメッセージの送受信を実装する方法
なにこれ
DM機能とメッセージを非同期で送受信するための実装手順をまとめました。
備忘録と言語化するために書いてます!前提条件
・Usersテーブルが作成済みであること
##全体の流れ
1,テーブル作成
2,モデルでアソシエーションを組む
3,usersコントローラーを編集テーブル設計
今回は合計4つのテーブルが必要になります。
ユーザー情報を保存するUsersテーブル
誰がDMのどのルームに参加したかuser_idとroom_idを管理するEntriesテーブル
ルーム自体を管理するroomsテーブル
どのルームで誰がメッセージを送ったのか管理するmessagesテーブル
文字だけの説明じゃなくて、
ご自身でテーブルを紙に書き出して見ると分かりやすいと思います!Usersテーブル
今回の場合はnicknameカラムとavatarカラムを追加してますが、
ここは個人で編集して下さい。
avatarは必要なさそうならコメントアウトすることをオススメします。マイグレーションファイルdef change add_column :users, :nickname, :string add_column :users, :avatar, :string endEntriesテーブル
どのユーザーがどのルームに入ったかを管理するのがEntriesテーブルの役割です!
user_idとroom_idに外部キー制約をかけます。マイグレーションファイルclass CreateEntries < ActiveRecord::Migration[5.2] def change create_table :entries do |t| t.references :user, foreign_key: true t.references :room, foreign_key: true t.timestamps end end endRoomsテーブル
どのルームか管理するのがRoomsテーブルの役割です。
このテーブルにカラムは必要ありません!マイグレーションファイルclass CreateRooms < ActiveRecord::Migration[5.2] def change create_table :rooms do |t| t.timestamps end end endMessagesテーブル
誰がどのルームでどんなメッセージを送ったのか管理するのがMessagesテーブルの役割!
Entriesテーブルと同じくuser_idとroom_idに外部キー制約をかけます。マイグレーションファイルclass CreateMessages < ActiveRecord::Migration[5.2] def change create_table :messages do |t| t.references :user, foreign_key: true t.references :room, foreign_key: true t.text :message, null: false t.timestamps end end endアソシエーションを組む
userモデル
1人のユーザーは複数のメッセージ、エントリー、ルームに加入してるので
全てhas_manyになります。
has_many :rooms, through: :entries
は、DMしてるユーザー一覧を作るために必要です。
entriesテーブルを経由してroomsの情報をかき集めてきます!user.rbhas_many :messages has_many :entries has_many :rooms, through: :entriesentryモデル
ユーザーの情報とルームはレコードごとに被ることはないように一意の制約をかけます。
Entriesテーブル使用中のレコードの一例)
Entry_id[0] user_id[1] room_id[0]
Entry_id[1] user_id[3] room_id[0]entry.rbbelongs_to :user belongs_to :room validates :room_id, uniqueness: { scope: :user_id }roomモデル
Roomsは複数のメッセージとエントリー情報を持つのでhas_manyになります!
has_many :users, through: :entries
はuserモデルにもあった、一覧表示をするのに必要です。room.rbhas_many :messages has_many :entries has_many :users, through: :entriesmessageモデル
message.rbbelongs_to :user belongs_to :room validates :message, presence: true#ルーティングの設定
全体の流れ
1,ユーザーの詳細ページからDMルームに飛ぶ
2,ルームは参加と一覧表示ができる
3,メッセージは投稿、編集、削除ができるroutes.rbresources :users, only: [:show] resources :rooms, only: [:index, :create:, :show] resources :messages, only: [:create, :edit, :update, :destroy]usersコントローラーの編集
以下の記述になります。上から順番に区切って説明します。
users_controller.rbdef show @user = User.find(params[:id]) if user_signed_in? @currentUserEntry = Entry.where(user_id: current_user.id) @userEntry = Entry.where(user_id: @user.id) unless @user.id == current_user.id @currentUserEntry.each do |cu| @userEntry.each do |u| if cu.room_id == u.room_id @haveRoom = true @roomId = cu.room_id end end end unless @haveRoom @room = Room.new @entry = Entry.new end end end endusers_controller.rb@user = User.find(params[:id]) if user_signed_in? @currentUserEntry = Entry.where(user_id: current_user.id) @userEntry = Entry.where(user_id: @user.id) unless @user.id == current_user.id
@user = User.find(params[:id])
でパラメーターから送られたuser_idを@userに代入
if user_signed_in?
でサインイン済みか判断。
@currentUserEntry=Entry.where(user_id: current_user.id)
でcurrent_userが
既にルームに参加してるか判断。
@userEntry=Entry.where(user_id: @user.id)
でユーザー詳細ページの表示しているユーザーがルームに参加してるか判断。
unless @user.id == current_user.id
で@userとcurrent_userが違うユーザーであることを確認。ここまで理解できてますか?
やってる内容は至ってシンプルです。
current_userと@userが部屋に入ってるかの情報を出してます。
次の内容が少し複雑ですが、シンプルに考えてみてほしいです!@currentUserEntry.each do |cu| @userEntry.each do |u| if cu.room_id == u.room_id @haveRoom = true @roomId = cu.room_id end end end
@currentUserEntry.each do |cu|
でcurrent_userが参加してる全てのルームidを出力します。
@userEntry.each do |u|
で@userが参加してる全てのルームidを出力します!
if cu.room_id == u.room_id
でcurrent_userの参加してるroom_idと@userのroom_idで一致するものがあるのか判断します。画像の例を出しておきます!
このテーブルの中に一致する内容があったらそこで処理が終わります。
既にルームが作成されてるので。
もし作成されてないなら、新規にルームを作成する処理に分岐します!もし部屋が作成されてなかったら、以下の処理をします。
@haveRoom = true
を代入
@roomId = cu.room_id
を代入します。
@haveRoomは既にルームがあるよ。という意味の変数です。
@roomIdは後ほど記述しますが、ルームにアクセスするための変数です。users_controller.rbunless @haveRoom @room = Room.new @entry = Entry.new end@habeRoomに値がなかったら、新規作成するための変数を作成します。
form_forで@roomと@entryのの情報を送るためです。ビューの編集
次はビューの編集をします。ここから必要な部分の一部抜粋になります
ユーザー詳細ページからDMルームに飛ぶための部分テンプレートを用意してrenderします/users/show.html.haml= render "contact"DMルームに飛ぶための記述です。
こちらも区切って解説します!/users/_contact.html.haml- if user_signed_in? - unless @user.id == current_user.id - if @haveRoom == true = link_to "ダイレクトメッセージ", room_path(@roomId) - else = form_with(model:@room, local: true) do |f| = fields_for @entry do |e| = e.hidden_field :user_id, value: @user.id = f.submit "ダイレクトメッセージ"/users/_contact.html.haml- if user_signed_in? - unless @user.id == current_user.id - if @haveRoom == true = link_to "ダイレクトメッセージ", room_path(@roomId)1行目でサインインしてるか確認
2行目で@userとcurrent_userのidが違うことを確認
3行目でusers_controllerのshowアクションで作成した変数@haveRoomがtrueであることを確認。
4行目にルームに参加するためのリンクを表示してます!/users/_contact.html.haml- if @haveRoom == true = link_to "ダイレクトメッセージ", room_path(@roomId) - else = form_with(model:@room, local: true) do |f| = fields_for @entry do |e| = e.hidden_field :user_id, value: @user.id = f.submit "ダイレクトメッセージ"@haveRoomがfalseだった場合(値が入ってない場合)の流れです。
form_withでデータを送信します。モデルは@roomで、local: trueで画面遷移するように命令します。
rooms_controllerのcreateアクションを呼び出すためにこの記述をしてます!
rooms_controllerは次に解説します。fields_forという珍しいメソッドが出てきました!
簡単に言うと、
form_with内で異なるモデルを編集することができるようになるもの。
今回はroomテーブルとentryテーブルが1対多の関係の時に
entryテーブルに値を保存できるようにするために使用した。つまり、ここでやりたいことはEntriesテーブルにuser_idとroom_idを追加したい!!
そういうことです。roomsコントローラーの編集
全体の記述は以下のようになります。上から順番に区切って解説します。
rooms_controller.rbclass RoomsController < ApplicationController before_action :authenticate_user! def index @rooms = current_user.rooms.includes(:messages).order("messages.created_at desc") end def create @room = Room.create @joinCurrentUser = Entry.create(user_id: current_user.id, room_id: @room.id) @joinUser = Entry.create(join_room_params) @first_message = @room.messages.create(user_id: current_user.id, message: "初めまして!") redirect_to room_path(@room.id) end def show @room = Room.find(params[:id]) if Entry.where(user_id: current_user.id, room_id: @room.id).present? @messages = @room.messages.includes(:user).order("created_at asc") @message = Message.new @entries = @room.entries else redirect_back(fallback_location: root_path) end end private def join_room_params params.require(:entry).permit(:user_id, :room_id).merge(room_id: @room.id) end enddef indexは参加中のDMルームの一覧表示をするための記述です。
rooms_controller.rbdef create @room = Room.create @joinCurrentUser = Entry.create(user_id: current_user.id, room_id: @room.id) @joinUser = Entry.create(join_room_params) @first_message = @room.messages.create(user_id: current_user.id, message: "初めまして!") redirect_to room_path(@room.id) end変数@roomを作成。
変数@joinCurrentUserでEntriesテーブルにuser_id(current_user.id)と参加するroom_idの情報を入れます。
変数@joinUserで参加するユーザー(今回の場合だと/users/showで表示していたuser_id)の情報を入れます。
細かい記述は省略します!
@first_messageは初めの一言を自動的に入力させてます。
作成したroom_idにリダイレクトさせます。rooms_controller.rbdef show @room = Room.find(params[:id]) if Entry.where(user_id: current_user.id, room_id: @room.id).present? @messages = @room.messages.includes(:user).order("created_at asc") @message = Message.new @entries = @room.entries else redirect_back(fallback_location: root_path) end end@roomにパラメーターから送られたroom_idを代入
3行目でEntriesテーブルに情報があるか確認
4行目で@roomの全てのメッセージを取得
5行目で新しくメッセージを作成できるようにする@messageを定義
@entriesに@roomに参加してる(Entriesテーブルでヒットした情報、またはエントリーしてる)ユーザーを取得次はルームのビューを作成します!
/rooms/show/1/.html.haml.div#chat_area = render "chat_area" .div#chat_form = render "chat_form"チャット全体表示と投稿フォームの部分テンプレートを表示してます!
/rooms/_chat_area.html.haml.left-button %h4.rooms-title 気になる同士 - @entries.each do |e| .user-name %strong = image_tag "#{e.user.avatar}", class:"avatar" %a.rooms-user-link{href: "/users/#{e.user.id}"} = e.user.nickname さん %hr .chats .chat__scroll - if @messages.present? - @messages.each do |m| %div{id: "message_#{m.id}"} .chatbox .chat-face1 = image_tag "#{m.user.avatar}", class:"avatar" .chat-hukidashi = m.user.nickname %br = m.message %br = m.created_at.strftime("%Y-%m-%d %H:%M") = form_with(model: @message, url: edit_message_path(m.id), remote: true, method: :get) do |f| = f.hidden_field :room_id, value: @room.id = f.submit "編集" = form_with(model: @message, url: message_path(m.id), remote: true, method: :delete) do |f| = f.hidden_field :room_id, value: @room.id = f.submit "削除"上から区切って説明します!
/rooms/_chat_area.html.haml.left-button %h4.rooms-title 気になる同士 - @entries.each do |e| .user-name %strong = image_tag "#{e.user.avatar}", class:"avatar" %a.rooms-user-link{href: "/users/#{e.user.id}"} = e.user.nickname さん先程@entriesに代入したユーザー情報を表示させてます。
/rooms/_chat_area.html.haml.chats .chat__scroll - if @messages.present? - @messages.each do |m| %div{id: "message_#{m.id}"} .chatbox .chat-face1 = image_tag "#{m.user.avatar}", class:"avatar" .chat-hukidashi = m.user.nickname %br = m.message %br = m.created_at.strftime("%Y-%m-%d %H:%M") = form_with(model: @message, url: edit_message_path(m.id), remote: true, method: :get) do |f| = f.hidden_field :room_id, value: @room.id = f.submit "編集" = form_with(model: @message, url: message_path(m.id), remote: true, method: :delete) do |f| = f.hidden_field :room_id, value: @room.id = f.submit "削除"全てのメッセージを表示してます。
form_withの編集と削除の部分だけ解説します!
メッセージを作成したいのでmessages_controllerに情報を送る必要があります。
よって、modelは@messageになります。
editアクションを発火させるパスを記述。
remote: trueでJS形式
モデルとurlを指定してるのでメソッドを記述
= f.hidden_field :room_id, value: @room.id
で現在表示してるroom_idを送ってあげます。この情報がないと、どのルームでやり取りしてるのかRailsは理解してくれません!残り3割ぐらいです!
messages_controller.rbclass MessagesController < ApplicationController before_action :set_room, only: [:create, :edit, :update, :destroy] before_action :set_message, only: [:edit, :update, :destroy] def create if Entry.where(user_id: current_user.id, room_id: @room.id) @message = Message.create(message_params) if @message.save @message = Message.new gets_entries_all_messages end else flash[:alert] = "メッセージの送信に失敗しました" end end def edit end def update if @message.update(message_params) gets_entries_all_messages end end def destroy if @message.destroy gets_entries_all_messages end end private def set_room @room = Room.find(params[:message][:room_id]) end def set_message @message = Message.find(params[:id]) end def gets_entries_all_messages @messages = @room.messages.includes(:user).order("created_at asc") @entries = @room.entries end def message_params params.require(:message).permit(:user_id, :message, :room_id).merge(user_id: current_user.id) end end先にprivate以下から解説します。
set_roomメソッドは先程form_withで送られたroom_idの情報を取得します。
set_messageメソッドも上と同様です
before_actionでset_roomメソッドとset_messageメソッドを適用してます。gets_all_messagesメソッドでメッセージを全件取得するのと、
@entriesでルームに参加してる人たちを改めて表示してます。
非同期通信する場合はこの記述が必ず必要です!
毎回データの情報を送ってあげないと、一覧表示できないので。
message_paramsメソッドは新しく送られたメッセージを取得してます。次はアクション4つを簡単に説明します。
messages_controller.rbdef create if Entry.where(user_id: current_user.id, room_id: @room.id) @message = Message.create(message_params) if @message.save @message = Message.new gets_entries_all_messages end else flash[:alert] = "メッセージの送信に失敗しました" end end def edit end def update if @message.update(message_params) gets_entries_all_messages end end def destroy if @message.destroy gets_entries_all_messages end endcreateアクションを主に解説します。
createの中のif文はセキュリティのために書いてます。でもぶっちゃけ無くてもいいです笑
@messageをmessage_paramsメソッドを使用してcreateします
メッセージが保存できたら、新しいメッセージを作るための@messageを作成するのと、
gets_entries_all_messagesメソッドでルーム参加者を表示する@entriesと
メッセージ一覧を持ってきます。updateとdestroyアクションは特に書くこともないので省略します。
コメント投稿機能と処理の流れは同じです。
違いは@entriesを取得する必要があるぐらいですね後はcreate,edit,update,destroyの各アクションのjs.hamlファイルを作成すれば完成です!
今回はあえて内容は載せません。
ヒントはコメントの非同期投稿機能と全く同じ仕組みです。
自分の[過去の記事][https://qiita.com/kaito_program/items/ef348e170c64d224dd06]
の終盤で詳しく解説してるので、そちらをご参考にして、
ご自身で考えていただければと思います!ここまでご覧いただきありがとうございます!
- 投稿日:2020-05-23T09:24:14+09:00
jQueryでパスワードの表示と非表示を切り替える
はじめに
railsでアプリケーションを行っている初学者のいりふねです。
今回、ユーザー登録画面のパスワード入力欄の表示と非表示を切り替える実装を行いました。ネット上に「カンタン」と書かれた同記事が多くありましたが、自分の環境で実装するのは少し時間がかかりました。
そこで、アウトプット兼自分用メモとして私の環境でうまく動作した手順を書いていきます。開発環境
- Rails 5.2.4.3
- Haml 5.1.2
- jquery-rails (4.4.0)
- form_forを使用して実装しています
実現すること
ユーザー登録画面などでデフォルトで非表示になっているパスワードをcheck_boxを使用して表示と非表示を切り替えます。具体的には、以下Gifのような状態を目指します。
手順
- check_boxの設置
- password_fieldにidを付与
- jsファイルにjQueryを記述
check_boxの設置
hamlファイルにcheck_boxを記述します。今回は、説明に不要なclass名などは予め外した状態にしています。cssなどをつけるときは別途設定して下さい。
example.html.haml= form_for @user, url: user_registration_path do |f| 〜中略、password_fieldの記述などがあります〜 %p = check_box_tag :passcheck = f.label "パスワードを表示する"順番に説明します。
まず、pタグで囲ったのは、チェックボックスや「パスワードを表示する」という文字が、直上のpassword_fieldの右に回り込んでしまうためです。cssを編集することでカバーも可能でしょうが、pタグだけで解決するので、今回はこの方法を採用しました。次に「check_box_tag」の部分です。これは、関連するモデルがない時のチェックボックスの書き方になります。他のフォームの記述に揃えて「= f.check_box」を使用すると、引数として保存先モデルのカラム名の記述が必須となってしまいます。今回のチェックボックスはレコードの保存が目的ではないので、「check_box_tag」を使用しました。
参考にさせていただいた記事【初心者向け】チェックボックスの書き方あれこれ[Ruby][Rails]
同行後半の「:passcheck」は、nameとidを指定しています。検証ツールで確認すると反映されていることが分かります。idはこの後、jQueryで要素を指定するための使用します。
password_fieldにidを付与
password_fieldにjQueryで要素を指定するためのidを付与します。コードの後半部分がidを付与している箇所となります。
example.html.haml〜省略〜 = f.password_field :password, autocomplete: "off", placeholder: "7文字以上の半角英数字", id: "js-password" 〜省略〜 = f.password_field :password_confirmation, autocomplete: "off", placeholder: "もう一度入力して下さい", id: "js-password_confirmation" 〜省略〜ここで、idを同じものにしてしまうと、検証ツールのコンソール上で「idが重複しているよ!!」と黄色で警告が出てきます。ページが表示されないほどのエラーにはなりませんが、気持ち良いものではないので、今回はid名を分けて書きました。
jsファイルにjQueryを記述
jsファイルを作成し、コードを記述します。
example.js$(function() { $('#passcheck').change(function(){ if ($(this).prop('checked')) { $('#js-password').attr('type','text'); $('#js-password_confirmation').attr('type','text'); } else { $('#js-password').attr('type','password'); $('#js-password_confirmation').attr('type','password'); } }); });実は、jQueryはまだ勉強中なので、十分説明ができません。汗
参考にされていただいた記事パスワード表示時にマスキング有無を選択できるようにする方法とはいえ、自分なりに言語化してみます。
まずは、先程チェックボックスに指定したid名#passcheckで要素を取得し、changeイベントを設定します。ちなみにイベントはclickに変えても動きました。次にif文を記述し、取得した要素からcheckedプロパティの有無をpropメソッドで確認します。チェックされていれば真、それ以外なら偽という扱いになります。
if結果が真の場合、つまりチェックが入った状態であれば、フォームに入力されたパスワードの値に対して、attrメソッドを使用して属性を取り出します。チェック済みならtype属性のtextを表示、そうでなければtype属性のpasswordを表示で隠すという具合です。
今日の積み上げ
改めて、jQueryの復習が必要であると感じました。
と同時に未熟な私でも機能実装できるようにわかりやすくまとめられた記事の多さに感謝しました。今後もアウトプットを続けて同じ初学者の方のためになればと思います。
- 投稿日:2020-05-23T07:27:53+09:00
Ubuntu18.04でrmagickをインストールしようとしたらできなかった
備忘がてら・・・。
発生した問題
Gemfileに以下を追記して実行したらエラーが発生した。
gem 'rmagick'No package 'MagickCore' found ERROR: Can't install RMagick 4.1.2. Can't find the ImageMagick library or one of the dependent libraries. Check the mkmf.log file for more detailed information.MagickCore が見つからないらしい。
ImageMagick自体はインストール済みだった。
% convert --version Version: ImageMagick 6.9.7-4 Q16 x86_64 20170114 http://www.imagemagick.org解決策
ここに解決策があった
sudo apt-get install libmagickwand-devその後、
bundle install
を実行したら無事にインストールができた。
- 投稿日:2020-05-23T01:17:28+09:00
rails s で Renderedから動かなくなった。原因と解決までについて (grid-template)
エラーの原因
初めに、今回のエラーの原因と、どうやって解決したかについて書きます。
原因は cssファイルの grid-templateの記述ミスでした。
NGなcss
ここでgrid-templateの「;」の位置が最後の記述の下に来ているのが原因でした。正しくは、
のように、 grid-templateの記述を"footer"; と変更してアクセスできるようになりました。grid-templateでエラーが出ているような記事などなかったためこれだけのエラーに半日使ってしまったので、
同じようなエラーで困った人の参考になれば幸いですエラーの内容
rails sでサーバーを起動後 localhost:3000 にアクセスすると読み込み状態のままになった。
renderd ~~/~.html.erb within layouts/application
から動かず、ctrl+cも効かない状態になった。どこが原因と考えたか
htmlファイルやコントローラーにエラーがある状態なら、エラー画面が表示された。
->rubyなどの環境が原因ではない。pcの環境ではない。と考えました。
->変更したファイルの中身が原因になる。何を行ったのか
githubで新しくブランチを作成して、commitを一つずつファイルを確認した。
(git cherry-pick commit_id でcommitを参照)
cssファイルをコメントアウトした際、画面が表示されたのでcssの中身が原因だと判明。
順にコメントアウトを消して、grid-templateでエラーが出ていることが判明。
;の位置を変えて画面の表示を確認。迷走の記録
初めのうちは、エラーの原因究明の方法がわからず迷走してました。
ちなみに、以下の記事を試しましたので記録として
https://qiita.com/sakuraniumarete/items/ac07d9d56c876601748c
https://teratail.com/questions/77742
https://teratail.com/questions/97670
https://b0npu.hatenablog.com/entry/2016/04/11/032826また、今回よく使ったコマンドとしてkillコマンドを使いましたがその参考記事も
lsof -wni tcp:3000 から kill -9 killしたいPID
https://qiita.com/motty93/items/d22c1eb8f5128f8cd7f8まとめ
grid-templateの「;」は誤った位置にあってもエラーが出ない時がある。
- 投稿日:2020-05-23T00:43:11+09:00
【学習メモ】tempusdominus turbolink form_with fields_for cron migration
0.目次
1.本日の学習内容
2.次週までの学習内容1.本日の学習内容
①ナビゲーションバーが実行されないエラーの解決
【実現したいこと】
ナビゲーションバー(ハンバーガーメニュー)でボタンをクリックして、メニューを開いたり閉じたりができる状態にすること【起こったこと・試したこと】
画面遷移すると、一度開くと閉じられなくなってしまった。【原因】
teratailの似た質問から、turbolinkが邪魔している?【解決策】
部分のturbolinkを消去したところ、解決。
app/views/layouts/application.html※本当はerbだがわかりやすいようhtmlで閉じている <head> <title>Testapp</title> <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag 'application', media: 'all'%> <%= javascript_include_tag 'application' %> </head>※参照:元々のナビゲーションバー
app/views/layouts/application.html※本当はerbだがわかりやすいようhtmlで閉じている <nav class="navbar navbar-expand-lg navbar-light bg-light"> <a class="navbar-brand" href="#">TTManager</a> <button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="ナビゲーションの切替"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarNavAltMarkup"> <div class="navbar-nav"> <a class="nav-item nav-link" href="#">ホーム</a> <a class="nav-item nav-link" href="#">リマインダー設定</a> <a class="nav-item nav-link" href="#">レポートを見る</a>②フォームをクリックしてもカレンダーが表示されないエラーの解決
【実現したいこと】
Bootstrap4で、フォームにカレンダーを表示させて日付をクリックし、フォームに表示させる【起こったこと・試したこと】
「tempusdominus-bootstrap-4」を実装し、カレンダーのマークは表示はされるが実行できなかった以下は下記3つを実装
app/assets/stylesheets/application.scss@import "tempusdominus-bootstrap-4.css";app/assets/javascripts/application.js//= require moment //= require moment/ja.js //= require tempusdominus-bootstrap-4.jsapp/views/layouts/application.html※本当はerbだがわかりやすいようhtmlで閉じている <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>【調査方法】
デベロッパーツールのConsole・Sourcesで確認。
※既に修正済で、別のエラーが出ていますがみているものはこれです
【調査結果・原因】
tempusdominus実行する前にjQueryを導入する必要があるため、導入してね
→jQueryがインストールされていなかったことが原因【解決策】
https://www.sejuku.net/blog/57790
を参考にjQueryをGemifile、app/assets/javascripts/application.js、app/assets/stylesheets/application.scssに追記gem 'jquery-rails'app/assets/javascripts/application.js//= require moment //= require moment/ja.js //= require jquery #ここを追記 //= require jquery_ujs #ここを追記 //= require tempusdominus-bootstrap-4.js③1つの画面(URL)で複数のテーブルのカラムをDBに格納させる方法
【実現したいこと】
複数の親・子関係のテーブルのDBに格納するフォーム入力事項について、どうやってそれぞれ正しいテーブルに正しいカラムを格納したら良いか?【起こったこと・試したこと】
「= fields_for」があるらしいが、どうやって使えば良いのか調べても理解仕切れず、質問。slimで実装はしていたが、、、= form_with model:record, local:true do |f| = f.submit【解決策】
下記が基本1.Model,DBの状態を確認し、ちゃんと反映されているか確認する(rails db:migrate:statusなど)
2.モデルで、親子関係・リレーションが正しいか確認する
→複数形をつけるのは、今見ているマイグレーションファイルから見て、
複数の関係性があるから(逆を言えば、単一の関係であれば、単数形にすること)user.rbhas_many :recordstask.rbhas_many :records3.コントローラを確認し、「.build」を活用しながら実装
controller.rbdef new @record = Record.new #newメソッドでインスタンス変数#record入れ込む output = @record.outputs.build #入れ込んだ#recordと結合させるために「.(モデル).build」を入れ込む practice = @record.practices.build #上記と同じ要領で task = @record.tasks.build #上記と同じ要領で end ※recordモデルは、ooutput,practice,taskモデルとは親・子関係4.viewを実装
new.html.slim= form_with model:record, local:true do |f| = fields_for :output .form-group = f.text_field :output_name, class: 'form-control mb-4', id: 'output_name' [中略] = f.submit 'この内容で登録する', new_record_path, class: 'btn btn-primary'※f.submitは1番下じゃなくてもOK。ちゃんと「form_with」と「f.submit」の関係性ができていればOK
※コードはこれから実装④マイグレーションの注意事項
【状況】
モデルに新しくカラムを追加して、db:migrateしたら、「既にできています」と表示された【原因】
マイグレートは、テーブルを作成するために実行するもの(マイグレーションファイルは、その実行内容を示したファイル)であって、モデルに変更を加えただけでは反映されない!!【解決策】
①db:reset
DBの内容を全てリセットし、もう一度マイグレーションする処理②変更用のマイグレーションファイルを作成すること
・UPしている状態を、DOWNにするためのマイグレーションの処理
・変更用のマイグレーションファイルを別途作成
※ここ理解が弱いので調べて追記予定⑤その他
・デベロッパーツールのエラーの「$」は「jQuery」を指す
・リマインダーメールの実装について
→Web上で動くものではない(=クライアントの操作によるトリガーがない)ため、サーバー(インフラ)でプログラムを実行させる必要あり!つまり、最後の実装
→方法の1つが、cron
・日付のみ示すデータ型:date2.次週までの学習内容
▼マイグレーションのUP、DOWNさせる事例・対応方法のインプット・実践
▼マイグレーションの変更方法のインプット・実践(Rails速習に載ってる?user_idをTaskテーブルに追加だったかな)
▼AWSのudemy動画学習
▼レポート表示のJS実装
▼Bootstrapのpadding設定の事例・方法のインプット・実践
▼cronのインプット参照
【BOOTSTRAP4でフォーム入力の際にカレンダーから日付入力する方法】
https://its-office.jp/blog/bootstrap/2019/02/18/bs4calendar.html
【bootstrapのNavbarのハンバーガーメニューが閉じない[原因はTurbolinks]】
https://qiita.com/kota-shidara/items/964f9fe92a467cd28583
【【Rails】turbolinksを無効化する】
https://qiita.com/kota-shidara/items/964f9fe92a467cd28583
【accepts_nested_attributes_forで親子孫関係のレコードを同時作成する方法【実装例・rspecあり】】
https://shinmedia20.com/rails-relation-create
【【Rails】form_withの使い方を徹底解説!】
https://pikawaka.com/rails/form_with
【fields_forの上手な使い方】
https://qiita.com/kouuuki/items/5daf2b5f34273d8457f7
【fields_forを使った子モデルへの複数レコード保存【cocoonが便利】】
https://www.y-hakopro.com/entry/2019/08/03/234126
【cronの設定 - プログラムを指定した時間に定期的に自動的に実行する方法】
https://memorva.jp/memo/linux/cron.php
【5.5. 日付/時刻データ型 ※DB:Postgresql】
https://www.postgresql.jp/document/7.3/user/datatype-datetime.html