Railsで簡単にビットフィールドを扱う

洋服の色を複数選択できるWebインターフェースを考えてみます
以下のような画面です



色選択のインターフェース


色の種類は16種類とします
あなたならどのようにデータをもたせますか?


大げさに実装するとこんな感じでしょうか?
例


作る側としては非常に面倒な仕事になります
createの時に relationを作成して、updateの時には不要となった中間テーブルを削除したり、
未選択のマークをしたりしないといけません


ここまで大げさにやる必要が無いのであればビットフィールドを使ってみましょう


ビットフィールドとは?


int32 または int64の各ビットに対して情報を付与する方法です



情報10進数
ブラック1
グレー2
ブルー4
レッド8
ホワイト16

上記のようにした場合 服のカラー属性には
ブラックとホワイトの洋服のカラーの値には 1 + 16 = 17 の値が保存されます
同様にグレーとレッドであれば 2 + 8 = 10 の値が保存されます


ビットフィールドは何が嬉しいのか?


  • 1つのフィールドで複数の状態を管理できる
  • 検索を拘束に行える

服のカラー属性1つでint32(unsigned)の場合は 32状態を管理できます
int64(unsigned)の場合は 64状態です


またSQLを高速に発行することが出来ます
例えばブラックの服を検索するのには以下のクエリですみます


SELECT `cloths`.* FROM `cloths` WHERE ((cloths.colors & 1) = 1)

メリットが有る一方 ビット演算を駆使して情報管理をするため、
ActiveRecordなどで実装しようとすると 中々大変な仕事になるため
先ほどの案を採用するほうが楽になってしまいます


ようやく本題... Railsで簡単にビットフィールドを扱うプラグインbitfields


Railsにおいては bitfieldsというgemを利用することで簡単にこの機能を利用することが出来ます


Gemfileへの追加


以下をGemfileに追加し、bundleを実行してください


# Gemfile
gem 'bitfields'

bitfieldsを使ったカラーの管理


default: 0null: falseを指定しないと正しく動かなくなりますので注意しましょう


# migration file
class CreateCloths < ActiveRecord::Migration
def change
create_table :cloths do |t|
t.integer :color_bitfield, default: 0, null: false

t.timestamps null: false
end
end
end

16種類の色を定義した例です
(2の倍数の数値を入れていくのが非常に面倒です)


class Cloth < ActiveRecord::Base
include Bitfields
bitfield :color_bitfield, {
1 => :black, 2 => :gray, 4 => :white, 8 => :brown, 16 => :khaki, 32 => :beige,
64 => :red, 128 => :pink, 256 => :orange, 512 => :yellow, 1024 => :purple,
2_048 => :green, 4_096 => :navy, 8_192 => :blue, 16_384 => :gold, 32_768 => :silver
}
end

bitfields を使って Insert文を発行する


Cloth.create(black: true, pink: true, blue: true)
# INSERT INTO `cloths` (`color_bitfield`, `created_at`, `updated_at`) VALUES (8321, '2016-01-28 15:05:54', '2016-01-28 15:05:54')

1 + 128 + 8192 = 8321 を計算して勝手に設定してくれました


bitfields を使って 指定したカラーを取り出す


ブルーの洋服を取り出す


Cloth.blue.all
# SELECT `cloths`.* FROM `cloths` WHERE ((cloths.color_bitfield & 8192) = 8192)

ブルーでない洋服を取り出す


Cloth.not_blue.all
# SELECT `cloths`.* FROM `cloths` WHERE ((cloths.color_bitfield & 8192) = 0)

ブルーかつブラックの洋服を取り出す


Cloth.blue.black.all
# SELECT `cloths`.* FROM `cloths` WHERE ((cloths.color_bitfield & 8192) = 8192) AND ((cloths.color_bitfield & 1) = 1)

ただし上は効率が悪いので以下の方法もあります


Cloth.where(Cloth.bitfield_sql(blue: true, black: true)).all
# SELECT `cloths`.* FROM `cloths` WHERE ((cloths.color_bitfield & 8193) = 8193)

ブルーかつブラックでない洋服を取り出す


Cloth.where(Cloth.bitfield_sql(blue: true, black: false)).all
# SELECT `cloths`.* FROM `cloths` WHERE ((cloths.color_bitfield & 8193) = 8192)

モデルに追加される各種メソッド


cloth = Cloth.new(black: true, blue: true)
cloth.black? # => true
cloth.blue? # => true
cloth.red? # => false
cloth.color_bitfield # => 8193
cloth.bitfield_values(:color_bitfield)
=> {:black=>true, :gray=>false, :white => false ... }

他にも Update文で利用する set_bitfield_sql(blue: true)などもあります

欠点等


ブラック または ブルーの服を取り出すなどができない?
scopeにprefixをつけたりができない


必要に応じてforkして自分独自のカスタマイズまたはPullRequestを送りましょう

スポンサーサイト

Ruby2.1 変更点メモ

変更点中で、個人的に注意するところだけをメモ


キーワード引数


キーワード引数を必須にできるようになった


def something(x:, y:)
end

something
# => ArgumentError: missing keywords: x, y

Arrayにto_hがついた


 x = [[:id, 1], [:name, "foo"]]
x.to_h
# => {:id=>1, :name=>"foo"}

Timeout.timeout


Timeout.timeoutのブロック中でrescue Exceptionできるようになった
※一定時間内で終わらなかった場合にTimeout::Errorをraiseする機能でTimeout.timeout(1) {}で1秒以内に終わらなかった場合はTimeout::Errorがraiseされる


require 'timeout'
begin
Timeout.timeout(1) do
begin
sleep 2
rescue Exception
puts "timeout block"
end
end
rescue Exception
puts "Exception!!"
end

# Ruby 2.0の場合
# => "timeout block"
# Ruby 2.1の場合
# => Exception!!

Numeric#step


ちょっと見やすくできるようになった


0.step(by: 5, to: 20){|i| puts i }

同じコードをRuby2.0でやる場合


0.step(20, 5){|i| puts i}

lambda


lambda中でreturnできるようになった


def call_with_yield
yield
end

def test
call_with_yield(&lambda {return "hello from lambda"})
"hello from method"
end

test

# Ruby 2.0
# => hello from lambda
# Ruby 2.1
# => hello from method

他にも


Process.setproctitleでプロセスタイトルを設定できたり、
GC周りが大きく変わっていたり、
String#scrubだったり、
refineが警告でなくなったり、、、


参考サイト


http://globaldev.co.uk/2014/05/ruby-2-1-in-detail/

[Rails4]Bootstrap3のBootstrap!

Rails4.0系で Twitter Bootstrap3を使ってみる


たまにはまともなネタを...


環境


ruby 2.1.1p76 (2014-02-24 revision 45161) [x86_64-darwin13.0]
Rails 4.0.4

Gemfileの準備


以下を追加して、bundle installを実行します。


gem 'bootstrap-sass', '~> 3.1.1.0'
gem 'bootstrap_form', git: 'git@github.com:bootstrap-ruby/rails-bootstrap-forms.git'

※bootstrap_formのv2.0.1だと、できないことが結構あるので、最新を使ってます。(製品ではまだ使わないように!)

application.css.scssに以下を追加します


@import "bootstrap";
@import 'bootstrap/theme';
@import 'rails_bootstrap_forms';

application.jsを以下のようにします。


//= require jquery
//= require jquery_ujs
//= require bootstrap
//= require_tree .

任意のフォームを以下のようにします。


<%= bootstrap_form_for(@member, layout: :horizontal, label_col: "col-sm-2", control_col: "col-sm-10") do |f| %>
<%= f.text_field :name %>
<%= f.collection_select :prefecture_id, Prefecture.select('id, name').all, :id, :name, control_col: 'col-sm-6' %>
<%= f.email_field :email, control_col: 'col-sm-8' %>
<%= f.date_field :birthday, control_col: 'col-sm-6' %>
<%= f.collection_check_boxes :hobbies, Hobby.all, :id, :name, inline: true %>
<%= f.form_group do %>
<%= f.submit %>
<% end %>
<% end %>

できあがりー
blog-173-01.png
何がうれしいって、面倒な、.checkbox-inlineとかが

f.collection_check_boxes :hobbies, Hobby.all, :id, :name, inline: true
みたいに簡単にかけることなんだよね。
inline: trueを外せば、縦方向にも並びますー
ぜひ色々とお試しあれ!


Links


https://github.com/bootstrap-ruby/rails-bootstrap-forms
https://github.com/twbs/bootstrap-sass


上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。