- 投稿日:2020-03-10T22:55:36+09:00
CSS、SCSSファイルの読み込み
CSSファイルの読み込み
rails newによってアプリケーションを作成した際にはapplication.cssに
*= require_tree .の記述がある。この記述によって同じディレクトリにあるcssファイルは読み込まれる。
またこのrequireはアセットパイプラインの仕組みによってファイルを読み込む。
アセットパイプラインはcssファイルやjavascriptファイルを一つにまとめて圧縮し処理速度をあげる仕組み。
アセットパイプラインはsprocketsと言うgemによって実装されている。SCSSファイルの読み込み
scssファイルはapplication.scssに@importを記述することでscssファイルをインポートする。
この@importはscssのメソッドであり拡張子をscss(application.scss)にしないと使えない。
application.scssからscssファイルをインポートするために使用するメソッドである。
- 投稿日:2020-03-10T22:26:33+09:00
アプリケーションを作ってみます
自分用メモ
new コマンド実行
rails 5.0.7.2 new Put.Memo -d mysql
データベースを作成
rails db:create作るのは単語帳アプリ
最初にデータベース設計をしてみて自分のしてみたいことを整理してみた
機能として
---アカウント機能
---投稿機能
---いいね機能
---検索機能
---意味の部分にurl,画像を描けるようにする
---ランキング機能次にフロント実装
topページには
:スクロール機能
:ランキング機能↑とりあえずこれだけ実装したい
できればタグごとのランキング,ページネーション機能をつけたいビューはこんな感じ
単語、意味とurlと画像
で一つの投稿としてこれをランキングで複数並べる形にしたい
Put.Memoのところをクリックするとでtopページに戻るようにした
ヘッダーだけだけどまぁ機能としては上だけで十分だと思う
https://gyazo.com/429eb62d3c07605ff058e91be4f43712後で
タイトルのところに作った人のアカウント名を付けようと思うとりあえず今日はここまで
- 投稿日:2020-03-10T22:24:13+09:00
can't find gem bundler (>= 0.a) with executable bundle (Gem::GemNotFoundException)が起きた時に試したこと
can't find gem bundler (>= 0.a) with executable bundle (Gem::GemNotFoundException)が起きた時に試したこと
rspec勉強のためにgit cloneしてテストコードを書いていこうと思った矢先に上記のエラー文を吐き出しbundle updateができなくて、ちょっと詰まったので同じところで躓かないようにするためにメモしておく。
原因
Gemfile.lockで記述されているbundleのバージョンと実際にインストールされているバージョンが違ったため上記のエラーが発生していた。
問題解決のために行ったこと
1.Gemfile.lockのbundleのバージョンを確認
⇨cd [railsのプロジェクト] で対象のrailsプロジェクトに移動する
⇨vi Gemfile.lockとターミナルに記述するBUNDLED WITH 1.14.6⇨上記のような記述があるためこのバージョンにあったbundleをインストールしなければならない
2.既存のbundleをアンインストールして新規にインストール
gem uninstall bundler -v 2.0.1 gem install bundler -v 1.14.6⇨その後bundle installしたら無事動いた。
- 投稿日:2020-03-10T22:11:22+09:00
prefixを正確に調べた
prefixの認識レベル
ミニブログを作っている最中、ふとprefixについて考えた時に
「パスを代入する変数」というレベルでしか認識していなかったので
詳しく理解するために調べてみた。【詳しく調べたかったポイント】
①prefixを使用するメリット(必要性。パス記入の方が分かりやすくない?)
②普通にパスを記入する事との使い分け調べてわかった事
まず第一に、prefixはRailsが推奨している記述方法らしい。
tweet_pathでパスの指定完成。
①prefixを使用するメリット(必要性。パス記入の方が分かりやすくない?)について
→結論あまりないらしい。
1,Railsが推奨している
2,シンプルで直感的に分かりやすくなる(?)というのが使用している理由らしい。
②普通にパスを記入する事との使い分け
→開発現場による。「こんな時はprefix!!」みたいなのはないっぽい。
結論
僕は普通にパスを書く方が好き。
「私はprefix派!」「prefixにはこんなメリットがあるぞ!」
というご意見があれば教えて下さい...!!
- 投稿日:2020-03-10T22:00:53+09:00
Sass::SyntaxError in Devise::Registrations#new 解決方法
前提条件
・gemfileに使いたいパッケージを記述している
Gemfilegem 'devise'・ターミナルで
bundle installが済んでいるエラー内容
どうやら
@import "modules/user"でうまく'_user.scss'が読み込めてないみたいです_user.scssを動かしてみます
ここから
modulesディレクトリの中へ入れてみました
無事に動くようになりました
ここで考えたこと
@import "modules/user"と書いてあったけど、
これをよく読むと、
modulesディレクトリの中にある、_user.scssを読み込んでね(@import)
ってことだから、試しに
@import "user"に変えて
_user.scssのファイルの位置を元に戻したら、
ちゃんと動きましたとさめでたしめでたし
- 投稿日:2020-03-10T21:45:12+09:00
Railsからデータベースを参照(集計)するときの書き方
前置き?
ビューを作成する前に、railsコントローラにおいて、データーベースの値を取ってくる場合にどのように記述するのか。今回はgroup by してからのカウントした値を取得する場合のRailsの書き方を調べるのに随分苦労したので、残しておく。
前提条件
JavaScriptで作成したタイピングゲームをRuby on Railsにのせようとしている。
集計が必要か?ってなったのは、タイピングした結果をランキング表示させようと思い立った時に、問題別のランキング表示をする前に、問題別にどれだけチャレンジャーがいたのか、っていうのを先に一覧表示した後で、ランキング表示をしようか、と思ったから。テーブル
Qfiles id title category results_count その他
Results id user_id qfile_id その他 出そうとした値
- 「Qfile辺りのResultを残したuser_id数」
- 「Qfile辺りのResultを残した数」
まぁ、単純だしすぐに終わると思いつつ、先にクエリを作成。
Select results.`qfile_id`, qfiles.`title`, count(distinct results.`user_id`), count(*) from results inner join qfiles on results.`qfile_id` = qfiles.`id` group by results.`qfile_id`, qfiles.`title` ;パンケーキ(Sequel Pro)で実行させて望み通りの値が表示されてることを確認。さあRailsに直すぞ、と意気込む。
調べる
ポイントは3点。
1. テーブルの結合はどう書くんだ?
2. group by はどう書くんだ?
3. countはどう書くんだ?(ついでにdistinctも)とにかくググって調べたコードをrails cで片っ端から実行。うまく実行できず、よく分からない時は「こう書けば動くんじゃね?」という勘に頼ったテストもした!笑
とりあえずページ保存
Rails controllerに書く記述をRails cでテスト
- テーブル結合はjoinsで行う。この時selectはいらないが、書かなかった場合、主にしたモデルのカラム(Qfiles.*)しか参照できない。
Qfile.joins(:results).select("results.*, qfiles.*").first.idここでもう一つ、先頭に記述するモデルは、親子関係の親の方のモデルでないといけない。これは先頭に記述するモデルを実際に変えてみて、サーバーログに表示されるクエリを見てたらわかる。
- 単純カウントは、最後に.count入れるだけ
- distinctカウントは、selectした後に.countを入れて、さらにcountオプション内でdistinctを記述する
Qfile.joins(:results).group("results.qfile_id").select("results.qfile_id").count("distinct results.user_id")どうやって一文で実行するのか?
色々調べてテストして分かったのは、Countを2ついっぺんには無理じゃね!?ってこと。ひょっとしたら何か方法があるのかもしれないが、もうギブアップした。
カウントの片一方だけなら合わせることができた。
Qfile.joins(:results).group("results.qfile_id").group("qfiles.category").select("results.qfile_id").order("qfiles.id ASC").count("distinct results.user_id")もう一個のカウントも一緒にやりたいんだよ・・・。
もう面倒い。生クエリ実行できるんじゃね?
find_by_sql で Rails から生 SQL クエリを直接実行する
ここの通りに実行できた。でもあれ?rails cで実行したらカウント要素が入ってなくね?どうゆうこと???
Railsのfind_by_sqlで取得できるモデルからは、select句で指定した名前で値が取れる
取得してるのに表示されないんか〜〜〜い!
これが分かればなんとか値を参照できるから、あとはビューに渡すだけ!!!
でもやっぱり生クエリは邪道な気がする
一生懸命探しました。ふとしたきっかけで新しいキーワードを用いてググったところ、ありました。
ふむ。「Qfile辺りのResultを残した数」はこれで保存しときゃいいじゃん。
gem 'counter_culture'を採用。これで作成したカラムが、Qfiles.results_countここまでで作成した実行文
Qfile.joins(:results).group("results.qfile_id").group("qfiles.category").group("results_count").select("results.qfile_id").order("qfiles.id ASC").count("distinct results.user_id")まぁ、なんとか値が出たから使えるんだけども。なんか参照しづらいハッシュ値で取得してしまう。group by の副作用っぽい。
=> {[1, "英語-単語", 4]=>1, [2, "英語-単語", 1]=>1, [4, "英語-単語", 1]=>1, [7, "英語-文章", 4]=>1}group by 使わなかった時みたいに、@モデル.カラムで参照させて欲しい!!
Rails ActiveRecordでgroup_by countによる集計結果をrelationとして取得する
なるほど、selectにまとめて入力できるのか!!!最終的に以下になった。
Qfile.joins(:results).select("qfiles.id, qfiles.title, qfiles.results_count, COUNT(distinct results.user_id) AS count_distinct_results_user_id").group("results.qfile_id, qfiles.category, results_count").order("qfiles.id ASC")=> [#<Qfile:0x00007f88586419b8 id: 1, title: "abide - certification 200語", results_count: 4>, #<Qfile:0x00007f8858641878 id: 2, title: "certify - drill 200語", results_count: 1>, #<Qfile:0x00007f8858641738 id: 4, title: "induction - painter 200語", results_count: 1>, #<Qfile:0x00007f88586415d0 id: 7, title: "英語例文 200件 1", results_count: 4>]上記にdistinct countが含まれてないが、select文中に指定した名前で参照できる。
Qfile.joins・・・・.count_distinct_results_user_idselectの記述が長くなってしまうのでなんかイヤな書き方ではあるけども、参照時にインデックス番号で指定しなければなくなるよりはマシか?と思った。
外部結合のメモ
Qfile.left_outer_joins(:results).where(user_id: 1).select("qfiles.id, qfiles.title, qfiles.results_count, COUNT(distinct results.user_id) AS count_distinct_results_user_id").group("qfiles.id, qfiles.category, results_count").order("qfiles.id ASC")
- 投稿日:2020-03-10T21:43:26+09:00
褒められて伸びる自分のためにポジティブなツイートしか表示されないエゴサーチツールを作った話
はじめに
世の中にアウトプットを出したら他人の反応は気になるもの。長文ブログを書いたときだったり、頑張って開発したサービスをリリース出したときだったり。みな、ツイッターでエゴサーチをしていますよね。
他人からの反応はポジティブなものからネガティブなものまで様々あるかと思いますが、人間誰しも褒められたらやっぱり嬉しいものです。逆に、ネガティブなツイートも貴重な意見と分かっているものの、見るのは勇気がいりますよね。
その気持ち、とっても分かります。
ということで、ポジティブなツイートしか表示されないエゴサツールを作ってみました?
なお、ドMの方はポジティブをネガティブに置き換えれば「ネガティブなツイートしか表示されない」ことも可能なので、安心してこのままお読みください。
まずは動かしてみよう
Webアプリケーションとして作ったのですが本記事では簡単のためrubyプログラムのみご紹介します。試しに私の大好きな「サウナ」で検索した結果がこちらです。
サウナを検索した例$ ruby twi_posi_search.rb "サウナ" ポジティブ度数:43.2% バイトした後、銭湯行ってサウナして牛乳飲んでパンク聴いて帰る!最高! ポジティブ度数:57.3% 気持ちかったぁ〜ガラガラだったし、やっぱ北欧サウナ最高‥眠いサウナ万歳 ?
刻々とツイートがなされているため通常の検索結果との違いがお伝えするのが難しいのですが、ポジティブなツイートのみが表示されかつそのツイートのポジティブ度数も可視化しております。
動作環境、利用したアセット
- 言語:ruby
- Twitter API
- COTOHA API(感情分析API)
COTOHA APIについて
テキストの感情を分析するためにCOTOHA APIの感情分析を利用しています。そもそもCOTOHA APIとは、
NTTコミュニケーションズが開発した日本最大級の日本語辞書を活用した自然言語処理、音声認識APIプラットフォーム
です。感情分析を含む、14の自然言語処理、音声処理をAPIで提供してくれています。ありがたや。APIを使うために必要な準備はスタートガイド(COTOHA公式)をご覧ください。
また、COTOHA APIをrubyで簡単に利用するためのgemを@tanaken0515さんが作ってくれていたので、今回はそちらを利用しています。詳細はコチラの記事をご覧あれ?
感情分析APIについて
感情分析APIは、入力として日本語で記述されたテキストを受け取り、そのテキストの書き手の感情(ネガティブ・ポジティブ)を判定します。返却結果には以下が含まれます。
- 感情ラベル:ポジティブ/ネガティブ/ニュートラル
- 感情スコア:1の信頼度(0.0〜1.0)
- 感情フレーズ:1の判定結果の元になったフレーズとその情報
サンプルとして、「人生の春を謳歌しています」という文章の返却結果はこちらです。
{ "result": { "sentiment":"Positive", "score":0.20766788536018937, "emotional_phrase":[ { "form":"謳歌", "emotion":"喜ぶ,安心" } ] }, "status":0, "message":"OK" }詳細は公式リファレンスをご覧ください。
実装の流れ
- Twitter APIを使ってキーワード検索の結果を取得
- 簡単のため、今回は最新10件を取得
- 取得したツイートを1件づつ感情分析 by COTOHA API
- ポジティブなツイートのみを表示
- ポジティブ度数も併せて
サンプルコード
技術的に新しいことはしていないため紹介のみ
twi_posi_search.rbrequire 'twitter' require 'cotoha' keyword= ARGV[0] twitter_client = Twitter::REST::Client.new do |config| config.consumer_key = "ホゲホゲ" config.consumer_secret = "ホゲホゲ" end client_id = 'ホゲホゲ' client_secret = 'ホゲホゲ' cotoha_client = Cotoha::Client.new(client_id: client_id, client_secret: client_secret) cotoha_client.create_access_token since_id = nil tweets = twitter_client.search(keyword, count: 10, result_type: "recent", exclude: "retweets", since_id: since_id) tweets.take(10).each do |tw| result = cotoha_client.sentiment(sentence: tw.full_text) if result['result']['sentiment'] == "Positive" score = result['result']['score'].round(3)*100 puts "ポジティブ度数:#{score}%" puts tw.full_text puts "" end end最後に
エゴサは手軽にお客様の反応を調べることができる便利な手段です。今回はポジティブなツイートのみを表示しましたが、ネガティブなツイートのみを表示するエゴササービスも面白いそうです。「あなたに対する辛辣な意見こそ、実は貴重なアドバイス」と言いますしね。はたまた、エゴサした結果を感情分析するサービスという方向性もありかなぁと思っています。
エゴサーチと感情分析
この組み合わせで良いアイデアあればぜひコメントください!ちょっとでも面白いなと思ったらイイねをしてくれるともっと喜びます。
参考文献
- 投稿日:2020-03-10T21:10:26+09:00
【初心者】長めのモデル名(スネークケース)からfindとかをする
コンソールでモデルから値を取得しようとしたとき
知らずにスネークケースでモデルを指定してエラーになっていたので調べました。
おそらく初心者や始めたばかりの頃はUserモデルとかPostモデルなどしか扱わないので悩まないと思います。。
そもそもスネークケース、キャメルケースとは?という方へ。UserとかPostとかの短いモデル名のとき
User.find(1)簡単に取得できますね。
長めのモデル名のとき
キャメルケースとスネークケースとは
CustomerOrderキャメルケース(大文字部分がラクダの背中っぽい)
CとOが大文字customer_orderスネークケース(ヘビっぽい)
単語と単語のつなぎ目に_が使われているモデルから値を取得
CustomerOrder.find(1)で取れます。(キャメルケース)
Customer_order.find(1)スネークケースでは不可。
- 投稿日:2020-03-10T20:11:24+09:00
RailsのViewからVue.jsのModalを表示させてみた
画像のように、一覧画面で
詳細を開くボタンで詳細画面を開くときに、Railsのviewは使用せずVue.jsのModalで表示することを試みた。
何とか実装出来たが、JSはjQueryから入門したこともあり結構大変だったのでメモとして残しておく。
環境
Ruby 2.6.5
Ruby on Rails 6.0.0
Vue.js 2.6.11ここではRailsやVueの設定は省略し、Railsのwebpackerでvueのインストール、セットアップが完了していることを前提として書いていく。
やりたい事
冒頭の通り、
詳細を開くボタンをクリックしたときに詳細画面をVue.jsのModalで開きたい。
しかし、今までRailsの中でVueを使った事はあっても、Vueインスタンスがどのタイミングで生成されるかなど無知であったため、最初はどうやるのかイメージがつかなかった。
jQueryからJavascriptに入門したので、なかなか生のJSを書く機会がなかった。Rails側
Modalを開く部分はBootstrapを使うことにした。
参照: https://getbootstrap.com/docs/4.0/components/modal/上記のURLを参考にして、BootstrapだけでModalを開けるようにしていく。
view/users/index.html.slim thead tr th id th 名前 th email th tell th tbody - @users.each do |user| tr th = user.id td = user.name td = user.email td = user.tell td / Vue側にuser_idを渡すために、data属性にuser_idを持たせている button.btn.btn-info.btn-modal data-target="#full-width-modal" data-toggle="modal" data-user_id="#{user.id}" 詳細を開く / Modalのコンポーネント部分。ここにVueのtemplateをマウントさせる。 #full-width-modal.modal.fade tabindex="-1" role="dialog" aria-hidden="true" aria-labelledby="full-width-modalLabel" data-keyboard="false" data-backdrop="static" #showUser = javascript_pack_tag 'users'Vue.js側
次にvueインスタンスを生成させていく。
javascript/packs配下にusers.jsを作成し、コードを記述javascript/packs/users.js import Vue from 'vue' // packs配下にcomponentsディレクトリを作成し、その中にcomponentを格納している import ShowUser from './components/ShowUser.vue' // ボタン要素をgetElementsByClassNameで配列として取得 const btnModals = document.getElementsByClassName('btn-modal') // for文で各ボタン要素を取り出し for(let btnModal of btnModals) { // 取り出したボタン要素に対し、addEventListenerでボタンクリックされた時にvueインスタンスを生成させる btnModal.addEventListener('click', () => { new Vue({ el: '#showUser', render: h => h( ShowUser, { // componentにpropsでuser_idを渡している props: { userId: Number(btnModal.getAttribute('data-user_id')) } } ) }) }) }ここまで出来たら、あとは
ShowUser.vueを書いていけば終わり。
Modalを閉じるときはどうすればいいの?となりそうだが、単純に閉じるボタンのアクションにthis.$destroy()を仕込んであげれば大丈夫。
modalを閉じる処理をbootstrapのjQueryではなく、Vue側で書いてあげないといけないので、そこも含めて次回の記事で頑張って書いていく。
- 投稿日:2020-03-10T17:30:51+09:00
Rails 〜コメント投稿の削除・編集機能について〜
概要
Railsで作成したコメントの削除・編集機能についてまとめる。
投稿の編集
投稿の編集については
① 編集したい投稿の取得
② その投稿のcontentの値を上書き
③ データベースに保存
この3つの処理が必要である。
post=Post.find_by(id:1)
post.content="コメント"
post.save
これらの処理が必要ビュー
HTMLに記載するコード
<%=link_to("/posts/#{@post.id}/edit")>
link_to + editアクションのURLを指定ルーティング
get "posts/:id/edit"=>"posts#edit"
編集したい投稿のidをURLに含むupdateアクションについて
フォームの値を受けとるのでルーティングはgetではなくpostにする必要あり。また特定のidの投稿を更新するためURLにidを含む。投稿編集から投稿一覧ページにリダイレクトさせるのでビューは不要。編集したものを上げる処理のルーティング
post "posts/:id/update"=>"posts#update"
コントローラ
def update
redirect_to("/posts/index")
end
入力したコメントの送信先を指定
フォームで入力した内容をデータベースに保存するためには、フォームのデータをupdateアクションに送信する必要あり。htmlには
<%=form_tag("/posts/#{@post.id}/update")do%>
~html~
<%end%>
とここまで記載。form_tagメソッドを用いて送信先を指定する。updateアクションの中身
投稿の内容を更新する手順
① URLに含まれたidを用いてデータベースから投稿データを取得
② フォームの編集内容を受け取り、投稿データを更新する
この2つの処理が必要
ルーティングにはidを含めたURL先を記載しつなぐ
コントローラには
def update
@post=Post.find_by(id:params[:id])
@post.content=params[:content]
@post.save
と記載。何かを見つけるときはfind_byメソッドを使用。投稿の削除
投稿の削除については
① データベースから削除したい投稿を取得
② destroyメソッドを用いて、投稿を削除する
post=Post.find_by(id:2)
post.destroy
これらの処理が必要今日はここまで
- 投稿日:2020-03-10T17:26:24+09:00
#Ruby で自前クラスのインスタンス同士で同値性チェックを実装するには == ===イコールメソッドを定義すれば良いかね?
- 強制的に同値の評価を真にしてしまう場合。別のクラスのインスタンスだろうとなんだろうと常に「同値だ!」と言い張るクラスを作ってみる。実用的には特に意味なし。
- Aのインスタンスが同値評価をしようとすると、常に true を返す。(特に意味はありません)
- 有意な同値評価を実装してみてください。
class A def ==(instance) true end def ===(instance) true end end class B end A.new == B.new # => true A.new === B.new # => true B.new == A.new # => false B.new === A.new # => false== と === だけ再定義可能みたいだ
Rubyにおける==,===,eql?,equal?の違い - ぬいぐるみライフ?
Original by Github issue
- 投稿日:2020-03-10T16:32:12+09:00
[Rails, jQ]インクリメンタルサーチ
インクリメンタルサーチ
本記事では前回作成したユーザーの名前検索機能を使用して実装していきます。
以下を使用しています。
- ruby 2.5.1
- rails 5.2.4.1
- gem 'jquery-rails'
- gem 'devise'
なお、使用するビューは以下を使用します。
index.html.erb<%= form_with(url: users_searches_path, local: true, method: :get, class: "search_form") do |f| %> <%= f.text_field :keyword, placeholder: "Name", class: "search_input" %> <%= f.submit "Search", class: "search_btn" %> <div class="contents"> <% @users.each do |user| %> <div class="user_content"> <p class="user_name"> user.name </p> </div> <% end %> </div>準備
以下が記入されていなければ記入します。
app/assets/javascripts/application.js
//= require jquery
フォーマット毎に処理を分ける
フォーマット毎に処理を分けるためコントローラーのindexアクションを編集します。
controllers/users/searches_controller.rbdef index @users = User.search(params[:keyword]) respond_to do |format| format.html format.json end endrespond_to
アクションの中でHTMLとJSONなどのフォーマット毎にhtmlかjsonかを条件分岐することができます。
jbuilderファイルの作成、編集
index.json.jbuilderを新規作成し内容を編集します。
app/views/tweets/searches/index.json.jbuilderjson.array! @users do |user| json.id user.id json.name name.name endjbuilderという拡張子を持つテンプレートでは、JSONという名前のJbuilderオブジェクトが自動的に利用できるようになります。
arrayメソッドはその内の一つでJavaScript側に配列で値を送ることができます。search.jsの作成、編集
検索フォームの値を取得
app/assets/javascripts/search.js$(function() { $(".search_input").on("keyup", function() { var input = $(".search_input").val(); }); });keyupイベントを使用して文字が入力される度に発火するようにします。
JSON形式で値を返す
app/assets/javascripts/search.js$(function() { $(".search_input").on("keyup", function() { var input = $(".search_input").val(); //---以下を追記--- $.ajax({ type: 'GET', url: '/users/searches', data: { keyword: input }, dataType: 'json' }) //---以上を追記--- }); });Ajax通信を実現するためには、上記のように$.ajaxメソッドを使用します。
また。上記のコードは
HTTPメソッドはGETで、/users/searchのURLに{ keyword: input }を送信。サーバーから値を返す際は、JSON。
という意味を持ちます。JSON形式の場合は、app/views/users/searches/index.json.jbuilderが読まれ,該当する投稿情報はjbuilderによってJSONに変換されてJavaScriptのファイルに返されます。レスポンス結果によって処理を分ける
app/assets/javascripts/search.js$(function() { $(".search_input").on("keyup", function() { var input = $(".search_input").val(); $.ajax({ type: 'GET', url: '/users/searches', data: { keyword: input }, dataType: 'json' }) //---以下を追記--- .done(function(users) { $(".contents").empty(); if (users.length !== 0) { users.forEach(function(user){ appendUser(user); }); } else { appendErrMsgToHTML("一致するユーザーはいません"); } }) .fail(function() { alert('error'); }); //---以上を追記--- }); });レスポンスが成功した場合は、ユーザーが表示される親要素の中身を都度空っぽにします。そしてusersが空ではない場合usersの中身の数だけappendUser関数を呼び出します。
該当ユーザーがいない場合は”一致するツイートがありません”という引数を与え、appendErrMsgToHTML関数を呼び出します。
また、レスポンスに失敗した場合はアラートを表示させます。empty()メソッド
指定したDOM要素の子要素のみを削除するメソッドです。
指定したDOM要素自体を削除するremoveメソッドとは異なります。forEachメソッド
forEachは、与えられた関数を配列に含まれる各要素に対して一度ずつ呼び出します。
検索に該当ユーザーいた場合、いない場合の関数を定義
app/assets/javascripts/search.js$(function() { //---以下を追記--- var search_list = $(".contents"); function appendUser(user) { var html = ` <div class="user_content"> <p class="user_name"> #{user.name} </p> </div> ` search_list.append(html); } function appendErrMsgToHTML(msg) { var html = ` <div class="user_content"> <p class="user_name"> ${ msg } </p> </div> ` search_list.append(html); } //---以上を追記--- $(".search_input").on("keyup", function() { var input = $(".search_input").val(); $.ajax({ type: 'GET', url: '/users/search', data: { keyword: input }, dataType: 'json' }) .done(function(users) { search_list.empty(); if (users.length !== 0) { users.forEach(function(user){ appendUser(user); }); } else { appendErrMsgToHTML("一致するユーザーはいません"); } }) .fail(function() { alert('error'); }); }); });検索に該当ユーザーがいた場合
変数htmlにユーザー情報を表示する要素を代入し、appendメソッドで親要素の一番下に追加します。
検索に該当ユーザがいない場合
変数htmlに"一致するユーザーはいません"を表示する要素を代入し、appendメソッドで親要素の一番下に追加します。おわり
これでインクルメンタルサーチが実装できました。
- 投稿日:2020-03-10T14:54:42+09:00
Rubyの通信処理におけるopensslのエラー
■ 背景
Slack通知が来なくなったので気づいたのですが、Ruby使って実行しているBatch処理における通信部分がコケていたのでその原因調査したメモです。
■ 目次
- 環境
- 事象
- やったこと
- まとめ
- 参考サイト
■ 内容
1. 環境
$ rbenv -v rbenv 1.1.2-26-gc6324ff $ rbenv versions system * 2.5.1 (set by /Users/y-agatsuma/.rbenv/version) $ bundler -v Bundler version 1.16.2 $ ruby -v ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin18] $ bundle exec gem list | grep slack slack-incoming-webhooks (0.3.0)2. 事象
SSL_connect returned=1 errno=0 state=error: tlsv1 alert protocol version Traceback (most recent call last): 23: from app/controllers/scout_actions/execute_bot_periodically.rb:84:in `<main>' 22: from app/controllers/scout_actions/execute_bot_periodically.rb:41:in `start' 21: from /Users/y-agatsuma/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/parallel-1.12.1/lib/parallel.rb:264:in `y-agatsuma' 20: from /Users/y-agatsuma/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/parallel-1.12.1/lib/parallel.rb:358:in `work_in_processes' 19: from /Users/y-agatsuma/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/parallel-1.12.1/lib/parallel.rb:418:in `create_workers' 18: from /Users/y-agatsuma/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/parallel-1.12.1/lib/parallel.rb:418:in `each_with_index' 17: from /Users/y-agatsuma/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/parallel-1.12.1/lib/parallel.rb:418:in `each' 16: from /Users/y-agatsuma/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/parallel-1.12.1/lib/parallel.rb:419:in `block in create_workers' 15: from /Users/y-agatsuma/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/parallel-1.12.1/lib/parallel.rb:428:in `worker' 14: from /Users/y-agatsuma/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/parallel-1.12.1/lib/parallel.rb:428:in `fork' 13: from /Users/y-agatsuma/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/parallel-1.12.1/lib/parallel.rb:437:in `block in worker' 12: from /Users/y-agatsuma/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/parallel-1.12.1/lib/parallel.rb:455:in `process_incoming_jobs' 11: from /Users/y-agatsuma/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/parallel-1.12.1/lib/parallel.rb:484:in `call_with_index' # <----- 何かしら処理 -----> 6: from /Users/y-agatsuma/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/slack-incoming-webhooks-0.2.0/lib/slack/incoming/webhooks/request.rb:11:in `post' 5: from /Users/y-agatsuma/.rbenv/versions/2.5.1/lib/ruby/2.5.0/net/http.rb:1455:in `request' 4: from /Users/y-agatsuma/.rbenv/versions/2.5.1/lib/ruby/2.5.0/net/http.rb:909:in `start' 3: from /Users/y-agatsuma/.rbenv/versions/2.5.1/lib/ruby/2.5.0/net/http.rb:920:in `do_start' 2: from /Users/y-agatsuma/.rbenv/versions/2.5.1/lib/ruby/2.5.0/net/http.rb:981:in `connect' 1: from /Users/y-agatsuma/.rbenv/versions/2.5.1/lib/ruby/2.5.0/net/protocol.rb:44:in `ssl_socket_connect' /Users/y-agatsuma/.rbenv/versions/2.5.1/lib/ruby/2.5.0/net/protocol.rb:44:in `connect_nonblock': SSL_connect returned=1 errno=0 state=error: tlsv1 alert protocol version (OpenSSL::SSL::SSLError)上記は、並列で何かしらの処理を実行した最後に、slack-incoming-webhooksというgemを使って
Slackに通知を送信する部分の処理で発生してる模様。他にも調べてみると通信全般でエラーが発生していました。
3. やったこと
■ rbenv/ruby-buildを最新化したら治るんじゃね? → ダメ
$ brew upgrade ruby-build $ 処理実行 → ダメ■ rubyをバージョンアップしたらいけるんじゃね?
1. ruby2.7.0にして、古いbunlderのままで実行してみる → ダメ
$ rbenv install 2.7.0 $ rbenv global 2.7.0 $ rbenv rehash $ ruby -v ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-darwin19] $ rbenv exec gem install bundler -v 1.16.2 $ 処理実行 → ダメ2. ruby2.7.0にして、bundlerをdefalut(=2.1.2)に変更して実行してみる → イケた
$ rbenv exec gem uninstall bundler → 古いやつ(1.16.2)をアンインストール $ rbenv exec gem list | grep bundler bundler (default: 2.1.2) # Gemfile.lockを削除してから、再度 $ bundle installしてあげて $ 処理実行 → イケた4. まとめ
- Rubyのバージョンというよりも、bundlerに付随するエラーだった模様
- bundlerをアップデートして、Gemfile/Gemfile.lockを更新したら直ったった
5. 参考
- 投稿日:2020-03-10T12:08:16+09:00
Cancancanを使っているときのtest
Cancancanとは?
この記事を見て下さっているということは既にご存知だと思いますが、 権限管理のgem です。
Cancancanを使っているときのテストはどうしたらいいのか?
権限があるときは、権限を持つテストユーザーでログインして
test "test#indexにmanager権限を持つユーザーがアクセスして、正常レスポンスが返されること" do # manager権限を持つユーザーでログイン login(:yama_p) get tests_path assert_response :success end正常に処理ができることを確認すればまぁいいかも知れませんが、 アクセスできない(権限がない)ことを確認する にはどうしたらよいでしょうか?
というのも、上記でassert_response :errorsなどとしてもcancancanで弾かれたときにエラーが返ってくるわけではなくそもそもアクセスできないのでCanCan::AccessDenied: You are not authorized to access this page.とテストが落ちてしまいます。
権限を持つかどうかをテストする
Cancancanのwikiにテストについての記述があります。
test "test#indexにmember権限を持つユーザーはアクセス権限を持たないこと" do # member権限を持つユーザー user = users('fan_bingbing') ability = Ability.new(user) assert ability.cannot?(:manage, Test.new) end上記のような感じで、ユーザーが権限を持たないことをテストすればOK!
- 投稿日:2020-03-10T11:35:31+09:00
シンボルとは
シンボルとは
Symbol とは、Ruby が内部でメソッド名などの識別に使っている数値で、任意の文字列に対して異なった値が割り当てらる。
なるほど、よく分かりませんね。
つまり、文字列だけど数値。みたいなものです。ハッシュのキーや文字列自体がデータでは無い物に使うことが吉です。
前コロンと後コロンの違い
シンボルとは主に文字列にコロン記号「:」を前置して定義したものです。
それにより、文字列を””で囲む必要がなくなります。
コロン記号「:」が、文字列記号「””」の代わりに、「これはシンボルだよ」とRubyに知らせています。
上記のように、コロン記号「:」を文字列に前置するとシンボルになります。
たとえば、ハッシュのキーとしてシンボルを使う際や、キーワード引数を使う際に、コロン記号「:」を後置します。
シンボルはオブジェクトの一つ
メソッドなどの名前を識別するためのラベルをオブジェクト化
samurai /*文字列 :samurai /*シンボルハッシュのキーとして利用する
よく使われるのが、ハッシュのキーだと思います。
hash_symbol = { tsuma: "sazae", otto: "masuo", kodomo: "tarao" }取り出しは
puts hash_string[:tsuma] # "sazae" と表示 puts hash_string[:otto] # "masuo" と表示 puts hash_string[:kodomo] # "tarao" と表示なり、キーをシンボルで定義しています。
ハッシュはデータを持たない
最初に記述した通り、ハッシュは「文字列だけど数値。」なのでデータを持ちません。
そのため、文字列そのもののデータを必要としない場合にシンボルが使われています。シンボルのメリット
シンボルを使うことでコードが短くなり可読性が上がったり、処理が早くなったり、メモリ消費が少なくなったりと、いいことばかりらしいので、一緒に勉強しましょう。
- 投稿日:2020-03-10T11:30:11+09:00
備忘録(TX50)シンボル型の説明
Rubyでは:(コロン)で始まるシンボルと呼ばれるものがありますが、これはどういったものか、またシンボルを使うことのメリットは何か解説してください。
A
Rubyの内部では整数として管理されているが、文字列のように呼び出せるオブジェクト。同じシンボルであれば同一のオブジェクトを参照するので、いくつ作成しても必要なメモリ容量は変わらない。また、文字列よりも高速に処理することができる。Q
tweet = Tweet.new(tweet_params)
if tweet.save
some_method(tweet)
endRailsアプリケーションではバリデーションに引っかかると保存がされない仕組みがあります。以下のコードで、保存されるべきtweetがなぜか保存されずロールバックされてしまう場合、どうすればその理由を確認できるか説明してください。
①1行目と2行の間にbinding.pryを記載し止める。
②pryの中で、tweet.saveを実行する。
③tweet.errorsを実行すると保存の際に出たエラーの内容が表示される。
- 投稿日:2020-03-10T10:56:05+09:00
Sinatraを使ってHeroku drainログをTDへ送るAppを作った時にハマった記録
はじめに
Heroku drainからhttp経由でログを取得し、TDへ送るアプリをsinatraで作っていた時ハマった時のことを記録として残す。
Heroku drainからログをpostで受け取る形式のアプリを作った。(Sinatraは初めて)最初はクラシック形式で作成。
app_main.rbrequire 'sinatra' require 'sinatra/reloader' if development? require 'td' require 'date' TreasureData::Logger.open('DB名', :apikey=>ENV['TD_API_KEY'], :auto_create_table=>true) post '/' do ・・・ requestの内容をパースする処理 ・・・ TD.event.post('テーブル名',{送るデータ}) endここで一つ注意点。
・TreasureData::Logger.openはpostのなかに記述してはいけない
理由は簡単。 postされるたびTreasureData::Logger.openが実行されるため
最終的にはThread errorでログが受け取れない状態に陥る。この記述にすると大体5分間隔でbufferに蓄積したデータをTDへpostする。
モジュールに書き直し
後々のメンテナンス性や拡張性、可読性を考えモジュール化させる。
app_main2.rbrequire 'sinatra/base' require 'sinatra/reloader' require 'td' require 'date' require 'ltsv' require "concurrent/map" require './object_ext' #.present?が使いたかったために拡張 class MyApp < Sinatra::Base configure :development do register Sinatra::Reloader end before do #TD初期設定 TreasureData::Logger.open('DB名', :apikey=>ENV['TD_API_KEY'], :auto_create_table=>true) class Lib include ObjectExtension def log_parser(log) ・・・ 受け取ったログをパースしてhashで返す ・・・ end end paser = Lib.new end post '/' do ・・・ logを受け取りlog_parser(log)でパースする ・・・ TD.event.post('テーブル名',{送るデータ}) end end・ここでのミス
beforeを使ったこと
実際のところ前処理をする必要は今回のアプリには無かったが、
beforeで事前に色々と設定や処理などをするものと勘違いしていたためまたもやThread errorの連発。
何が起きていたかと言うと、herokuからドレインされたログを受ける度にbeforeが呼ばれTreasureData::Logger.openを
呼び出すと処理を行っていた。そのため、今度はbufferに溜まるのを待たずに即TDにpostする現象に見舞われblockされてしまう状況に陥った。app_main2.rbrequire 'sinatra/base' require 'sinatra/reloader' require 'td' require 'date' require 'ltsv' require "concurrent/map" require './object_ext' #.present?が使いたかったために拡張 class MyApp < Sinatra::Base configure :development do register Sinatra::Reloader end #TD初期設定 TreasureData::Logger.open('DB名', :apikey=>ENV['TD_API_KEY'], :auto_create_table=>true) class Lib include ObjectExtension def log_parser(log) ・・・ 受け取ったログをパースしてhashで返す ・・・ end end paser = Lib.new post '/' do ・・・ logを受け取りlog_parser(log)でパースする ・・・ TD.event.post('テーブル名',{送るデータ}) end end MyApp.run!結果から言うとbeforeは要らなかった。
これで、約5分間隔でTDへpostする様に安定した。結論
TreasureData::Logger.openは一回呼び出せばそれで良い代物であって、
何度も呼び出される様な場所に記述をしてはいけない。
- 投稿日:2020-03-10T10:54:29+09:00
DB接続がない状態でassets:precompileを行う
本番とは違う環境だったり、Dockerfile内でassets:precompileを行ったりするときにDB接続でエラーになるときがある。これを回避する方法ってあるのかなと思ったので調べてみた
activerecord-nulldb-adapterを使う
github
https://github.com/nulldb/nulldbgem 'activerecord-nulldb-adapter'config/database.ymldefault: &default adapter: <%= ENV['DB_ADAPTER'] ||= "mysql2" %>database.ymlに環境変数で
DB_ADAPTERを指定する。$ DB_ADAPTER=nulldb bundle exec rake assets:precompile上記を実行すればDB接続なしでprecompileできる
参考
Rails × ECS でオートスケーリング&検証環境の自動構築
https://tech.medpeer.co.jp/entry/2018/06/20/080000
- 投稿日:2020-03-10T03:32:37+09:00
[Rails]rails sの時のエラー
rails sを行なった時のエラーの解消方法を書いて行きたいと思います。
私はこれで直りましたが皆が当てはまるかは分かりません。よろしくお願いします!環境
・macOS Catalina
・Rails 5.2.0
・Ruby 2.5.0エラー文
terminal$ rails s Traceback (most recent call last): 4: from bin/rails:3:in `<main>' 3: from bin/rails:3:in `load' 2: from /Users/name/App/Parttime-job/bin/spring:8:in `<top (required)>' 1: from /Users/name/.rbenv/versions/2.5.0/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require' /Users/name/.rbenv/versions/2.5.0/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require': cannot load such file -- bundler (LoadError)試したこと
パソコンに入っているbundlerとRailsプロジェクトのGemfile.lockに書いてあるbundlerのバージョンが違っていたのでとりあえずそこのバージョンを合わせました。
バージョン確認
terminal$ bundler -vbundlerバージョン変更
ご自身のbundlerのバージョンに合わせてバージョンを変更して下さい。terminal$ gem install bundler -v 1.3.0 $ gem uninstall bundler -v 2.1.4そして
terminal$ bundle update --bundler試したこと(2)
これは私だけかもしれませんがterminalで確認したRubyのバージョンとRailsのGemfileにあるRubyのバージョンが違っていたのでそこのバージョンを合わせました。
インストールするバージョンを確認
terminal$ rbenv install -listバージョンを指定してインストール
terminal$ rbenv install 2.5.0バージョンを確認してインストールできたか確認
terminal$ rbenv versionsバージョンを変更
terminal$ rbenv local 2.6.3バージョンを変更したらrehash
terminal$ rbenv rehash以上で私はrails sができました。
最後に
今回、以上の方法でエラーが直りましたがもしかしたら正しい方法ではないかもしれませんが何か役にたったら嬉しいです!
読んで頂きありがとうございました!!
- 投稿日:2020-03-10T02:16:40+09:00
Ubuntuで使う言語のインストール方法とか環境構築とか
最近はバックエンド言語毎にVMで環境用意して勉強したりしてて、その環境構築方法の管理を最近はGistでしてるのですが、何となくQittaに。※※但し、Gistは英語で書いてるので。
environment
- host OS: Windows
- VM: Virtual Box with Vagrant
- Ubuntu 18.0
CUIまたはGUIの仮想環境をUbuntuを使って構築するのはこっち。
Ruby on Rails
Install latest version
terminal# install in one time sudo apt install autoconf bison build-essential libssl-dev libreadline-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm-dev # install rbenv # rbenv is tool to manage a few of ruby versions and enable to change ruby ver. project by project. git clone https://github.com/rbenv/rbenv.git ~/.rbenv echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc echo 'eval "$(rbenv init -)"' >> ~/.bashrc source ~/.bashrc git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build # Install ruby rbenv install --list rbenv install 2.〇.〇 rbenv global 2.〇.〇 # Instal yarn # Rails6 needs webpacker, and Webpacker needs yarn to install curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list sudo apt update sudo apt install yarn # Install Rails gem install rails --no-document # install webpacker # inner App rails webpacker:installInstall RubyonRails by "apt install"
terminalsudo apt install -y ruby ruby-dev build-essential sudo apt install yarn sudo gem install rails
- "-y" means "All Yes"
- build-essential contain information about package to build Debian pack.
- If do not build Debian, build~ is not needed
- Reference
Nodejs
rails6 uses webpacker, which needs nodejs
terminal# first, install nodejs and npm sudo apt install -y nodejs npm # install n-package sudo npm install n -g # by n-package, install node sudo n stable # uninstal old nodejs and npm, and re-login sudo apt purge -y nodejs npm exec $SHELL -l # confirm node -vRust
when discord changed golang to Rust, I just tried this and coded a little.
terminalsudo apt install build-essential # install rust curl https://sh.rustup.rs -sSf | sh # add the pass source $HOME/.cargo/envJava
terminalsudo apt update sudo apt install git sudo apt install openjdk-11-jdk # confirmation java --versionPHP
Python
- 投稿日:2020-03-10T00:46:17+09:00
【Ruby on Rails】Google Books APIを叩く際の5つのTips
想定読者
- Railsで読書系ポートフォリオを作っている方
$ ruby -v ruby 2.6.5 $ rails -v Rails 5.2.4.1その1.APIを叩くロジックはcontrollerから切り分ける
まず以下記事(私の前記事です)のようにAPIを叩くわけですが、これをcontrollerに書いたらあっという間にFat controllerになりました。
Ruby on RailsでGoogle Books APIを叩くAPIを叩くロジックは、以下を参考にmodlueとしてapp/libに置きました。
Rails 5 で自作のモジュールを読み込む方法APIを叩く自作module
app/lib/google_books_api.rbmodule GoogleBooksApi def get_json_from_url(url) JSON.parse(Net::HTTP.get(URI.parse(Addressable::URI.encode(url)))) end # ①検索するAPIを叩く def url_from_keyword(keyword) "https://www.googleapis.com/books/v1/volumes?q=#{keyword}&country=JP&maxResults=20" end # ②IDから本の情報を取得するAPIを叩く def url_from_id(googlebooksapi_id) "https://www.googleapis.com/books/v1/volumes/#{googlebooksapi_id}" end endcontrollerにincludeする
app/controllers/books_controller.rbclass BooksController < ApplicationController include GoogleBooksApi (略) endbooks_controller内で、GoogleBooksAPIというmoduleをincludeしたので、そのmoduleの関数が使えます。
app/libというディレクトリが自作なので、
「そもそもgoogle_books_api.rbを読み込んでくれるの?」
と不安に思うかもしれません。しかし、実はRailsでは自動的に
app/〇〇(ディレクトリ)/〇〇.rbを読み込んでくれるという仕様があるようです。
※ さらに階層が深くなるとNGみたいです。その2.APIの構造のうち、itemを理解する
Google Books APIからは下記のように色々な情報が入ってるので、APIに慣れていないと混乱するかもしれません。
https://www.googleapis.com/books/v1/volumes?q=Rails先に結論を書きます。
①https://www.googleapis.com/books/v1/volumes?q=#{keyword}では以下が取り出せます。
{ (他のハッシュ),
"items" => [{ item1 }, { item2 }, { item3 }, ....] }②
https://www.googleapis.com/books/v1/volumes/#{googlebooksapi_id}
では以下が取り出せます。
{ item }①本を検索するAPIも、②IDから本情報を取得するAPIも、結局itemを取り出せるわけです。
(このitemの中に、本1つの情報が入っています)
このitemに対してのロジックを書くだけで、①本を検索するAPIに対しても、②IDから本情報を取得するAPIに対しても、共通のロジックを使うことができます。
よって①本を検索するAPIに対しては、以下のように処理するのが良いと思われます。(検索するAPIから得たjson文字列)["items"].each do |item| (itemに対するロジック) endその3.検索時にはActiveRecordのオブジェクトを作らないようにする
これは私がやってしまったアンチパターンです。
最終的には検索した本をActionViewで使う際に、
render @books
あるいは
@books.each do |book|
のような繰り返し処理を書きたいかと思います。その際に、以下のようにActiveRecordのオブジェクトを大量生成するロジックを作ってしまいました。
ActiveRecordから@booksを作る(アンチパターン)
books_controllerdef search @books = [] (items).each do |item| (中略) @books << Book.new( author: (itemから引っ張ってきた著者) title: (itemから引っ張ってきたタイトル) (その他 略) ) end endFat controllerになる以外にも、これの問題点は2つあります。
- ActiveRecordのインスタンス生成(
Book.new)はコストが高いのに、それを繰り返し処理させている- モデルと同じattirbuteを使わなくてはいけない
1の解説
実際にやってみるとわかります。
検索し始めてから結果が表示されるまで5〜10秒くらいかかってて、UXが悪かったです。参考(理解はできてません笑):
ActiveRecordのパフォーマンス・チューニング2の解説
例えば検索結果としては詳細な情報が表示できるようにしたいが、アプリのDBに保存させるつもりは無い、というような場合があります。
パッと思いつくのは、
averageRating: 4.0
amount: 3960.0
みたいな情報でしょうか。レーティングや値段はその時々で変わるので、DBに保存しようとは思わないでしょう。こういう場合は検索結果表示のときだけ
book.averageRatingでレーティングを返せるようにし、DBには保存はしない、という設計が思いつきます。
しかし、ActiveRecordを使うとDBにも同一カラムが存在する必要がある、というわけです。2つの問題点の原因
実は1,2とも原因は共通していて、要はActiveRecordはO/Rマッパーであるからです。
- 検索結果はDBに保存するわけではありません(=DBを使いません)
- ActiveRecordはView <-> DBの仲介役(O/Rマッパー)です。
- よってActiveRecordを使う必要はありません。(少なくともそういう設計になってません)
ということになります。
その4.APIの情報は、オレオレクラス内に格納する
その3に対して、では具体的にどうするかをお伝えします。
結論から言うと、以下のようなオレオレクラスを作成しました。オレオレクラス
app/lib/google_book.rbclass GoogleBook attr_reader :googlebooksapi_id, :author, :buy_link, :description, :image, :published_at, :title class << self include GoogleBooksApi def new_from_id(googlebooksapi_id) url = url_of_creating_from_id(googlebooksapi_id) item = get_json_from_url(url) new(item) end def search(keyword) url = url_of_searching_from_keyword(keyword) json = get_json_from_url(url) books = [] if items = json['items'] items.each do |item| books << GoogleBook.new(item) end end books end end def initialize(item) @item = item @volume_info = @item['volumeInfo'] retrieve_attribute end def retrieve_attribute @googlebooksapi_id = @item['id'] @author = @volume_info['authors'].first @buy_link = @item['saleInfo']['buyLink'] @description = @volume_info['description'] @image = @volume_info['imageLinks']['smallThumbnail'] @published_at = @volume_info['publishedDate'] @title = @volume_info['title'] end end重要なポイントはitemを引数にinitializeができるようにすることです。
その2でも述べたように、itemに対して同じ処理をするように心がければ、共通のロジックを用いることができます。使用例
これにより、以下のようにオブジェクト志向っぽく扱えるようになります。
book = GoogleBook.new_from_id("axicQgAACAAJ") book.title => "影響力の武器" book.author => "ロバート・B. チャルディーニ" books = GoogleBook.search("影響力の武器") => [ book1, book2, book3, .... ](例) book = books.first book.id => "axicQgAACAAJ" book.title => "影響力の武器"このクラスはインスタンス生成にかかるコストは大したことはありません。
よってその3のような、ActiveRecordのインスタンスを複数生成時に発生していたコストも解消できています。注意点
books = GoogleBook.search
のbooksのクラスは、ただのArrayです。そのままではgem kaminariによるpaginateとか、render @booksとかが出来ません。
以下のように続けて書くことで、kaminariのpaginateを利用できます。@books = Kaminari.paginate_array(books).page(params[:page])その5.リソース登録時にはモデルにロジックを書く
上記で作ったGoogleBookクラスを使って、いざDBに本を保存するロジックを書こうとすると、これまたFat controllerになりがちです。
app/controllers/books_controller.rbdef create google_book = GoogleBook.new_from_id(取ってきたid) @book = Book.new( author: google_book.author title: google_book.title (その他 略) ) if @book.save (以下略) endcontrollerというのは、DBの情報を知りすぎない、というのが良い設計らしいです。上のような書き方は「DBにこれとこれが入るんでしょ」って言ってしまっています。
実装
app/controllers/books_controller.rbdef create google_book = GoogleBook.new_from_id(取ってきたid) @book = current_user.books.build @book = @book.substitute_for_googlebook(google_book) if @book.save (以下略) endapp/models/book.rbdef substitute_for_googlebook(google_book) self.author = google_book.author self.description = google_book.description self.googlebooksapi_id = google_book.googlebooksapi_id self.published_at = google_book.published_at self.title = google_book.title self.buy_link = google_book.buy_link self.image = google_book.image self end割とcontrollerはスッキリできたのでは無いでしょうか。
といいながら実はその5はあんまり自信無いです笑
もうちょっと上手くできる気がします。


