Help us understand the problem. What is going on with this article?

静的データやマスターデータの為だけにtableを持つのを止めよう。 active_hash が便利そうだ

More than 3 years have passed since last update.

active_hash

zilkey/active_hash

HashデータをActiveRecordのように扱う事の出来るgem。
少し使った感じはかなり便利そう。

Qiitaにactive_hashだけの記事が無かったので書いてみる。

用途

例えば、カテゴリデータや都道府県など、プルダウンに使うような静的データは、色々な持ち方ができます。

単純に定数で扱ってもいいですし、

HOGES = {
  1 => 'aaa',
  2 => 'bbb',
  3 => 'ccc',
  4 => 'ddd'
}.with_indifferent_access

enumerize も便利です。

enumerize :hoge, in: {aaa: 1, bbb: 2, ccc: 3, ddd: 4}

brainspec/enumerize
Rails - Enumerizeを数値型カラムで使う - Qiita

DB

ただ、DBにhoges tableとしてデータでもつ場合も多いかと。
Relationを張れるので、他tableとの連動なども扱いやすいですしね。

active_hash

active_hashの場合、hashデータとして定義したデータを、擬似的にActiveRecordのように扱う事ができます。

また個人的に使いやすいと思ったのが、

  • ActiveRecord <=> ActiveHash へのrelationを張る事が出来る点
  • Decorator層に受け渡して加工もしやすい

点です。

サンプル

install

gem 'active_hash'
# or
gem install active_hash

app/model

app/models/country.rb
class Country < ActiveHash::Base
  self.data = [
    {:id => 1, :name => "US"},
    {:id => 2, :name => "Canada"}
  ]
end

app/model内にhashで定義を書きます。
これは下記のようにも書けます

app/model/country.rb
class Country < ActiveHash::Base
  field :name
  add :id => 1, :name => "US"
  add :id => 2, :name => "Canada"
end

また、yamlにも定義できます

app/model/country.rb
class Country < ActiveYaml::Base
  set_root_path "/u/data"
  set_filename "sample"
end

# array style
- id: 1
  name: US
- id: 2
  name: Canada
- id: 3
  name: Mexico

# hash style
us:
  id: 1
  name: US
canada:
  id: 2
  name: Canada
mexico:
  id: 3
  name: Mexico

Class method

ActiveRecordでおなじみのmethodが色々使えるようになっています。

Country.all             # => returns all Country objects
Country.count           # => returns the length of the .data array
Country.first           # => returns the first country object
Country.last            # => returns the last country object
Country.find 1          # => returns the first country object with that id
Country.find [1,2]      # => returns all Country objects with ids in the array
Country.find :all       # => same as .all
Country.find :all, args # => the second argument is totally ignored, but allows it to play nicely with AR
Country.find_by_id 1    # => find the first object that matches the id

Country.find_by_name "foo"                    # => returns the first object matching that name
Country.find_all_by_name "foo"                # => returns an array of the objects with matching names
Country.find_by_id_and_name 1, "Germany"      # => returns the first object matching that id and name
Country.find_all_by_id_and_name 1, "Germany"  # => returns an array of objects matching that name and id

Instance method

Country#id          # => returns the id or nil
Country#id=         # => sets the id attribute
Country#quoted_id   # => returns the numeric id
Country#to_param    # => returns the id as a string
Country#new_record? # => returns true if is not part of Country.all, false otherwise
Country#readonly?   # => true
Country#hash        # => the hash of the id (or the hash of nil)
Country#eql?        # => compares type and id, returns false if id is nil

ActiveRecordからのrelation

参考: zilkey/active_hash#referencing-activehash-objects-from-activerecord-associations

app/model/country.rb
class Country < ActiveHash::Base
  include ActiveHash::Associations
  field :name
  add :id => 1, :name => "US"
  add :id => 2, :name => "Canada"

  has_many :persons
end

class Person < ActiveRecord::Base
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to_active_hash :country
end

belongs_to_active_hash が用意されているのでこれを使います。
(ActiveRecordのversionによって、記述方法が若干異なるようです。

person = Person.new
person.location = Country.first
person.save
person.location # => Country.first

decorator層で扱う

元々このgemを使おうと思った動機が、プルダウンやcheckbox、radio butonなど、静的データをformで扱う場合に、少しずつデータの形を整えたいからでした。
(form_forとsimple_formとかでも

見た目の為にデータを整えるには、decorator層で行いたいですが、定数とかだとやり難いし、他のmodelがdrapergem/draperを使っているので、合わせたいな〜と。

CountryDecorator.decorate_collection(Country.all)

これで、Decorator層で扱えます。
が、(active_hashだからなのか、わからないですが)少しはまったところもありました。

.allの戻り値はArray

通常のmodelは.allすると戻り値は、Relationですが、active_hashの場合はArrayみたいです。

[1] pry(main)> Hoge.all.class
=> Hoge::ActiveRecord_Relation
[3] pry(main)> Country.all.class
=> Array

Draperのgithubを見ると、

Note: In Rails 3, the .all method returns an array and not a query. Thus you cannot use the technique of Article.all.decorate in Rails 3. In Rails 4, .all returns a query so this techique would work fine.

と、Rails 3とか、Arrayが帰ってきた場合は、.all.decorate使えないよ、って言われるので、素直に別のやり方をしましょう。

Collectionデータを扱う

Collectionデータを扱う場合、

@articles = ArticleDecorator.decorate_collection(Article.all)

# app/decorators/articles_decorator.rb
class ArticlesDecorator < Draper::CollectionDecorator
  def page_number
    42
  end
end

# elsewhere...
@articles = ArticlesDecorator.new(Article.all)
# or, equivalently
@articles = ArticlesDecorator.decorate(Article.all)

の二つのやり方があるようなのですが、自分の環境では、前者しかうまく動きませんでした。
しかも、前者のやり方をすると、ArticlesDecoratorのinstanceができます。(sがついて複数形のほう)

[11] pry(main)> 
ArticleDecorator.decorate_collection(Article.all).class.name
=> "ArticlesDecorator

ArticleDecoratorでdecorate_collectionすると、ArticlesDecoratorのmethodが呼ばれる。
こういうものなのかな。。。?
あまり調べきれてません。。。orz

参考

zilkey/active_hash
【Rails】静的データのモデル化にactive_hash gemが便利・・! - kotatu.org
ActiveHashを使ってRailsで区分値を扱う方法 - TIM Labs

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away