20201207のRubyに関する記事は30件です。

ユーザー名の表示

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は変数だからここは自由

今回はこれでおしまい
さあ寝よう

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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から「解決方法」で実行したコマンドを把握しました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

10の位・1の位を計算するプログラム

本日はこの問題についてです。

二桁の整数を入力すると、十の位と一の位の数字の足し算、十の位と一の位の数字の掛け算をそれぞれ行い、
最後に2つの結果を足し合わせて出力するプログラムをRubyで作成してください。
なお、2桁の数字以外が入力として与えられた場合を考慮する必要はありません。

例)

> 二桁の整数を入力してください
> 15

# 1 + 5 と 1 × 5 をそれぞれ計算

> 足し算結果と掛け算結果の合計値は11です

以下模範回答

ruby
def 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つのメソッド内で行っても構いません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

第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.green

StringをGreeterに継承しているため,GreeterはStringのメソッドを扱える.

overrideに関しては,親クラスで定義しているメソッドと同名のメソッドを子クラスで再定義するものと思っていたためここでの使い方はいまいち理解できていない.要勉強.


  • source ~/grad_members_20f/members/yoshida/c6_class_hello.org
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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)
# => 1

equal

同じオブジェクトの場合にtrueを返す

h1 = {}
h2 = {}
h1.equal?(h2)
# => false
h1.equal?(h1)
# => true

invert

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"}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録】deviseの導入

deviseとは?

Railsアプリケーションに認証機能(ユーザー登録機能、ログイン機能、ログアウト機能)を実装することができるgemです。数回のコマンドを実行するだけで簡単に認証機能を実装することができます。

導入の流れ

1. Gem導入

Gemfileに追記。

Gemfile.
gem 'devise'

bundle install実行。

bundle install

2. インストール

アプリケーションにdeviseをインストール。ここは忘れがちなので注意!

rails g devise:install

3. Userモデルの作成

rails g devise user

モデル作成と同時に、マイグレーションファイルやルーティングも自動的に作成されます。

4. 必要に応じてカラムを追加

deviseを介してモデルを作成した場合、emailとpasswordはデフォルトで用意されているので、他に必要なカラムがある時は追加します。

5. マイグレーション実行

マイグレーションファイルの内容をテーブルに反映させます。

rails db:migrate

6. ビューの作成

新規登録・ログイン用のビューを作成します。

rails g devise:views

こんな感じのビューが出来上がります⬇️

新規登録画面

Image from Gyazo

ログイン画面

Image from Gyazo

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

いいね機能を非同期で実装する方法

はじめに

前回いいね機能を実装しました。
ただ、いいねをするたびにページがリダイレクトされてしまいます。
そこで非同期通信という通信方法を使い、リダイレクトすることなくにビューに反映できるようにしていきたいと思います。
少しだけjQueryの知識が必要になります。

こんな人に向けて

1.いいね機能を実装済みの人。
2.非同期通信での実装をしたい人。

1.前提条件

・いいね機能を実装済みであること(実装していない人は こちら
(前回のいいね機能を改良していきます)

2.実装準備

非同期通信を実装するにあたって、新たなgemをインストールする必要があります。

Gemfile
gem "jquery-rails"
ターミナル
bundle install
app/assets/javascripts/application.js
//= require jquery   (←この行を新しく記載)
//= require rails-ujs 

jQueryという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.rb
  def 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_toremote: 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.erbdestroy.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.rb
  def 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を使うことです。
少しだけややこしくなりますが、作る流れは基本的に一緒だと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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}]}}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.rb
  def user_params
    params.require(:user).permit(:name, :address, :age, blogs_attributes: [:title, :content, :blog_num])
  end

:id と言う記述をしていません。

エラーを修正

不具合が発生しているのが子メソッドなので、strong parameter の小メソッド(blogs_attributes)の中に :id と言う記述を追加します。

user_controller.rb
  def 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 を含める」と覚えてしまうことで乗り切ろうと思います。

はっきりした原因を解説できるようになったら、またその時に解説記事を。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.rb
  def user_params
    params.require(:user).permit(:name, :address, :age, blogs_attributes: [:title, :content, :blog_num])
  end

:id と言う記述をしていません。

エラーを修正

不具合が発生しているのが子メソッドなので、strong parameter の小メソッド(blogs_attributes)の中に :id と言う記述を追加します。

user_controller.rb
  def 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 を含める」と覚えてしまうことで乗り切ろうと思います。

はっきりした原因を解説できるようになったら、またその時に解説記事を。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 #=> "次郎"

参考記事、書籍

チェリー本

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【剰余演算子】%ってなんだっけ?編

読んでなるほど!と思うのに、しばらくするとなんだっけ?ってなるシリーズその2

本日読んでいて忘れていた部分を抜粋。
剰余演算子は馴染み深い「 +-*/ 」以外に「 % 」というのがいることを忘れていました。

number.js
const 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の倍数です

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsエンジニアがGraphQLを触って比較してみた

はじめに

こんにちは!榊原です。
トレタに入社して1年が経ちました。
この1年はとても変化が多くあった年だったと感じています。

特に今年の1月から会社全体でフルリモート化や、
DXを意識した飲食店での自分のスマホでオーダーできるシステムや予約時に座席を指定して予約できるシステムなど、
時代の変化に対応した様々な新規サービスを発表した年でもあります。

発表したサービスの中にはまだ市場に受け入れられるか仮設段階のものも数多くありました。
エンジニアの人数も限りがある中で、最速かつ柔軟な変化を求められる開発を進めるに当たって、
最近弊社で使われ始めているのがGraphQLです。

今回はそんなGraphQLを弊社でも創業当初から運用されてきたRuby on Railsとの比較を中心とした説明とさせていただきます。

GraphQLを説明をするに当たって、グラフ理論は欠かせないのですがそちらは他の記事が多数存在するため今回は割愛させていただきます。

対象となる読者

  • Railsは使用していてもGraphQLは触ったことがないけど触ってみたい
  • サーバーサイドを担当していたけど、業務で使用しなければならなくなった

対象アプリケーション

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の説明

schema.png

今回使用するのは下記の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

実際にサービスを作成していると複数のテーブル情報を一度に取得したいというケースは多くの場面で遭遇すると思います。

ここでは、シンプルに personsphotos のデータを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

personsphotos をそれぞれ別で取得を行いました。
ですが実際にはリレーションに紐づく値のみを取得したいというケースの方が多いかと思います。

次は 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.save

GraphQL

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.destroy

GraphQL

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のドキュメントを読んでみてください。

https://hasura.io/docs/1.0/graphql/core/index.html

最後に

弊社ではGraphQLを仮説検証段階の、速度感をもったサービス開発で少しづつ取り入れ始めています。

そんな技術もサービスも一緒に検証したいエンジニアを募集しています。

カジュアルな面談もありますので、一度下記のURLから確認してみてください!
https://corp.toreta.in/recruit/midcareer/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ローカルホストに接続できない時の対処法

オリジナルアプリやポートフォリオ作成中に、ローカルホストに接続できないという経験はないでしょうか?

私は、あります(笑)

そんな時の対処法を備忘録を兼ねて、ご紹介します。

解決方法

その1 ターミナルの再起動

ターミナルを強制終了する
※私はダメでした(笑)

その2 server.pidファイルを削除

サーバーを起動する際、/ユーザー名/アプリケーション名/tmp/pids/server.pid.というファイルが生成されるそうです。
サーバーを閉じるとファイルは削除される、といったようにサーバーの起動と終了を行う際は、裏でこういったことが起きているみたいです。
※このファイルを削除するとうまくいくことがあるみたいです。

その3 Address already in use - bind(2) for "〜〜〜" port 3000 (Errno::EADDRINUSE)

私の場合は、ターミナル上にこの一文が太くなっていました。
その際は、以下のコマンドを実行してみたください。

qiita.rb
lsof -i :3000

そうすると、以下のようなものが、ターミナル上に表示され表示されると思います。

スクリーンショット 2020-12-07 19.47.07.png

あとは、以下のコマンドを実行するだけです。

qiita.rb
kill -QUIT "PIDの数字"

お試しあれ!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker開発環境でCapybara, FactoryBotを使ったテスト環境を構築追加する

概要

docker開発環境でRSpecテスト環境を構築するやり方の備忘録としてまとめました。
独学なので間違っている部分があると思いますが、その時はご指摘頂けると嬉しいです。

以下の記事を参考にしました。
https://qiita.com/at-946/items/e96eaf3f91a39d180eb3
https://qiita.com/na-tsune/items/91630257294aa0ea4fc8

1.docker-compose.ymlの編集

docker-compose.yml
version: '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を追記

Gemfile
group :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'
end

dockerの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]

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.rb
require '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_methods

5.redis-actionpackをアップデートしてみる

上記のredis-rackのアップデート実行後、
こちらの記事
https://github.com/redis-store/redis-rack/pull/50#issuecomment-567649953
を参考に
bundle update redis-actionpack

これでサーバー再起動後、Sidekiqの管理画面の500エラーが解消し、テストも通った。

まとめ

redis-rackredis-actionpackのアップデートで解決しました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails newが上手くいかない時の対処法

今日1時間ほどコマンドプロンプトでrails newをしても必要なファイルが作成されず、苦戦したのでその対処法を残しておきます。

誰かの参考になれば嬉しいです。

では、本題ですがコマンドプロンプトでrails newを実行するとこんな画面が表示されてばかりで、欲しいファイルが作成されませんでした。
gemfileでdeviseをインストール出来ないことからこの異変に気付いたのですが、最初はずっとgemfile側の問題だと思っていたのですが、全く改善できず。。。

そこで、最初に上手くいっていたアプリがあったのでそれを参考にしようと思ったのですが、まさか一番最初の時点でミスっているなんて思わずドツボにハマり、1時間ちょっと使いました(笑)

スクリーンショット (3).png

で、改めてこのコマンドプロンプトの表示を見てみるとgitのところで止まっていることに気づき、ググってみるとgitのコマンドがコマンドプロンプト上で使えるようになっていないからだと分かりました!!

だから、解決策としてはrails new アプリ名 -Gと後ろに-Gをつけてgitをスキップすれば解決することが判明し、無事解決することが出来ホッとしているところです。

gitをwindowsのコマンドプロンプトで使えるように設定しておけばよかった。。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsについて初心者なりに調べてみた

ドキュメント

Ruby on Rails ガイド:体系的に 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開発フレームワークのシェアと推移

Stack Overflow

image.png

djangoとlaravelがトレンド上昇している。

ruby on rails は2011年以降、下降している。

Ruby on rails のトレンド下降している要因

Twitterが、Ruby on RailsからJavaVMへ移行する理由

Twitterの膨大化したアクセスを、railsで構築されたシステムよりもJavaVMの方が速やかに処理できる。

→大規模システム開発で使われるケースが世界的に減っている。

なぜ一時期、一世を風靡したRuby on railsが、「railsはもう終わった」と言われるようになったのか?

その一部の背景を上記で説明しました。以下は具体的にかかれている記事

「Railsは終わった」と言われる理由 - Qiita

ただ日本ではスタートアップ中心に仕事がまだまだたくさんある。

例、Cookpad, Gunosy, 食べログ, Freee, Crowdworks

開発環境

  • ローカル
  • Virtual Box
  • Docker
  • Cloud9(AWS)

開発の流れ(ローカル環境)

基本的にMVCモデルの設計に沿って、ファイルを作成する。

image.png
↑railsチュートリアルから抜粋

参考文献

Ruby on Railsだけじゃない!Rubyフレームワーク6選

Ruby on Rails チュートリアル:プロダクト開発の0→1を学ぼう

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】チャットの吹き出しの向きを変える方法

本記事は 駆け出しエンジニアの第一歩!AdventCalendar2020 7日目の記事です。

作りたいもの

下図のように、自分のメッセージと自分以外のメッセージでユーザーアイコンの位置や吹き出しの向きが変わるチャット機能を作ります。

image.png

前提条件

  • チャット機能自体はできている
  • bootstrap4がインストールされている

やることまとめ

  1. 自分用の吹き出しと相手用の吹き出しのCSSクラスを用意する
  2. Viewファイルにて、メッセージごとに自分or自分以外のメッセージかを判定する
  3. それぞれの判定ごとに適用する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 %>

終わりに

今回は私個人のアプリで実際に実装したものを題材にしたので、人によってはビューの書き方は異なってくると思います!

ご参考になれば幸いです…!

参考サイト

RailsでDM(ダイレクトメッセージ)を送れるようにしよう

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.rb
require '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.size
bit.rb
0.upto(2**p.size - 1) do |bit|
  # 処理
end

ここが、ビット全検索の部分になります。

動的計画法

ruby.rb
require '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 に詳しくなった
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ああああああ

ああああああ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

第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_equal

fib(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_equal

fib(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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

第11回

Ubuntu-20.04.1 ruby-2.7.0p0

クラス

これぞオブジェクト指向…

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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsテスト駆動する理由

railsのテスト駆動に関する参考になった記事があるので共有
https://teratail.com/questions/121833?link=qa_related_pc_sidebar

テストをする理由は今のコードと自分の認識を一致させることだったということ。

user_test.rb

class 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

end

assert_notはassert_notは引数の結果がfalseの時にテストが成功とする命令
つまり現時点でバリテーションがかかれていないため、@user.validがtrueとなるため、テストは失敗。
今のコードと自分の認識はあっていたということになる。
じゃあバリテーションを書こう。
こうなるわけなのか。。。なるほど。
テストって意味あるの?って疑問だったが、効率良く開発を進めていく上で必要不可欠なんだと理解した。

(1) 仕様を先に決める(頭の中だけでもいいし、紙に書いたりしてもいい)
(2) 仕様をテストとして記述する
(3) 仕様を満たすために実装する

このステップが大事なんですね。
めちゃめちゃ参考になりました。テストの書き方とかも勉強しないとな

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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です!”」と書き出される
※継承後にクラス独自のメソッドも同じように定義可能

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails マイグレーションファイルあれこれ

rails generate model User name:string email:string

Userのモデルをnameはstring型で、emailはstring型で作成という意味。
今までrails g model User のみをターミナルで入力してマイグレーションファイルをいじっていたので、このやり方は時短になるなあとおもった

以下マイグレーションファイル

XXXXXXXXXXXXX_create_users.rb
class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email

      t.timestamps
    end
  end
end

changeメゾットはcreate_tableというrailsのメゾットを呼び出し
create_tableメソッドはブロック変数を1つ持つブロックを受け取り,ここでは(“table”の頭文字を取って)t
ブロックの最後の行t.timestampsは特別なコマンドで
created_atとupdated_atという2つの「マジックカラム(Magic Columns)」を作成

$ rails db:migrate

でマイグレーションファイルが実行される。
実行されたマイグレーションファイルはいじっても更新されない
マイグレーションファイルが実行される前に戻りたいときは
$ rails db:rollback
このコマンドで戻すことができる

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コンソールでDelayed::Jobを実行する手順

ローカル開発中にたまに使うときに忘れがちなので備忘録として。

以下でキューに入ったjobを実行可能

Delayed::Job.find(x).invoke_job

実行した後はレコード削除

Delayed::Job.find(x).destroy

以上、

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails Chart グラフ】Railsで、投稿に連動した美しいグラフ(レーダーチャート)を作ってみよう

【Rails Chart グラフ】Railsで、投稿に連動した美しいグラフを作ってみよう

Railsで、例えば
・投稿した成績をグラフ化したい時
・勉強時間をグラフ化したい時
など、様々な場面で、グラフを生成したいことがあるかと思います!

そこで今回は、chart.jsを使って、グラフを生成し、投稿の情報と連動させてみたいと思います!
image.png

ちなみに、カラムは
Postモデルの
・title(投稿のタイトル)
・rate(グラフの観点1個目)
・kindness(グラフの観点2個目)
・sadness(グラフの観点3個目)
・bitterness(グラフの観点4個目)
を用意し、それぞれの観点からの点数をグラフ化したレーダーチャートを実装します

実装までのステップ

1:chart.jsをCDN経由でRailsアプリに導入
2:グラフ生成のコード記述
3:ちょこっとコードいじって投稿と連動させる

それでは張り切っていきましょう!

1:chart.jsをCDN経由でRailsアプリに導入

こちらからCDNを作成しましょう!
スクリーンショット 2020-12-07 14.49.44.png

スクリーンショット 2020-12-07 14.52.25.png
そして、view > layouts > application.html.erbの

タグにscriptを記述しましょう!
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>

こんな感じになりましたかね!?
image.png

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>

こんな感じに投稿と連動します
スクリーンショット 2020-12-07 15.24.23.png

グラフの観点を増やしたい時

index.html.erb
 data: {
    labels: ["Rate", "Kindness", "Sadness", "Bitterness"],

ここに観点を追加し、

index.html.erb
 data: {
    data: [40, 42, 42, 43]

ここにもデータの値を追加してください。

データの数値の最小値、最大値を変えたい時

index.html.erb
  options: {
      scale: {
          ticks: {
              suggestedMin: 0,
              suggestedMax: 100
          }
      }
  }

ここの
suggestedMin: 0,
suggestedMax: 100
値を変えてあげてください!?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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) 管理者ユーザー名
email varchar(255) メールアドレス
password_digest varchar(255) ハッシュ化されたパスワード

全てのテーブルにはdatetime型のcreated_at updated_atカラムが存在しています。

ホーム画面

Screenshot from 2020-12-03 13-12-43.png
ホーム画面です。
今回はページのスタイルテーマとして、bootstrap無料テンプレートテーマとfont awesomeを使用しました。

font awesomeの導入は以下の記事を参考にさせていただきました。

サイドバーには各ページへのリンクが貼られています。
サイドバー上のアイコンをクリックすることによりホーム画面へ戻ってこれます。
トップバー右の「管理者ログイン」ボタンからログイン画面に遷移します。

ペルソナ情報編集画面へは管理者のみ扱えるようにしています。ログインしていない状態ではサイドバーには表示されていません。
ログインすることで編集機能が使えるという旨のメッセージをホーム画面に表示しています。

ペルソナ一覧画面

Screenshot from 2020-12-03 13-42-47.png

全232体のペルソナ一覧を表示する一覧画面です。
各ペルソナの情報をテーブル表示しています。
目的のペルソナを検索するための絞り込みフォームも実装しています。検索機能はajaxによる非同期処理で実装しており、「検索」ボタン押下時には画面を再読込することなく絞り込み結果を表示するようにしています。

ペルソナ名部分がリンクになっており、クリックすることでそれぞれのペルソナの詳細画面が別タブで開きます。

ペルソナ詳細画面

Screenshot from 2020-12-03 16-19-18.png
ペルソナ詳細画面ではペルソナのアルカナ、ペルソナ名、画像、習得スキルが表示されます。
習得スキルにマウスオーバーするとスキル説明がカーソル近くに表示されるようになっています。(javascriptで実装)
Screenshot from 2020-12-03 16-21-45.png
実装にあたっては以下の記事を参考にさせていただきました。

ペルソナ合体結果シミュレーター画面

Screenshot from 2020-12-03 16-32-49.png

ペルソナ合体結果シミュレーター画面では、画面についての説明がアコーディオンメニュー形式で書かれている部分と、実際にペルソナを2体選んで合体結果を表示させる部分があります。
ペルソナ5Rにおけるペルソナの合体法則を簡単に説明すると、

  1. 合体元ペルソナのアルカナによる組み合わせで誕生するペルソナのアルカナが決まる
  2. 「(1体目元ペルソナ初期レベル + 2体目元ペルソナ初期レベル)÷ 2 + 1」で仮レベルが算出される
  3. 決定したアルカナと仮レベル以上を満たすペルソナのうち、最も初期レベルの低いペルソナが誕生する

となります。
アルカナの組み合わせ表は以下のリンクを参照です。

アルカナの組み合わせについては法則等がなかった(わからなかった)ので、表の組み合わせを全て「arcana_combinations」テーブルにマスタデータとして保存することで対応しました。
ペルソナを2体選択して「合体」ボタンを押せば、合体結果のペルソナが画面下部に表示されます。ここもajaxによる非同期処理で実装しているので、画面読み込みが発生することなく結果を表示することが可能です。

Screenshot from 2020-12-03 17-02-19.png

合体結果で表示されたペルソナ名をクリックすることで詳細画面を別タブで開かせることができます。

※P5Rをご存知の方に一応補足すると、ゲーム中にある一部の例外的な組み合わせ(シヴァやアリス)や、「宝魔」カテゴリのペルソナの合体には対応していません。

ペルソナ情報編集画面

ペルソナ情報編集画面ではデータベースに保存されているペルソナ情報を編集することができます。
ペルソナの情報を扱う本アプリケーションでは、ペルソナ情報の編集は全体の機能に大きく影響するので、「users」テーブルに保存されている管理者としてログインした場合のみ扱うことのできる機能としています。
「管理者ログイン」ボタンからログインフォームに遷移し、メールアドレスとパスワードによる認証でセッション管理しています。
Screenshot from 2020-12-03 17-16-20.png

ログイン周りの機能実装にあたっては以下の記事を参考にさせていただきました。

ログインに成功すると「ログインに成功しました」のフラッシュメッセージとともに、サイドバーに「ペルソナ情報編集」のリンクが表示されるようになります。ホーム画面のメッセージも変化します。

Screenshot from 2020-12-03 17-17-31.png

リンクをクリックすることで編集画面に遷移します。

Screenshot from 2020-12-03 17-22-37.png

railsチュートリアルで見られる編集機能のような頻繁な画面遷移が煩わしいと思ったので、ペルソナ情報編集画面では入力フォームを一覧表示する形式で情報編集ができるようにしてみました。
項目を編集すると、「変更」ボタンが押せるようになって、「変更」を押すと該当情報に対してupdateアクションがはしり、変更が適用されるようになっています。

  • 項目に変化があると「変更」ボタンが押せるようになる
    Screenshot from 2020-12-03 17-38-20.png

  • 変更が成功するとフラッシュメッセージが表示される
    Screenshot from 2020-12-03 17-38-36.png

編集画面にあたってはログインしている場合のみ使用可能な画面なので、ログインしていない状態から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開発アプリケーションのテーマはなんであれ、最初から最後まで実際に自分で手を動かして開発することで理解が格段に深まることが実感できました。
今回のアプリケーションは練習として作ったものなので公開するということなどは考えていませんが、いつかこういったアプリを開発して公開していけるようになっていきたいですね。

次の記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

serializerを使って紐付けされたモデルの値を簡単に取る方法

概要

プログラミング初心者がRailsでAPIを作るときの発見を書いてみました
誰かの参考になれば幸いです

やりたいこと

comment_serializerでcommentモデルに紐づいているuserモデルの中のaccountnameだけをjsonで渡したい

model同様にhas_manyなどを使用して書く

comments_serializer.rb
class CommentSerializer < ActiveModel::Serializer
  attributes :id, :content

  has_many :user
end

これでも取ることができるがuser_serializerファイルの作成やattributesの記述だったり色々と面倒
もっと簡単に値を取得したい!!

objectを使用する

comments_serializer.rb
class CommentSerializer < ActiveModel::Serializer

  attributes :id, :content, :accountname

  def accountname
    object.user.accountname
  end

end

objectを使うことでuser_serializerファイル作成も行うことなく値を取ることができる

 
 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む