• Stars
    star
    242
  • Rank 167,048 (Top 4 %)
  • Language
    Ruby
  • License
    MIT License
  • Created over 11 years ago
  • Updated 9 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Adds typed hstore-backed field support to ActiveRecord models.

HstoreAccessor

Starting a new project? Use Jsonb Accessor instead! It has more features and is better maintained.

Description

Hstore Accessor allows you to treat fields on an hstore column as though they were actual columns being picked up by ActiveRecord. This is especially handy when trying to avoid sparse columns while making use of single table inheritence. Hstore Accessor currently supports ActiveRecord versions 4.0, 4.1, 4.2, 5.0, and 5.1.

Table of Contents

Installation

Add this line to your application's Gemfile:

gem "hstore_accessor", "~> 1.1"

And then execute:

$ bundle

Or install it yourself as:

$ gem install hstore_accessor

Setup

The hstore_accessor method accepts the name of the hstore column you'd like to use and a hash with keys representing fields and values indicating the type to be stored in that field. The available types are: string, integer, float, decimal, datetime, date, boolean, array, and hash. It is available on an class that inherits from ActiveRecord::Base.

class Product < ActiveRecord::Base
  hstore_accessor :options,
    color: :string,
    weight: :integer,
    price: :float,
    built_at: :datetime,
    build_date: :date,
    tags: :array, # deprecated
    ratings: :hash # deprecated
    miles: :decimal
end

Now you can interact with the fields stored in the hstore directly.

product = Product.new
product.color = "green"
product.weight = 34
product.price = 99.95
product.built_at = Time.now - 10.days
product.build_date = Date.today
product.popular = true
product.tags = %w(housewares kitchen) # deprecated
product.ratings = { user_a: 3, user_b: 4 } # deprecated
product.miles = 3.14

Reading these fields works as well.

product.color # => "green"
product.price  # => 99.95

In order to reduce the storage overhead of hstore keys (especially when indexed) you can specify an alternate key.

hstore_accessor :options,
  color: { data_type: :string, store_key: "c" },
  weight: { data_type: :integer, store_key: "w" }

In the above example you can continue to interact with the fields using their full name but when saved to the database the field will be set using the store_key.

Additionally, dirty tracking is implemented in the same way that normal ActiveRecord fields work.

product.color          #=> "green"
product.color = "blue"
product.changed?       #=> true
product.color_changed? #=> true
product.color_was      #=> "green"
product.color_change  #=> ["green", "blue"]

ActiveRecord methods generated for fields

class Product < ActiveRecord::Base
  hstore_accessor :data, field: :string
end
  • field
  • field=
  • field?
  • field_changed?
  • field_was
  • field_change
  • reset_field!
  • restore_field!
  • field_will_change!

Overriding methods is supported, with access to the original Hstore Accessor implementation available via super.

Additionally, there is also hstore_metadata_for_<fields> on both the class and instances. column_for_attribute will also return a column object for an Hstore Accessor defined field. If you're using ActiveRecord 4.2, type_for_attribute will return a type object for Hstore Accessor defined fields the same as it does for actual columns.

Scopes

The hstore_accessor macro also creates scopes for string, integer, float, decimal, time, date, boolean, and array fields.

String Fields

For string types, a with_<key> scope is created which checks for equality.

Product.with_color("green")

Integer, Float, Decimal Fields

For integer, float and decimal types five scopes are created:

Product.price_lt(240.00)  # price less than
Product.price_lte(240.00) # price less than or equal to
Product.price_eq(240.00)  # price equal to
Product.price_gte(240.00) # price greater than or equal to
Product.price_gt(240.00)  # price greater than

Datetime Fields

For datetime fields, three scopes are created:

Product.built_at_before(Time.now)         # built before the given time
Product.built_at_eq(Time.now - 10.days)   # built at an exact time
Product.built_at_after(Time.now - 4.days) # built after the given time

Date Fields

For date fields, three scopes are created:

Product.build_date_before(Date.today)         # built before the given date
Product.build_date_eq(Date.today - 10.days)   # built at an exact date
Product.built_date_after(Date.today - 4.days) # built after the given date

Array Fields

Note: the array field type is deprecated. It is available in version 0.9.0 but not > 1.0.0

For array types, two scopes are created:

Product.tags_eq(%w(housewares kitchen))       # tags equaling
Product.tags_contains("kitchen")              # tags containing a single value
Product.tags_contains(%w(housewares kitchen)) # tags containing a number of values

Boolean Fields

Two scopes are created for boolean fields:

Product.is_popular  # => when popular is set to true
Product.not_popular # => when popular is set to false

Predicate methods are also available on instances:

product = Product.new(popular: true)
product.popular? # => true

Single-table Inheritance

One of the big issues with ActiveRecord single-table inheritance (STI) is sparse columns. Essentially, as sub-types of the original table diverge further from their parent more columns are left empty in a given table. Postgres' hstore type provides part of the solution in that the values in an hstore column does not impose a structure - different rows can have different values.

We set up our table with an hstore field:

# db/migration/<timestamp>_create_players_table.rb
class CreateVehiclesTable < ActiveRecord::Migration
  def change
    create_table :vehicles do |t|
      t.string :make
      t.string :model
      t.integer :model_year
      t.string :type
      t.hstore :data
    end
  end
end

And for our models:

# app/models/vehicle.rb
class Vehicle < ActiveRecord::Base
end

# app/models/vehicles/automobile.rb
class Automobile < Vehicle
  hstore_accessor :data,
    axle_count: :integer,
    weight: :float,
    engine_details: :hash
end

# app/models/vehicles/airplane.rb
class Airplane < Vehicle
  hstore_accessor :data,
    engine_type: :string,
    safety_rating: :integer,
    features: :hash
end

From here any attributes specific to any sub-class can be stored in the hstore column avoiding sparse data. Indices can also be created on individual fields in an hstore column.

This approach was originally concieved by Joe Hirn in this blog post.

Upgrading

Upgrading from version 0.6.0 to 0.9.0 should be fairly painless. If you were previously using a time type fields, simply change it to datetime like so:

# Before...
hstore_accessor :data, published_at: :time
# After...
hstore_accessor :data, published_at: :datetime

While the array and hash types are available in version 0.9.0, they are deprecated and are not available in 1.0.0.

Contributing

Basics

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Write code and tests
  4. Commit your changes (git commit -am 'Add some feature')
  5. Push to the branch (git push origin my-new-feature)
  6. Create new Pull Request

Developing Locally

Before you make your pull requests, please make sure you style is in line with our Rubocop settings and that all of the tests pass.

  1. bundle install
  2. appraisal install
  3. Make sure Postgres is installed and running
  4. appraisal rspec to run all the tests
  5. rubocop to check for style

More Repositories

1

jsonb_accessor

Adds typed jsonb backed fields to your ActiveRecord models.
Ruby
1,101
star
2

practical-object-oriented-javascript

Practical Object Oriented Javascript - CWC 2015 Workshop
JavaScript
68
star
3

tabs

A redis-backed metrics tracker for keeping tabs on pretty much anything ;)
Ruby
28
star
4

row_boat

Import CSVs into your ActiveRecord models
Ruby
13
star
5

fig_rails_demo

Demonstration of Rails app configured with dockerized/figified environment.
Ruby
12
star
6

angular_rrule_recurring_select

Angular directive to select recurrence rule pattern powered by RRule.js
JavaScript
11
star
7

finish-line

Handy React components and functions to cut down on some of the boiler plate in Relay Modern apps.
JavaScript
8
star
8

drip

Dagger-ish dependency injector for Swift
Swift
8
star
9

react-native-x-platform-letter-spacing

Demonstrates how to implement cross platform letter spacing in React Native
JavaScript
8
star
10

scssketch

JavaScript
7
star
11

heroku-buildpack-angular-spa

Buildpack for Heroku with Angular and Nginx
Shell
6
star
12

faker-vehicle

Faker::Vehicle is a small extension to the Faker gem that adds additional methods for generating fake vehicle data.
Ruby
5
star
13

harbor

Monitor your Codeship builds from the comfort of your OSX status bar.
Swift
4
star
14

present_foo

Very simple presenter library for Rails.
Ruby
3
star
15

object-oriented-javascript

Object Oriented Javascript Class Project
JavaScript
3
star
16

SetupScript

Joining Tandem setup script
Shell
3
star
17

eslint-config-devmynd

DevMynd's standard eslint config
JavaScript
2
star
18

ember-cli-s3

An Ember CLI add-on for hosting assets on S3
JavaScript
2
star
19

black_book

Ruby
1
star
20

laminate

Route stack components for React Router v4
JavaScript
1
star
21

staffing-strategy

Small React project to make it easy to visualize possible upcoming staffing scenarios and plan for multiple possible futures at once
CSS
1
star
22

ember-facebook-component

Ember.js component for handling Facebook authentication.
JavaScript
1
star
23

devmynd-rails-template

A template for making new Rails projects.
Ruby
1
star
24

estimation-station

Ruby
1
star