- 投稿日:2019-05-02T21:06:38+09:00
Vueでドロップダウンメニューを作成する
目標
下記のようなドロップダウンメニューを作成しましたので記載します。
今回はjQueryではなくVue.jsを使って作成してみました。
Rails+Vueあれば使い方はjQueryに近く、コードの記述量はjQueryよりも少なくて済む(気がする)と思っています・カテゴリ一覧をドロップダウンで表示させる
・カテゴリーの値はデータベースから取得する
・mouseoverでカテゴリの子要素を表示、mouseleaveで非表示となる
・クリックで各カテゴリページに遷移する・Rails+Vue.jsで作成する
初学者のため不備等多々あるかと思いますが、参照記事は良い記事ばかりです。
間違いやより良い記述方法があればご教示ください
https://i.gyazo.com/24baf3b1c603ee655eb45ecf4d8e4fdf.mp4①Vue.js導入
Webpackerのインストール
gem 'webpacker', github: 'rails/webpacker'bundle installyarnのインストール
brew install yarnWebpackerインストール
rails webpacker:install create config/webpacker.yml Copying webpack core config create config/webpack create config/webpack/development.js create config/webpack/environment.js create config/webpack/production.js create config/webpack/test.js Copying postcss.config.js to app root directory create postcss.config.js Copying babel.config.js to app root directory create babel.config.js Copying .browserslistrc to app root directory create .browserslistrc Creating JavaScript app source directory create app/javascript create app/javascript/packs/application.js apply /Users/sotatakahashi/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/webpacker-2387331b33c6/lib/install/binstubs.rb Copying binstubs exist bin create bin/webpack create bin/webpack-dev-server append .gitignore Installing all JavaScript dependencies [4.0.2] run yarn add @rails/webpacker from "." : ✨ Done in 7.46s. Webpacker successfully installed ? ?↑終わったら絵文字が出ます(かわいい)
Vueインストール
rails webpacker:install:vueこれでRails上でVueを使う準備はOKです!
詳しくは下記記事にて。参考記事
https://qiita.com/cohki0305/items/582c0f5ed0750e60c951
https://qiita.com/saongtx7/items/fdb77901e7fcf291e2ad②カテゴリーメニューの作成
マウスオーバーとかは後にして、とりあえず見た目の部分を作っていきます。
(CSS割愛します).category_box .genre-box %ul.genre - @parents.each do |parent| = link_to category_path(parent.id), method: :get do %li.genre_list =parent.name .next-box .sub-box %ul.sub %li.sub_list .detail-box %a %ul.detail %li.detail_list親、子、孫にそれぞれulとliを持たせて、リストを作成します。
基本はこの形を使ってドロップダウンメニューを作成していきます!③マウスオーバーで表示/非表示を切り替える
.category_box{"v-show": "menuBool","v-on:mouseleave": "hiddenMenu"} #v-showなどはvueで使われるアクション、menuの表示非表示をこちらで設定 .genre-box %ul.genre - @parents.each do |parent| = link_to category_path(parent.id), method: :get do %li.genre_list{'v-on:mouseover': "setCategoryInfo(#{parent.id})"} =parent.name .next-box{'v-show': 'categoryInfoBool',"v-on:mouseleave": "hiddenSubInfo"} #子カテゴリーのメニューの表示、非表示をこちらで設定 .sub-box %a %ul.sub %li.sub_list{"v-on:mouseover": "setSubInfo(category.id)"} {{category.name}} #マウスオーバーした時に値を取得、表示させる。{{}}には表示する内容が入り、vueで切り替えられる! .detail-box{'v-show': 'SubInfoBool'} %a{"v-for": "sub in SubInfo","v-bind:href": "'/categories/' + sub.id"} %ul.detail %li.detail_list {{sub.name}}new Vue({ el:"#app", data:{ categoryInfo:[], //子カテゴリーの情報を入れる箱 SubInfo:[], //孫カテゴリーの情報を入れる箱 menuBool: false, categoryInfoBool: false, SubInfoBool: false, //基本非表示にさせるため、falseとしておく }, methods: { setCategoryInfo(id){ axios.get(`/api/genres/${id}.json`) //axiosでapiを使う(理屈はまだわかっていません、、) .then(res => { this.categoryInfo = res.data; this.categoryInfoBool = true; }); }, setSubInfo(id){ axios.get(`/api/genres/${id}.json`) .then(res => { this.SubInfo = res.data; this.SubInfoBool = true; }); }, showMenu:function(){ return this.menuBool = true }, hiddenMenu:function(){ return this.menuBool = false; }, hiddenCategory:function(){ return this.categoryInfoBool = false; }, hiddenSubInfo:function(){ return this.SubInfoBool = false }, //viewに乗っかった時にtrue,falseを切り替える } });api/genre
show.json.jbuilderjson.array! @category_children do |category_child| json.name category_child.name json.id category_child.id end④それぞれにリンクを貼る
%header#app.header .header__top .header__top__logo = link_to root_path do %img{alt: "mercari", src: "//www-mercari-jp.akamaized.net/assets/img/common/common/logo.svg?228111635"}/ .header__top__search = form_tag(search_items_path, method: :get, class: "header__top__search__box") do %input.header__top__search__box__input{name: "keyword", placeholder: "何かお探しですか?", type: "search"}/ %button.header__top__search__box__submit{type: "submit"}= fa_icon ("search lg") %form{"accept-charset" => "utf-8", :action => "/", :method => "get"} .header__bottom .header__bottom__leftside{"v-on:mouseleave": "hiddenMenu"} .header__bottom__leftside__left_box{"v-on:mouseover": "showMenu","v-on:mouseleave": "hiddenCategory"} = link_to categories_path, class: "header__bottom__leftside__left_box__content" do = fa_icon ("list-ul") %span カテゴリから探す -# ここからドロップダウンメニュー .category_box{"v-show": "menuBool","v-on:mouseleave": "hiddenMenu"} .genre-box %ul.genre - @parents.each do |parent| = link_to category_path(parent.id), method: :get do %li.genre_list{'v-on:mouseover': "setCategoryInfo(#{parent.id})"} =parent.name .next-box{'v-show': 'categoryInfoBool',"v-on:mouseleave": "hiddenSubInfo"} .sub-box %a{"v-for": "category in categoryInfo","v-bind:href": "'/categories/' + category.id"} #v-bindでaタグにリンクを貼っつける! %ul.sub %li.sub_list{"v-on:mouseover": "setSubInfo(category.id)"} {{category.name}} .detail-box{'v-show': 'SubInfoBool'} %a{"v-for": "sub in SubInfo","v-bind:href": "'/categories/' + sub.id"} %ul.detail %li.detail_list {{sub.name}} -# ここまでドロップダウンメニュー .header__bottom__leftside__right_box = link_to '', class: "header__bottom_box__leftside__right_box__content" do = fa_icon ("tag") %span ブランドから探す .header__bottom__rightside .header__bottom__rightside__bell = link_to '', class: "header__bottom__rightside__bell__content" do = fa_icon("bell lg") %span お知らせ .header__bottom__rightside__check = link_to '', class: "header__bottom__rightside__check__content" do %span = fa_icon("check lg") %span やることリスト .header__bottom__rightside__mypage = link_to '', class: "header__bottom__rightside__mypage__content" do %img.header__bottom__rightside__mypage__content__icon{alt: "", src: "//static.mercdn.net/images/member_photo_noimage_thumb.png", width: "32"}/ %div マイページnew Vue({ el:"#app", data:{ categoryInfo:[], SubInfo:[], menuBool: false, categoryInfoBool: false, SubInfoBool: false, }, methods: { setCategoryInfo(id){ axios.get(`/api/genres/${id}.json`) .then(res => { this.categoryInfo = res.data; this.categoryInfoBool = true; }); }, setSubInfo(id){ axios.get(`/api/genres/${id}.json`) .then(res => { this.SubInfo = res.data; this.SubInfoBool = true; }); }, showMenu:function(){ return this.menuBool = true }, hiddenMenu:function(){ return this.menuBool = false; }, hiddenCategory:function(){ return this.categoryInfoBool = false; }, hiddenSubInfo:function(){ return this.SubInfoBool = false }, } });かんせい!
Vue楽しいので何か違うこともやってみたいです!コンポーネント作るとかも楽しそう参考記事
インストール〜作成までの流れ
https://qiita.com/cohki0305/items/a678b0b17c5b496c1de9
https://qiita.com/saongtx7/items/fdb77901e7fcf291e2ad
axiosの使い方
https://www.webprofessional.jp/fetching-data-third-party-api-vue-axios/
https://qiita.com/ryouzi/items/06cb0d4aa7b6527b3645
vue.jsでmouseoverした時だけ表示する方法
https://qiita.com/sukechansan/items/07a415e4e7e5ce358afc
vue.jsでのリンクの貼り方
https://qiita.com/asaokamei/items/6afa7e2f33207d041588
- 投稿日:2019-05-02T20:26:28+09:00
ログイン機能の実装
ユーザ登録機能の作成
ユーザ登録用のモデルを作る
作成カラム(名前,メール,パスワード)
rails g model User name email password_digest
- Userモデルファイル内に記述し有効化(app/model/user.rb) 同時にバリデーションも追加
has_secure_password
- bcryptをインストール(Gemfileのコメントアウトを外す)
gem 'bcrypt','~>3.1.7'$ bundle installルーティング設定
resources :users, only: [:index, :show, :new, :create]
only
で必要なアクションを指定コントローラ作成
rails g cntoroller users index show new create
index show new create
を指定するとこで必要なControllerとViewファイルを自動生成ユーザ登録画面の作成
Controller
def new @user = User.new endView
<%= form_with(model: @user, local: true) do |f| %> <%= f.label :name, "名前" %> <%= f.text_field :name, class: "form-control" %>その他のカラムも同様に作成する
ログイン機能の作成
ルーティング設定
get "login", to: "sessions#new" post "login", to: "sessions#create" delete "logout", to: #sessions#destroy"コントローラ作成
$ rails g controller sessions new create destroydef create email = [:session][:email].downcase password = [:session][:password] if login(emial, password) flash[:success] = "" redirect_to root_path else flash.now[:danger] = "" render "new" end
params[:session][:email].downcase
でフォームデータを取得できるprivate def login(email, password) @user = User.find_by(email: email) if @user && @user.authenticate(password) session[:user_id] = @user.id return true else return false end end
入力フォームと同じ
@user
に代入する。見つからなければnil
を代入する。ユーザの情報があり、そのユーザの持つパスワードと入力パスワードが一致した場合
true
扱いView作成(登録時と同様)
<%= form_with(url: login_path, scope: :session, local: true) do |f| %>
- 投稿日:2019-05-02T16:55:58+09:00
本番環境にデプロイできない時の対処法(初心者向け)
AWSで本番環境にデプロイできない時の対処法(初心者向け)
前提
AWSを利用してwebサイトを公開し、開発したものをデプロイすると本番環境に反映されないことや、サイトが閲覧できない状態(We're sorry ~ )になることがよくあるので、対処法を解説します。
開発環境
Ruby 2.3.1
Rails 5.2.2.1
AWS EC2(無料枠使用)
※一旦デプロイが完了し、自動デプロイの設定が完了している前提で解説します。
自動デプロイには、capistranoを使用しています。対処法(1) EC2のインスタンスを再起動
何度も自動デプロイを行なっていると、EC2側で変更が反映されず、場合によっては変更箇所が見れないことがあります。(ex.ユーザーログインの機能を実装したのに反映されていない等)
その場合、AWSのマネージメントコンソールから
EC2 → インスタンス → 該当のインスタンスをクリック → アクションのインスタンスの状態 → 再起動 を行います。
その後、再度ターミナルからEC2にログインし
sudo service nginx start
sudo service mysqld start
のコマンドを実行し、WEBサーバのnginxとmysqlを立ち上げます。
その後、ローカルで自動デプロイのコマンドを実行すると、アプリケーションサーバのunicornが立ち上がり、サイト上に変更が反映され、閲覧できるようになります。対処法(2) ローカルでエラーが起きていないか確認する
ローカルでは反映されないエラーが、本番ではエラーと認識されることがあります。
例えば、ECサイトを作成し商品一覧は問題なく表示されるが、商品の詳細画面にアクセスするとsyntaxエラー(hamlで記述している場合はインデント等)が起きている場合、本番ではエラーがあると認識されサイト全体が閲覧できないことがあります。
ローカルで確認し、エラーの箇所が分からなければ、サーバー側から
less log/production.log
less log/unicorn.log
のコマンドでログを確認し、エラーの箇所を特定します。
ここにsyntaxエラーやFATALと記載されている項目があれば、そのエラーを解消した上で再度デプロイします。
具体的な例としては、gemでfont-awesome-railsを使用していると、記述の方法によって、ローカルでエラーは出ないが本番ではエラーとなることがあり、このようなエラーはログを確認しなければ特定することは困難です。対処法(3) AWSの無料枠を確認する
直接的なエラーではありませんが、AWSの無料枠(メモリの容量等)を越えるとデプロイできなくなることがあります。
AWSの無料枠はあくまでお試しのような枠なので、同時に2つサイトを公開するとなると容量が足りなくなることがあり、その結果デプロイできないことや変更内容が反映されないことがあります。
また、gemもメモリに影響を与えるので、一度確認しておきましょう。
私の場合、無料枠を利用して2つ目のアプリケーションを公開し、gemのfont-awesome-sassを導入しようとしたら、ターミナルでメモリの容量が足りないというエラーが発生しました。font-awesome-sassを削除し、再度デプロイするとエラーが解消されました。以上が初歩的な本番環境で発生するエラーの解消方法です。
他にも対処法は多くありますので、参考程度にして下さい。
- 投稿日:2019-05-02T16:45:30+09:00
日時の表示変更をどうするか。
地味にポートフォリオ作成で詰まった点
・日時の表示変更をどうするか。
デフォルトではとても長ったらしく日頃あまり見ない日時表記になってしまっている。
ツイッターでこの指摘をしていただいて、この表記はかなり微妙だなと気づいたので
改善に向けて調べて見ました!↓結果、
strftime
メソッドを使って変更してみました。
created_at
や上記画像のupdated_at
の後ろに
strftime('%Y年%m月%d日 %H:%M')
をつけるだけでした。ただ、調べていて思ったのは、
今回は小規模なポートフォリオで作成したアプリなので
上記の方法でも問題ないですが
大規模なアプリの場合はいちいち表記を変更するの大変だと思うので、・
time_formats
を使う 方法が良さそうな気がします。方法としては、
まず config/initializers/time_formatsに フォーマットを指定してconfig/initializers/time_formats.rbTime::DATE_FORMATS[:ymd] = ("%Y年%m月%d日") Date::DATE_FORMATS[:ymd] = ("%Y年%m月%d日")その後、
user.created_at.to_s(:ymd)
# => 2016年10月23日
とするとフォーマットが
initializers/time_formats.rb
に集約されていい感じになるようです。今度また色々試してみたいと思います。
- 投稿日:2019-05-02T16:16:12+09:00
【Rails】ローカルにないエラーがherokuで表示された時の対処法
「ローカル環境での挙動はバッチリ、さあ herokuへアップロードだ」
と思ったら、アップロード後にはなんのヒントもないエラーページ。
オリジナルアプリケーション開発で遭遇したシチュエーションをもとに、このような時の解決法を整理します。
1.エラー画面を表示する
config/environments/production.rbconfig.consider_all_requests_local = false本番環境だとエラー画面のデフォルトが下記のように設定されています。
これではエラー内容がわかりづらいので、本番環境でもエラーを表示できるようにしましょう。エラー解決後は、速やかに戻しましょう。
config/environments/production.rbconfig.consider_all_requests_local = true2.ログを確認する
heroku logs -t #-tでも-tailでもOKログを確認すると、
- 本番環境ではカラムが追加されていない(※
heroku rails db:migrate
で解消)などのエラー内容ががつかめるはずです。
- 投稿日:2019-05-02T15:25:21+09:00
臭うコードを消臭しよう Railsのリファクタリング
こんにちは。じゅんといいます。(ついったー)
普段仕事でRubyを使っています。フレームワークはRailsです。ゼロイチのフェーズのプロジェクトに関わっていると、スピードをとにかく重視することがあります。
その結果、これまでに私は数々の「臭うコード」を生み出してしまいました。スピードを重視していたとしても、「臭うコード」を生み出さずクリーンなコードを生み出し続ける優秀なエンジニアもいます。
そう。私はまだまだエンジニアとして未熟なので「臭うコード」を生み出してしまっていたのです。エンジニアになって一年が経とうとしているので、いつまでも未熟とは言っていられません。
これを機にこれまで私が生み出してきた「臭うコード」を消臭していきたいと思います。(随時追加していきます)
1, evalを使って消臭(リファクタリング)してみる
元々のコードがこちら。(メソッド名や変数名などは変えてあります。)
profile.rbclass Profile < ApplicationRecord #色ごとの勝利数を変更するメソッド #red_win_num, blue_win_num, white_win_num, green_win_numというカラムが存在するよ def change_win_num(color) if color == "red" self.red_win_num += 1 elsif color == "blue" self.blue_win_num += 1 elsif color == "white" self.white_win_num += 1 elsif color == "green" self.green_win_num += 1 end end endこのchange_win_numというインスタンスメソッドですが、ほとんど同じ処理を繰り返し書いています。
色(color)によって処理が違うならまだしも、今回の場合やっている処理は「その色のwin_numを+1する」だけで全て共通です。
このように、if文で分岐しているのに繰り返し同じ処理を記述している場合は、evalを使えないか検討してみましょう。evalの詳しい説明はリファレンスをみていただくとして、簡単に説明すると、evalは第一引数として渡した文字列をRubyプログラムとして実行してくれます。
以下の例をみていただければevalがやっていることが分かると思います。
(文字列 "p message" をevalに渡すとそれを実行してくれるという例)evalの例message = "I love Ruby" eval "p message" # => "I love Ruby"今回の「臭うコード」にはぴったりそうです。
早速evalを使って先ほどのコードをリファクタリングしていきます。profile.rbclass Profile < ApplicationRecord def change_win_num(color) eval "self.#{color}_win_num += 1" end endめちゃくちゃスッキリしましたね。
colorをいちいちベタがきしていたらそこでバグを埋め込んでしまう可能性もありますが、これなら安心です。万が一他の色が増えたとしても、このメソッドを変更する必要はありません。(若干の懸念点としては、カラムに存在しないcolorをこのメソッドの引数として渡された場合、エラーが起きてしまいます。)
(また、そもそもProfileに色ごとの勝利数のカラムを持たせていることへの懸念も残ります。このままだと色が増えて行ったらそれだけカラムを増やさないといけなくなってしまいます。それを考慮すると、「profile_id」と「color」と「win_num」の3つのカラムをもつ別テーブルを作った方がよりいい気がします。が今回はevalを使ったリファクタリングのみで一旦やめておきます。)
戻り値が複数のものを1つにしてみよう
Rubyでは、戻り値を複数持つことができます。
この場合、メソッドの呼び出し側でも複数の受け皿(変数)を用意しておく必要があります。class Sample < ApplicationRecord #2つの数字を渡すと、足し算、引き算、掛け算、割り算した値を返してくれるクラスメソッド #本来それぞれは別のメソッドにすべきだが、複数の戻り値をもつメソッドの例として無理やり1つのメソッドにしました def self.calculate(num_1, num_2) add_result = num_1 + num_2 #足し算 substract_result = num_1 - num_2 #引き算 multiple_result = num_1 * num_2 #掛け算 divide_result = num_1 / num_2 #割り算 #複数の結果をまとめて返す return add_result, substrac_result, multiple_result, divide_result end end呼び出す方はこんな感じで呼び出せます。
add, substract, multiple, divide = Sample.calculate(30, 10) p add #=> 40 p substract #=> 20 p multiple #=> 300 p divide #=> 3今回の例だと4つの戻り値があります。これがさらに増えると、メソッドの呼び出し元のコードがかなり汚くなります。
呼び出すたびに、その戻り値分の受け皿を用意する必要があるからです。また、戻り値の順番も気にしないといけなくなるので、これも厄介です。
今回の例だと、1つ目の戻り値が足し算の結果で、2つ目が引き算で、3つ目が掛け算で、4つ目が割り算ということを呼び出し元が認識しておく必要があります。これを間違えると事故が起きる可能性があります。これらの問題を解消する為に、戻り値を1つにしてみましょう。
class Sample < ApplicationRecord def self.calculate(num_1, num_2) add_result = num_1 + num_2 #足し算 substract_result = num_1 - num_2 #引き算 multiple_result = num_1 * num_2 #掛け算 divide_result = num_1 / num_2 #割り算 calculated_result = { add: add_result, substract: substract_result, multiple: multiple_result, divide: divide_result } return calculated_result end endやっていることは一緒ですが、結果的に戻り値がcalculated_resultという1つのハッシュだけになりました。
呼び出し元を見てみましょう。
data = Sample.calculate(30, 10) p data[:add] #=> 40 p data[:substract] #=> 20 p data[:multiple] #=> 300 p data[:divide] #=> 3戻り値が1つなので、呼び出す際にかなりスッキリしました。また、戻り値の順番を機にする必要もなくなりました。
これで多少は消臭(リファクタリング)できたかと思います。今後随時追加していきます(週に一個のペースで追加していく予定です)
- 投稿日:2019-05-02T14:41:50+09:00
連番のインスタンス変数をinstance_variable_getメソッドでDRYにする
DRY後
- コントローラー側で
@day_1
と@day_2
の2つのインスタンス変数をセットしているとします。- 下のコードでは
_day.html.erb
パーシャルを呼び出していますが、こう書くことで動的に生成したインスタンス変数名を指定できます。schedule.html.erb<% 2.times do |n| %> ...中略(Rubyとは直接関係のないHTML、CSSの記述など)... <% day_i = "@day_#{n + 1}" %> <%= render 'day', day: instance_variable_get(day_i) %> <% end %>DRY前
- 下記のように
render
メソッドを並べても動きますが、中略にした部分も並べないといけませんので、やはりDRYにした方が良い感じですね。schedule.html.erb...中略(Rubyとは直接関係のないHTML、CSSの記述など)... <%= render 'day', day: @day_1 %> ...中略(Rubyとは直接関係のないHTML、CSSの記述など)... <%= render 'day', day: @day_2 %>参考
- 投稿日:2019-05-02T14:41:50+09:00
連番のインスタンス変数をinstance_variable_getメソッドでDRYにしようとしたけど用途が違った
追記:DRY後(最終形)
@znz さんにコメントでご指摘をいただきました。ありがとうございます。
結局instance_variable_get
メソッドを使うより普通に配列でやるべきでした。
以下のように書けました。schedule_controller.rb# day_1,day_2 に値をセットする過程は省略 @days = [day_1,day_2]schedule.html.erb<% @days.each do |day| %> ...中略(Rubyとは直接関係のないHTML、CSSの記述など)... <%= render 'day', day: day %> <% end %>DRY後(一回目)
- コントローラー側で
@day_1
と@day_2
の2つのインスタンス変数をセットしているとします。- 下のコードでは
_day.html.erb
パーシャルを呼び出していますが、こう書くことで動的に生成したインスタンス変数名を指定できます。schedule.html.erb<% 2.times do |n| %> ...中略(Rubyとは直接関係のないHTML、CSSの記述など)... <% day_i = "@day_#{n + 1}" %> <%= render 'day', day: instance_variable_get(day_i) %> <% end %>DRY前
- 下記のように
render
メソッドを並べても動きますが、中略にした部分も並べないといけませんので、やはりDRYにした方が良い感じですね。schedule.html.erb...中略(Rubyとは直接関係のないHTML、CSSの記述など)... <%= render 'day', day: @day_1 %> ...中略(Rubyとは直接関係のないHTML、CSSの記述など)... <%= render 'day', day: @day_2 %>参考
- 投稿日:2019-05-02T14:24:58+09:00
Railsで多対多のリレーションを作るときにはまったこと
概要
Railsで多対多モデルのリレーションを貼る際にハマったことを記載しておきます。
環境
- Ruby 2.5.1
- Rails 5.2.3
モデル
下記のようなモデルを作るとします。
+------------+ +------------+ | PostTag | | Post | +------------+ +------------+ +------------+ 1 * | id | | Tag | | id |--------| post_id | * 1 +------------+ | content | | tag_id |--------| id | +------------+ +------------+ | name | +------------+ハマった内容
通常はPostからTag(その逆も)を参照できるように以下のようなリレーションを定義すると思います。
class Post < ApplicationRecord has_many :post_tags has_many :tags, through: :post_tags endclass PostTag < ApplicationRecord belongs_to :post belongs_to :tag endclass Tag < ApplicationRecord has_many :post_tags has_many :posts, through: :post_tags endこの際、上記のような記述順ではうまくいきますが、Postモデル、Tagモデルの
has_many
の順番が逆の場合は、上記の環境ではうまくいきませんでした。class Post < ApplicationRecord has_many :tags, through: :post_tags has_many :post_tags end多対多で検索すると下記のような順番で記述している例もたくさんあるのですが、Rails5になって厳しくなったんでしょうかね。
ActiveRecord::HasManyThroughOrderError
エラーが出てしまうようです。[1] pry(main)> Post.find(1).tags Post Load (0.4ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 1 LIMIT 1 ActiveRecord::HasManyThroughOrderError: Cannot have a has_many :through association 'Post#tags' which goes through 'Post#post_tags' before the through association is defined. from /mnt/e/Development/gunplus/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.3/lib/active_record/reflection.rb:943:in `check_validity!'
- 投稿日:2019-05-02T10:54:12+09:00
[Rails] select, optionでinclude_blank(空白)とselectedを同時に設定する方法
select, option使用時に
include_blank(空白)
とselected(初期値)
を
同時に設定したかったができなかった。しかし、以下のように設定したら解決した。
<%= select_tag なにか, options_for_select(値, selected: 値), include_blank: true, class: "example" %>
options_for_select
設定時は、
selected
:options_for_selectの第2引数
include_blank
:select_tagの第3引数
に設定しなければならない。
それまでは、
selected
もselect_tagの第3引数に設定していた。
おわり
- 投稿日:2019-05-02T10:44:27+09:00
jsonapi-resources で nil を返したい
やりたいこと
Rails で jsonapi-resources を用いて REST API を構築した場合 controller-resource-model がそれぞれ密接に連携した状態になるため(まぁ json api specification 準拠だしそういう趣旨のライブラリだという前提はあるんですが)、以下のようなかたちで
nil
を返そうとするとInternal Server Error: undefined method 'id' for nil:NilClass
が出てしまう。if something_wrong? jsonapi_render json: nil, status: 500 and return endこれは返却しようとしている
json:
データをjsonapi-resources
が再構築する際、json:
データがモデルのインスタンスであることを期待されているにもかかわらず nil になってしまっていることから.id
メソッドが参照できすエラーとなるかたちだ。方法
jsonapi-resources のコントローラで
nil
を返したい場合は、if something_wrong? jsonapi_render_errors status: 500 and return endとしてやる。こうすると
500 Internal Server Error
かつnil
のデータが返される。この
jsonapi_render_errors
は依存ライブラリである jsonapi-utilsのヘルパメソッド となっているため、jsonapi-resources の API ドキュメントには掲載されていない。ここで挙げた例の他に、例えば
Bad Request
の返送なども jsonapi-utils で実装されているので、必要な場合は参考にされたい。
- 投稿日:2019-05-02T03:21:42+09:00
【10日間でポートフォリオ作成に挑戦】5日目:CKEditorへ画像アップロード機能を追加
概要
今回は、2019年のGW期間(10日間)を全て費やして取り組む
ポートフォリオの製作過程
を
取りまとめた内容を投稿させて頂きます。(投稿は毎日行う予定)全体通した取り組みの詳細については、前回までの記事をご参照ください。
【10日間でポートフォリオ作成に挑戦】1日目:要件定義〜記事投稿のCRUD
【10日間でポートフォリオ作成に挑戦】2日目:アクセス制限〜コメントのCRUD機能
【10日間でポートフォリオ作成に挑戦】3日目:ページネーション~CKEditorの導入
【10日間でポートフォリオ作成に挑戦】4日目:テーブル分割〜CKEditorのフォームへの反映今日一日の作業内容
ここからは、今日1日で取り組んだ作業内容をご説明します。
CKEditorへ画像アップロード機能を追加
先日実装したCKEditorで、画像を利用した編集が出来る様に
画像アップロード機能を実装しました。画像アップロードについては
shrine
をベースに下記のgemを導入しています。gem 'shrine' gem 'image_processing' gem 'mini_magick'最終的に出来上がったものが、こちらです。
'shrine'は初めて利用したという事もあり、かなり苦労しました。
その辺りのエピソードは、「今日の失敗」にてご紹介します。テストコードの記述
テストについては、
単体テスト(モデル・コントローラー)と統合テストの
両方を書いて行きます。テストには、
Rspec
を利用しています。$ rails g rspec:installまだ全てのテストを描ききれていませんが、
参考にPostモデルのテストコードを掲載しておきます。spec/models/post_spec.rbrequire 'rails_helper' RSpec.describe Post, type: :model do describe '#create' do let!(:user) { create(:user) } context 'can save' do it 'is valid with title' do expect(build(:post, user_id: 1)).to be_valid end end context 'can not save' do it 'is invalid without title' do messages = build(:post, title: nil, user_id: 1) messages.valid? expect(messages.errors[:title]).to include('を入力してください') end it 'is invaid without user_id' do messages = build(:post, user_id: nil) messages.valid? expect(messages.errors[:user]).to include('を入力してください') end end end endまだまだ触り程度ですが、
やっぱりテストコードはいいですね!!
ゲームみたいで書いていて楽しいです!!!!今日の失敗
ここからは今日の失敗をまとめて行きます。
CKEditorへ画像アップロード機能追加で手間取る
401エラー
一通りコードを書いて、実際にアップロードを試そうとすると、
下記の様なエラーが発生しました。ターミナルを見てみると、下記の様な処理結果が出ていました。
Started POST "/images?image_relation=DescriptionImages&csrf_token=" for ::1 at 2019-05-01 13:25:52 +0900 Processing by ImagesController#create as */* Parameters: {"upload"=>#<ActionDispatch::Http::UploadedFile:0x00007fbcffb81848 @tempfile=#<Tempfile:/var/folders/m0/h9_nmc1n5lg1ghdq5s4_4jc40000gn/T/RackMultipart20190501-49423-16e02tc.png>, @original_filename="pop_reiwa_hatsu.png", @content_type="image/png", @headers="Content-Disposition: form-data; name=\"upload\"; filename=\"pop_reiwa_hatsu.png\"\r\nContent-Type: image/png\r\n">, "ckCsrfToken"=>"xJ5fKnWr0dw2IepjwvbFBWvHbJ7o3TqH9qpYKyK7", "image_relation"=>"DescriptionImages", "csrf_token"=>""} Completed 401 Unauthorized in 1ms (ActiveRecord: 0.0ms)
401
エラーは認証に失敗している事を表しますが、
慌ててページを更新すると、ログイン状態が解除されていました。そういえば、初期データを投入し直した(bin/setup)ので、
その時にログインが解除されていたのですが、ページを更新していなかったので、
それに気づかなかったという間抜けなエラーです・・・image_processingの導入漏れ
次にログインしてアップロードを試しましたが、同じ動きになりました。
再度ターミナルを見ると、下記の様なエラーでした。Completed 500 Internal Server Error in 16ms (ActiveRecord: 0.4ms) LoadError (cannot load such file -- image_processing/mini_magick): app/uploaders/image_uploader.rb:1:in `<top (required)>' app/models/image.rb:2:in `<class:Image>' app/models/image.rb:1:in `<top (required)>' app/controllers/images_controller.rb:6:in `create'また、gemの導入漏れです・・・何回やったら気が済むのでしょう・・・
imageableの追加漏れ
次に発生したエラーは下記の内容です。
ちなみにエラーが発生した箇所は、
下記のコントローラーのimage.save
です。controllers/images_controller.rbdef create image = Image.new( image: params[:upload], image_relation: params[:image_relation] ) if image.save render json: { url: image.image[:standard].url, uploaded: true } else render json: { error: { message: image.errors.full_messages }, uploaded: false } end end今回、Imageテーブルは、Postの画像以外にも、Userのアイコンなどでも
利用する可能性がある事から、ポリモーフィック関連付けを使用しています。なので、生成したImageインスタンスには、
imageable_type
とimageable_id
のデータを持たせる必要があるのですが、
pryで生成されたインスタンスの中身を確認してみるとnil
になっていました。正直、このポリモーフィック関連付けの仕組みを理解しておらず、
本来はPostDescriptionの情報を持たせるべきなのでしょうが
新しく記事を投稿する際など、idが確定していない状態で、
どの様に紐づければ良いのか、判断が付きませんでした・・・なので、一旦
current_user
の情報を持たせることにしました。controllers/images_controller.rbdef create image = current_user.images.new( image: params[:upload], image_relation: params[:image_relation] ) if image.save (中略)とりあえずUserのアイコンは、現時点で実装の予定は無いので、
一先ずはこれで動作する状態に持って行きたいと考えています。Turbolinksの干渉
下記の通り、新しく編集ページを開くと、CKEditorが表示されず、
再読み込みを行うと表示されるというバグが発生しました。コンソールのエラーを確認すると、どうやらTurbolinksが干渉している様でした。
なので、一旦Turbolinksは外しています。
明日の予定
- 現時点で実装済みの機能のテストコードを全て記述
- 検索機能(ransack)の実装
- いいね機能実装の下準備
ransack
は使った事が無いのですが、
またそれで予定が狂いそうな予感がしています・・・
見積もりスキルをきちんと伸ばしたいですね。※追記:六日目を投稿しました
【10日間でポートフォリオ作成に挑戦】6日目:テストコードの実装おまけ
最後になりますが、現在、私は下記の目標を立てて学習に取り組んでいます。
- 3年間で「10,000時間」をプログラミングに費やす
- その間、毎日ブログの投稿を行う
Twitterでは、その過程で学んだ事などを発信しています。
もし宜しければフォローしてみてください。
- 投稿日:2019-05-02T00:48:42+09:00
本番環境(AWS)でjavascriptを読み込む方法
はじめに
某プログラミングスクールの課題で、Railsを使ってECサイトを作成。
開発環境
Ruby 2.3.1
Rails 5.2.2.1発生時の状況
トップページに掲載している画像を、javascriptを使ってスライドさせる機能を開発環境で実装。
本番環境にデプロイすると、画像がスライドされず、また大きくレイアウトも崩れました。エラーの原因
原因は、application.jsに
=require rails-ujs
=require jquery-ujs
の2つの記述があり、Rails側とjQuery側でファイルの呼び出しを行いエラーが発生しているようでした。解決策
調べた結果、上記のコードのいずれかを削除すると正常に動作する模様。
今回は、上の方のコードを削除したところ正常に動作しました。恐らく下のコードを削除しても正常に動作する。