- 投稿日:2020-12-07T23:37:32+09:00
ユーザー名の表示
sessionを使ってユーザー名の表示をさせる方法
前提条件
1.userテーブルがあること。DBのことsession[:user_id]の値をもとにしてログイン中のユーザー情報をDB(データベース)から取得する。
その際には、find_byメソッドを使い、userテーブルからidカラムの値がsession[:user_id]と同じユーザーを取得して、変数に代入する。つまり?
*DBに登録されているユーザーのidを探してsession[:user_id]と同じであれば代入される形【使い方】
layouts/application.html.erb<% cureent_user = User.find_by(id:session[:user_id]) %>
<% = link_to(cureent_user.name, "/users/#{current_user.id}") %>
*cureent_userは変数だからここは自由
今回はこれでおしまい
さあ寝よう
- 投稿日:2020-12-07T23:25:03+09:00
WSL2上で動くUbuntu20.04でrbenvでのrubyインストールが失敗する問題への対応
問題
タイトル通りですが、Windows10のWSL2上で動くUbuntu20.04でrbenvでのrubyインストールが失敗する問題に直面しました。
環境
- Windows10 Pro 64bit
- WSL2
- Ubuntu 20.04.1 LTS
- ruby-build 20201118(以下のコマンドよりrbenvに問題ないこと確認済み)
$ curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash Checking for `rbenv' in PATH: /home/linuxbrew/.linuxbrew/bin/rbenv Checking for rbenv shims in PATH: OK Checking `rbenv install' support: /home/linuxbrew/.linuxbrew/bin/rbenv-install (ruby-build 20201118) Counting installed Ruby versions: none There aren't any Ruby versions installed under `/home/rikoroku/.rbenv/versions'. You can install Ruby versions like so: rbenv install 2.2.4 Checking RubyGems settings: OK Auditing installed plugins: OK実行コマンド
$ rbenv install 2.6.3 perl: warning: Setting locale failed. perl: warning: Please check that your locale settings: LANGUAGE = (unset), LC_ALL = (unset), LANG = "ja_JP.UTF-8" are supported and installed on your system. perl: warning: Falling back to the standard locale ("C"). Downloading ruby-2.6.3.tar.bz2... -> https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.3.tar.bz2 Installing ruby-2.6.3... ruby-build: using readline from homebrew BUILD FAILED (Ubuntu 20.04 using ruby-build 20201118) Inspect or clean up the working tree at /tmp/ruby-build.20201207224042.2086.9ACiG6 Results logged to /tmp/ruby-build.20201207224042.2086.log Last 10 log lines: /tmp/ruby-build.20201207224042.2086.9ACiG6/ruby-2.6.3 /tmp/ruby-build.20201207224042.2086.9ACiG6 ~ checking for ruby... false checking build system type... x86_64-pc-linux-gnu checking host system type... x86_64-pc-linux-gnu checking target system type... x86_64-pc-linux-gnu checking for gcc... gcc checking whether the C compiler works... no configure: error: in `/tmp/ruby-build.20201207224042.2086.9ACiG6/ruby-2.6.3': configure: error: C compiler cannot create executables See `config.log' for more details解決方法
以下のコマンドを実行し解決しました。タイトルにWSL2とありますが関係なさそうですね。。
$ sudo apt-get install autoconf bison build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm6 libgdbm-dev libdb-dev問題解決までの過程
不要だとは思いますが、参考までに解決までの過程を記載しておきます。
1. config.logを確認
エラー文
See 'config.log' for more details
にもある通り、まずはこのファイルを確認しました。$ less /tmp/ruby-build.20201207222731.172.GUu0j7/ruby-2.6.3/config.log一瞬、ファイルどこにあるの?と迷いましたが、
less
コマンドで/tmp/
ディレクトリにいきtab保管したところディレクトリがあるのに気づき、ディレクトリに入ったところconfig.log
ファイルがありました。$ less /tmp/ruby-build.2020120722 ruby-build.20201207222731.172.GUu0j7/ ruby-build.20201207223551.918.log ruby-build.20201207222731.172.log ruby-build.20201207224042.2086.9ACiG6/ ruby-build.20201207223551.918.TMNgQy/ ruby-build.20201207224042.2086.logちなみに
config.log
ファイルの中身を見ましたが、Cのヘッダーファイルの情報や変数情報、プラットフォームの情報などがありましたが、僕的には有意な情報を得られませんでした。2. error: C compiler cannot create executables で検索
https://github.com/rbenv/ruby-build/issues/779 にいきつき、Did you follow the instructions on the wiki for Fedora and try again? のコメントを発見。wikiに遷移。
3. ruby-buildのwikiのTroubleshootingを参考
C compiler cannot create executables の説明より、ビルドに必要なパッケージがインストールされてない可能性があることを知り、Suggested build environmentから「解決方法」で実行したコマンドを把握しました。
- 投稿日:2020-12-07T22:43:17+09:00
10の位・1の位を計算するプログラム
本日はこの問題についてです。
二桁の整数を入力すると、十の位と一の位の数字の足し算、十の位と一の位の数字の掛け算をそれぞれ行い、
最後に2つの結果を足し合わせて出力するプログラムをRubyで作成してください。
なお、2桁の数字以外が入力として与えられた場合を考慮する必要はありません。例)
> 二桁の整数を入力してください > 15 # 1 + 5 と 1 × 5 をそれぞれ計算 > 足し算結果と掛け算結果の合計値は11です以下模範回答
rubydef addition(a, b) a + b end def multiplication(a,b) a * b end def slice_num(num) # 10の位 tens_place = (num / 10) % 10 # 1の位 ones_place = num % 10 return tens_place, ones_place end puts "二桁の整数を入力してください" input = gets.to_i X, Y = slice_num(input) add_result = addition(X, Y) multiple_result = multiplication(X, Y) puts "足し算結果と掛け算結果の合計値は#{add_result + multiple_result}です"解説
ある整数について、
整数を10で割った計算結果の余りが1の位、
整数を10で割ったものを更に10で割った計算結果の余りが10の位であるという法則を利用して、
slice_numメソッドでは10の位と1の位を取得しています。例えば「15」の場合
15/10=1.5の小数点以下の「5」が一の位、
15/10=1.5/10=0.15の小数点以下の「1」が十の位として扱うことができます。また、変数を定義する際に、カンマで区切ることによって、同時に複数の変数を定義することができます。
今回は、X, Y に10の位、1の位を同時に代入しています。また、全ての処理を1つのメソッド内で行っても構いません。
- 投稿日:2020-12-07T22:28:58+09:00
第11回(class化)
お題:class化
解法:最初のclass
def puts_hello name puts "Hello #{name}." end def gets_name name = ARGV[0] || 'world' return name end name = gets_name puts_hello nameこれをクラスで書くと
class Greeter def initialize @name = gets_name puts_hello end def puts_hello #salute puts "Hello #{@name}." end def gets_name name = ARGV[0] || 'world' return name.capitalize end end Greeter.new> ruby hello_class.rb Hello world
- Greeter.new
- class Greeter
- initialize method
- @name
@nameはインスタンス変数というらしい.
classはmain loopを無くして代わりにclassにし,イニシャライズを定義したもの.
解説(指向):method的かobject思考的か
methodとclassを比べるとオブジェクト指向の書き方の流儀の違いが実感できるらしい.
method名をverbとすると,
- methodでは,verb(subject, object)
- object指向では, subject.verb(object)とりあえずサンプルコードを見る.
上から,method, 継承を使ったclass, classへの上乗せ(override)
require 'colorize' # method def hello(name) "Hello #{name}." end # inherited class class Greeter < String def hello "Hello #{self}." end end # extend class class String def hello "Hello #{self}." end end # method call name = ARGV[0] puts hello(name).green # inherited class call greeter = Greeter.new(ARGV[0]) puts greeter.hello.green # extend class call, override puts ARGV[0].hello.greenStringをGreeterに継承しているため,GreeterはStringのメソッドを扱える.
overrideに関しては,親クラスで定義しているメソッドと同名のメソッドを子クラスで再定義するものと思っていたためここでの使い方はいまいち理解できていない.要勉強.
- source ~/grad_members_20f/members/yoshida/c6_class_hello.org
- 投稿日:2020-12-07T22:13:22+09:00
RubyのHashに対するメソッドまとめ
はじめに
rubyのhashに対するメソッドで、普段あまり使わないメソッドをまとめてみました。
compact
valueがnilのものを取り除きます。
user = { id: 1, name: nil } user.compact # => {:id=>1}dig
valueにhashがある場合に、その値を取得する
user = { user: { id: 1 } } # => {:user=>{:id=>1}} user.dig(:user, :id) # => 1equal
同じオブジェクトの場合にtrueを返す
h1 = {} h2 = {} h1.equal?(h2) # => false h1.equal?(h1) # => trueinvert
keyとvalueを入れ替える
h = { id: 1 } # => {:id=>1} h.invert # => {1=>:id}assoc
keyの検索を行い、無い場合はnil、あった場合はkeyとvalueの配列を返す
h = { id: 1, name: 'bob', age: 12 } h.assoc(:id) # => [:id, 1]rassoc
valueの検索を行い、無い場合はnil、あった場合はkeyとvalueの配列を返す
h = { name: 'bob', age: 12 } # => {:name=>"bob", :age=>12} h.rassoc('bob') # => [:name, "bob"]replace
対象のhashを別のhashの内容に置き換える
h1 = { id: 1 } h2 = { id: 2 } h1.replace(h2) # => {:id=>2} h1 # => {:id=>2}slice
対象のkeyのみを取り出す
h1 = { id: 1, name: 'bob', age: 12 } h1.slice(:id) # => {:id=>1}store
hashにkeyとvalueを設定する
h = {} h.store(:id, 1) h # => {:id=>1}transform_keys
それぞれのkeyの値を書き換える
h = { id: 1, name: 'bob', age: 12 } h.transform_keys(&:to_s) # => {"id"=>1, "name"=>"bob", "age"=>12}transform_values
それぞれのvalueの値を書き換える
h = { id: 1, name: 'bob', age: 12 } h.transform_values(&:to_s) # => {:id=>"1", :name=>"bob", :age=>"12"}
- 投稿日:2020-12-07T22:03:41+09:00
【備忘録】deviseの導入
deviseとは?
Railsアプリケーションに認証機能(ユーザー登録機能、ログイン機能、ログアウト機能)を実装することができるgemです。数回のコマンドを実行するだけで簡単に認証機能を実装することができます。
導入の流れ
1. Gem導入
Gemfileに追記。
Gemfile.gem 'devise'bundle install実行。
bundle install2. インストール
アプリケーションにdeviseをインストール。ここは忘れがちなので注意!
rails g devise:install3. Userモデルの作成
rails g devise userモデル作成と同時に、マイグレーションファイルやルーティングも自動的に作成されます。
4. 必要に応じてカラムを追加
deviseを介してモデルを作成した場合、emailとpasswordはデフォルトで用意されているので、他に必要なカラムがある時は追加します。
5. マイグレーション実行
マイグレーションファイルの内容をテーブルに反映させます。
rails db:migrate6. ビューの作成
新規登録・ログイン用のビューを作成します。
rails g devise:viewsこんな感じのビューが出来上がります⬇️
新規登録画面
ログイン画面
- 投稿日:2020-12-07T21:46:44+09:00
いいね機能を非同期で実装する方法
はじめに
前回いいね機能を実装しました。
ただ、いいねをするたびにページがリダイレクトされてしまいます。
そこで非同期通信という通信方法を使い、リダイレクトすることなくにビューに反映できるようにしていきたいと思います。
少しだけjQueryの知識が必要になります。こんな人に向けて
1.いいね機能を実装済みの人。
2.非同期通信での実装をしたい人。1.前提条件
・いいね機能を実装済みであること(実装していない人は こちら)
(前回のいいね機能を改良していきます)2.実装準備
非同期通信を実装するにあたって、新たなgemをインストールする必要があります。
Gemfilegem "jquery-rails"ターミナルbundle installapp/assets/javascripts/application.js//= require jquery (←この行を新しく記載) //= require rails-ujsjQueryというJavaScriptで作成されたライブラリを使うので、上記の方法でインストールしてください。
これで準備完了です。さっそく実装していきます。
3.非同期通信でのいいね機能の実装
3-1.部分テンプレートの作成
前回は投稿一覧画面のみにいいね機能をつけました。
ただ実際は投稿の詳細画面やマイページにも機能をつけると思います。
ビューごとに毎回同じ記述をしても動きますが、非同期通信をするときに大変になります。
なので部分テンプレートを作成してまとめます。作成方法を
①部分テンプレートのファイルをつくる
②ファイル内にコードを書き写す
③作成したファイルをビューに反映させる
の3つに分けて説明します。3-1-1.部分テンプレートのファイルをつくる
作成場所は
app/views
下の各フォルダ内であればどこでも大丈夫です。
app/views/posts
の中でもapp/views/layouts
の中でも動きます。
ただ今回はいいね機能ということでapp/views/favorites
の中に作ります。(favoritesフォルダは前回の2-2の作業で自動作成されます。)
各フォルダにカーソルを合わせ右クリックでファイルの新規作成をし、_favorite.html.erb
と名前をつけます。
(先頭にアンダーバーをつけて .html.erb で終わればなんでもOKですが、何の部分テンプレートかファイル名で分かるのがベター。)3-1-2.ファイル内にコードを書き写す
作成したファイルにコードを書いていきます。
前回の2-7で書いたコードを利用しましょう。app/views/favorites/_favorite.html.erb<% if post.favo?(current_user) %> <%= link_to favorites_path(post), method: :delete do %> ♥ <%= post.favorites.count %> <% end %> <% else %> <%= link_to favorites_path(post), method: :post do %> ♡ <%= post.favorites.count %> <% end %> <% end %>いいね機能のみを部分テンプレート化するので、each文は除きました。
(今回の説明では登場しませんが、部分テンプレート内にインスタンス変数(例:@post)がある場合、ローカル変数(例:post)に書き換える必要があります。)3-1-3.作成したファイルをビューに反映させる
app/views/posts/index.html.erb<% @posts.each do |post| %> <%= render "favorites/favorite", post: post %> <% end %>上記のように書きます。
<%= render "favorites/favorite", post: post %>分解して説明すると、
まずrenderを使って特定のファイルを呼び出します。その特定のファイルというのが
"favorites/favorite"
で指定しているものです。
(app/views/favorites/_favorite.html.erb
を作成しましたが、views以下のフォルダ及びファイル名の2つをここに記述しています。その際にアンダーバーは記述しません。)そして最後に
post: post
で変換作業をしています。
(②の最後に説明しましたが、②でインスタンス変数をローカル変数に変えた場合、ローカル変数を再度インスタンス変数に戻す作業をここでします。その際は<%= render "favorites/favorite", post: @post %>と記述します。今回は
post: post
としてください。)これで部分テンプレートの完成です。
3-2.ビューを非同期化し、コントローラを変える
app/views/favorites/_favorite.html.erb<% if post.favo?(current_user) %> <%= link_to favorites_path(post), method: :delete, remote: true do %> ♥ <%= post.favorites.count %> <% end %> <% else %> <%= link_to favorites_path(post), method: :post, remote: true do %> ♡ <%= post.favorites.count %> <% end %> <% end %>app/controllers/favorites_controller.rbdef create post = Post.find(params[:post_id]) favorite = Favorites.new(post_id: post.id) favorite.user_id = current_user.id favorite.save end def destroy post = Post.find(params[:post_id]) favorite = current_user.favorites.find_by(post_id: post.id) favorite.destroy end各
link_to
にremote: true
を付け加えて非同期化しています。
また、コントローラのredirect_to request.referer
を消しています。今まではリンクが押された際に
favorites_controller.rb
のcreateアクションまたはdestroyアクションに処理がとび、redirect_toのもとページがリクエストされていました。それがビューに
remote: true
を付け加え、redirect_toを消したことにより、次のリクエスト先がjsファイルに変わります。
jsとはjavascriptのことですが、少し説明したjQueryを使います。3-3.jsファイルの作成
ここではリクエスト先のjsファイルをつくっていきます。
部分テンプレートの時と異なり、特定のフォルダ下に作成します。
今回はfavoritesコントローラから飛ぶリクエスト先なので、app/views/favorites
下にcreate.js.erb
とdestroy.js.erb
をつくります。
(ファイル名はアクション名と同じものにします。like.js.erb
などとファイル名を異なったものにするとエラーが起きます。)
jsファイル内に記述していく前にビューを整えていきます。3-3-1.ビューのセレクタ設定
jsファイル内で処理を記述していきますが、cssのように「どこの要素に何の処理をするか」と指示します。
この「どこの要素に」ですが少し工夫をする必要があります。
例えば下記のようにしてcssセレクタを設定するとします。app/views/posts/index.html.erb<% @posts.each do |post| %> <div id="iine"><%= render "favorites/favorite", post: post %></div> <% end %>こうしてしまうと仮に1つのいいねボタンを押したときに、全てのいいねボタンが反応してしまいます。
それを防ぐために既にあるeachを利用して、投稿ごとにcssセレクタを設定できるようにします。
app/views/posts/index.html.erb<% @posts.each do |post| %> <div id="post_<%= post.id %>"><%= render "favorites/favorite", post: post %></div> <% end %>
<%= post.id %>
の部分が投稿によって変わるようにしました。
これでいいねボタンが個別のものとして認識されるようになります。3-3-2.jsファイルの記述
それではjsファイルを記述していきます。
app/views/favorites/create.js.erb$("#post_<%= @post.id %>").html("<%= j(render 'favorites/favorite', post: @post) %>")jQueryの書き方は上記のような形になります。
$("#post_<%= @post.id %>")
はどこを変えるかという記述になっており、cssとあまり変わりません。
仮に<div class="post_<%= post.id %>">
とあれば$(".post_<%= @post.id %>")
となります。
.html("<%= j(render 'favorites/favorite', post: @post) %>")
でどう変えるかを指定しています。分解して説明します。
.html()
で先ほど指定した部分の中身を変えることを宣言しています。
j()
は部分テンプレートをjsファイルに読み込む際に使うものです。escape_javascript()
と書かれているものもありますが同じ意味です。
render 'favorites/favorite', post: @post
は3-1で説明したとおりです。destroyも同様に記述します。
app/views/favorites/destroy.js.erb$("#post_<%= @post.id %>").html("<%= j(render 'favorites/favorite', post: @post) %>")3-4.コントローラの追記
最後にコントローラ内に記述を加えて完成です。
3-3で作成したjsファイルですがこのままは使えません。
理由はfavoriteコントローラ内で@post
が定義されていないからです。「
$("#post_<%= @post.id %>")
ってあるけど、@post
って何?」とエラーが出ます。中身はjsですがビューと同じなので、コントローラ内で変数定義をしなければ使うことができません。
またビューで使うものなのでローカル変数ではなくインスタンス変数で記述する必要があります。app/controllers/favorites_controller.rbdef create @post = Post.find(params[:post_id]) post = Post.find(params[:post_id]) favorite = Favorites.new(post_id: post.id) favorite.user_id = current_user.id favorite.save end def destroy @post = Post.find(params[:post_id]) post = Post.find(params[:post_id]) favorite = current_user.favorites.find_by(post_id: post.id) favorite.destroy endこれで完成です。
4.終わりに
これでいいね機能の実装は完成しました。
コメント機能も同じような考え方でできます。
異なる点はいいね機能ではlink_toを使いましたが、コメント機能はform_withを使うことです。
少しだけややこしくなりますが、作る流れは基本的に一緒だと思います。
- 投稿日:2020-12-07T21:35:41+09:00
GraphQL アンチパターン - 孫煩悩 -
この記事は GraphQL Advent Calendar 2020 7 日目の記事です。
前回の記事は @indigolain さんの クエリ結果を軸としたGraphQLのエラーハンドリング でした。概要
孫の面倒を見すぎると分岐が大変になるので、Loader を活用して宣言的に書くとよさそう、という話です。
例としては Ruby (graphql-ruby) で示しますが、考え方は他の言語、ライブラリでも同じになるはずではないかと考えています。詳細
Tweet にいいねする機能があるとします。
Tweet 1-* Like *-1 User
このとき、タイムラインで Tweet を並べつつ、各々の Tweet を自分がいいねしたかどうかを表示するとします。
type Query { timelineTweets: [TimelineTweet!]! } type TimelineTweet { id: ID! liked: Boolean! }一般的に REST API を実装するように、最初に必要なものすべてを取ってくるアプローチを考えると、例えば次のように JOIN すると一発で全部取ってこれます。
Ruby (ActiveRecord) がわからない方向けに、どういう SQL になるかも書いてあります。
class TimelineTweet < GraphQL::Schema::Object field :id, ID, null: false field :liked, Boolean, null: false def liked # sqlite だと bool がないので変換 object.liked == 1 end end class QueryType < GraphQL::Schema::Object field :timeline_tweets, '[TimelineTweet]', null: true def timeline_tweets # SELECT tweets.*, (likes.id IS NOT NULL) AS liked FROM "tweets" LEFT OUTER JOIN "likes" ON "likes"."tweet_id" = "tweets"."id" WHERE ("likes"."user_id" = ? OR "likes"."user_id" IS NULL) [["user_id", 1]] Tweet.left_outer_joins(:likes).select("tweets.*, (likes.id IS NOT NULL) AS liked").merge(Like.where(user_id: [nil, context[:current_user].id])) end end class Schema < GraphQL::Schema query QueryType endしかし、GraphQL では、クライアントのクエリを見て必要なデータが変わってきます。
クエリによってはliked
は必要かもしれませんし、必要でないかもしれません。
liked
が必要かどうかは、例えば graphql-ruby では lookahead という方法で、どの field が要求されているかをチェックすることが可能です。class QueryType < GraphQL::Schema::Object field :timeline_tweets, '[TimelineTweet]', null: true, extras: [:lookahead] def timeline_tweets(lookahead:) # SELECT tweets.*, (likes.id IS NOT NULL) AS liked FROM "tweets" LEFT OUTER JOIN "likes" ON "likes"."tweet_id" = "tweets"."id" WHERE ("likes"."user_id" = ? OR "likes"."user_id" IS NULL) [["user_id", 1]] if lookahead.selects?(:liked) Tweet.left_outer_joins(:likes).select("tweets.*, (likes.id IS NOT NULL) AS liked").merge(Like.where(user_id: [nil, context[:current_user].id])) else Tweet.all end end end分岐が苦しいですね。
このように、親が子や孫の面倒を見すぎるとパラメータのバリエーションが豊富で複雑に絡み合った REST API のようになってしまいます。GraphQL では、このような場合には立ち止まって loader として切り出すことを考えたほうがよいです。
ここでは graphql-batch を使いますが、これも他の言語にも loader/data loader/batch loader といった名前で調べると何かライブラリがあるはずです。class LikesLoader < GraphQL::Batch::Loader def initialize(user_id) @user_id = user_id end def perform(tweet_ids) Like.where(tweet_id: tweet_ids, user_id: @user_id).each {|l| fulfill(l.tweet_id, true) } tweet_ids.each {|id| fulfill(id, false) unless fulfilled?(id) } end end class TimelineTweet < GraphQL::Schema::Object field :id, ID, null: false field :liked, Boolean, null: false def liked LikesLoader.for(context[:current_user].id).load(object.id) end endこうすれば、timeline_tweets から分岐が消えます。
class QueryType < GraphQL::Schema::Object field :timeline_tweets, '[TimelineTweet]', null: true def timeline_tweets Tweet.all end endまとめ
GraphQL resolver の実装が難しくなってきたら、孫の面倒を見すぎていないか考えてみましょう。
とはいえ、GraphQL において、親がどの程度まで孫の面倒を見るべきかの線引は難しいです。
孫の面倒は見ないほうがいいと必ずしも言い切ることはできません。
例えば今回の例でも、Tweet.all
は暗黙的に TimelineTweet の id を読み込んでいると言えます。私もまだ明確に言語化できてはいませんが、一つの孫煩悩の指標として「JOIN クエリ」があるのではないかと考えています。
コード全体
保存して
$ ruby foo.rb
すればそのまま実行可能です。require 'bundler/inline' gemfile do source 'https://rubygems.org' gem 'graphql' gem 'activerecord', require: 'active_record' gem 'sqlite3' gem 'graphql-batch' end require 'logger' ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') ActiveRecord::Base.logger = Logger.new(STDOUT) ActiveRecord::Schema.define do create_table :tweets, force: true do |t| end create_table :likes, force: true do |t| t.references :tweet t.references :user end create_table :users, force: true do |t| end end class Tweet < ActiveRecord::Base has_many :likes end class Like < ActiveRecord::Base belongs_to :tweet belongs_to :user end class User < ActiveRecord::Base has_many :likes end user = User.create! tweet_1 = Tweet.create! tweet_2 = Tweet.create! tweet_3 = Tweet.create! Like.create!(user: user, tweet: tweet_1) Like.create!(user: user, tweet: tweet_3) class LikedLoader < GraphQL::Batch::Loader def initialize(user_id) @user_id = user_id end def perform(tweet_ids) Like.where(tweet_id: tweet_ids, user_id: @user_id).each {|l| fulfill(l.tweet_id, true) } tweet_ids.each {|id| fulfill(id, false) unless fulfilled?(id) } end end class TimelineTweet < GraphQL::Schema::Object field :id, ID, null: false field :liked, Boolean, null: false def liked LikedLoader.for(context[:current_user].id).load(object.id) end end class QueryType < GraphQL::Schema::Object field :timeline_tweets, '[TimelineTweet]', null: true def timeline_tweets Tweet.all end end class Schema < GraphQL::Schema query QueryType use GraphQL::Batch end result = Schema.execute(<<~GQL, context: {current_user: user}) query { timelineTweets { id liked } } GQL pp result.as_json # {"data"=> # {"timelineTweets"=> # [{"id"=>"1", "liked"=>true}, # {"id"=>"2", "liked"=>false}, # {"id"=>"3", "liked"=>true}]}}
- 投稿日:2020-12-07T21:28:03+09:00
accepts_nested_attributes_for メソッド:編集画面の不具合を解消!
前回に引き続きaccepts_nested_attributes_for メソッドについての記事になります。
結論
「accepts_nested_attributes_for を使うときは、controller の strong parameter に注意。permit の中に関連づけたメソッドの id を追加しておく」という結論になります。
起きていた現象
Railsで accepts_nested_attributes_for メソッドを使い作成したWebサービスのサイトにて。
index、new、show、deleteなどは問題なく行えるが、編集画面(edit, update)での挙動で異常が発生。前回の記事でいうと、編集画面を表示させるたびに、子メソッドの タイトル(title), 記事内容(content), ブログ番号(blog_num)がデーターベースに2重に登録され、同じ記事が2つずつ登録されてしまうのです。
確認したこと
ヒントを探してターミナルを確認。すると、今回は早速それらしき内容が表示されていました。
Unpermitted parameter: :id
これは、以前の記事(画像アップロードでエラーメッセージの重要性を再認識した話)と同じエラー、つまりstrong parameter で id という要素が許可されていないみたいです。
確かに、strong parmeter をチェックしても、、、
user_controller.rbdef user_params params.require(:user).permit(:name, :address, :age, blogs_attributes: [:title, :content, :blog_num]) end:id と言う記述をしていません。
エラーを修正
不具合が発生しているのが子メソッドなので、strong parameter の小メソッド(blogs_attributes)の中に :id と言う記述を追加します。
user_controller.rbdef user_params params.require(:user).permit(:name, :address, :age, blogs_attributes: [:id, :title, :content, :blog_num]) endこれで子メソッドの同じ記事が2回ずつ登録される不具合は解消しました。
不具合の背景
この不具合、accepts_nested_attributes_for メソッドを使う場合に起こりがちらしいのですが、なぜこのような不具合が起きるのか、公式リファレンスの類にははっきりした解説のようなものはありませんでした。
私の調べた限り、Rails は パラメーターに :id が含まれているかどうかで create アクションか update アクションを判断しているようなので、「update アクションの時にはパラメーターに id を含める」と覚えてしまうことで乗り切ろうと思います。
はっきりした原因を解説できるようになったら、またその時に解説記事を。
- 投稿日:2020-12-07T21:28:03+09:00
accepts_nested_attributes_for メソッド:編集画面不具合の原因は。
前回に引き続きaccepts_nested_attributes_for メソッドについての記事です。Ruby on Rails を使っての開発になります。
結論
「accepts_nested_attributes_for を使うときは、controller の strong parameter に注意。permit の中に関連づけたメソッドの id を追加しておく」という結論になります。
起きていた現象
Railsで accepts_nested_attributes_for メソッドを使い作成したWebサービスにて。
index、new、show、deleteなどは問題なく行えるが、編集画面(edit, update)での挙動で異常が発生。前回の記事でいうと、編集画面を表示させるたびに、子メソッドの タイトル(title), 記事内容(content), ブログ番号(blog_num)がデーターベースに2重に登録され、同じ記事が2つずつ登録されてしまうのです。
確認したこと
ヒントを探してターミナルを確認。すると、今回は早速それらしき内容が表示されていました。
Unpermitted parameter: :id
これは、以前の記事(画像アップロードでエラーメッセージの重要性を再認識した話)と同じエラー、つまりstrong parameter で id という要素が許可されていないみたいです。
確かに、strong parmeter をチェックしても、、、
user_controller.rbdef user_params params.require(:user).permit(:name, :address, :age, blogs_attributes: [:title, :content, :blog_num]) end:id と言う記述をしていません。
エラーを修正
不具合が発生しているのが子メソッドなので、strong parameter の小メソッド(blogs_attributes)の中に :id と言う記述を追加します。
user_controller.rbdef user_params params.require(:user).permit(:name, :address, :age, blogs_attributes: [:id, :title, :content, :blog_num]) endこれで子メソッドの同じ記事が2回ずつ登録される不具合は解消しました。
不具合の背景
この不具合、accepts_nested_attributes_for メソッドを使う場合に起こりがちらしいのですが、なぜこのような不具合が起きるのか、公式リファレンスの類にははっきりした解説のようなものはありませんでした。
私の調べた限り分かったことは「 accepts_nested_attributes_for を使用した場合、 パラメーターに :id が含まれているかどうかで create アクションか update アクションかが決まるらしい」ということです。
「update アクションの時にはパラメーターに id を含める」と覚えてしまうことで乗り切ろうと思います。
はっきりした原因を解説できるようになったら、またその時に解説記事を。
- 投稿日:2020-12-07T21:01:14+09:00
Ruby インスタンス変数を外部から操作する
ゲッターや、セッターを調べていたら
まずインスタンス変数の外部操作がでてきたので
そこをまず調べてみるインスタンスメソッドの定義
クラス構文の内部でメソッドを定義すると、そのメソッドは
インスタンスメソッド
になります
そのクラスのインスタンスに対して、呼び出すことができるメソッドのことclass User def hello "Hello!" end end user = User.new user.hello #=> "Hello!"インスタンス変数とアクセサメソッド
クラスの内部では、インスタンス変数に使うことができます
インスタンス変数とは、同じインスタンスの内部で共有される変数のことclass User def initialize(name) @name = name end def hello "Hello, I am #{@name}" end end user = User.new('太郎') user.hello #=> "Hello I am 太郎"ローカル変数では、値を代入する前にいきなり参照してもエラーにならない
class User def initialize(name) # インスタンス変数の代入をコメントアウトしてみる # @name = name end def hello "Hello, I am #{@name}" end end user = User.new('太郎') #@nameを参照すると、「nil」になる、つまり名前の部分に何も出力されない user.hello #=> "Hello I am "なので、タイポとかにも気をつける
参照用メソッド
インスタンス変数は、クラスの外部から参照できない
もし、参照したい場合は参照用のメソッドを作成する必要があるclass User def initialize(name) # インスタンス変数の代入をコメントアウトしてみる # @name = name end #@nameを外部から参照するメソッド def name @name end end user = User.new('太郎') user.name #=> "太郎"変更用メソッド
また、インスタンス変数の内容を外部から変更したい場合も
変更用のメソッドを定義する必要があるclass User def initialize(name) # インスタンス変数の代入をコメントアウトしてみる # @name = name end #@nameを外部から参照するメソッド def name @name end #@nameを外部から変更するメソッド def name=(value) @name = value end end user = User.new('太郎') #変数に代入しているように見えるが実際には、メソッドを呼び出している user.name = 次郎 user.name #=> "次郎"参考記事、書籍
チェリー本
- 投稿日:2020-12-07T20:16:02+09:00
【剰余演算子】%ってなんだっけ?編
読んでなるほど!と思うのに、しばらくするとなんだっけ?ってなるシリーズその2
本日読んでいて忘れていた部分を抜粋。
剰余演算子は馴染み深い「 +-*/ 」以外に「 % 」というのがいることを忘れていました。number.jsconst num = 60 if (num % 15 == 0) { console.log(`${num}は3と5の倍数です`) } else if (num % 3 == 0) { console.log(`${num}は3の倍数です`) } else if (num % 5 == 0) { console.log(`${num}は5の倍数です`) } else { console.log(`${num}は3の倍数でも、5の倍数でもありません`) }const num = 60
だから
60 % 15 == 0
ということただの割り算は「 / 」なので考え方が違う
「 % 」は割り算した答えの数字じゃなくて、その答えの余りのこと
60 / 15 = 4
4で割り切れてるから余りは0
つまり60 % 15 == 0は、
0 == 0だよね?ということで→true⬇️出力結果⬇️
60は3と5の倍数です
- 投稿日:2020-12-07T20:04:54+09:00
RailsエンジニアがGraphQLを触って比較してみた
はじめに
こんにちは!榊原です。
トレタに入社して1年が経ちました。
この1年はとても変化が多くあった年だったと感じています。特に今年の1月から会社全体でフルリモート化や、
DXを意識した飲食店での自分のスマホでオーダーできるシステムや予約時に座席を指定して予約できるシステムなど、
時代の変化に対応した様々な新規サービスを発表した年でもあります。発表したサービスの中にはまだ市場に受け入れられるか仮設段階のものも数多くありました。
エンジニアの人数も限りがある中で、最速かつ柔軟な変化を求められる開発を進めるに当たって、
最近弊社で使われ始めているのがGraphQLです。今回はそんなGraphQLを弊社でも創業当初から運用されてきたRuby on Railsとの比較を中心とした説明とさせていただきます。
GraphQLを説明をするに当たって、グラフ理論は欠かせないのですがそちらは他の記事が多数存在するため今回は割愛させていただきます。
対象となる読者
- Railsは使用していてもGraphQLは触ったことがないけど触ってみたい
- サーバーサイドを担当していたけど、業務で使用しなければならなくなった
対象アプリケーション
- GraphQLエンジン v1.3.3
- DB: PostgreSQL v13.1
GraphQLって何者なの?
GraphQLはFacebookによって開発されました。
元々モバイルアプリはWeb用のラッパーアプリでした。
しかしRESTfulAPIサーバーとFQL(Facebook用のSQL)のデータテーブルで運用されてましたが、
パフォーマンスの低下や度々クラッシュするなど改善が求められました。
そこでFacebookのクライアント、サーバーアプリケーションの性能上のかだいと、データ構造の要件を満たす解決策として誕生したのがGraphQLです。
そして現在ではFacebookのほぼ全てのデータ取得にGraphQLが用いられています。GraphQLのクライアントアプリケーションとしては、 Relay, Apollo, そして最近ではHasura等があります。
初めの一歩 (GraphQLでできること)
シンプルなGraphQLの記述例ですが、
例えばRailsでPersonsテーブルの情報全て取得するAPIがあるとしますRails
def PersonsController def index data = Person.all render json: data end endこの場合GraphQLでは下記の様なクエリを記述するだけで取得できます
GraphQL
query { persons { id name birthday created_at updated_at } }出力結果はGraphQLでは
data
のハッシュ内に梱包される形になりますが、下記の様な形式で出力されます。{ "data": { "persons": [ { "id": 1, "name": "佐藤太郎", "birthday": "2000-01-01", "created_at": "2020-12-05T06:31:43.250885+00:00", "updated_at": "2020-12-05T06:31:43.250885+00:00" }, 〜 略 〜 { "id": 4, "name": "伊藤花子", "birthday": "2010-03-03", "created_at": "2020-12-05T06:32:37.952758+00:00", "updated_at": "2020-12-05T06:32:37.952758+00:00" } ] } }この様にGraphQLはクエリ言語なため、RailsではあったModelやController、Render等を省き、
簡単なコードだけででRESTFullなAPIを作成することができます。この後のRailsのサンプルコードとしては、Controllerや
render
は極力記述せず、ActiveRecordを用いたデータを中心とした記述とします。使用するSchemaの説明
今回使用するのは下記の3つのテーブルで、単純なSNSを想定して作成しました。
- persons: ユーザー
- photos: ユーザーが投稿した写真
- photo_person_tags: 写真にタグ付けされた人シンプルなQuery
それでは先ほどのクエリを見ていきましょう。
query { // Select句 persons { // テーブル名 id // カラム名 (以下同一) name birthday created_at updated_at } }GraphQLではCQRSを採用しているため、最初にQuery(Select句)は
query
を、Command(Create、Update、Delete句)はmutation
を記述します。
今回はSelectなのでquery
を使用しました。次に、取得したいテーブル名とカラム名を記述すれば、先程の様な値が取得できます。
条件のあるQuery
全てを取得することはできても、条件に合ったもののみ取得したいケースは勿論あるでしょう。
その場合は下記の様に記述します。
名前に佐藤という文字が入っている人を検索します。Rails
persons.where('name like ?','佐藤%').limit(2)GraphQL
query { persons(where: {name: {_like: "佐藤%"}}, limit: 2) { id name birthday created_at updated_at } }
persons
の中にwhere句、Limit句が追加されただけですね。
基本的な表現は同じです。
条件式に使用できるものとしては下記で確認してください。https://hasura.io/docs/1.0/graphql/core/queries/query-filters.html
複数のテーブルに対するQuery
実際にサービスを作成していると複数のテーブル情報を一度に取得したいというケースは多くの場面で遭遇すると思います。
ここでは、シンプルに
persons
とphotos
のデータを1つのJSON形式で出力してみます。Rails
persons = persons.all photos = photos.all data = { persons: persons, photos: photos }GraphQL
query { persons { id name birthday created_at updated_at } photos { id person_id url created_at updated_at } }出力結果
{ "data": { "persons": [ { "id": 1, "name": "佐藤太郎", "birthday": "2000-01-01", "created_at": "2020-12-05T06:31:43.250885+00:00", "updated_at": "2020-12-05T06:31:43.250885+00:00" }, 〜 略 〜 { "id": 3, "name": "佐藤花子", "birthday": "2010-03-03", "created_at": "2020-12-05T06:32:37.249905+00:00", "updated_at": "2020-12-05T08:21:50.992633+00:00" } ], "photos": [ { "id": 1, "person_id": 1, "url": "https://example.com/photos/1", "created_at": "2020-12-05T06:33:19.847425+00:00", "updated_at": "2020-12-05T06:33:19.847425+00:00" }, 〜 略 〜 { "id": 5, "person_id": 2, "url": "https://example.com/photos/4", "created_at": "2020-12-05T06:33:43.201778+00:00", "updated_at": "2020-12-05T06:33:43.201778+00:00" } ] } }この様にGraphQLでは、ただ取得したいテーブル、カラムを追加するだけで取得できます。
リレーションを使用したQuery
persons
とphotos
をそれぞれ別で取得を行いました。
ですが実際にはリレーションに紐づく値のみを取得したいというケースの方が多いかと思います。次は
person_id
が既知の場合に、それに紐づくphoto
を取得する例です。Rails
person = Person.find(1) photo_urls person&.photos&.each_with_object([) do |photo, hash| hash << {url: photo.url} end data = { name: person.name, birthday: person.birthday, photos: photo_urls }GraphQL
{ persons_by_pk(id: 1) { photos{ url } name birthday } }出力結果
{ "data": { "persons_by_pk": { "photos": [ { "url": "https://example.com/photos/1" }, { "url": "https://example.com/photos/2" }, { "url": "https://example.com/photos/3" }, { "url": "https://example.com/photos/3" } ], "name": "佐藤太郎", "birthday": "2000-01-01" } } }この様にリレーションを貼っていれば、通常のカラムを取得するように取得することができます。
この辺りからGraphQLの方がnull回避やループ処理等を記述しなくて良くなるので、単純なります。
(実際にはRailsの場合はURLを詰め直す作業は行わないと思いますが)件数を算出するQuery
開発する上で、合計値、最大値、平均値等の値を算出したくなる時があると思います。
今回はIDが1のperson
が投稿した写真の件数を取得してみましょうRails
photos = Person.find(1).photos data = photos.each_with_object({}) do |photo, hash| hash << { id: photo.id, url: photo.url, photo_person_tag: { count: photo.photo_person_tags.count } } end※
aggregate
の様なGraphQL側でしか使用しない名前は省いています。GraphQL
query { persons_by_pk(id: 1){ photos { url id photo_person_tags_aggregate { aggregate { count(columns: id) } } } } }出力結果
{ "data": { "persons_by_pk": { "photos": [ { "url": "https://example.com/photos/1", "id": 1, "photo_person_tags_aggregate": { "aggregate": { "count": 1 } } }, 〜 略 〜 { "url": "https://example.com/photos/3", "id": 4, "photo_person_tags_aggregate": { "aggregate": { "count": 0 } } } ] } } }この様にGraphQLでも単純な演算処理は備わっています。
ただ、単純な演算自体はRailsが圧倒的に記述しやすいですね。フラグメントを使用したQuery
フラグメントは同じクエリを複数の場所で使い回すことができる選択セットです。
Railsで言う所の、処理をメソッドに切り出す事ですね。
それでは一つ前の例を元に見ていきましょう。Rails
今回はRails側は既に
count
メソッドとして切り出されていたものを使用したので割愛します。GraphQL
fragment personInThePhotoCount on photos { photo_person_tags_aggregate { aggregate { count(columns: id) } } } query { persons_by_pk(id: 1){ photos { url id ...personInThePhotoCount } } }出力結果
{ "data": { "persons_by_pk": { "photos": [ { "url": "https://example.com/photos/1", "id": 1, "photo_person_tags_aggregate": { "aggregate": { "count": 1 } } }, 〜 略 〜 { "url": "https://example.com/photos/3", "id": 4, "photo_person_tags_aggregate": { "aggregate": { "count": 0 } } } ] } } }変数を使用したQuery
実際にアプリケーションとして運用する際にClientを操作するユーザによって入力されたものをWhere句などでクエリに使用する事があります。
その中で意識する事として入力値のバリデーションがあると思います。
そんな時に使用するものとしてクエリ変数 (Query Variables)があります。
クエリ中で使用される動的な変数を別で定義することにより、
型のチェックやnull回避等はもちろん、再利用性も高くなります。次の例は名前が
佐藤
から始まり、2005-01-01
より前に誕生日の人を取得するクエリです。GraphQL
Query
query MyQuery($name: String!, $birthday: date) { persons(where: {name: {_like: $name}, birthday: {_lt: $birthday}}) { id name birthday } }Query Variables
{ "name": "佐藤%", "birthday": "2005-01-01" }出力結果
{ "data": { "persons": [ { "id": 1, "name": "佐藤太郎", "birthday": "2000-01-01" } ] } }
query MyQuery($name: String!, $birthday: date)
このクエリの引数は2つで、
名前はString
型で入力必須
誕生日はdate
型でnull許可されている例です。Mutation
次はMutaionを説明します。Mutationは変更を加えたい時に使用します。
種類としては大きく分けて作成、更新、削除の3種類あります。それではそれぞれ見ていきましょう。
レコード作成のMutation
Rails
Person.create( name: "佐藤三郎", birthday: "2005-01-01" )GraphQL
Query
mutation MyMutation($name: String!, $birthday: date!) { insert_persons_one(object: {birthday: $birthday, name: $name}) { id name birthday created_at updated_at } }Query Variables
{ "name": "佐藤三郎", "birthday": "2005-01-01" }出力結果
{ "data": { "insert_persons_one": { "id": 6, "name": "佐藤三郎", "birthday": "2005-01-01", "created_at": "2020-12-08T10:27:17.430325+00:00", "updated_at": "2020-12-08T10:27:17.430325+00:00" } } }この様にQueryとMutationは基本同一の形になります。
また、insert_persons_oneの{}
の部分は返却値になります。必要なものだけを入力してください。
insert_persons_one(key: value) { 【ここの部分】 }
また、
insert_persons_one
という名前からわかるように、これは単数のレコード作成になるので、
複数の場合はinsert_persons
で作成できます。値変更のMutation
今回は値を変更しようと思います。
person
のIDが5のユーザの誕生日を 2002-01-01 に変更します。Rails
person = Person.find(5) person.birthday = "2002-01-01" person.saveGraphQL
Query
mutation MyMutation($birthday: date) { update_persons(where: {id: {_eq: 5}}, _set: {birthday: $birthday}) { affected_rows } }Query Variables
{ "birthday": "2002-01-01" }出力結果
{ "data": { "update_persons": { "affected_rows": 1 } } }更新対象の特定は、今回はIDを用いましたが普通に他のものを使用して問題ありません。
また、更新内容は_set
内に更新情報を入力することで更新できます。今回の返却値は
"affected_rows": 1
となっていますが、これは返却値を更新した行数を設定したため
1行だけ変更があった事が確認できます。削除を行うMutation
今回はIDが5の
person
を削除してみたいと思います。Rails
person = Person.find(5) person.destroyGraphQL
mutation { delete_persons(where: {id: {_eq: 5}}) { affected_rows } }出力結果
{ "data": { "delete_persons": { "affected_rows": 1 } } }こちらも同様に簡単に削除を行う事ができました。
まとめ
最後まで読んでくださってありがとうございます。
GraphQLは表現には制限がありますし、スキーマ情報を外に公開しているので、
スキーマの変更に対して脆くなってしまう問題こそあれ、Rails以上に簡単にRESTFul APIを作成することができます。今回記述させて頂いた内容はまだGraphQLの一部でしかありません。
より一層GraphQLを知りたい、使いたいという方はまずはGraphQLの公式ドキュメントを見てみると良いでしょう。
https://graphql.org/learn/ただし、今回使用したGraphQLエンジンのHasuraはGraphQLとは大きく異なっている部分があるので、 HasuraのGraphQLを使用したい場合はHasuraのGraphQLのドキュメントを読んでみてください。
最後に
弊社ではGraphQLを仮説検証段階の、速度感をもったサービス開発で少しづつ取り入れ始めています。
そんな技術もサービスも一緒に検証したいエンジニアを募集しています。
カジュアルな面談もありますので、一度下記のURLから確認してみてください!
https://corp.toreta.in/recruit/midcareer/
- 投稿日:2020-12-07T19:52:20+09:00
ローカルホストに接続できない時の対処法
オリジナルアプリやポートフォリオ作成中に、ローカルホストに接続できないという経験はないでしょうか?
私は、あります(笑)
そんな時の対処法を備忘録を兼ねて、ご紹介します。
解決方法
その1 ターミナルの再起動
ターミナルを強制終了する
※私はダメでした(笑)その2 server.pidファイルを削除
サーバーを起動する際、/ユーザー名/アプリケーション名/tmp/pids/server.pid.というファイルが生成されるそうです。
サーバーを閉じるとファイルは削除される、といったようにサーバーの起動と終了を行う際は、裏でこういったことが起きているみたいです。
※このファイルを削除するとうまくいくことがあるみたいです。その3 Address already in use - bind(2) for "〜〜〜" port 3000 (Errno::EADDRINUSE)
私の場合は、ターミナル上にこの一文が太くなっていました。
その際は、以下のコマンドを実行してみたください。qiita.rblsof -i :3000そうすると、以下のようなものが、ターミナル上に表示され表示されると思います。
あとは、以下のコマンドを実行するだけです。
qiita.rbkill -QUIT "PIDの数字"お試しあれ!
- 投稿日:2020-12-07T19:12:06+09:00
Docker開発環境でCapybara, FactoryBotを使ったテスト環境を構築追加する
概要
docker開発環境でRSpecテスト環境を構築するやり方の備忘録としてまとめました。
独学なので間違っている部分があると思いますが、その時はご指摘頂けると嬉しいです。以下の記事を参考にしました。
https://qiita.com/at-946/items/e96eaf3f91a39d180eb3
https://qiita.com/na-tsune/items/91630257294aa0ea4fc81.docker-compose.ymlの編集
docker-compose.ymlversion: '3' services: db: image: mysql:5.7 environment: MYSQL_USER: root MYSQL_ROOT_PASSWORD: password ports: - "3306:3306" volumes: - ./db/mysql/volumes:/var/lib/mysql web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp - gem_data:/usr/local/bundle ports: - 3000:3000 depends_on: - db - chrome # ←追加 tty: true stdin_open: true chrome: image: selenium/standalone-chrome:latest ports: - 4444:4444 volumes: gem_data:web:配下のdepends_onに-chromeを追記。
services:配下にchrome:以下を追記。2.RSpecの導入
Gemfileに必要なgemを追記
Gemfilegroup :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] gem 'pry-rails' gem 'pry-byebug' #rspec用のgem 2つ追記 gem 'rspec-rails', '~> 4.0.1' gem 'factory_bot_rails', '~>4.11' enddockerのbuild, up, gemのインストール,Rspecのインストールをする。
$ docker-compose run web bundle install$ docker-compose build$ docker-compose up$ docker-compose run web rails g rspec:install次に、
RSpecのインストールによって作成されるファイルの中に、"rails_helper.rb"の設定を記述していきます。ついでにtestディレクトリは削除しておく。/spec/rails_helper.rb#↓追記 Capybara.register_driver :remote_chrome do |app| url = "http://chrome:4444/wd/hub" caps = ::Selenium::WebDriver::Remote::Capabilities.chrome( "goog:chromeOptions" => { "args" => [ "no-sandbox", "headless", "disable-gpu", "window-size=1680,1050" ] } ) Capybara::Selenium::Driver.new(app, browser: :remote, url: url, desired_capabilities: caps) end #↑追記 #↓追記 config.before(:each, type: :system) do driven_by :rack_test end config.before(:each, type: :system, js: true) do driven_by :remote_chrome Capybara.server_host = IPSocket.getaddress(Socket.gethostname) Capybara.server_port = 4444 Capybara.app_host = "http://#{Capybara.server_host}:#{Capybara.server_port}" end #↑追記最後に.rspecを編集して、rails_helper.rbの設定を読み取るようにします。
.rspec--require spec_helper #削除する --require rails_helper #追記あとはテスト記述、実行できるか確認する。
terminal
$ docker-compose run web rspec [rspecテストファイルのpath]
- 投稿日:2020-12-07T19:04:15+09:00
Sidekiqの管理画面で500エラーが発生
Sidekiqの管理画面(Sidekiq::Web)で500エラーが発生しており、無事に解決できたので、対応方法をまとめておきます。
開発環境
- Rails: 5.2.3
- Ruby: 2.6.3
- rack: 2.2.3
- Redis: 4.1.2
- Sidekiq: 6.0.0
発生していたエラー
NoMethodError (undefined method `match' for #<Rack::Session::SessionId:xxxxxxxxxxxxxx>):やったこと
試してみたこと
1.セッションを消してみる
2.rackをアップデート
3.bundle updateしてみる解決した方法
4.redis-rackをアップデートしてみる
5.redis-actionpackをアップデートしてみる
1.セッションを消してみる
config/routes.rbrequire 'sidekiq/web' Sidekiq::Web.set :sessions, false # 追加管理画面は表示されるが、セッションを消しているだけなので、
根本的な解決とはなっていない。2.rackをアップデート
bundle update rack
Note: rack version regressed from 2.2.3 to 2.0.9エラーが変わらず。
3.bundle updateしてみる
bundle update
正常に動いたのでgemに問題がありそう。
今回の修正範囲以外にも影響が大きいため、gem全てがアップデートされるbundle update
は避けて、
一旦もとに戻して原因を探る。
4.redis-rackをアップデートしてみる
bundle update redis-rack
管理画面は正常に表示されたが、テストが失敗してしまう。
NoMethodError: undefined method `private_id' for "xxxxxxxxxxxxxxx":String Did you mean? private_methods5.redis-actionpackをアップデートしてみる
上記の
redis-rack
のアップデート実行後、
こちらの記事
https://github.com/redis-store/redis-rack/pull/50#issuecomment-567649953
を参考に
bundle update redis-actionpack
これでサーバー再起動後、Sidekiqの管理画面の500エラーが解消し、テストも通った。
まとめ
redis-rack
とredis-actionpack
のアップデートで解決しました。
- 投稿日:2020-12-07T18:49:32+09:00
Rails newが上手くいかない時の対処法
今日1時間ほどコマンドプロンプトでrails newをしても必要なファイルが作成されず、苦戦したのでその対処法を残しておきます。
誰かの参考になれば嬉しいです。
では、本題ですがコマンドプロンプトでrails newを実行するとこんな画面が表示されてばかりで、欲しいファイルが作成されませんでした。
gemfileでdeviseをインストール出来ないことからこの異変に気付いたのですが、最初はずっとgemfile側の問題だと思っていたのですが、全く改善できず。。。そこで、最初に上手くいっていたアプリがあったのでそれを参考にしようと思ったのですが、まさか一番最初の時点でミスっているなんて思わずドツボにハマり、1時間ちょっと使いました(笑)
で、改めてこのコマンドプロンプトの表示を見てみるとgitのところで止まっていることに気づき、ググってみるとgitのコマンドがコマンドプロンプト上で使えるようになっていないからだと分かりました!!
だから、解決策としてはrails new アプリ名 -Gと後ろに-Gをつけてgitをスキップすれば解決することが判明し、無事解決することが出来ホッとしているところです。
gitをwindowsのコマンドプロンプトで使えるように設定しておけばよかった。。。
- 投稿日:2020-12-07T18:34:52+09:00
Railsについて初心者なりに調べてみた
ドキュメント
Ruby on Rails ガイド:体系的に Rails を学ぼう
Railsとは
- Rubyにより構築されたWEBアプリケーションフレームワーク
- MVCアーキテクチャに基づいて構築されている
- 他のフレームワークより少ないコードでアプリケーション開発ができるように考慮されている
- それを実現するためにRailsには制約が多く存在し、慣れるまでは少し窮屈に感じることもあるかも?
Rubyの他フレームワーク
Sinatra
・軽量なフレームワークで簡潔に記述できる、最小限の労力でWebアプリケーションをすばやく作れる。
・MVCアーキテクチャに基づいた設計ではない。
小規模開発に向き
http://sinatrarb.com/HANAMI
・バージョン1.0が2017年4月にリリースされた比較的新しいフレームワーク。
・メモリの消費を抑えるために提供されている100以上の安定したAPIを利用できる
・応答速度などで高いパフォーマンスを発揮
長期的なメンテナンスを考え作られている
https://hanamirb.org/Ramaze
・Sinatraと同様にシンプルかつ軽量で柔軟性のあるフレームワーク
・Rubyの書き方をそのまま踏襲できるようになっている
http://ramaze.net/他言語フレームワークとの比較
Web開発フレームワークのシェアと推移
djangoとlaravelがトレンド上昇している。
ruby on rails は2011年以降、下降している。
Ruby on rails のトレンド下降している要因
Twitterが、Ruby on RailsからJavaVMへ移行する理由
Twitterの膨大化したアクセスを、railsで構築されたシステムよりもJavaVMの方が速やかに処理できる。
→大規模システム開発で使われるケースが世界的に減っている。
なぜ一時期、一世を風靡したRuby on railsが、「railsはもう終わった」と言われるようになったのか?
その一部の背景を上記で説明しました。以下は具体的にかかれている記事
ただ日本ではスタートアップ中心に仕事がまだまだたくさんある。
例、Cookpad, Gunosy, 食べログ, Freee, Crowdworks
開発環境
- ローカル
- Virtual Box
- Docker
- Cloud9(AWS)
開発の流れ(ローカル環境)
基本的にMVCモデルの設計に沿って、ファイルを作成する。
参考文献
- 投稿日:2020-12-07T18:13:04+09:00
【Rails】チャットの吹き出しの向きを変える方法
本記事は 駆け出しエンジニアの第一歩!AdventCalendar2020 7日目の記事です。
作りたいもの
下図のように、自分のメッセージと自分以外のメッセージでユーザーアイコンの位置や吹き出しの向きが変わるチャット機能を作ります。
前提条件
- チャット機能自体はできている
- bootstrap4がインストールされている
やることまとめ
- 自分用の吹き出しと相手用の吹き出しのCSSクラスを用意する
- Viewファイルにて、メッセージごとに自分or自分以外のメッセージかを判定する
- それぞれの判定ごとに適用するCSSクラスを変える
1.自分用の吹き出しと相手用の吹き出しのCSSクラスを用意する
吹き出しの枠をデザインするCSSクラスを作成します。
自分のメッセージの場合、吹き出しの尻尾が左側に来るように、
自分以外のメッセージの場合、吹き出しの尻尾が右側に来るようにします。// 自分のメッセージの吹き出し .says { float: left; position: relative; width: calc(100% - 56px); padding: 16px; background: #ffffff; border-radius: 15px; line-height: 1.5; word-break: break-all; } .says:after { content: ""; display: inline-block; position: absolute; top: 3px; left: -19px; border: 8px solid transparent; border-right: 18px solid #ffffff; -webkit-transform: rotate(35deg); transform: rotate(35deg); } // 自分以外のメッセージの吹き出し .other-user-says { float: right; position: relative; width: calc(100% - 56px); padding: 16px; background: #ffffff; border-radius: 15px; line-height: 1.5; word-break: break-all; } .other-user-says:after { content: ""; display: inline-block; position: absolute; top: 3px; right: -19px; border: 8px solid transparent; border-right: 18px solid #ffffff; -webkit-transform: rotate(145deg); transform: rotate(145deg); }2.Viewファイルにて、メッセージごとに自分or自分以外のメッセージかを判定する
each文でmessageを全て読み込んだ後、メッセージごとに紐づいているユーザーが自分か自分以外かをif文で判定します。
この後、判定結果に応じて、適用する吹き出しのCSSクラスを変えていきます。
message.html.erb<% messages.each do |m| %> <!-- 自分のメッセージの場合 --> <% if m.user == current_user %> <!-- 自分以外のメッセージの場合 --> <% else %> <% end %> <% end %>3. それぞれの判定ごとに適用するCSSクラスを変える
自分のメッセージの場合、自分用の吹き出しのCSSクラスを適用し、
自分以外のメッセージの場合、自分以外用の吹き出しのCSSクラスを適用するようにします。message.html.erb<% messages.each do |m| %> <!-- 自分のメッセージの場合 --> <% if m.user == current_user %> <tr class="row justify-content-center"> <!-- アイコンを左側に表示する --> <td class="col-2"> <%= link_to attachment_image_tag(m.user, :image, :fill, 80, 80, fallback: "noimage.png", size:'80x80', class:"profile-image align-top"), user_path(m.user) %> </td> <!-- メッセージを右側に表示する --> <td class="col-10"> <%= m.user.display_name %> <br> <!-- 自分用の吹き出しCSSクラスを適用する --> <div class="says"> <p><%= safe_join(m.content.split("\n"),tag(:br)) %></p> <span><%= l m.created_at %></span> </div> </td> </tr> <!-- 自分以外のメッセージの場合 --> <% else %> <tr class="row justify-content-center"> <!-- メッセージを左側に表示する --> <td class="col-10"> <div class="col-11 float-right"> <%= m.user.display_name %> <br> </div> <!-- 自分以外用の吹き出しCSSクラスを適用する --> <div class="other-user-says"> <p><%= safe_join(m.content.split("\n"),tag(:br)) %></p> <span><%= l m.created_at %></span> </div> </td> <!-- アイコンを右側に表示する --> <td class="col-2"> <%= link_to attachment_image_tag(m.user, :image, :fill, 80, 80, fallback: "noimage.png", size:'80x80', class:"profile-image align-top"), user_path(m.user) %> </td> </tr> <% end %> <% end %>終わりに
今回は私個人のアプリで実際に実装したものを題材にしたので、人によってはビューの書き方は異なってくると思います!
ご参考になれば幸いです…!
参考サイト
- 投稿日:2020-12-07T17:19:45+09:00
Ruby で解く AtCoder ABC057 C 動的計画法
はじめに
AtCoder Problems の Recommendation を利用して、過去の問題を解いています。
AtCoder さん、AtCoder Problems さん、ありがとうございます。今回のお題
AtCoder Beginner Contest C - Digits in Multiplication
Difficulty: 904今回のテーマ、動的計画法
この問題は、以前に Ruby と Python で解く AtCoder ABC057 C 素因数分解 ビット全探索 で解いております。
今回、動的計画法で解くことにより、実行時間が大幅に縮小したので投稿します。
ビット全検索
ruby.rbrequire 'prime' n = gets.to_i h = n.prime_division p = [] h.each do |k, v| v.times do p << k end end min = Float::INFINITY 0.upto(2**p.size - 1) do |bit| a = 1 p.size.times do |i| a *= p[i] if bit[i].zero? end min = [a, n / a].max if min > [a, n / a].max end puts min.to_s.sizebit.rb0.upto(2**p.size - 1) do |bit| # 処理 endここが、ビット全検索の部分になります。
動的計画法
ruby.rbrequire 'prime' n = gets.to_i if n == 1 puts 1 exit end p = n.prime_division dp = Hash.new(0) dp[1] = 1 min = Float::INFINITY p.each do |k, v| v.times do dp.keys.each do |u| dp[k * u] = 1 tmin = [(k * u).to_s.size, (n / (k * u)).to_s.size].max min = tmin if min > tmin end end end puts min動的計画法の処理内容については、Ruby の Hash における keys.each と each_key の違い を参照願います。
ビット全検索 動的計画法 コード長 (Byte) 318 344 実行時間 (ms) 1504 69 メモリ (KB) 14416 14468 実行時間が
1504
->69
と、大幅に縮小。どうして早いのか
ビット全検索
の場合、その都度計算される(上記ソースですと乗算)
1 = 1 1 * 2 = 2 1 * 2 * 3 = 6 1 * 2 * 3 * 5 = 30
動的計画法
の場合、前回の計算結果を使用する
1 = 1 1 * 2 = 2 2 * 3 = 6 6 * 5 = 30 よって、計算コストが低くなります。
まとめ
- ABC 057 C を解いた
- Ruby に詳しくなった
- 投稿日:2020-12-07T16:55:43+09:00
第10回(フィボナッチ数列)
お題:Fibonacci数列
fib(n) = fib(n-1)+fib(n-2) 0 1 1 2 3 5 8 13 21 ...をrecursion(再帰)で求めなさい.
解説
fib(0) = 0
Fibonacci数列の初項は0
def fib(n) if n==0 return 0 end end前回作成したassert_equal.rbで出力が期待値と一致しているかを確認.
require './assert_equal' puts assert_equal(0, fib(0))結果
expected :: 0 result :: 0 succeeded in assert_equalfib(1) =1
次はfib(1)=1.まずはassertion
puts assert_equal(1, fib(1))もちろん失敗するので書き換える.
def fib(n) if n==0 return 0 end if n==1 return 1 end end結果
expected :: 0 result :: 0 succeeded in assert_equal expected :: 1 result :: 1 succeeded in assert_equalこれでOK.
テスト側の重複が気になってきたので,配列に
[[0,0],[1,1]].each do |pair| puts assert_equal(pair[0], fib(pair[1])) end結果は同じ
fib(2) = fib(1) + fib(0) = 1
まずはテスト
[[0,0],[1,1],[2,1]].each do |pair|これではダメ.
> ruby fibonacci.rb expeced:0 result :0 succeeded in assert_equal expeced:1 result :1 succeeded in assert_equal expeced:2 result :1 failed in assert_equal条件分岐をもう少しいじって,
def fib(n) if n==0 return 0 end if n<=2 return 1 end endこれでいけると思うので実行.
> ruby fibonacci.rb expeced :: 0 result :: 0 succeeded in assert_equal expeced :: 1 result :: 1 succeeded in assert_equal expeced :: 2 result :: 1 failed in assert_equalあれ?
実は,assert_equalは(expect, result)とうけとっているため配列変数pairの示数indexが逆.
require './assert_equal' [[0,0],[1,1],[2,1]].each do |pair| puts assert_equal(pair[1], fib(pair[0])) endが正解.
refactoring
もう少し配列の受け取りを明示的にすると,
index, expected = pairと修正できて
require './assert_equal' [[0,0],[1,1],[2,1]].each do |index, expected| puts assert_equal(expected, fib(index)) endこっちのほうが分かりやすい.
refactoring
メソッドが長くなってきたので短く書き直す.
def fib(n) return 0 if n==0 return 1 if n<=2 endと2行に削減
fib(3) = 2 = fib(2) + fib(1)
テストは
[[0,0],[1,1],[2,1],[3,2]].each do |index, expected|return 2になってほしいのでfib(2)+fib(1)にする.
def fib(n) return 0 if n==0 return 1 if n<=2 return fib(2) + fib(1) end結果
> ruby fibonacci.rb expeced :: 0 result :: 0 succeeded in assert_equal expeced :: 1 result :: 1 succeeded in assert_equal expeced :: 1 result :: 1 succeeded in assert_equal expeced :: 2 result :: 2 succeeded in assert_equalfib(4) = fib(3) + fib(2) = 2 + 1 = 3
次は4. 期待値は3
[[0,0],[1,1],[2,1],[3,2],[4,3]].each do |index, expected|テスト結果は当然fail.最後のreturnを定義通りに
fib(n) = fib(n-1) + fib(n-2)と修正
結果
> ruby fibonacci.rb expeced :: 0 result :: 0 succeeded in assert_equal expeced :: 1 result :: 1 succeeded in assert_equal expeced :: 1 result :: 1 succeeded in assert_equal expeced :: 2 result :: 2 succeeded in assert_equal expeced :: 3 result :: 3 succeeded in assert_equal全てsucceed
そのまま続きもやってみる.
[[0,0],[1,1],[2,1],[3,2],[4,3], [5,5],[6,8],[7,13],[8,21],[9,34] ].each do |index, expected| puts assert_equal(expected,fib(index)) end結果
expected :: 0 result :: 0 succeeded in assert_equal expected :: 1 result :: 1 succeeded in assert_equal expected :: 1 result :: 1 succeeded in assert_equal expected :: 2 result :: 2 succeeded in assert_equal expected :: 3 result :: 3 succeeded in assert_equal expected :: 5 result :: 5 succeeded in assert_equal expected :: 8 result :: 8 succeeded in assert_equal expected :: 13 result :: 13 succeeded in assert_equal expected :: 21 result :: 21 succeeded in assert_equal expected :: 34 result :: 34 succeeded in assert_equal全部いけた.
ただ一つ抜けているのは,
fib(2) = fib(1) + fib(0) = 1 + 0なんで,fibのなかの条件分岐はもう少し狭められて,
return 1 if n == 1で十分
最終的に
require './assert_equal.rb' def fib(n) return 0 if n == 0 return 1 if n == 1 return fib(n-1)+fib(n-2) end [[0,0],[1,1],[2,1],[3,2],[4,3], [5,5],[6,8],[7,13],[8,21],[9,34] ].each do |index, expected| puts assert_equal(expected,fib(index)) end
- source ~/grad_members_20f/members/yoshida/c5_fibonacci.org
- 投稿日:2020-12-07T16:29:08+09:00
第11回
クラス
これぞオブジェクト指向…
class Hello #クラス定義 def print #メソッド定義 p "Hello" end end hello = Hello.new #インスタンス生成 hello.print[実行結果]
"Hello"クラスの初期化メソッド
def initializeが初期化メソッド.Pyhtonで言うところのdef __init__(self).
@変数名
がクラス変数.
コマンドライン入力で受けとった名前に挨拶するクラス
class Hello def initialize(input_name) @name = input_name end def print p "Hello #{@name}" end end hello = Hello.new(ARGV[0]) hello.print[実行結果]
> ruby Hello_class.rb Muku "Hello Muku"初期化メソッドはインスタンス生成の時に一度だけ実行されるので,初期化メソッドにクラス内メソッドのprintを入れても良い.
継承
新クラス名 < 継承元クラス名で継承できる.先ほどのHelloクラスを継承するByeクラスを作る.
class Hello def initialize(input_name) @name = input_name print end def print p "Hello #{@name}" end end class Bye < Hello #Helloクラスを継承 def print p "ByeBye #{@name}" end end Hello.new(ARGV[0]) Bye.new(ARGV[0])[実行結果]
> ruby Hello_class.rb Muku "Hello Muku" "ByeBye Muku"参考サイト
この記事は以下のサイトを参考に作成しました.
- source ~/grad_members_20f/members/musutafakemaru/11.org
- 投稿日:2020-12-07T16:14:05+09:00
railsテスト駆動する理由
railsのテスト駆動に関する参考になった記事があるので共有
https://teratail.com/questions/121833?link=qa_related_pc_sidebarテストをする理由は今のコードと自分の認識を一致させることだったということ。
user_test.rbclass UserTest < ActiveSupport::TestCass test "nameの長さは50文字以内である事" do # エラーを起こすために、わざと51文字を設定する @user.name = "a" * 51 # もし、正しくバリデーションが設定されていたら、 # バリデーションがfalseになるはず # assert_notは引数の結果がfalseに時にテストが成功する assert_not @user.valid? end test "emailの長さは255文字以内である事" do # エラーを起こすために、わざと255文字を超える文字列を設定 # "a" * 256だけでもいいでしょう @user.email = "a" * 244 + "@example.com" assert_not @user.valid? end endassert_notはassert_notは引数の結果がfalseの時にテストが成功とする命令
つまり現時点でバリテーションがかかれていないため、@user.validがtrueとなるため、テストは失敗。
今のコードと自分の認識はあっていたということになる。
じゃあバリテーションを書こう。
こうなるわけなのか。。。なるほど。
テストって意味あるの?って疑問だったが、効率良く開発を進めていく上で必要不可欠なんだと理解した。(1) 仕様を先に決める(頭の中だけでもいいし、紙に書いたりしてもいい)
(2) 仕様をテストとして記述する
(3) 仕様を満たすために実装するこのステップが大事なんですね。
めちゃめちゃ参考になりました。テストの書き方とかも勉強しないとな
- 投稿日:2020-12-07T16:06:13+09:00
Ruby , Ruby on Railsの根っこを理解するための基礎知識② 〜クラスとインスタンス〜
【クラスとインスタンス】
クラスとは
共通で定義される部分をまとめる大元の型みたいなもの
インスタンスとは
クラスの型を使って作られた派生した型みたいなもの
※“”で囲うと文字列を入力ができるメソッドや、[]で囲う事でArrayメソッドが利用ができるのは、予めStringクラスやArrayクラスという用意されたクラスがあるからであり、クラス自体を自分で定義する事も可能<クラスを自分で定義する方法>
class クラス名 処理 end※クラス名の頭文字は必ず大文字
<インスタンスを作成する方法>
変数名 = クラス名.new
※「.new」で、変数に代入<クラスメソッドを定義する方法>
class クラス名 def self.メソッド名 end end※classメソッドは定義するメソッド名の前にself.をつける
※使い方は、クラス名.メソッド名<クラスメソッドの使用例>
class Animal def self.greet p "こんにちは!Animalです!" end endとクラスとクラスメソッドを定義すると、「Animal.greet」と書く事で、「"こんにちは!Animalです!”」が出力される
※クラスメソッドは、クラスしか呼び出せないため、「animal = Animal.new」とインスタンスを作成して「animl.greet」では「"こんにちは!Animalです!”」を書き出す事は出来ない<インスタンスメソッドを定義する方法>
class クラス名 def メソッド名 end end※クラスメソッドと違い「self.」をつけないだけ
※使い方は、インスタンス名.メソッド名<インスタンスメソッドの使用例>
class Animal def greet p "こんにちは!Animalのインスタンスです!" end endと定義する事で、「animal = Animal.new」とインスタンスを呼び出せば、「animal.greet」と書く事で、「"こんにちは!Animalのインスタンスです!”」と書き出す事が出来る
【initialize】
コンストラクタと呼ばれ、インスタンスが作成された(newメソッドが実行された)タイミングで呼ばれるメソッドのこと
※クラスからインスタンスを作成するときに共通の処理を行いたい場面で使用<使用例>
class Animal def initialize p "インスタンスが作られました" end endと定義しておく事で「animal = Animal.new」とインスタンスが作成された時点で「"インスタンスが作られました”」と書き出される
【クラス変数とインスタンス変数】
クラス内に定義する変数のこと
クラス変数とは
クラスで使用できる変数のことで、クラスと、そのクラスからできたインスタンスから、呼び出すことができる変数
<クラス変数の定義と代入>
class クラス名 @@クラス変数名 = 代入したいデータ endインスタンス変数とは
インスタンスごとに独立し、インスタンスからのみアクセスすることができ、クラスからアクセスすることはできない変数
<インスタンス変数の定義と代入>
class クラス名 def インスタンス変数名=(代入したいデータ名) @インスタンス変数名 = 代入したいデータ名 end def インスタンス変数名 @インスタンス変数名 end endと定義し、「インスタンス名 = クラス名.new」でインスタンスを作成後、「インスタンス名.インスタンス変数名 = 代入したいデータ」で代入が可能
※インスタンス変数はクラス内からしか呼び出せないため、上記の通りセッターとゲッターと呼ばれるメソッドをそれぞれ定義しているセッターとは
インスタンス変数の値をセットするためのメソッドで「def インスタンス変数名=」と定義するのが一般的
ゲッターとは
インスタンス変数の値を取得するためのメソッドで「def インスタンス変数名」と定義するのが一般的
※流れ的には、インスタンスを作成し、インスタンス変数にデータを代入し、代入された変数をゲッターでクラスが取得し、セッターでセットし、インスタンス名.インスタンス変数名で呼び出されている<attr_accessor>
上記のゲッターとセッターは、この「attr_accessor」を使用する事で、一括で指定することもできる
class クラス名 attr_accessor :インスタンス変数名 endと定義し、「インスタンス名 = クラス名.new」でインスタンス作成後、「インスタンス名.インスタンス変数名 = 代入したいデータ」
<クラス変数の使用例>
class Animal @@counter = 0 def initialize @@counter += 1 end def self.get_counter return @@counter end endと定義する事で、
Animal.new p Animal.get_counterと書かれた際に、カウントされた数字が出力される
※「+= 1」は、クラス変数に1加算すると言う定義
※「return」は、メソッドの途中で抜け出し、その行の戻り値を返したい時に定義するメソッドで、省略も可能だが、複数行ある場合は、最後の戻り値が返される<インスタンス変数の使用例>
class Animal def name=(value) @name = value end def name @name end endと定義する事で、「animal = Animal.new」とインスタンスを作成した後、「animal.name = “サル”」と代入する事で、「p animal.name」と書けば「”サル”」と書き出される
<インスタンス変数で、attr_accessorを使用した場合>
class Animal attr_accessor :name endと定義する事で、「animal = Animal.new」とインスタンスを作成した後、「animal.name = “サル”」と代入する事で、「p animal.name」と書けば「”サル”」と書き出される
【継承】
既存のクラスを元に新しいクラスを作成すること
<定義の仕方>
class クラス名 < 継承したいクラス名<使用例>
class Animal def self.greet p "こんにちは!Animalです!" end endと定義し、
class Dog < Animal endと継承し「Dog.greet」を書けば、「"こんにちは!Animalです!”」と書き出される
※継承後にクラス独自のメソッドも同じように定義可能
- 投稿日:2020-12-07T15:47:36+09:00
rails マイグレーションファイルあれこれ
rails generate model User name:string email:string
Userのモデルをnameはstring型で、emailはstring型で作成という意味。
今までrails g model User のみをターミナルで入力してマイグレーションファイルをいじっていたので、このやり方は時短になるなあとおもった以下マイグレーションファイル
XXXXXXXXXXXXX_create_users.rbclass CreateUsers < ActiveRecord::Migration[6.0] def change create_table :users do |t| t.string :name t.string :email t.timestamps end end endchangeメゾットはcreate_tableというrailsのメゾットを呼び出し
create_tableメソッドはブロック変数を1つ持つブロックを受け取り,ここでは(“table”の頭文字を取って)t
ブロックの最後の行t.timestampsは特別なコマンドで
created_atとupdated_atという2つの「マジックカラム(Magic Columns)」を作成
$ rails db:migrate
でマイグレーションファイルが実行される。
実行されたマイグレーションファイルはいじっても更新されない
マイグレーションファイルが実行される前に戻りたいときは
$ rails db:rollback
このコマンドで戻すことができる
- 投稿日:2020-12-07T15:40:22+09:00
コンソールでDelayed::Jobを実行する手順
ローカル開発中にたまに使うときに忘れがちなので備忘録として。
以下でキューに入ったjobを実行可能
Delayed::Job.find(x).invoke_job実行した後はレコード削除
Delayed::Job.find(x).destroy以上、
- 投稿日:2020-12-07T15:27:18+09:00
【Rails Chart グラフ】Railsで、投稿に連動した美しいグラフ(レーダーチャート)を作ってみよう
【Rails Chart グラフ】Railsで、投稿に連動した美しいグラフを作ってみよう
Railsで、例えば
・投稿した成績をグラフ化したい時
・勉強時間をグラフ化したい時
など、様々な場面で、グラフを生成したいことがあるかと思います!そこで今回は、chart.jsを使って、グラフを生成し、投稿の情報と連動させてみたいと思います!
ちなみに、カラムは
Postモデルの
・title(投稿のタイトル)
・rate(グラフの観点1個目)
・kindness(グラフの観点2個目)
・sadness(グラフの観点3個目)
・bitterness(グラフの観点4個目)
を用意し、それぞれの観点からの点数をグラフ化したレーダーチャートを実装します実装までのステップ
1:chart.jsをCDN経由でRailsアプリに導入
2:グラフ生成のコード記述
3:ちょこっとコードいじって投稿と連動させるそれでは張り切っていきましょう!
1:chart.jsをCDN経由でRailsアプリに導入
こちらからCDNを作成しましょう!
タグにscriptを記述しましょう!
そして、view > layouts > application.html.erbのapplication.html.erb<!DOCTYPE html> <html> <head> <title>StarFunction</title> <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> <script src="ここに先ほどコピペしたものを貼り付けでください!"></script> </head>2:グラフ生成のコード記述
グラフを出したいViewに記述してください!
index.html.erb<canvas id="myChart"></canvas> <script> var ctx = document.getElementById("myChart"); var myChart = new Chart(ctx, { type: 'radar', data: { labels: ["Rate", "Kindness", "Sadness", "Bitterness"], datasets: [ { label: '自己分析第1回', backgroundColor: 'rgba(102,255,129,0.2)', borderColor: 'rgba(122,255,129,0.2)', data: [40, 42, 42, 43] }, { label: '自己分析第2回', backgroundColor: 'rgba(122,205,129,0.2)', borderColor: 'rgba(122,255,129,0.2)', data: [40, 42, 42, 43] }, { label: '自己分析第3回', backgroundColor: 'rgba(122,255,109,0.2)', borderColor: 'rgba(122,255,129,0.2)', data: [40, 42, 42, 43] } ] }, options: { scale: { ticks: { suggestedMin: 0, suggestedMax: 100 } } } }); </script>3:ちょこっとコードいじって投稿と連動させる
index.html.erb<canvas id="myChart"></canvas> <script> var ctx = document.getElementById("myChart"); var myChart = new Chart(ctx, { type: 'radar', data: { labels: ["Rate", "Kindness", "Sadness", "Bitterness"], datasets: [ <% @posts.each do |post| %> { label: '<%= post.title %>', backgroundColor: 'rgba(122,255,129,0.2)', borderColor: 'rgba(122,255,129,0.2)', data: [<%= post.rate %>, <%= post.kindness %>, <%= post.sadness %>, <%= post.bitterness %>] }, <% end %> ] }, options: { scale: { ticks: { suggestedMin: 0, suggestedMax: 5 } } } }); </script>グラフの観点を増やしたい時
index.html.erbdata: { labels: ["Rate", "Kindness", "Sadness", "Bitterness"],ここに観点を追加し、
index.html.erbdata: { data: [40, 42, 42, 43]ここにもデータの値を追加してください。
データの数値の最小値、最大値を変えたい時
index.html.erboptions: { scale: { ticks: { suggestedMin: 0, suggestedMax: 100 } } }ここの
suggestedMin: 0,
suggestedMax: 100
値を変えてあげてください!?
- 投稿日:2020-12-07T15:21:54+09:00
mysql + railsでペルソナ5R(ゲーム)のペルソナ情報まとめアプリを作ってみた
はじめに
railsを使ってゲーム、ペルソナ5Rのペルソナ情報をまとめるアプリケーションを作ってみました。
なぜやったのか
- railsを使ったwebアプリケーション開発を一通り行ってrailsの理解を深めるため
- jQueryを使ったajax非同期処理の実装方法について学習するため
- セッション管理機能実装について学習するため
- ある程度しっかりしたrspecテストコードを書いてみたかった
開発にあたって以下の項目を最低限組み込みたい機能として設定しました。
- データベース情報検索機能
- データベース情報編集機能
- jQueryによる非同期処理
- ログイン・ログアウトなどのセッション管理
- rspecテストコード
上記の機能実装をするにあたって、ゲームのモンスター情報などをまとめたアプリケーションが良いと考え、ちょうどP5Rにハマっていたということもあり作ってみました。ペルソナ(モンスター)のデータ量も程よかったというのも理由のひとつです。
後述するペルソナ合体(モンスター合体)結果シミュレーター機能については、P5Rにおいて合体法則が明確にルール化されていたので、自分なりにロジックに落とし込んで再現できると思い、挑戦してみた機能になります。環境
- ruby 2.6.5
- rails 6.0.3.4
- mysql 5.7.32
画面構成
- ホーム画面
- ペルソナ一覧画面
- ペルソナ詳細画面
- ペルソナ合体結果シミュレーター画面
- ペルソナ情報編集画面
データベース テーブル構成
- personas(ペルソナ情報テーブル)
カラム名 データ型 カラム説明 id bigint(20) 主キー name varchar(255) ペルソナ名 arcana_number int(11) アルカナ番号(arcanasテーブルの外部キー) category_id int(11) categoriesテーブルの外部キー initial_level int(11) ペルソナ初期レベル img_path varchar(255) ペルソナ画像パス
- arcanas(アルカナ名・番号マスタテーブル。ペルソナにはそれぞれタロットカードの大アルカナが設定されている。)
カラム名 データ型 カラム説明 id bigint(20) 主キー number varchar(255) アルカナ番号 name int(11) アルカナ名
- categories(ペルソナのカテゴリマスタテーブル。「通常」「宝魔」「集団ギロチン」「DLC」の4種類が登録されている。)
カラム名 データ型 カラム説明 id bigint(20) 主キー name varchar(255) カテゴリ名
- skills(ペルソナスキルのテーブル。personasと多対多の関係。)
カラム名 データ型 カラム説明 id bigint(20) 主キー name varchar(255) スキル名 description varchar(255) スキル説明文
- persona_skills(personasとskillsの中間テーブル)
カラム名 データ型 カラム説明 id bigint(20) 主キー persona_id int(11) personasテーブルの外部キー skill_id int(11) skillsテーブルの外部キー level int(11) ペルソナがスキルを習得するレベル
- arcana_combinations(ペルソナ合体結果を表示するための組み合わせ表テーブル)
カラム名 データ型 カラム説明 id bigint(20) 主キー key_number varchar(255) アルカナ結果を出すためのキーとなる文字列 first_arcana_number int(11) 1体目のアルカナ番号 second_arcana_number int(11) 2体目のアルカナ番号 result_arcana_number int(11) 合体結果のアルカナ番号
- users(管理者ユーザー情報テーブル)
カラム名 データ型 カラム説明 id bigint(20) 主キー name varchar(255) 管理者ユーザー名 varchar(255) メールアドレス password_digest varchar(255) ハッシュ化されたパスワード 全てのテーブルには
datetime
型のcreated_at
updated_at
カラムが存在しています。ホーム画面
ホーム画面です。
今回はページのスタイルテーマとして、bootstrap無料テンプレートテーマとfont awesomeを使用しました。font awesomeの導入は以下の記事を参考にさせていただきました。
サイドバーには各ページへのリンクが貼られています。
サイドバー上のアイコンをクリックすることによりホーム画面へ戻ってこれます。
トップバー右の「管理者ログイン」ボタンからログイン画面に遷移します。ペルソナ情報編集画面へは管理者のみ扱えるようにしています。ログインしていない状態ではサイドバーには表示されていません。
ログインすることで編集機能が使えるという旨のメッセージをホーム画面に表示しています。ペルソナ一覧画面
全232体のペルソナ一覧を表示する一覧画面です。
各ペルソナの情報をテーブル表示しています。
目的のペルソナを検索するための絞り込みフォームも実装しています。検索機能はajaxによる非同期処理で実装しており、「検索」ボタン押下時には画面を再読込することなく絞り込み結果を表示するようにしています。ペルソナ名部分がリンクになっており、クリックすることでそれぞれのペルソナの詳細画面が別タブで開きます。
ペルソナ詳細画面
ペルソナ詳細画面ではペルソナのアルカナ、ペルソナ名、画像、習得スキルが表示されます。
習得スキルにマウスオーバーするとスキル説明がカーソル近くに表示されるようになっています。(javascriptで実装)
実装にあたっては以下の記事を参考にさせていただきました。ペルソナ合体結果シミュレーター画面
ペルソナ合体結果シミュレーター画面では、画面についての説明がアコーディオンメニュー形式で書かれている部分と、実際にペルソナを2体選んで合体結果を表示させる部分があります。
ペルソナ5Rにおけるペルソナの合体法則を簡単に説明すると、
- 合体元ペルソナのアルカナによる組み合わせで誕生するペルソナのアルカナが決まる
- 「(1体目元ペルソナ初期レベル + 2体目元ペルソナ初期レベル)÷ 2 + 1」で仮レベルが算出される
- 決定したアルカナと仮レベル以上を満たすペルソナのうち、最も初期レベルの低いペルソナが誕生する
となります。
アルカナの組み合わせ表は以下のリンクを参照です。アルカナの組み合わせについては法則等がなかった(わからなかった)ので、表の組み合わせを全て「arcana_combinations」テーブルにマスタデータとして保存することで対応しました。
ペルソナを2体選択して「合体」ボタンを押せば、合体結果のペルソナが画面下部に表示されます。ここもajaxによる非同期処理で実装しているので、画面読み込みが発生することなく結果を表示することが可能です。合体結果で表示されたペルソナ名をクリックすることで詳細画面を別タブで開かせることができます。
※P5Rをご存知の方に一応補足すると、ゲーム中にある一部の例外的な組み合わせ(シヴァやアリス)や、「宝魔」カテゴリのペルソナの合体には対応していません。
ペルソナ情報編集画面
ペルソナ情報編集画面ではデータベースに保存されているペルソナ情報を編集することができます。
ペルソナの情報を扱う本アプリケーションでは、ペルソナ情報の編集は全体の機能に大きく影響するので、「users」テーブルに保存されている管理者としてログインした場合のみ扱うことのできる機能としています。
「管理者ログイン」ボタンからログインフォームに遷移し、メールアドレスとパスワードによる認証でセッション管理しています。
ログイン周りの機能実装にあたっては以下の記事を参考にさせていただきました。
ログインに成功すると「ログインに成功しました」のフラッシュメッセージとともに、サイドバーに「ペルソナ情報編集」のリンクが表示されるようになります。ホーム画面のメッセージも変化します。
リンクをクリックすることで編集画面に遷移します。
railsチュートリアルで見られる編集機能のような頻繁な画面遷移が煩わしいと思ったので、ペルソナ情報編集画面では入力フォームを一覧表示する形式で情報編集ができるようにしてみました。
項目を編集すると、「変更」ボタンが押せるようになって、「変更」を押すと該当情報に対してupdateアクションがはしり、変更が適用されるようになっています。編集画面にあたってはログインしている場合のみ使用可能な画面なので、ログインしていない状態からurlをいじる等で遷移できることのないよう、画面遷移時にログインしているかどうか判定を行って、ログインしていなければホーム画面にリダイレクトさせるような機能も実装しています。updateアクション実行時にも同様の判定を行い、ログインしていない状態から不正に編集できることがないようにしています。これらはコントローラーのbefore_actionの機能を使って実現されています。
rspecテスト
rspecを使ったテストコードも作成してみました。rspecとFactoryBotの導入については以下の記事を参考にさせていただきました。
今回は以下のようにテストを作成しています。
- モデルテスト
- Personaモデルテスト
- フィーチャーテスト
- ペルソナ一覧画面テスト
- ペルソナ詳細画面テスト
- 合体結果シミュレーター画面テスト
- ログイン関連テスト
- ペルソナ編集画面テスト
テストコードの詳しい内容は省きますが、それぞれのテストシナリオ名とテスト結果が以下のようになっています。
ペルソナ編集画面テスト Capybara starting Puma... * Version 4.3.6 , codename: Mysterious Traveller * Min threads: 0, max threads: 4 * Listening on tcp://127.0.0.1:41535 ログインしているとサイドバーのリンクから編集画面に遷移できる ログインしていない状態で編集画面に行こうとするとホーム画面にリダイレクトする 画面遷移した時にペルソナ情報が表示されている 初期状態で「変更」ボタンが押せない フォームに変化があると「変更」ボタンが押せるようになる ペルソナ名が変更できる アルカナが変更できる 初期レベルが変更できる 種別が変更できる 変更時に「変更されました」のフラッシュメッセージが出る ペルソナ一覧画面テスト ペルソナ一覧画面に遷移できる 画面遷移したときにペルソナが表示されている ペルソナ名でlike検索ができる アルカナで検索ができる 種別で検索ができる デフォルトのソートはアルカナ順である 初期レベル順でソートができる アルカナ順でソートできる ペルソナ詳細画面テスト ペルソナ一覧画面から詳細画面が別タブで開かれる ペルソナ名が表示されている ペルソナの習得スキルが表示されている ログイン関連テスト ログインしていないとトップ画面に特定のメッセージが表示される ログインしていないとサイドバーに「ペルソナ情報編集」リンクが表示されない 「管理者ログイン」ボタンをクリックするとログイン画面に遷移する ログイン画面で「ホーム画面に戻る」ボタンでホーム画面に遷移する 誤ったフォーム内容だとログインできない フォーム内容に誤りがあるとflashメッセージが表示される 適切なフォーム内容だとログインできる ログイン時に「ログインしました」のflashメッセージが表示される ログインしているとトップ画面に特定のメッセージが表示される ログインしているとサイドバーに「ペルソナ情報編集」リンクが表示される ログイン状態から「ログアウト」ボタンをクリックすることでログアウトできる ログアウト時に「ログアウトしました」のflashメッセージが表示される 合体結果シミュレーター画面テスト 合体結果シミュレーター画面に遷移できる アコーディオンメニューアイコンをクリックすると、説明欄が表示される 二体のペルソナを選択して「合体」をクリックすると合体結果のペルソナが表示される ペルソナを選択せずに「合体」をクリックするとアラートが出る 同じペルソナを選択して「合体」をクリックするとアラートが出る 合体結果ペルソナ名をクリックするとペルソナ詳細画面が別タブで開かれる 合体不可な組み合わせのペルソナの合体結果は「合体不可」となる ペルソナモデルテスト ペルソナ名でlike検索ができる アルカナ番号で検索できる カテゴリIDで検索できる ペルソナIDから獲得スキル一覧を取得できる 二体のペルソナの合体結果を取得できる 「愚者」✕「愚者」は「愚者」を取得する 「愚者」✕「魔術師」は「死神」を取得する 合体結果のペルソナは仮レベル以上かつ最も初期レベルが低いものを取得する 「審判」✕「戦車」はfalseとなる 「審判」✕「正義」はfalseとなる 「審判」✕「剛毅」はfalseとなる 「審判」✕「死神」はfalseとなる Finished in 26.86 seconds (files took 0.90139 seconds to load) 52 examples, 0 failuresさいごに
今回のアプリケーション開発で、最初に設定した実装したい機能は全て実装できました。rails開発アプリケーションのテーマはなんであれ、最初から最後まで実際に自分で手を動かして開発することで理解が格段に深まることが実感できました。
今回のアプリケーションは練習として作ったものなので公開するということなどは考えていませんが、いつかこういったアプリを開発して公開していけるようになっていきたいですね。次の記事
- 投稿日:2020-12-07T14:41:57+09:00
serializerを使って紐付けされたモデルの値を簡単に取る方法
概要
プログラミング初心者がRailsでAPIを作るときの発見を書いてみました
誰かの参考になれば幸いですやりたいこと
comment_serializerでcommentモデルに紐づいているuserモデルの中のaccountnameだけをjsonで渡したい
model同様にhas_manyなどを使用して書く
comments_serializer.rbclass CommentSerializer < ActiveModel::Serializer attributes :id, :content has_many :user endこれでも取ることができるがuser_serializerファイルの作成やattributesの記述だったり色々と面倒
もっと簡単に値を取得したい!!objectを使用する
comments_serializer.rbclass CommentSerializer < ActiveModel::Serializer attributes :id, :content, :accountname def accountname object.user.accountname end endobjectを使うことでuser_serializerファイル作成も行うことなく値を取ることができる