https://github.com/rafbm/submodel
Submodel maps ActiveRecord columns to ActiveModel models
https://github.com/rafbm/submodel
Last synced: 11 months ago
JSON representation
Submodel maps ActiveRecord columns to ActiveModel models
- Host: GitHub
- URL: https://github.com/rafbm/submodel
- Owner: rafbm
- License: mit
- Created: 2014-05-28T19:54:42.000Z (about 12 years ago)
- Default Branch: master
- Last Pushed: 2014-08-05T19:09:52.000Z (almost 12 years ago)
- Last Synced: 2024-04-23T22:38:32.766Z (over 2 years ago)
- Language: Ruby
- Size: 176 KB
- Stars: 5
- Watchers: 3
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
README
# Submodel
Submodel maps ActiveRecord columns to ActiveModel models, so that [hstore](http://www.postgresql.org/docs/9.3/static/hstore.html) or [serialized](http://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html) hash columns can share validations and be augmented with some methods. This can greatly help cleanup your business logic.
## Usage
```ruby
# Gemfile
gem 'submodel'
```
Create a submodel with `ActiveModel::Model`. Here’s an example `Address` model using [Carmen](http://github.com/jim/carmen) to provide country and state validations.
```ruby
# app/submodels/address.rb
class Address
include ActiveModel::Model
COUNTRY_CODES = Carmen::Country.all.map(&:code)
CA_STATE_CODES = Carmen::Country.coded('CA').subregions.map(&:code)
US_STATE_CODES = Carmen::Country.coded('US').subregions.map(&:code)
attr_accessor :street_1, :street_2, :city, :state, :country, :postal_code
validates_inclusion_of :country, in: COUNTRY_CODES
validates_inclusion_of :state, in: CA_STATE_CODES, if: :canada?
validates_inclusion_of :state, in: US_STATE_CODES, if: :united_states?
def canada?
country == 'CA'
end
def united_states?
country == 'US'
end
end
```
Use the `submodel` method to map your ActiveRecord columns to the submodel.
```ruby
# app/models/order.rb
class Order < ActiveRecord::Base
submodel :billing_address, Address
end
```
Then, accessing `#billing_address` will return an instance created with `Address.new`. Similarly, passing a hash to `#billing_address=` will create a new instance with the hash as argument.
```ruby
order = Order.new
order.attributes # => { "id" => nil, "billing_address" => nil }
order.billing_address # => #
order.billing_address.blank? # => true
order.billing_address.street_1 = '123 Fake Street'
order.billing_address # => #
order.billing_address.blank? # => false
order.billing_address = { country: 'CA', state: 'QC' }
order.billing_address # => #
```
Note: While the getter creates an instance on demand, blank submodels are persisted as `NULL`.
## Comparison
When using `==`, your submodel columns will be compared based on the stringified hash of their instance variables. Blank variables are ignored.
```ruby
order = Order.new
order.billing_address # => #
order.billing_address == Address.new # => true
order.billing_address == {} # => true
order.billing_address == { street_1: '', street_2: ' ' } # => true
order.billing_address == { street_1: 'foo', street_2: 'bar' } # => false
order.billing_address.country = 'CA'
order.billing_address.state = 'QC'
order.billing_address == { 'country' => 'CA', :state => 'QC' } # => true
order.billing_address == Address.new(country: 'CA') # => false
```
## Extending submodels per-column
You can pass the `submodel` method a block to be executed at the class level. For instance, this adds an (unfortunate) validation to `shipping_address`, leaving `billing_address` as is.
```ruby
class Order < ActiveRecord::Base
submodel :billing_address, Address
submodel :shipping_address, Address do
validates :country, inclusion: { in: %w[US CA] }
end
end
```
## This gem seems overkill.
You might think “Why not just override the getter and setter?” In my experience, getting this *right* is always more complex. If you want proper behavior (validation, comparison, FormBuilder support, persistence) you basically have to repeat [this code](lib/submodel/active_record.rb) for every column.
---
© 2014 [Rafaël Blais Masson](http://rafbm.com). Submodel is released under the MIT license.