このエントリーをはてなブックマークに追加
ラベル Ruby の投稿を表示しています。 すべての投稿を表示
ラベル Ruby の投稿を表示しています。 すべての投稿を表示

2017年2月9日木曜日

はじめの一歩 -Rails ActiveRecord編- UPDATE2


どうも、はじめです。
前回はUPDATEについて書いてみました。
はじめの一歩 -Rails ActiveRecord編- UPDATE
今回はUPDATEの際readonlyやvalidationで更新ができなかった場合に関して書いていこうと思います。


はじめに


今回も前回と同じテーブルを使用しようと思います。
usersテーブル
[id: 1, user_name: 'Aさん'],
[id: 2, user_name: 'Bさん'],
[Id: 3, user_name: 'Cさん']

itemsテーブル
[id: 1, item_name: 'Rubyの本'],
[id: 2, item_name: 'Railsの本'],
[id: 3, item_name: 'PHPの本']

user_itemsテーブル(userが持っているitemを管理するテーブル)
[id: 1, user_id: 1, item_id: 1],
[id: 2, user_id: 1, item_id: 2],
[id: 3, user_id: 2, item_id: 3]

関連性
user : user_item => 1 : n
item : user_item => 1 : n


ではまず【readonly】で更新できなかった場合の対処法を書いてみます。

readonly


下のように更新をしてみます。
[id: 2, user_id: 1, item_id: 2]
↓
[id: 2, user_id: 1, item_id: 3]
手元でなかなかreadonlyになる状況を再現できなかったため
以下の方法でreadonlyにしました。
user_item = UserItem.readonly(true)

この状態で変更しようとしても更新できません。
user_item.find(2).update(item_id: 3)
# ActiveRecord::ReadOnlyRecord: UserItem is marked as readonly

これだとreadonlyでrollbackされてしまいます。

モデルからインスタンスを取得する際にreadonlyをfalseにしてあげれば更新が可能になります。
user_item = UserItem.readonly(false)
user_item.find(2).update(item_id: 3)


次に【validation】でエラーになった場合です。

validation


ユーザーの登録の際、user_nameを入力必須にしていたとします。
入力必須のバリデーションを定義するにはUserモデルに対し以下のように定義します。

# app/models/user.rb
class User < ApplicationRecord
 validates :user_name, presence: true
end

この状態でユーザー名を空で登録しようとするとエラーになります。
user = User.new
user.save

次のように記述することで一時的にバリデーションを無効にすることができます。
user = User.new
user.save(:validate => false)


最後に


validationを一時的に無効にすることは可能ですが、
今回例としてあげたのケースの場合、
DBでuser_nameにnot null制約がかかっているとバリデーションを無視することができても、
DBへの登録時にエラーになってしまいますので気をつけましょう。

次回はupdate_attributeとupdate_attributesを使ってみようと思います。

2017年2月2日木曜日

はじめの一歩 -Rails ActiveRecord編- UPDATE


どうも、はじめです。
前回はJOINについて書いてみました。
はじめの一歩 -Rails ActiveRecord編- SELECT4
今回はUPDATEに関して書いていこうと思います。


はじめに


今回も前回と同じテーブルを使用しようと思います。
usersテーブル
[id: 1, user_name: 'Aさん'],
[id: 2, user_name: 'Bさん'],
[Id: 3, user_name: 'Cさん']

itemsテーブル
[id: 1, item_name: 'Rubyの本'],
[id: 2, item_name: 'Railsの本'],
[id: 3, item_name: 'PHPの本']

user_itemsテーブル(userが持っているitemを管理するテーブル)
[id: 1, user_id: 1, item_id: 1],
[id: 2, user_id: 1, item_id: 2],
[id: 3, user_id: 2, item_id: 3]

関連性
user : user_item => 1 : n
item : user_item => 1 : n


UPDATEを行うには主に以下の二つの方法があります。
・saveメソッドを使用する。
・updateメソッドを使用する。

でははじめに【saveメソッド】を使用した方法から書いていきます。


save


user_itemsを以下のように変更したいと思います。
[id: 3, user_id: 2, item_id: 3]
↓
[id: 3, user_id: 3, item_id: 3]

1.変更したいモデルインスタンスを取得
user_item = UserItem.find(3)

2.変更したい値を代入
user_item.user_id = 3

3.代入が完了したら登録
user_item.save

以上の手順でupdateが完了します。
2で複数のカラムを変更することも可能です。


次に【updateメソッド】を使用した方法を書いてみます。

update


updateでも同様にuser_itemsを以下のように変更したいと思います。
[id: 3, user_id: 2, item_id: 3]
↓
[id: 3, user_id: 3, item_id: 3]

updateメソッドを使用する際も2つの方法があります。
一つはsaveメソッドと同じように変更したいモデルインスタンスを取得し、
updateという流れになります。

user_item = UserItem.find(3)
user_item.update(user_id: 3)

もう一つは一度変数にインスタンスを取得するという工程を飛ばす方法です。

UserItem.find(3).update(user_id: 3)

どれも結果は同じになります。


まとめ


updateをする際変更したいレコードがreadonlyになっていた場合や、
validation等の関係で更新が失敗する場合があります。
その場合の対処法等を次回書いてみようと思います。

2017年1月31日火曜日

Gemのバージョンをプロジェクトごとに分ける方法

こんにちは。h_ono_222です。

複数のアプリケーションを同時に開発している場合、同じGemでもそれぞれのアプリケーションごとにバージョンを分けたい場合があると思います。
今回はその方法を紹介します。


1. 空のプロジェクトを作成する

$ mkdir test_app && cd test_app
$ bundle init
Writing new Gemfile to /Users/user_name/test_app/Gemfile


2. Gemfileを修正する

# gem "rails"のコメントアウトを外します。


3. インストール先を指定してbundle install

$ bundle install --path vendor/bundle
Fetching gem metadata from https://rubygems.org/ 
Fetching version metadata from https://rubygems.org/ 
Fetching dependency metadata from https://rubygems.org/ 
Resolving dependencies....
# 〜中略〜
Bundle complete! 1 Gemfile dependency, 38 gems now installed.
Bundled gems are installed into ./vendor/bundle.


4. アプリケーションを作成する

$ bundle exec rails new .
       exist  
      create  README.md
      create  Rakefile
      create  config.ru
      create  .gitignore
    conflict  Gemfile
Overwrite /Users/DevWork/Documents/test_app/Gemfile? (enter "h" for help) [Ynaqdh] Y # Yを入力してEnter
force  Gemfile
# 〜中略〜
Bundle complete! 15 Gemfile dependencies, 62 gems now installed.
Bundled gems are installed into ./vendor/bundle.
         run  bundle exec spring binstub --all
* bin/rake: spring inserted
* bin/rails: spring inserted

以上です。

2017年1月26日木曜日

はじめの一歩 -Rails ActiveRecord編- SELECT4


どうも、はじめです。
前回はJOINについて書いてみました。
はじめの一歩 -Rails ActiveRecord編- SELECT3
今回も引き続きJOINに関する内容ですが、JOINでも3つ以上のテーブルの結合について書いていこうと思います。


はじめに


3つ以上のテーブルの結合なので、結合できるテーブルを3つ用意します。

usersテーブル
[id: 1, user_name: 'Aさん'],
[id: 2, user_name: 'Bさん'],
[Id: 3, user_name: 'Cさん']

itemsテーブル
[id: 1, item_name: 'Rubyの本'],
[id: 2, item_name: 'Railsの本'],
[id: 3, item_name: 'PHPの本']

user_itemsテーブル(userが持っているitemを管理するテーブル)
[id: 1, user_id: 1, item_id: 1],
[id: 2, user_id: 1, item_id: 2],
[id: 3, user_id: 2, item_id: 3]

関連を説明すると以下のようになります。
user : user_item => 1 : n
item : user_item => 1 : n

それでは前回使用したincludesを使用して書いていこうと思います。


取得


・Aさんが持っているアイテムを全て取得したい場合
user_items = User.includes(user_items: :item).find(1)

上記のように記述することで以下のようなSQLが実行されます。
SELECT `users`.* FROM `users` WHERE `users`.`id` = 1;
SELECT `user_items`.* FROM `user_items` WHERE `user_items`.`id` in (1,2);
SELECT `items`.* FROM `items` WHERE `items`.`id` in (1,2);

joinsの場合も同様の書き方となります。


表示


アイテム名を表示したい場合は以下のようにします。
user_items.user_items.each do |user_item|
  p user_item.item.item_name
end
# => 'Rubyの本'
# => 'Railsの本'


さらにテーブルが増えた場合


itemsにcategoryテーブルを関連付けして色々なパターンで取得をしてみたいと思います。
以下のような関連性でitemsに対し、categoriesテーブルを作成します。
items : category => n : 1

itemsテーブル
[id: 1, item_name: 'Rubyの本', category_id: 1],
[id: 2, item_name: 'Railsの本', category_id: 1],
[id: 3, item_name: 'PHPの本', category_id: 2]

categoriesテーブル
[id: 1, category_name: 'Ruby'],
[id: 2, category_name: 'PHP']

パターン1
・Aさんが持っているアイテムのカテゴリーまで取得したい場合
User.includes(user_items: {item: :category}).find(1)

パターン2
・"Rubyの本"のカテゴリーと持っているユーザーを取得したい場合
Item.includes(:category, {user_items: :user}).find(1)


まとめ


以下のような関連性だった場合
a:b:c => 1:n:1
c:d =>n:1

隣接しているテーブルを複数結合したい場合はのように「,」でつなげることができます。
B.includes(:a, :c)
二つ隣のテーブルの情報を取得する場合は「:」を向かい合わせに記述をします。
A.includes(b: :c)
さらに深いテーブルの情報を取得する場合は{}を使用します
A.includes(b: {c: :d})

前回も記述をしたように
3つ以上のテーブルを結合する場合でもテーブル名は
複数系、単数系を意識しなければいけませんので注意してください。

以上でSELECT系(JOIN系)を終了し、
次回からはUPDATEに入ろうと思います。

2017年1月19日木曜日

はじめの一歩 -Rails ActiveRecord編- SELECT3


どうも、はじめです。
前回はActiveRecordのSELECTについて書いてみました。
はじめの一歩 -Rails ActiveRecord編- SELECT2
今回はActiveRecordのデータの取得(SELECT)の中でもJOIN系に関して書いていこうと思います。


はじめに


booksテーブルに以下のデータが登録済みであることを前提とします。
[id: 1, title: ‘Rubyの本’, publisher_id: 1],
[id: 2, title: ‘Railsの本’, publisher_id: 1],
[Id: 3, title: ‘PHPの本’, publisher_id: 2]

今回はJoin系のメソッドについて書いていくので、
booksに対し多対1で紐づくpublishersテーブルを以下のように用意します。
[id: 1, publisher_name: '出版社1'],
[id: 2, publisher_name: '出版社2']


joins


joinsはinner joinでの結合を行います。
Books.joins(:publisher)
# SELECT `books`.* FROM `books` INNER JOIN `publishers` ON `publishers`.`id` = `books`.`publisher_id`

出版社名を表示したい場合は以下のように表示をすることができます。
例としてbooksテーブルのID:1のレコードで取得してみます。
book = Book.joins(:publisher).find(1)
p book.publisher.publisher_name
# '出版社1'

※joinsを使用するとreadonlyとなるため更新ができないと
いろいろなところで目にしましたが、手元で確認をしたところreadonlyはfalseとなっていました。
ちなみにRailsのバージョンは5.0.0.1です。
バージョンによるものか環境によるものかはわかりませんが、
実際に使用する場合は一度確認をしてみるべきだと思います。

joinsを使用した状態でwhereを使用したい場合は以下のように書くことができます。
# Bookの情報で検索を行いたい場合
Book.joins(:publisher).where(title: ‘Railsの本’)

# Publisherの情報で検索を行いたい場合(以下の二つは同じ内容が実行されます)
Book.joins(:publisher).where(publishers: {publisher_name: '出版社1'})
Book.joins(:publisher).where("publishers.publisher_name = '出版社1'")
mergeを使用するとActiveRecord::Relationを使用して検索することもできます。
Book.joins(:publisher).merge(Publisher.where(publisher_name: '出版社1'))
※後で記述するincludesでは使用できませんでした。
使用できない理由はこの後書かせていただきます。


includes


includesは結合先のテーブルと結合元のテーブルに対し
別々にクエリを実行してデータを取得します。
Book.includes(:publisher)
# SELECT `books`.* FROM `books`
# SELECT `publishers`.* FROM `publishers` WHERE `publishers`.`id` IN (1, 2)

表示の仕方はjoinsと同じです。
book = Book.joins(:publisher).find(1)
p book.publisher.publisher_name
# '出版社1'

それぞれに対して別々にクエリを実行していることから、
指定したデータがBookに存在していない場合はPublisherに対するクエリは実行されません。
Book.joins(:publisher).find(4)
# SELECT `books`.* FROM `books` WHEHE `books`.`id` = 4

includesを使用した状態でwhereを使用したい場合は、
どのカラムで検索するかによって実行されるSQLの内容が変わってくるので注意が必要です。


実際に違いを見てみます。
検索条件のパターンとして以下の2つをあげます。
1.Book内の情報で検索をする
2.Publish内の情報で検索をする
実際に試してみます。

1.Book内の情報で検索をする
Book.includes(:publisher)where(title: ‘Railsの本’)
# SELECT `books`.* FROM `books` WHERE `books`.`title` = "Railsの本"
# SELECT `publishers`.* FROM `publishers` WHERE `publishers`.`id` = 1
こちらは条件指定をしない場合と変わりません。

2.Publish内の情報で検索をする
Book.includes(:publisher)where(publishers: {publisher_name: '出版社1'})
# SELECT `books`.`id, `books`.`title`, `books`.`publisher_id`, `publishers`.`id`, `publishers`.`publisher_name`
# FROM `books` LEFT OUTER JOIN `publishers` ON ``publishers`.`id` = `books`.`publisher_id`
# WHERE `publishers`.`publisher_name` = "出版社1"
このように結合先のテーブル情報で検索を行うと"LEFT OUTER JOIN"を使った結合が行われます。

includesでmergeを使用した場合以下のようなSQLが実行されます。
Book.includes(:publisher).merge(Publisher.where(publisher_name: '出版社1'))
# SELECT `books`.* FROM `books` WHERE `publishers`.`publisher_name` = "出版社1"
クエリが各テーブル毎に発行されているためこのようなクエリが生成されるのだと思います。


最後に


今回のJOIN系に関して個人的に大変だと思ったのはテーブル名の記述です。
joinsやincludesで指定するテーブル名ですが、
自分から見て結合するテーブルがどのような関連性を持っているかによって
記述するテーブル名が変わってきます。

・自分から見て結合先のテーブルが一つしか存在しない場合(Bookから見たPublishのような関係)
単数系のテーブル名で指定する。
・自分から見て結合するテーブルのデータが複数存在する場合(Publishから見たBookのような関係)
複数形のテーブル名で指定する。

また、結合先のテーブル情報でwhereを使用する場合は
複数形のテーブル名で指定をします。

今回書ききれなかったこともあるので、次回もJOIN系の続きを書きます。

2017年1月10日火曜日

はじめの一歩 -Rails ActiveRecord編- SELECT2


どうも、はじめです。

前回はActiveRecordのSELECTについて書いてみました。
はじめの一歩 -Rails ActiveRecord編- SELECT
今回も引き続きActiveRecordのデータの取得(SELECT)に関して書いていこうと思います。


はじめに、


前回と同様に以下のデータが登録済みであることを前提とします。
[id: 1, name: ‘名前1’, mail: ‘111@xxx.xxx’],
[id: 2, name: ‘名前2’, mail: ‘222@xxx.xxx’],
[Id: 3, name: ‘名前3’, mail: ‘333@xxx.xxx’]

前回記述したallやwhereを使用してデータの取得をした場合、
ActiveRecord::Relationというクラスのオブジェクトが返却されます。
取得したレコードが1件だけであったとしても使用する際は以下のように記述する必要があります。
user = User.where(id: 1)
p user[0][:name]
# => '名前1'
上記のように1件のみの取得と確定している場合でも[0]をつけなくてはなりません。
今回は1件のみを取得する方法を書いていきたいと思います。


find


findはIDの直指定でレコードを取得することができます。
実行文と実行結果は以下のようになります。
user = User.find(2)
p user.mail
# => ‘222@xxx.xxx’
findでもし存在しないIDを指定した場合は例外が発生します。

ちなみにIDの複数指定も可能です。
複数のIDを指定する際は以下のように配列で複数のIDを指定します。
user = User.find([2,3])
p user[0][:name]
# => '名前2'
p user[1][:name]
# => '名前3'
こちらは以下のようにwhereで取得した場合と同じ結果になります。
user = User.where(id: [2,3])


find_by


findの場合はIDしか指定できませんでしたが、
他のカラムでの検索で1件のみ取得したい場合はfind_byを使用します。
user = User.find_by(name: '名前1', mail: ‘111@xxx.xxx’)
p user.id
# => 1
find_byでの検索はSQLの末尾に[limit 1]を追加して検索をしてくれるので、
検索結果が複数あった場合でもヒットした中の一番最初のレコード1件のみを取得してくれます。
上記のように複数条件を指定することもできますが、すべてAND条件での検索となります。

find_byでIDを指定することもできますが、
findとの違いとしましては、検索結果が0件だった場合に例外ではなくnilを返します。


first


firstはを実行した場合は[order by `id` asc limit 1]が実行されます。
引数で数値を渡してlimitの数を指定することもできます。
user = User.first
user.id
# => 1
user = User.first(2)
user[1][:name]
# => '名前2'


最後に、


今後もしばらくデータの取得に関して書いていこうと思っております。
次回はjoin系を書いてみようと思っております。

2017年1月7日土曜日

はじめの一歩 -Rails ActiveRecord編- SELECT


どうも、はじめです。

あけましておめでとうございます。
今年もよろしくお願いいたします。

さて前回はActiveRecordのINSERTについて書いてみました。
はじめの一歩 -Rails ActiveRecord編- INSERT
今回はActiveRecordのデータの更新(update)について書こうと思っていましたが、
先にデータの取得(select)について書いてみようと思います。


データの取得をしたい場合


モデルに登録されているすべてのデータを取得したい場合はallを、
検索をしたい場合はwhereを使用します。

Userモデルに以下でデータを登録したとします。
[id: 1, name: ‘名前1’, mail: ‘111@xxx.xxx’],
[id: 2, name: ‘名前2’, mail: ‘222@xxx.xxx’],
[Id: 3, name: ‘名前3’, mail: ‘333@xxx.xxx’]

all
User.all
# [Id: 1, name: ‘名前1’, mail: ‘111@xxx.xxx’],
# [Id: 2, name: ‘名前2’, mail: ‘222@xxx.xxx’],
# [Id: 3, name: ‘名前3’, mail: ‘333@xxx.xxx’]

where
User.where(id: 2)
# [Id: 2, name: ‘名前2’, mail: ‘222@xxx.xxx’]
allの場合は指定したモデルに登録されているデータをすべて取得することができます。
whereの場合はwhere()内に「カラム名: 検索したい値」を指定することで、
指定した値で検索した結果を取得することができます。


複数条件で検索をしたい場合


複数の条件でデータの取得を行いたい場合はwhereにて条件を複数指定します。
Idが2と3のレコードを取得したい場合は以下のようにidに対し配列で条件を指定します。

User.where(id: [2, 3])
# [Id: 2, name: ‘名前2’, mail: ‘222@xxx.xxx’],
# [Id: 3, name: ‘名前3’, mail: ‘333@xxx.xxx’]

複数カラムを指定したい場合は以下のように指定します。
User.where(id: 2, name: ‘名前2’)
# [Id: 2, name: ‘名前2’, mail: ‘222@xxx.xxx’]
このように指定した場合はAND条件で検索されるので注意が必要です。


最後に、


OR条件での検索や、AND条件でも他の記述方法があったりと
他にもデータの抽出には種類があるため、
次回も引き続きSELECT関連を書いていこうと思います。

2016年12月29日木曜日

はじめの一歩 -Rails ActiveRecord編- INSERT


どうも、はじめです。

前回はActiveRecordについて書いてみました。
はじめの一歩 -Rails ActiveRecord編- 序章
今回はActiveRecordのデータの保存(create,new)について
描いてみようと思います。


データを保存したい場合


ActiveRecordにてDBにデータを保存する場合は
createまたは、newを使用します。
それぞれの記述方法は以下のようになります。

create
User.create(name: '山田', mail: 'xxx@xxx.xxx')
new
user = User.new(name: '山田', mail: 'xxx@xxx.xxx')
user.save()

#  または
user = User.new()
user.name = '山田'
user.mail = 'xxx@xxx.xxx'
user.save()
上記のように記述することでusersテーブルに対し、
name: 山田
mail: xxx@xxx.xxx
というレコードがinsertされます。


例外を発生させたい場合


create,new共に「!」をつけるだけでinsertに失敗した際に例外を発生させてくれます。

create

User.create!(...)

new

user = User.new(...)
・
・
user.save!


createとnewの違い


違いとしてあげられるものはDBにinsertをするタイミングです。
それぞれ以下のタイミングでDBへinsertされます。
create -> createメソッドの実行時
new -> saveメソッドの実行時

ちなみに、
以下のようにsaveメソッドを複数回記述しても
insertされるのは最初の一度のみとなります。
user = User.new(...)
・
・
user.save # ←ここでのみinsertされる
user.save # 何もinsertされない
user.save # 何もinsertされない
試してみたところ2回目以降のinsertは何もinsertされませんが、
返り値はtrueが返却されました。


最後に


createを実行した際は裏でnewとsaveを順番に行なっているだけなので、
結果やっていることは変わりません。
なので、インスタンスを生成するタイミングで登録に必要な値が全て揃っている場合はcreate。
それ以外の場合。
例として、
・任意のタイミングでsaveしたい。
・インスタンスの生成後に何かの処理をしたい。
・特定の条件でのみ登録するカラムが存在する。
等の理由がある場合はnewを使用するといった形になるのではないかと思いました。


次回


次回は更新(update)について書いていきたいと思います。

2016年12月25日日曜日

はじめの一歩 -Rails ActiveRecord編- 序章


どうも、はじめです。

前回はおまけとしてapplication.rbでのタイムゾーンの設定の仕方を紹介しました 。
「はじめの一歩 -Rails 時間・タイムゾーン編- おまけ」
今回から「ActiveRecord編」に入っていこうと思います。


ActiveRecordとは


「Ruby on Rails」にて用意されているORMになります。
AcriveRecordを使用することでSQLを記述しなくても
わずかなアクセスコードでBDへのデータの保存や取得を行うことができます。


AcriveRecordのルール


AcriveRecordを使用する上でのルールとして以下の命名規則があります。
・モデル名、クラス名には単数形のキャメルケース(語頭を大文字)を使用する
・テーブル名、カラム名には複数形で語の区切り文字として「_(アンダーバー)」を使用する。
・テーブルの外部キーは「テーブルの単数形_id」とする。
・主キーには「id」を使用する。

例としてユーザーテーブルとユーザー投稿テーブルを使用する際には以下のようになります。
usersテーブル           -> Userモデル
user_postsテーブル -> UserPostモデル
という形でテーブル名、モデル名が決まります。

両テーブルの主キーは「id」
user_postsテーブルが持つ外部キーは「user_id」となります。

他にも幾つか用意されているカラム名が存在しています。
 ・created_at - レコード作成時の日時が自動的に登録されます。
 ・updated_at - レコードが更新された時の日時が自動的に登録されます。
 ・lock_version - モデルにoptimistic lockingを追加します。
 ・type - モデルでSingle Table Inheritanceを使用する場合に指定します
 ・関連付け名_type - ポリモーフィック関連付けの種類を保持します。
 ・テーブル名_count - 関連付けにおいて、所属しているオブジェクトの数をキャッシュするのに使用されます。


ActiveRecordのモデルを作成


例としてUserモデルの作成を行います。
# app/models/user.rb
class User < ActiveRecord::Base
end

AcriveRecordの命名規則に反した名前になってしまった場合
以下のようにオーバーライドすることも可能です。
# app/models/user.rb
class User < ActiveRecord::Base
    self.table_name = ‘USER’
end
主キーとなるカラム名が「id」以外の場合は以下のように指定をすることも可能です。
# app/models/user.rb
class User < ActiveRecord::Base
    self.primary_key = ‘user_id'
end

このようにテーブルに対して紐づくモデルの作成が完了すれば
テーブルからのデータの呼び出し、登録等の操作が可能になります。


次回


モデルの作成が完了したので、次回は登録系について書いていこうと思います。

2016年12月23日金曜日

はじめの一歩 -Rails 時間・タイムゾーン編- おまけ

どうも、はじめです。

前回は月や年の最初や最後の取得をやってみました。
「はじめの一歩 -Rails 時間・タイムゾーン編- その5」
今回はapplication.rbでのタイムゾーンの設定方法を紹介したいと思います。

はじめに

application.rbでタイムゾーンを指定をしなかった場合はデフォルトでUTCが指定されます。
任意のタイムゾーンを設定するには以下のように指定をします。
# config/application.rb
config.time_zone = 'Tokyo'
ここで指定している’Tokyo’という文字列ですが何でも良いという訳ではありません。 RailsのActiveSupportにて予め用意されているものの中から任意の文字列を指定する事により、
該当するタイムゾーンを指定してくれるようになっています。

それではActiveSupportが用意している文字列を幾つか紹介したいと思います。

Asia/Tokyo

このタイムゾーンを指定したい場合は以下のように指定をします。
config.time_zone = 'Tokyo'
他にも’Osaka’や’Sapporo'と指定しても同様に”Asia/Tokyo”のタイムゾーンが指定されます。

America/New_York

アメリカのニューヨークのタイムゾーンを指定したい場合はこのように指定をします。
config.time_zone = 'Eastern Time (US & Canada)'

Asia/Seoul

こちらは韓国のソウルのタイムゾーンです。
config.time_zone = 'Seoul'

Asia/Taipei

こちらは中国の台北のタイムゾーンです。
config.time_zone = 'Taipei'

ちなみに、
Tokyoのように同じタイムゾーンを示す文字列が複数存在するものは他にもあります。

Pacific/Midway

こちらはアメリカのミッドウェイ島というところで、国際日付変更線も表しています。
以下の文字列で指定が可能です。
config.time_zone = 'International Date Line West'
config.time_zone = 'Midway Island'

America/Mexico_City

こちらはアメリカのメキシコのタイムゾーンです。
以下の文字列で指定が可能です。
config.time_zone = 'Guadalajara'
config.time_zone = 'Mexico City'

Europe/London

こちらはロンドンのタイムゾーンで、以下の文字列での指定が可能です。
config.time_zone = 'Edinburgh'
config.time_zone = 'London'

他にも幾つかあるので興味のある方は調べてみて下さい。

最後に、

ここで紹介した'Asia/Taipei’や'America/New_York’は
「はじめの一歩 -Rails 時間・タイムゾーン編- その4」
で紹介した、任意のタイムゾーンに変換して表示をする
time.in_time_zone('America/Chicago')
でも使用することができます。

次回

次は「時間・タイムゾーン編」のまとめをやりたいと思います。

2016年12月18日日曜日

はじめの一歩 -Rails 時間・タイムゾーン編- その5

どうも、はじめです。

前回は幾つかのタイムゾーンの紹介と、任意のタイムゾーンでの日時の取得について書いてみました。
「はじめの一歩 -Rails 時間・タイムゾーン編- その4」
今回は月や週などの最初と最後を取得する方法を書いていきます。

はじめに、

前提として今回はTimeWithZoneクラスを使用して、
タイムゾーンはJSTでの実行例を載せていきます。

それでは、年月週などそれぞれの最初と最後を取得する方法を書いていきます。

年

Time.zone.now.beginning_of_year
# => Fri, 01 Jan 2016 00:00:00 JST +09:00
Time.zone.now.end_of_year
=> Sat, 31 Dec 2016 23:59:59 JST +09:00

月

Time.zone.now.beginning_of_month
# => Thu, 01 Dec 2016 00:00:00 JST +09:00
Time.zone.now.end_of_month
# => Sat, 31 Dec 2016 23:59:59 JST +09:00

週

Time.zone.now.beginning_of_week
# => Mon, 12 Dec 2016 00:00:00 JST +09:00
Time.zone.now.end_of_week
# => Sun, 18 Dec 2016 23:59:59 JST +09:00
週の最初は月曜日、最後は日曜日と言うかたちで取得されます。

日

Time.zone.now.beginning_of_day
# => Sun, 18 Dec 2016 00:00:00 JST +09:00
Time.zone.now.end_of_day
# => Sun, 18 Dec 2016 23:59:59 JST +09:00
ちなみに、
Time.zone.now.beginning_of_day
は以下のようにも書くことができます。
Time.zone.now.midnight
# => Sun, 18 Dec 2016 00:00:00 JST +09:00

時

現在時刻を14時代だとした場合、
以下のように14時の0分0秒、59分59秒を取得してくれます。
Time.zone.now.beginning_of_hour
# => Sun, 18 Dec 2016 14:00:00 JST +09:00
Time.zone.now.end_of_hour
# => Sun, 18 Dec 2016 14:59:59 JST +09:00

分

時と同様に現在時刻を14時27分とした場合、
以下のように取得できます。
Time.zone.now.beginning_of_minute
# => Sun, 18 Dec 2016 14:27:00 JST +09:00
Time.zone.now.end_of_minute
# => Sun, 18 Dec 2016 14:27:59 JST +09:00

ちなみに、
以下のように書くことで、任意の曜日の最初を取得することもできます。
Time.zone.now.beginning_of_week(:tuesday)
# => Tue, 13 Dec 2016 00:00:00 JST +09:00
end_of_weekに曜日を指定すると以下のようになります。
Time.zone.now.end_of_week(:tuesday)
# => Mon, 19 Dec 2016 23:59:59 JST +09:00
来週の指定した曜日の最後ではなく、
その日の前日の最後を取得するようなので注意が必要です。


最後に、

曜日を表示する際は日曜日を0として取得をしますが、
beginning_of_weekやend_of_weekで取得する日時は
日曜日ではなく月曜日を最初として取得するので意外でした。
end_of_weekで曜日を指定した場合も含め週を扱う際は、
一度確認をしてから使用すると良いと思います。

次回

次はおまけでapplication.rbにて
タイムゾーンを設定する方法を書きたいと思います。

2016年12月15日木曜日

はじめの一歩 -Rails 時間・タイムゾーン編- その4

どうも、はじめです。

前回はさまざまな日時の取得方法について書いてみました。
「はじめの一歩 -Rails 時間・タイムゾーン編- その3」
今回はいくつかのタイムゾーンの紹介や
任意のタイムゾーンでの出力方法について書いてみます。

はじめに、

タイムゾーンの基準となる「UTC(協定世界時)」というものがあり、
タイムゾーンを表示した際に表示される「+0900」とは、
この「UTC」からの時差になります。

それではいくつかタイムゾーンを紹介していきます。

UTC (+0000)

こちらは先程も書いたとおり「協定世界時」と言って
世界の時間の基準となるものです。
スペインやポルトガルの時間を表示したい際にも用いられるそうです。
また、タイムゾーンを指定しなかった場合に
デフォルトで指定されているのも「UTC」になります。

JTC (+0900)

こちらは日本のタイムゾーンになります。
または、Asia/Tokyoと指定する事もできます。

KST (+0900)

こちらは韓国の標準時になります。
または、Asia/Seoulと指定する事もできます。

CST (+0600)

こちらはアメリカ・カナダ(中部標準時)になります。
または、America/Chicagoと指定する事もできます。

ちなみに、

中国の場合は文字列(UTCやJSTのようなもの)はなく、
+0800と指定するのが良さそうです。


任意のタイムゾーンでの取得

次に任意のタイムゾーンで時間を取得したい場合ですが、
文字列を指定する場合とUTCとの時差(+0900)を指定する場合があります。

文字列での指定はこのように行います。
Time.parse('2016-12-15 19:28:18 CST')
# => 2016-12-15 19:28:18 -0600

parseの場合はUTCからの時差で指定することもできます。
Time.parse('2016-12-15 19:28:18 -0600')
# => 2016-12-15 19:28:18 -0600

一時的にタイムゾーンを変更して取得を行いたい場合は、
以下のようにすることも可能です。
SYSTEM_TZ = ENV[‘TZ’]
ENV[’TZ’] => ‘CST’
Time.local('2016,12,15,19,28,18')
# => 2016-12-15 19:28:18 +0600
EVN[‘TZ’] = SYSTEM_TZ


任意のタイムゾーンでの表示

時刻の取得後に任意のタイムゾーンで表示をしたい場合は、
以下のように指定をすることで変更が可能となります。
time = Time.zone.now
# => Thu, 15 Dec 2016 19:28:18 JST +09:00
time.in_time_zone('UTC')
# => Thu, 15 Dec 2016 10:28:18 UTC +00:00

またはこのように指定することもできます。
time.in_time_zone('America/Chicago')
# => Thu, 15 Dec 2016 04:28:18 CST -06:00


最後に

UTCやJSTなどの文字列でタイムゾーンを指定する場合、
想定しているものとは違っていたり、
parseで指定したとしても反映されない場合等がありました。
明確に分かっているものならともかく確証がないものの場合は、
[Asia/Tkyo]のような文字列を使用するか[+0900]と時差指定の方が確実ではないかと思いました。

次回

月の始めや終わり、週の始めや終わりの取得方法を書いていきたいと思います。

2016年12月12日月曜日

ObjectクラスにBooleanへの変換メソッド「to_b」を追加する

 こんにちは、Hiroです。本日は、Ruby on Rails上であればちょっと便利なメソッドをObjectクラスに追加する方法を紹介します。
 皆さんもObjectを数値(Integer)に変換する「to_i」や文字列(String)に変換する「to_s」などは使ったことがあると思います。(下記のように使います。)
[1] pry(main)> "1".to_i
=> 1
[2] pry(main)> 1.to_s
=> "1"

 今回は、あまり使用頻度は高くはないのですが、Objectを論理値(Boolean)に変換するためのメソッド「to_b」というのを実装してみようと思います。


1. to_bメソッドの使い方

まずは、いきなり使い方からですが、下記に例を記載します。APIでJSONが送信されてくるような時に、params[:spot_request]に文字列で"true"または、"false"が入っていて、"true"なのか"false"なのかで処理を分ける場合のものです。
def create
  if params[:spot_request].to_b
      # TODO: 申請処理をする
  else
      # TODO: 通常の処理をする
  end
end


2. to_bメソッドの実装内容

to_bメソッドですが、今回は文字列・数値・シンボルなどから変換できるようにしたいと思います。下記のような形で、「lib/extensions/object/to_b.rb」として保存します。(trueとしたいものが他にあれば、追加してみるのもよいと思います。)
class Object
  def to_b
    target_value = self.class == String ? self.downcase : self
    case target_value
    when 'yes', 'true', 'ok', '1', :yes, :true, :ok, 1, true
      true
    else
      false
    end
  end
end


3. to_bメソッドがアプリケーション内で利用できるように読み込ませる

to_bメソッドをcontrollerやmodel内で利用できるようにto_b.rbを読み込ませる必要があります。「config/initializers」配下に新しいファイルを作っても構いませんが、今回は「config/application.rb」のファイルの末尾に下記を追加します。
Dir[Rails.root.join('lib/extensions/**/*.rb')].sort.each do |file|
  require file
end


4. to_bメソッドを実際に使ってみる

これで、to_bメソッドが使えるようになりましたので、簡単に実行結果をのせておきます。
[1] pry(main)> "true".to_b
=> true
[2] pry(main)> "false".to_b
=> false
[3] pry(main)> 1.to_b
=> true
[4] pry(main)> 0.to_b
=> false
[5] pry(main)> :true.to_b
=> true
[6] pry(main)> :false.to_b
=> false

 いかがでしたでしょうか。今回は単純なメソッドをObjectクラスに追加してみましたが、同じような方法で既存のクラスにメソッドを追加したりすることができますので、ぜひ色々と試してみてください。

2016年12月11日日曜日

はじめの一歩 -Rails 時間・タイムゾーン編- その3

どうもはじめです。

前回は時間に関するクラスである
「Time,Date,DateTime,TimeWithZone」の違い
について書いてみました。

はじめの一歩 -Rails 時間・タイムゾーン編- その2
今回は様々な日時の取得方法を見ていきます。
例としてTimeクラスとTimeWithZoneクラスを使用していきます。


Time

前回までも使用していますが、まずは現在時刻の取得から見ていきます。
Time.now
# => 2016-12-11 13:19:53 +0900

次に任意の時間を設定します。
Time.gmではUTCで時刻を設定し、
環境変数で設定しているタイムゾーンで時刻を設定したい場合は
Time.localを使用します。
Time.gm('2016,12,11,13,19,53')
# => 2016-12-11 13:19:53 UTC
Time.local('2016,12,11,13,19,53')
# => 2016-12-11 13:19:53 +0900

Timeクラスでは任意の秒数を加算減算して、
指定時刻からの数秒後、数秒前を取得することができます。
1日前、1日後を取得する場合
time = Time.now
# => 2016-12-11 13:19:53 +0900
time - 24 * 60 * 60
# => 2016-12-10 13:19:53 +0900
time + 24 * 60 * 60
# => 2016-12-12 13:19:53 +0900

文字列への変換は以下のように行います。
time = Time.now
time.to_s
# => "2016-12-11 13:19:53 +0900”

文字列からの変換を行うには
require 'time'
を記述する必要があります。
require 'time'
Time.parse('2016-12-11 13:19:53 JST')
# => 2016-12-11 13:19:53 +0900

unixtimeへの変換とunixtimeからの変換は以下のように行います。
time = Time.now
time.to_i
# => 1481429993
Time.at(1481429993)
# => 2016-12-11 13:19:53 +0900

Timeクラスには2038年問題がありましたが、
現在はすでに解消されているそうです。


TimeWithZone

現在時刻の取得は以下のように行います。
Time.zone.now
# => Sun, 11 Dec 2016 17:23:24 JST +09:00
Time.current
# => Sun, 11 Dec 2016 17:23:24 JST +09:00
Time.zone.nowまたはTime.currentで取得する時刻は
application.rbに設定したタイムゾーンを使用します。

任意の時間設定は以下のように行います。
Time.zone.parse('Sun, 11 Dec 2016 17:23:24 JST +09:00')
# => Sun, 11 Dec 2016 17:23:24 JST +09:00

次にさまざまの時刻を取得する方法です。
昨日明日
time = Time.zone.now
time.yesterday
# => Sat, 10 Dec 2016 17:23:24 JST +09:00
time.tomorrow
# => Mon, 12 Dec 2016 17:23:24 JST +09:00
先月来月
time = Time.zone.now
time.prev_month
# => Fri, 11 Nov 2016 17:23:24 JST +09:00
time.next_month
# => Wed, 11 Jan 2017 17:23:24 JST +09:00
去年来年
time = Time.zone.now
time.prev_year
# => Fri, 11 Dec 2015 17:23:24 JST +09:00
time.next_year
# => Mon, 11 Dec 2017 17:23:24 JST +09:00
その他にも以下のように表現することができます。
例として2日前と3年後を表示してみます。
time.ago(2.days)
# => Fri, 09 Dec 2016 17:23:24 JST +09:00
time.since(3.years)
# => Wed, 11 Dec 2019 17:23:24 JST +09:00

文字列への変換は以下のように行います。
time = Time.zone.now
time.to_s
# => "2016-12-11 17:23:24 +0900”

unixtimeへの変換とunixtimeからの変換
time = Time.zone.now
time.ti_s
# => 1481444604
Time.zone.parse(1481444604)
# => 2016-12-11 17:23:24 +0900

まとめ

TimeWithZoneではyesterdayやtomorrowなど 直感的に記述することができますが、
Timeクラスでも
require ‘active_support/all’
と記述することでTimeWithZoneと同様に
翌日や前日等の日時を表現することができます。

次回

次回はいくつかのタイムゾーンの紹介や
任意のタイムゾーンでの出力等ついて書いてみようと思います。

2016年12月8日木曜日

はじめの一歩 -Rails 時間・タイムゾーン編- その2

どうも、はじめです。

前回は指定したフォーマットで時刻を表示するstrftimeについて書いてみました。
「はじめの一歩 -Rails 時間・タイムゾーン編-」
今回は日付や時間に関するクラスである
こちらの4つについて書いていきます。
「Time、Date、DateTime、TimeWithZone」

はじめに、

現在時刻の取得をした後、strftime()で「年月日、時刻、曜日、タイムゾーン」を表示して
それぞれの違いを見ていきます。


Time

こちらはRubyで用意されている時刻を表すクラスです。
現在時刻の取得、表示はこのように行います。
time = Time.now
# => 2016-12-07 21:28:39 +0900
time.strftime('%Y * %m * %d * %w * %U * %X * %Z')
# => "2016 * 12 * 07 * 3 * 49 * 21:28:39 * JST"
年月日、時分秒、曜日、タイムゾーンなどの情報を表示することができました。


Date

こちらはRubyで用意されている日付を表すクラスです。
使用する際は
require ‘date’
と記述をする必要があります。
現在時刻の取得、表示はこのように行います。
require ‘date'
date = Date.today
date.strftime('%Y * %m * %d * %w * %U * %X * %Z')
# => "2016 * 12 * 07 * 3 * 49 * 00:00:00 * +00:00"
Timeとは違って年月日、曜日のみ表示されました。
その他の時分秒、タイムゾーンの情報は全て0で表示されています。


DateTime

こちらは日付と時刻を扱えるDateのサブクラスです。
DateTimeもDateクラスと同様に使用する際には
require ‘date’
と記述する必要があります。
現在時刻の取得、表示はこのように行います。
requrire ‘date’
datetime = DateTime.now
datetime.strftime('%Y * %m * %d * %w * %U * %X * %Z')
# => "2016 * 12 * 07 * 3 * 49 * 21:28:39 * +09:00”
Timeクラスと同じように年月日、時分秒、曜日の情報が表示されましたが、
タイムゾーンに関しては表示が違います。
Timeクラスでは”JST"と文字列で表示されていますが、
DateTimeクラスの場合は”+09:00”とUTC(協定世界時)からの時差が表示されています。


TimeWithZone

こちらはRailsで使用することができるActiveSupport::TimeWithZoneクラスです。
素のRubyでは使用することはできません。
TimeWithZoneクラスでの現在時刻の取得、表示は以下のようになります。
timewithzone = Time.zone.now
timewithzone.strftime('%Y * %m * %d * %w * %U * %X * %Z’)
# => "2016 * 12 * 07 * 3 * 49 * 21:28:39 * JST”
こちらはTimeクラスと全く同じ表示になりました。

まとめ

Time、DateTime、TimeWithZoneの3つに関しては、
大きな違いはないように見えます。

調べてみたところTime、DateTimeの違いとしては以下の点があげられるようです。

Timeの場合、
・require無しで使える
・タイムゾーンの指定なしでインスタンスの生成を行った時、システムまたは環境変数で設定しているタイムゾーンが使用される
(DateTimeの場合はUTCが使用される)
・うるう秒を使える
・サマータイムを使える

ということからDateTimeよりTimeを使用したほうが良いとのことです。

Timeに対してTimeWithZoneはシステムや環境変数ではなく、
application.rbに設定したタイムゾーンが使用されるようです。
また、"Time.current"でタイムゾーンを指定して時刻の取得を行うこともできます。


つまり。。。
Time、DateTime、TimeWithZoneの全てが使える環境であれば、
タイムゾーンを柔軟に扱えるTimeWithZoneを使用し、
そうでない環境である場合はTimeを使用する形になるのではないかと思います。


次回

前回から今回にかけて現在時刻を取得して比較をしていましたが、
次回は翌日や翌年、時刻の指定やUnixTimeの変換等について
書いてみようと思います。

2016年12月4日日曜日

はじめの一歩 -Rails 時間・タイムゾーン編-

どうも、はじめです。
本日から「はじめの一歩 -時間・タイムゾーン編」ということで、
何度かに分けてRailsの時間・タイムゾーンについての記事を連載していきます。

今回の内容は「strftime」についてです。

はじめに

strftimeとは、

Rubyの組み込みオブジェクトとして用意されており、
時刻を指定したフォーマットに沿って文字列へ変換してくれるものになります。

例
time = Time.now # Sun Dec 03 16:06:18 +0900 2016
time.strftime('%Y年%m月%d日 %H:%M:%S')
# => 2016年12月04日 16:06:18

それではフォーマットについて見ていきたいと思います。
今回は例としてTime型での実行結果をまとめてみました。

フォーマット


フォーマット説明実行結果
年
%Y西暦を表す数 2016
%y西暦の下2桁16
月
%m月を表す数字(01-12)12
%B月の名称(January,February,March,April,May,June,July,August,September,October,November,December)December
%b月の省略名(Jan,Feb,Mar,Aprl,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec)Dec
日
%d日(01-31)04
%x日付 ('%m/%d/%y')と同じ意味になります12/04/16
%j年中の通算日(001-366)339
曜日
%A曜日の名称(Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday)Sunday
%a曜日の略称(Sun,Mon,Tue,Wed,Thu,Fri,Sat)Sun
%w曜日を表す数。日曜日が0(0-6)0
週
%U週を表す数。最初の日曜日が第1週の始まり(00-53)49
%W週を表す数。最初の月曜日が第1週の始まり(00-53)48
時
%H24時間制の時(00-23)16
%I12時間制の時(01-12)04
分
%M分(00-59)06
秒
%S週秒(00-60)(60はうるう秒)18
その他
%c日付と時間"Sun Dec 4 16:06:18 2016"
%p午前または午後(AM,PM)PM
%X時刻"16:06:18"
%ZタイムゾーンJST
%%パーセント%

下記はオプション一覧です。


オプション
フォーマット説明
^大文字で出力
#小文字は大文字に、大文字は小文字に変換
-左寄せ
_空白埋め
00埋め
数値指定した表示桁数での表示

オプションを幾つか組み合わせて実行した結果がこちらになります。
桁数指定をしている状態で左寄せを行うと通常と同じ表示になるようです。

time.strftime('%^10A') # => "    SUNDAY"
time.strftime('%0#6Z') # => "000jst"
time.strftime('%Y/%-m/%-d %X') # => "2017/1/4 16:06:18"
ちなみに、、、
曜日を日本語で表示したいときは以下のように書きます。
Time.now.strftime("#{%w(日 月 火 水 木 金 土)[Time.now.wday]}")
# => 日

まとめ


年月日、曜日、週に関してはTime型、Date型、DateTime型、TimeWithZone型のどれであっても
表示される内容は同じでした。
ですが、時刻やその他のフォーマットに関してはデータ型によって出力内容が異なってきます。
実際に使用する際には、一度想定通りに表示されているかを確認してから使用する用にしましょう。
(今回の時刻、その他の項目に表示されている実行結果はTimeWithZone型となっております。)

次回


今回の最後に出てきましたデータ型について、
時刻だけでも幾つかのデータ型がありますのでそちらをやってみようと思います。

2016年11月27日日曜日

PHPer (PHPプログラマー)から Rubyist (Rubyプログラマー)へ

初めて投稿させていただきます。
よろしくお願いします。

今回とあることをきっかけに初めてRubyを勉強することになり、
その上本格的にRuby on Railsを触れる機会を頂くことができました。

これまで実戦で使っていた言語は基本的にPHPが中心だった私からすれば、
驚きだらけだったので下記のブログを参考にPHPとRubyとで比較をしてみました。

Rubyで使える配列関連のメソッドを学習した

まず先ほどのブログと同様に前提として以下のようなテーブルがあるとし、
[books]という連想配列にデータを取得したとして比較をしていきます。

id name price description
1 Rubyの本 1000 Rubyの本です。
2 JavaScriptの本 1000 JavaScriptの本です。
3 PHPの本 1000 PHPの本です。
4 HTMLの本 2000 HTMLの本です。
5 CSSの本 2000 CSSの本です。

1. IDのみを取得して配列にしたい


where句でin演算子を使用したいときなどによく使うと思います。


PHP

$ids = array();
foreach ($books as $book) {
 $ids[] = $book['id'];
}
# => [1, 2, 3, 4, 5]


Ruby

ids = books.map { | book | book.id }
# [1, 2, 3, 4, 5]
私は普段PHPではこのように書いていましたが、
 Rubyで書くとすごく単純にかけてしまうものだなと思いました。 

調べてみたところPHPでもこのような書き方があるそうです。

$ids = array_column($books, 'id');

このarray_columnですが、
第一引数に値を取り出したい多次元配列を、
第二引数に取り出したいカラムのkeyや名前を設定していますが、
第三引数値を設定すると、その値をkeyとして連想配列を生成してくれます。

例
$id_to_names = array_column($books, 'name', 'id');
# => [1 => "Rubyの本", 2 => "JavaScriptの本", 3 => "PHPの本"...]

こんな便利なものがあったなんて。。。
知らなかった。
  



2.特定の値でのグループ分け


参考にした記事ですごいと思ったのでPHPとの比較をしてみました。

PHP


  function group_by_price(array $books, $keyName) {
    $keys = array_column($books, $keyName);
    $books_group_by_price = array();
    foreach ($books as $book) {
      $books_group_by_price[$book[$keyName]][] = $book;
    }
      return $books_group_by_price;
  }
  $books_group_by_price = group_by_price($books, 'price');

Ruby

books_group_by_price = Books.all.group_by{ |book| book.p }
どちらも同様に下記のような出力結果になります。

  # [1000] =>
  #  [0] => ["id" => "1", "name" => "Rubyの本", "price" => "1000", "description" => "Rubyの本です。"],
  #  [1] => ["id" => "2", "name" => "JavaScriptの本", "price" => "1000", "description" => "JavaScriptの本です。"],
  #  [2] => ["id" => "3", "name" => "PHPの本", "price" => "1000", "description" => "PHPの本です。"]
  # [2000]=>
  #  [0] => ["id" => "4", "name" => "HTMLの本", "price" => "2000", "description" => "HTMLの本です。"],
  #  [1] => ["id" => "5", "name" => "CSSの本",  "price" => "2000", "description" => "CSSの本です。"]


こちらはもう全然変わってきますね。 PHPで書く場合ほかの書き方もあるのかもしれませんが、 Rubyではこのように1行で端的に書けてしまうんです。

3. その他個人的にここが良いと思ったことをいくつか


・変数名 先ほどまでも例として挙げているソースを見ていただいてもわかる通り、
PHPでは変数名の前に「 $ 」を付けますが、Rubyでは何も記載する必要はありません。
その他にも、PHPでは1行の最後には必ず「 ; (セミコロン)」を付けなければエラーとなりますが Rubyでは省略可能です

 このようにRubyでは省略可能なものが多く存在するという印象を強く受けました。
「省略可能 = 文字数が少なくなる = すっきりする = コードが見やすくなる」
といったメリットがあります。

連想配列やハッシュの書き方でもかなり変わってくると思います。



  ・文字列結合
どんなアプリやサイトを作っていてもかなりの確率で文字列結合をする場面はあると思います。
PHPにもRubyにもsprintfはありますがその他での記述方法を用いた場合かなり差が出るなと驚きました。
"「Rubyの本」の価格は1000円です。"と表示する例を挙げてみます。

PHP

echo('「'.$book['name'].'」の価格は'.$book['price'].'円です。');

Ruby

puts "「#{books.name}」の価格は#{books.price}円です。"

この例だけではあまりわからないかもしれませんが、
もう少し入り乱れてくるとPHPよりもRubyの方が見やすいのではないかと思いました。

全体的に見てもRubyで書くと比較的シンプルに書けるといった印象を受けました。
私自身PHPに関してもRubyに関しても未熟で知らないことばかりなので、
こんな書き方もあるぞ!とかこういった面ではPHPのほうが優れている!とかがあれば
コメントしていただければと思います。

 では、最後まで見て頂きありがとうございました。
 メントしていただければと思います。

2016年11月23日水曜日

Rubyで使える配列関連のメソッドを学習した


はじめに

こんにちは、Taroです。 Rubyの勉強を始めて早3ヶ月。
これだけはRubyを使う上で覚えておけ、という配列関連のメソッドを教えていただいたので、簡単にまとめてみました。
イメージしやすいよう、下記のようなテーブルとレコードを使います。
ID name price description
1 Rubyの本 1000 Rubyの本です。
2 JavaScriptの本 1000 JavaScriptの本です。
3 PHPの本 1000 PHPの本です。
4 HTMLの本 2000 HTMLの本です。
5 CSSの本 2000 CSSの本です。

Enumerable#map (collect)

要素の数だけ繰り返しブロックを実行します。
collectはmapの別名で、同様の動きをします。
items = Item.all
item_ids = items.map { |item| item.id }
# => [1, 2, 3, 4, 5]
上記のように各要素に対してメソッドを適用する場合は&を使って以下のようにも書くことができます。
item_ids = items.map(&:id)

Enumerable#select

ブロックを各要素に対し実行し、真となった要素を返します。
item_price_array = Item.all.map(&:price)
# => [1000, 1000, 1000, 2000, 2000]
only_price_1000 = item_price_array.select { |price| price == 1000 }
# => [1000, 1000, 1000]
上記では、mapを使用して1回値段のみの配列にしてしまったが、下記のようにすればprice = 1000だけ配列で取得できます。
(ちょっと例が悪かったため、whereで解決できてしまいますが。)
only_price_1000_items = Item.all.select { |item| item.price == 1000 }
# => [#{Item id: 1, name: "Rubyの本", price: 1000, description: "Rubyの本です。", created_at: "2016-10-02 05:32:52", updated_at: "2016-10-02 05:32:52"}, #{Item id: 2, name: "JavaScriptの本", pricdescription: "JavaScriptの本です。", created_at: "2016-10-02 07:42:16", updated_at: "2016-10-02 07:42:16"}, #{Item id: 3, name: "PHPの本", price: 1000, description: "PHPの本です。", created_at: 09:24:55", updated_at: "2016-10-02 09:24:55"}]
なお、selectとは逆で、偽を返す場合はrejectを使用します。
not_price_1000_items = Item.all.reject { |item| item.price == 1000 }
# => [#{Item id: 4, name: "HTMLの本", price: 2000, description: "HTMLの本です。", created_at: "2016-10-02 09:25:12", updated_at: "2016-10-02 09:25:12"}, #{Item id: 5, name: "CSSの本", price: 2000tion: "CSSの本です。", created_at: "2016-10-02 09:25:59", updated_at: "2016-10-02 09:25:59"}]

Enumerable#reduce (inject)

`inject`は`reduce`の別名で、同様の動きをする。
畳み込み演算を行うメソッドをし、その結果を返す。
item_price_array = Item.all.map(&:price)
# => [1000, 1000, 1000, 2000, 2000]
item_price_array.reduce(0) { |result, price|
  result + price
}
# => 7000
まず、reduce(0)にある0は、次に記載するresultの初期値になります。
ブロック内に記載のあるresult(1つ目のブロック引数)はブロックの戻り値が入ります。
price(2つ目のブロック引数)は配列の各要素になります。
# 1週目
result = 0 (初期値), price = 1000
# 2週目
result = 1000, price = 1000
# 3週目
result = 2000, price = 1000
# 4週目
result = 3000, price = 2000
# 5週目
result = 5000, price = 2000
# 戻り値
=> 7000
なお、簡単な演算であれば下記のようにシンボルで記述することもできます。
item_price_array.reduce(:+)
=> 7000
初期値に配列を渡すこともできて、フィボナッチ数列などは下記のように書けるようです。 (引用:ちょっとわかりにくいけど非常に便利なinjectメソッド)
(0..5).reduce([1, 1]) { |fib, i| fib << fib[i] + fib[i+1] }
# => [1, 1, 2, 3, 5, 8, 13, 21]

Enumerable#index_by

特定の値をキーとしたハッシュを生成してくれます。
items_index_by_name = Item.all.index_by { |item| item.name }
# => {"Rubyの本"=>#{Item id: 1, name: "Rubyの本", price: 1000, description: "Rubyの本です。", created_at: "2016-10-02 05:32:52", updated_at: "2016-10-02 05:32:52"}, "JavaScriptの本"=>#{Item id: 2vaScriptの本", price: 1000, description: "JavaScriptの本です。", created_at: "2016-10-02 07:42:16", updated_at: "2016-10-02 07:42:16"}, "PHPの本"=>#{Item id: 3, name: "PHPの本", price: 1000, de"PHPの本です。", created_at: "2016-10-02 09:24:55", updated_at: "2016-10-02 09:24:55"}, "HTMLの本"=>#{Item id: 4, name: "HTMLの本", price: 2000, description: "HTMLの本です。", created_at: "20162", updated_at: "2016-10-02 09:25:12"}, "CSSの本"=>#{Item id: 5, name: "CSSの本", price: 2000, description: "CSSの本です。", created_at: "2016-10-02 09:25:59", updated_at: "2016-10-02 09:25:59"}
ここでも&を使用すると便利です。
items_index_by_name = Item.all.index_by(&:name)

Enumerable#group_by

特定の値をキーとしてグルーピングした、新しいハッシュを生成してくれます。
items_group_by_price = Item.all.group_by{ |item| item.price }
# => {1000=>[#{Item id: 1, name: "Rubyの本", price: 1000, description: "Rubyの本です。", created_at: "2016-10-02 05:32:52", updated_at: "2016-10-02 05:32:52"}, #{Item id: 2, name: "JavaScriptの本 1000, description: "JavaScriptの本です。", created_at: "2016-10-02 07:42:16", updated_at: "2016-10-02 07:42:16"}, #{Item id: 3, name: "PHPの本", price: 1000, description: "PHPの本です。", crea6-10-02 09:24:55", updated_at: "2016-10-02 09:24:55"}], 
#     2000=>[#{Item id: 4, name: "HTMLの本", price: 2000, description: "HTMLの本です。", created_at: "2016-10-02 09:25:12", updated_at: "2016-1:25:12"}, #{Item id: 5, name: "CSSの本", price: 2000, description: "CSSの本です。", created_at: "2016-10-02 09:25:59", updated_at: "2016-10-02 09:25:59"}]}
ここでも&を使用すると便利です。
items_group_by_price = Item.all.group_by(&:price)

Enumerable#partition

group_byと似たグルーピングのメソッドで、partitionというものもあります。
これはブロック内の要素が真か偽かで新しい配列を生成してくれます。
items_partition_with_price = Item.all.partition {|item| item.price == 1000 }
# => [
#       [#{Item id: 1, name: "Rubyの本", price: 1000, description: "Rubyの本です。", created_at: "2016-10-02 05:32:52", updated_at: "2016-10-02 05:32:52"}, #{Item id: 2, name: "JavaScriptの本", pri description: "JavaScriptの本です。", created_at: "2016-10-02 07:42:16", updated_at: "2016-10-02 07:42:16"}, #{Item id: 3, name: "PHPの本", price: 1000, description: "PHPの本です。", created_at2 09:24:55", updated_at: "2016-10-02 09:24:55"}], 
#       [#{Item id: 4, name: "HTMLの本", price: 2000, description: "HTMLの本です。", created_at: "2016-10-02 09:25:12", updated_at: "2016-10-02 09:25:12"},#{Item id: 5, name: "CSSの本", price: 2000, description: "CSSの本です。", created_at: "2016-10-02 09:25:59", updated_at: "2016-10-02 09:25:59"}]
#    ]

おわりに

簡単ではありますが、復習としてまとめました。
mapとcollectのように、同一の処理なのにメソッド名が異なるのは、LispとSmalltalkの影響のようです。
下記に詳しく記載されております。
map と collect、reduce と inject ―― 名前の違いに見る発想の違い
もし間違っている、もしくは他にもこんな使い方や便利なメソッドがある、といったことがありましたらぜひ教えてください!

2016年11月9日水曜日

Paginatorについて再確認(Ruby on Rails、FuelPHP)

こんにちは、Taroです。
寒くなってきた今日この頃、室内外の寒暖差でとても眠くなります。
Ruby on RailsのkaminariというGem(ライブラリ)に触れる機会を得ました。
何を今更、有名なGemじゃないか、と言わないでください。
確かに非常にページングが簡潔にできました。
普段PHPで頑張って書いている私からしたら感動です。
私はPHPのフレームワークとしてはFuelPHP、CakePHPに触れたことがあるのですが、どちらでもページングのクラスやライブラリを使っていなかった(勉強していなかった)と思い、この機に再確認しようと思った次第です。
なお、今回はRuby on RailsとFuelPHPを取り上げさせてください。
(普段はもう少し古いバージョンを使用していますが、とりあえず現在の最新ドキュメントを参考にしております。)
フレームワーク名 バージョン ページングライブラリ ドキュメント
Ruby on Rails 5.0 kaminari https://github.com/amatsuda/kaminari
FuelPHP 1.7 Paginationクラス http://fuelphp.jp/docs/1.7/classes/pagination.html

サンプル

説明しやすいよう下記のようなテーブルを使用します。
商品名、値段、説明を管理しているitemsです。
id name price description created_at updated_at
ID 商品名 値段 説明 作成日 更新日

Ruby on Rails(kaminari)

# 2ページ目を取得(デフォルトは1ページ25件です。)
@items = Item.page(2);
pageメソッドで取得できるのはいいですね!
とはいえ、1ページあたりの表示件数を10件にしたい、などサービスごとに要望があるかと思います。
各コントローラーで制御もできるようですが、基本値はコンフィグにまとめるのが良いかな、と思います。
rails g kaminari:config
これでkaminari用のコンフィグファイルが生成されます。
Kaminari.configure do |config|
  # config.default_per_page = 25    ページあたりの表示件数(デフォルトは25)
  # config.max_per_page = nil          ページあたりの表示件数の最大(デフォルトはnil=>無限)
  # config.window = 4                         表示中のページの左右何ページ分のリンクを表示するかを指定(デフォルトは4)
  # config.outer_window = 0             先頭ページ、及び最終ページから何ページ分のリンクを表示するかを指定(デフォルトは0)
  # config.left = 0                                先頭ページから何ページ分のリンクを表示するかを指定(デフォルトは0)
  # config.right = 0                              最終ページから何ページ分のリンクを表示するかを指定(デフォルトは0)
  # config.page_method_name = :page    モデルに追加されるページ番号を指定するスコープの名前(デフォルトはpage)
  # config.param_name = :page    ページ番号を渡すために使用するリクエストパラメータの名前(デフォルトはpage)
end
なお、それぞれの値は各コントローラーで再度記載することで上書きすることも可能です。
ビューにページング番号を出す際には、下記でOKです。
これでいちいちページング番号をループさせて表示させるような面倒とはおさらばです。
<%= paginate(@items) %>
参考資料:TECHSCORE BLOG

FuelPHP

FuelPHPにはPaginationクラスがあります。
このクラスのインスタンスを使用することで、ページャーを設定することができます。
以下、ドキュメントを参考にしました。
// 基本値の設定
$config = array(
    // ページネーションの対象URLを設定できます。(特に指定がなければnullにしておくのが良いかと思います。)
    'pagination_url' => null,
    // URLのどのセグメントがページ番号か?(ItemsControllerのinsexメソッドであれば、items/index/3と3つ目のセグメントになります。)
    'uri_segment'    => 3,
    // 表示するリンクの総数
    // 'num_links' => 5
    // 対象の総数。基本はcount()した結果になります。
    // 'total_items'    => 10,
    // 1ページあたりの表示件数
    'per_page'       => 5,
    // trueかつ最初のページではない場合に '最初のページヘ' のリンクが生成されます。
    'show_first' => true,
   // trueかつ最後のページではない場合に '最後のページヘ' のリンクが生成されます。
    'show_last' => true,
);

// 'mypagination' という名前のpaginationインスタンスを作る
$pagination = Pagination::forge('mypagination', $config);

// クエリ発行
$data['example_data'] = DB::select('id', 'name', 'price', 'description')
                            ->from('pagination')
                            ->limit($pagination->per_page)
                            ->offset($pagination->offset)
                            ->execute()
                            ->as_array();

// オブジェクトをビューに渡す
$data['pagination'] = $pagination;

// ビューを返す
return \View::forge('welcome/index', $data);
設定値にてインスタンスを作成することで、その後のクエリビルダーにて使用することができます。
なお、バージョン1.7では過去のページネーションの設定だとうまくいかないようなので、1.7以前を使用する際には気をつけてください。
セグメントからページ番号を取得するため、パラメータをわざわざ受け取る必要はありません。
ただ、基本設定をいちいちコントローラーに書くのも面倒なので、コンフィグファイルにまとめてしまって、適宜呼び出すのが良いかと思います。
Config::load('pagination');
ビュー側でページ番号を出すのは楽です。
render(); ?>

2016年10月26日水曜日

FactoryGirlのsequenceで詰まったメモ

こんにちは、Taroです。
最近Railsを勉強し始め、やっとテストコードを書くようになりました。
FactoryGirlって便利ですよね。
特に連番を作成する際に使用するsequenceがお気に入りです。
しかし先日愛するsequenceに頭を悩まされることになりました。
(当たり前だよ、とは言わないでください。)

sequenceのnって、テスト2週目でも値がリセットされるわけじゃないんだぜ!


まずは状況を理解していただくため、例をあげたいと思います。
例えば下記のようにModelを定義します。

spec/factories/item.rb
FactoryGirl.define do
  factory :item do
    sequence(:name) { |n| "sample#{n}" }
    sequence(:category) { |n|
      n % 4 == 0 ? 4 : n % 4
    }
  end
end
意図としては、名前は「sample1」、「sample2」。。。といった具合に増やします。
ただカテゴリは1〜4の中に収めようとしました。
そしてテストコードを書きます。

spec/models/item_spec.rb
require 'rails_helper'
RSpec.describe Item, type: :model do
  context 'itemのレコードが存在するとき' do   
    before do
      FactoryGirl.create_list(:mst_content_type, 12)
    end
    it 'テスト1' do
      # 省略
    end
    it 'テスト2' do
      # 省略
    end
  end
end
create_listメソッドを使うことで、第2引数の数だけサンプルレコードを作成できます。
(ただし、1つ1つ指定できないことがネックです。)
12レコード作成しておりますが、これは意図した数です。
テストを実行しました。何やら狙った結果が得られません。
どうやらサンプルデータがおかしいということが分かりました。
# テスト1週目
id: 1 name: sample1 category: 1
id: 2 name: sample2 category: 2
...以下10個同様のものが作成された。

# テスト2週目
id: 13 name: sample13 category: 3
id: 14 name: sample14 category: 4
...以下10個同様のものが作成された。
お気づきでしょうか?1週目と2週目でレコードが違うことに。
カテゴリーで絞り込みを行っていると、この辺りで引っかかり、狙った結果を得ることができません。
また、私のように不作法なリレーションのテストもわけが分からないことになります。
(テストコードが悪いのか、それともそもそもテストしたいソースが悪いのか)

いやいや、なんでお前、カテゴリーみたいな範囲のある値にsequence使ってるの?


おっしゃる通りです。。。FactoryGirlにはtraitというグループ分けの機能がありました。

spec/factories/item.rb
FactoryGirl.define do
  factory :item do
    sequence(:name) { |n| "sample#{n}" }
    
    trait category_1 do
      category 1
    end

    trait category_2 do
      category 2
    end

    trait category_3 do
      category 3
    end

    trait category_4 do
      category 4
    end
  end

  factory :item_content_1, traits: [:category_1], parent: :item
  factory :item_content_2, traits: [:category_2], parent: :item
  factory :item_content_3, traits: [:category_3], parent: :item
  factory :item_content_4, traits: [:category_4], parent: :item
end
親を:itemとして、属性を付加します。
下手なことをするよりも、これで3つずつ計12個レコードを作成すればよさそうです。
呼ぶ時は先ほどと同様にcreateやcreate_listでいけます。

spec/models/item_spec.rb
require 'rails_helper'
RSpec.describe Item, type: :model do
  context 'itemのレコードが存在するとき' do   
    before do
          FactoryGirl.create_list(:item_content_1, 3)
          # もしくはこんな感じ(createならばその他の値も設定できるので、融通がききます。)
          3.times { FactoryGirl.create(:item_content_2) }
      end

      # 省略

  end
end
もしもっと良い方法をご存知の方がいらっしゃいましたら教えてください!

余談:sequenceに初期値を設定する


ちなみにsequenceには初期値が設定できます。
といっても簡単で、第2引数に値を設定します。(何も指定しなければ1です。)
# 100番からスタート
sequence(:name, 100) { |n| "sample#{n}" }