• Stars
    star
    126
  • Rank 284,543 (Top 6 %)
  • Language
    Ruby
  • License
    Other
  • Created over 9 years ago
  • Updated over 4 years ago

Reviews

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

Repository Details

ModelAttribute gem - attributes for non-ActiveRecord models

ModelAttribute Gem Version Build Status

Simple attributes for a non-ActiveRecord model.

  • Stores attributes in instance variables.
  • Type casting and checking.
  • Dirty tracking.
  • List attribute names and values.
  • Default values for attributes
  • Handles integers, floats, booleans, strings and times - a set of types that are very easy to persist to and parse from JSON.
  • Supports efficient serialization of attributes to JSON.
  • Mass assignment - handy for initializers.

Why not Virtus? Virtus doesn't provide dirty tracking, and doesn't integrate with ActiveModel::Dirty. So if you're not using ActiveRecord, but you need attributes with dirty tracking, ModelAttribute may be what you're after. For example, it works very well for a model that fronts an HTTP web service, and you want dirty tracking so you can PATCH appropriately.

Also in favor of ModelAttribute:

  • It's simple - less than 200 lines of code.
  • It supports efficient serialization and deserialization to/from JSON.

Integrating with Rails

If you're using ModelAttribute in a Rails application, you will probably want to augment your model with other methods to make it behave more like ActiveRecord. ActiveModel provides a very useful set of mixins, described in the Rails guide. You can also see an example of the methods we found useful at Yammer described in this blog post, with full source in this Gist.

Usage

require 'model_attribute'
class User
  extend ModelAttribute
  attribute :id,         :integer
  attribute :paid,       :boolean
  attribute :name,       :string
  attribute :created_at, :time
  attribute :grades,     :json

  def initialize(attributes = {})
    set_attributes(attributes)
  end
end

User.attributes # => [:id, :paid, :name, :created_at, :grades]
user = User.new

user.attributes # => {:id=>nil, :paid=>nil, :name=>nil, :created_at=>nil, :grades=>nil}

# An integer attribute
user.id # => nil

user.id = 3
user.id # => 3

# Stores values that convert cleanly to an integer
user.id = '5'
user.id # => 5

# Protects you against nonsense assignment
user.id = '5error'
ArgumentError: invalid value for Integer(): "5error"

# A boolean attribute
user.paid # => nil
user.paid = true

# Booleans also define a predicate method (ending in '?')
user.paid?  # => true

# Conversion from strings used by databases.
user.paid = 'f'
user.paid # => false
user.paid = 't'
user.paid # => true
user.paid = 'false'
user.paid # => false
user.paid = 'true'
user.paid # => true

# A :time attribute
user.created_at = Time.now
user.created_at # => 2015-01-08 15:57:05 +0000

# Also converts from other reasonable time formats
user.created_at = "2014-12-25 14:00:00 +0100"
user.created_at # => 2014-12-25 13:00:00 +0000
user.created_at = Date.parse('2014-01-08')
user.created_at # => 2014-01-08 00:00:00 +0000
user.created_at = DateTime.parse("2014-12-25 13:00:45")
user.created_at # => 2014-12-25 13:00:45 +0000
# Convert from seconds since the epoch
user.created_at = Time.now.to_f
user.created_at # => 2015-01-08 16:23:02 +0000
# Or milliseconds since the epoch
user.created_at = 1420734182000
user.created_at # => 2015-01-08 16:23:02 +0000

# A :json attribute is schemaless and accepts the basic JSON types - hash,
# array, nil, numeric, string and boolean.
user.grades = {'maths' => 'A', 'history' => 'C'}
user.grades # => {"maths"=>"A", "history"=>"C"}
user.grades = ['A', 'A*', 'C']
user.grades # => ["A", "A*", "C"]
user.grades = 'AAB'
user.grades # => "AAB"
user.grades = Time.now
# => ArgumentError: JSON only supports nil, numeric, string, boolean and arrays and hashes of those.

# read_attribute and write_attribute methods
user.read_attribute(:created_at)
user.write_attribute(:name, 'Fred')

# View attributes
user.attributes # => {:id=>5, :paid=>true, :name=>"Fred", :created_at=>2015-01-08 15:57:05 +0000, :grades=>{"maths"=>"A", "history"=>"C"}}
user.inspect # => "#<User id: 5, paid: true, name: \"Fred\", created_at: 2015-01-08 15:57:05 +0000, grades: {\"maths\"=>\"A\", \"history\"=>\"C\"}>"

# Mass assignment
user.set_attributes(name: "Sally", paid: false)
user.attributes # => {:id=>5, :paid=>false, :name=>"Sally", :created_at=>2015-01-08 15:57:05 +0000}

# Efficient JSON serialization and deserialization.
# Attributes with nil values are omitted.
user.attributes_for_json
# => {"id"=>5, "paid"=>true, "name"=>"Fred", "created_at"=>1421171317762}
require 'oj'
Oj.dump(user.attributes_for_json, mode: :strict)
# => "{\"id\":5,\"paid\":true,\"name\":\"Fred\",\"created_at\":1421171317762}"
user2 = User.new(Oj.load(json, strict: true))

# Change tracking.  A much smaller set of functions than that provided by
# ActiveModel::Dirty.
user.changes # => {:id=>[nil, 5], :paid=>[nil, true], :created_at=>[nil, 2015-01-08 15:57:05 +0000], :name=>[nil, "Fred"]}
user.name_changed?  # => true
# If you need the new values to send as a PUT to a web service
user.changes_for_json # => {"id"=>5, "paid"=>true, "name"=>"Fred", "created_at"=>1421171317762}
# If you're imitating ActiveRecord behaviour, changes are cleared after
# after_save callbacks, but before after_commit callbacks.
user.changes.clear
user.changes # => {}

# Equality if all the attribute values match
another = User.new
another.id = 5
another.paid = true
another.created_at = user.created_at
another.name = 'Fred'

user == another   # => true
user === another  # => true
user.eql? another # => true

# Making some attributes private

class User
  extend ModelAttribute
  attribute :events, :string
  private :events=

  def initialize(attributes)
    # Pass flag to set_attributes to allow setting attributes with private writers
    set_attributes(attributes, true)
  end

  def add_event(new_event)
    events ||= ""
    events += new_event
  end
end

# Supporting default attributes

class UserWithDefaults
  extend ModelAttribute

  attribute :name, :string, default: 'Charlie'
end

UserWithDefaults.attribute_defaults # => {:name=>"Charlie"}

user = UserWithDefaults.new
user.name # => "Charlie"
user.read_attribute(:name) # => "Charlie"
user.attributes # => {:name=>"Charlie"}
# attributes_for_json omits defaults to keep the JSON compact
user.attributes_for_json # => {}
# You can add them back in if you need them
user.attributes_for_json.merge(user.class.attribute_defaults) # => {:name=>"Charlie"}
# A default isn't a change
user.changes # => {}
user.changes_for_json # => {}

user.name = 'Bob'
user.attributes # => {:name=>"Bob"}

Installation

Add this line to your application's Gemfile:

gem 'model_attribute'

And then execute:

$ bundle

Or install it yourself as:

$ gem install model_attribute

Testing

Running specs:

$ rspec

Contributing

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

Code of Conduct

This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.

More Repositories

1

circuitbox

Circuit breaker built with large Ruby apps in mind.
Ruby
702
star
2

circuit-breaker-js

Hystrix-like circuit breaker for JavaScript.
JavaScript
265
star
3

tenacity

Dropwizard + Hystrix Module.
Java
203
star
4

YMSwipeTableViewCell

YMSwipeTableViewCell is a lightweight library that enables table view cell swiping.
Objective-C
110
star
5

breakerbox

Frontend for Tenacity + Archaius
Java
63
star
6

yam

The official Yammer Ruby gem..
Ruby
45
star
7

dropwizard-auth-ldap

Dropwizard Authentication Module for LDAP using JNDI.
Java
36
star
8

sched.do

Ruby
30
star
9

yam-python

A python wrapper around the Yammer API.
Python
29
star
10

telemetry

A dapper-like substance in Java.
Java
18
star
11

schedulizer

An app to manage schedules
JavaScript
15
star
12

dropwizard-testing-integration

Utilities for writing dropwizard integration tests.
Java
15
star
13

EspressoApplicationSample-

EspressoApplicationSample
Java
14
star
14

dotnet-yammersdk

dotnet-yammersdk - build windows universal apps using the yammer fabric
C#
10
star
15

ios-oauth-demo

A sample application to demonstrate how the Yammer OAuth API works.
Objective-C
9
star
16

ios-yammer-sdk

Yammer iOS SDK
Objective-C
8
star
17

backbone-component

A thin layer on top of Backbone's view class to add nested child views.
JavaScript
7
star
18

windows-phone-oauth-sdk-demo

SDK tools and a demo for Windows Phone users to consume the Yammer API via Oauth.
C#
7
star
19

JS_SDK_Sample

6
star
20

yammer-collections

Collections utilities which build on top of Guava and the standard collections library.
Java
6
star
21

docker-registry-cache

Shell
5
star
22

backups

Backups as a Service
Java
4
star
23

mcrouter-build-docker

An Ubuntu 12.04 package builder for mcrouter
Makefile
4
star
24

powershell_example

A Powershell example of using the Yammer Oauth 2 to retrieve a token
PowerShell
3
star
25

azure-table

Azure Table backed Guava Table implementation.
Java
3
star
26

yamoverflow

YamOverflow.
Ruby
3
star