hashme 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +95 -0
- data/Rakefile +1 -0
- data/hashme.gemspec +26 -0
- data/lib/hashme.rb +45 -0
- data/lib/hashme/attributes.rb +56 -0
- data/lib/hashme/castable.rb +10 -0
- data/lib/hashme/casted_array.rb +61 -0
- data/lib/hashme/properties.rb +80 -0
- data/lib/hashme/property.rb +63 -0
- data/lib/hashme/version.rb +3 -0
- data/spec/hashme/attributes_spec.rb +79 -0
- data/spec/hashme/base_spec.rb +38 -0
- data/spec/hashme/casted_array_spec.rb +56 -0
- data/spec/hashme/properties_spec.rb +101 -0
- data/spec/hashme/property_spec.rb +92 -0
- data/spec/spec_helper.rb +8 -0
- metadata +125 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4f9cb6f4cdaff6724a27b50649492558e063d895
|
4
|
+
data.tar.gz: 821a1a3dd493decc292af29ba65b25ded28a6ce8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2ce2e52be2471ebc0ea3a4e379d0b03722f9e393548e6daa22ace5f110e655198771161ccd3351a49cd9ab365f9a4d4ed6dab2c31525b58bedca5f2d700a43f8
|
7
|
+
data.tar.gz: 913eeffb93e61a1304dd37297b39f0e0b896a2e39f37d45ce100bac68c16ec320d3ceeb7f1c54ba6052148fe24c2e07c6b142acc772df794de99649639feb7e3
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Sam Lown
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# Hashme
|
2
|
+
|
3
|
+
Hashes are great, but sometimes you need a bit more structure.
|
4
|
+
|
5
|
+
Hashme helps you create simple models that allow you to initialize and
|
6
|
+
generate hashes that you can serialize and store as you wish.
|
7
|
+
|
8
|
+
Its a bit like ActiveRecord, but without all the messy database stuff,
|
9
|
+
and of course you can nest to you're hearts content.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem 'hashme'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install hashme
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
Create a class, include the `Hashme` module, and define some properties:
|
28
|
+
|
29
|
+
````ruby
|
30
|
+
# Our cat class!
|
31
|
+
class Cat
|
32
|
+
include Hashme
|
33
|
+
|
34
|
+
property :name, String
|
35
|
+
property :description, String
|
36
|
+
end
|
37
|
+
|
38
|
+
# Do something with it
|
39
|
+
kitty = Cat.new(:name => "Catso", :description => "Meows a lot")
|
40
|
+
kitty.name # Catso
|
41
|
+
kitty.to_hash # {:name => "Catso", :description => "Meows a lot"}
|
42
|
+
kitty.to_json # "{\"name\":\"Catso\",\"description\":\"Meows a lot\"}"
|
43
|
+
|
44
|
+
kitty2 = Cat.new(kitty.to_hash)
|
45
|
+
kitty2.to_hash == kitty.to_hash # true!
|
46
|
+
````
|
47
|
+
|
48
|
+
Models can also be nested, which is probably the most useful part:
|
49
|
+
|
50
|
+
````ruby
|
51
|
+
# A kennel full of kitties
|
52
|
+
class Kennel
|
53
|
+
include Hashme
|
54
|
+
|
55
|
+
property :name, String
|
56
|
+
property :location, Point
|
57
|
+
property :cats, [Cat]
|
58
|
+
end
|
59
|
+
|
60
|
+
# Build a kennel
|
61
|
+
kennel = Kennel.new(:name => "Goaway Kitty Home", :location => Point.new(40.333,-3.4555))
|
62
|
+
|
63
|
+
# Add a kitten using an object
|
64
|
+
kennel << kitty
|
65
|
+
|
66
|
+
# Add a new cat using a raw hash
|
67
|
+
kennel << {
|
68
|
+
:name => "Felix",
|
69
|
+
:description => "Black and white"
|
70
|
+
}
|
71
|
+
|
72
|
+
# Serialize and deserialize to recreate the whole structure
|
73
|
+
store = kennel.to_hash
|
74
|
+
kennel = Kennel.new(store)
|
75
|
+
kennel.cats.length == 2 # true!
|
76
|
+
````
|
77
|
+
|
78
|
+
## Contributing
|
79
|
+
|
80
|
+
1. Fork it
|
81
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
82
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
83
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
84
|
+
5. Create new Pull Request
|
85
|
+
|
86
|
+
## Authors
|
87
|
+
|
88
|
+
* Sam Lown <me@samlown.com>
|
89
|
+
|
90
|
+
## History
|
91
|
+
|
92
|
+
### 0.1.0 - 2014-01-15
|
93
|
+
|
94
|
+
* First release!
|
95
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/hashme.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'hashme/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "hashme"
|
8
|
+
spec.version = Hashme::VERSION
|
9
|
+
spec.authors = ["Sam Lown"]
|
10
|
+
spec.email = ["me@samlown.com"]
|
11
|
+
spec.description = %q{Modeling with Hashes made easy.}
|
12
|
+
spec.summary = %q{Easily define simple models that can be easily serialized and de-serialized.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "activemodel", "~> 3.0"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
end
|
data/lib/hashme.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require "hashme/version"
|
2
|
+
|
3
|
+
# External dependencies
|
4
|
+
|
5
|
+
require "active_model"
|
6
|
+
require "active_model/naming"
|
7
|
+
require "active_model/serialization"
|
8
|
+
|
9
|
+
require "active_support/core_ext"
|
10
|
+
require "active_support/json"
|
11
|
+
|
12
|
+
# Local dependencies
|
13
|
+
|
14
|
+
require "hashme/attributes"
|
15
|
+
require "hashme/castable"
|
16
|
+
require "hashme/casted_array"
|
17
|
+
require "hashme/properties"
|
18
|
+
require "hashme/property"
|
19
|
+
|
20
|
+
module Hashme
|
21
|
+
extend ActiveSupport::Concern
|
22
|
+
|
23
|
+
include ActiveModel::Validations
|
24
|
+
|
25
|
+
included do
|
26
|
+
include Castable
|
27
|
+
include Attributes
|
28
|
+
include Properties
|
29
|
+
# Eventually! include Validations
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(attrs = {})
|
33
|
+
# Use the `Properties` module's `#set_attribtues` method
|
34
|
+
set_attributes(attrs)
|
35
|
+
end
|
36
|
+
|
37
|
+
module ClassMethods
|
38
|
+
# Little help. Equivalent to new.tap
|
39
|
+
def build(*attrs)
|
40
|
+
instance = self.new(*attrs)
|
41
|
+
yield instance if block_given?
|
42
|
+
instance
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Hashme
|
2
|
+
module Attributes
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def_delegators :_attributes, :to_a,
|
8
|
+
:==, :eql?, :keys, :values, :each,
|
9
|
+
:reject, :reject!, :empty?, :clear, :merge, :merge!,
|
10
|
+
:encode_json, :as_json, :to_json, :to_hash,
|
11
|
+
:frozen?
|
12
|
+
|
13
|
+
def []=(key, value)
|
14
|
+
_attributes[key.to_sym] = value
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](key)
|
18
|
+
_attributes[key.to_sym]
|
19
|
+
end
|
20
|
+
|
21
|
+
def has_key?(key)
|
22
|
+
_attributes.has_key?(key.to_sym)
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(key)
|
26
|
+
_attributes.delete(key.to_sym)
|
27
|
+
end
|
28
|
+
|
29
|
+
def dup
|
30
|
+
new = super
|
31
|
+
@_attributes = @_attributes.dup
|
32
|
+
new
|
33
|
+
end
|
34
|
+
|
35
|
+
def clone
|
36
|
+
new = super
|
37
|
+
@_attributes = @_attributes.clone
|
38
|
+
new
|
39
|
+
end
|
40
|
+
|
41
|
+
def inspect
|
42
|
+
string = keys.collect{|key|
|
43
|
+
"#{key}: #{self[key].inspect}"
|
44
|
+
}.compact.join(", ")
|
45
|
+
"#<#{self.class} #{string}>"
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def _attributes
|
51
|
+
@_attributes ||= {}
|
52
|
+
@_attributes
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Hashme
|
4
|
+
|
5
|
+
# The Hashme CastedArray is a special object wrapper that allows other Model's
|
6
|
+
# or Objects to be stored in an array, but maintain casted ownership.
|
7
|
+
#
|
8
|
+
# Adding objects will automatically assign the Array's owner, as opposed
|
9
|
+
# to the array itself.
|
10
|
+
#
|
11
|
+
class CastedArray
|
12
|
+
extend Forwardable
|
13
|
+
include Castable
|
14
|
+
|
15
|
+
def_delegators :@_array,
|
16
|
+
:to_a, :==, :eql?, :size,
|
17
|
+
:first, :last, :at, :length,
|
18
|
+
:each, :reject, :empty?, :map,
|
19
|
+
:clear, :pop, :shift, :delete, :delete_at,
|
20
|
+
:encode_json, :as_json, :to_json,
|
21
|
+
:inspect, :any?
|
22
|
+
|
23
|
+
def initialize(owner, property, values = [])
|
24
|
+
@_array = []
|
25
|
+
self.casted_by = owner
|
26
|
+
self.casted_by_property = property
|
27
|
+
if values.respond_to?(:each)
|
28
|
+
values.each do |value|
|
29
|
+
self.push(value)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def <<(obj)
|
35
|
+
@_array << instantiate_and_build(obj)
|
36
|
+
end
|
37
|
+
|
38
|
+
def push(obj)
|
39
|
+
@_array.push(instantiate_and_build(obj))
|
40
|
+
end
|
41
|
+
|
42
|
+
def unshift(obj)
|
43
|
+
@_array.unshift(instantiate_and_build(obj))
|
44
|
+
end
|
45
|
+
|
46
|
+
def [] index
|
47
|
+
@_array[index]
|
48
|
+
end
|
49
|
+
|
50
|
+
def []= index, obj
|
51
|
+
@_array[index] = instantiate_and_build(obj)
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
def instantiate_and_build(obj)
|
57
|
+
casted_by_property.build(casted_by, obj)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Hashme
|
2
|
+
module Properties
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def get_attribute(name)
|
6
|
+
self[name]
|
7
|
+
end
|
8
|
+
|
9
|
+
def set_attribute(name, value)
|
10
|
+
property = get_property(name)
|
11
|
+
if property.nil?
|
12
|
+
self[name.to_sym] = value
|
13
|
+
else
|
14
|
+
self[property.name] = value.present? ? property.cast(self, value) : value
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
# Internal method to go through each attribute and set the
|
21
|
+
# values via the set_attribute method.
|
22
|
+
def set_attributes(attrs = {})
|
23
|
+
attrs.each do |key, value|
|
24
|
+
set_attribute(key, value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def get_property(name)
|
31
|
+
# not only search in the class, search in the superclass too if the superclass can respond to properties[]. Using a class method for this
|
32
|
+
self.class.search_property(name)
|
33
|
+
end
|
34
|
+
|
35
|
+
module ClassMethods
|
36
|
+
|
37
|
+
attr_accessor :properties
|
38
|
+
|
39
|
+
def property(*args)
|
40
|
+
self.properties ||= {}
|
41
|
+
|
42
|
+
# Prepare the property object and methods
|
43
|
+
property = Property.new(*args)
|
44
|
+
properties[property.name] = property
|
45
|
+
define_property_methods(property)
|
46
|
+
|
47
|
+
property
|
48
|
+
end
|
49
|
+
|
50
|
+
# Recursive search the property in the superclass chain
|
51
|
+
def search_property(name)
|
52
|
+
name = name.to_sym
|
53
|
+
|
54
|
+
if properties[name]
|
55
|
+
properties[name]
|
56
|
+
elsif superclass.respond_to?(:search_property)
|
57
|
+
superclass.search_property(name)
|
58
|
+
else
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
def define_property_methods(property)
|
66
|
+
# Getter
|
67
|
+
define_method(property.name) do
|
68
|
+
get_attribute(property.name) || property.default
|
69
|
+
end
|
70
|
+
# Setter
|
71
|
+
define_method "#{property.name}=" do |value|
|
72
|
+
set_attribute(property.name, value)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Hashme
|
2
|
+
class Property
|
3
|
+
|
4
|
+
attr_accessor :name, :type, :default
|
5
|
+
|
6
|
+
def initialize(name, type, opts = {})
|
7
|
+
self.name = name.to_sym
|
8
|
+
|
9
|
+
# Always set type to base type
|
10
|
+
if type.is_a?(Array) && !type.first.nil?
|
11
|
+
@_use_casted_array = true
|
12
|
+
klass = type.first
|
13
|
+
else
|
14
|
+
@_use_casted_array = false
|
15
|
+
klass = type
|
16
|
+
end
|
17
|
+
|
18
|
+
self.type = klass
|
19
|
+
|
20
|
+
# Handle options
|
21
|
+
self.default = opts[:default]
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
name.to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_sym
|
29
|
+
name
|
30
|
+
end
|
31
|
+
|
32
|
+
# Use cast method when we do not know if we may need to handle a
|
33
|
+
# casted array of objects.
|
34
|
+
def cast(owner, value)
|
35
|
+
if use_casted_array?
|
36
|
+
CastedArray.new(owner, self, value)
|
37
|
+
else
|
38
|
+
build(owner, value)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Build a new object of the type defined by the property.
|
43
|
+
# Will not deal create CastedArrays!
|
44
|
+
def build(owner, value)
|
45
|
+
obj = nil
|
46
|
+
if value.is_a?(type)
|
47
|
+
obj = value
|
48
|
+
elsif type == Date
|
49
|
+
obj = type.parse(value)
|
50
|
+
else
|
51
|
+
obj = type.new(value)
|
52
|
+
end
|
53
|
+
obj.casted_by = owner if obj.respond_to?(:casted_by=)
|
54
|
+
obj.casted_by_property = self if obj.respond_to?(:casted_by_property=)
|
55
|
+
obj
|
56
|
+
end
|
57
|
+
|
58
|
+
def use_casted_array?
|
59
|
+
@_use_casted_array
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hashme::Attributes do
|
4
|
+
|
5
|
+
|
6
|
+
before :each do
|
7
|
+
@model = Class.new do
|
8
|
+
include Hashme
|
9
|
+
end
|
10
|
+
@obj = @model.new
|
11
|
+
end
|
12
|
+
|
13
|
+
let :attribs do
|
14
|
+
@obj.send(:_attributes)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "forwarded methods" do
|
18
|
+
before :each do
|
19
|
+
@hash = {:key1 => 'value1', :key2 => 'value2'}
|
20
|
+
@obj[:key1] = 'value1'
|
21
|
+
@obj[:key2] = 'value2'
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should assign some of the basic hash methods" do
|
25
|
+
(@obj == @hash).should be_true
|
26
|
+
@obj.eql?(@hash).should be_true
|
27
|
+
@obj.keys.should eql(@hash.keys)
|
28
|
+
@obj.values.should eql(@hash.values)
|
29
|
+
@obj.to_hash.should eql(@hash)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#[]=" do
|
34
|
+
it "should assign values to attributes hash" do
|
35
|
+
@obj[:akey] = "test"
|
36
|
+
attribs[:akey].should eql("test")
|
37
|
+
@obj['akey'] = "anothertest"
|
38
|
+
attribs[:akey].should eql("anothertest")
|
39
|
+
attribs['akey'].should be_nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#delete" do
|
44
|
+
it "should remove attribtue entry" do
|
45
|
+
@obj[:key] = 'value'
|
46
|
+
@obj.delete(:key)
|
47
|
+
@obj[:key].should be_nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "#dup" do
|
52
|
+
it "should duplicate attributes" do
|
53
|
+
@obj[:key] = 'value'
|
54
|
+
@obj2 = @obj.dup
|
55
|
+
@obj2.send(:_attributes).object_id.should_not eql(@obj.send(:_attributes).object_id)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "#clone" do
|
60
|
+
it "should clone attributes" do
|
61
|
+
@obj[:key] = 'value'
|
62
|
+
@obj2 = @obj.clone
|
63
|
+
@obj2.send(:_attributes).object_id.should_not eql(@obj.send(:_attributes).object_id)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
describe "#inspect" do
|
69
|
+
|
70
|
+
it "should provide something useful" do
|
71
|
+
@obj[:key1] = 'value1'
|
72
|
+
@obj[:key2] = 'value2'
|
73
|
+
@obj.inspect.should match(/#<.+ key1: "value1", key2: "value2">/)
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hashme do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
@model = Class.new do
|
7
|
+
include Hashme
|
8
|
+
property :name, String
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '.build' do
|
13
|
+
it "should create a Model and give a block to build it" do
|
14
|
+
@model.should_receive(:call_in_block)
|
15
|
+
@model.build do |model|
|
16
|
+
@model.call_in_block
|
17
|
+
model.should be_kind_of(@model)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#initialize" do
|
23
|
+
|
24
|
+
it "should accept nil" do
|
25
|
+
expect {
|
26
|
+
@obj = @model.new
|
27
|
+
}.to_not raise_error
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should accept and set attributes" do
|
31
|
+
@obj = @model.new(:name => "Sam")
|
32
|
+
@obj.name.should eql("Sam")
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hashme::CastedArray do
|
4
|
+
|
5
|
+
let :owner do
|
6
|
+
double()
|
7
|
+
end
|
8
|
+
|
9
|
+
let :submodel do
|
10
|
+
Class.new do
|
11
|
+
include Hashme
|
12
|
+
property :name, String
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#initialize" do
|
17
|
+
before :each do
|
18
|
+
@prop = Hashme::Property.new(:name, String)
|
19
|
+
@obj = Hashme::CastedArray.new(owner, @prop, ['test'])
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should prepare array" do
|
23
|
+
@obj.length.should eql(1)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should set owner and property" do
|
27
|
+
@obj.casted_by.should eql(owner)
|
28
|
+
@obj.casted_by_property.should eql(@prop)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should instantiate and cast each value" do
|
32
|
+
@obj.first.should eql("test")
|
33
|
+
@obj.first.class.should eql(String)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "adding to array" do
|
38
|
+
|
39
|
+
before :each do
|
40
|
+
@prop = Hashme::Property.new(:item, submodel)
|
41
|
+
@obj = Hashme::CastedArray.new(owner, @prop, [{:name => 'test'}])
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should cast new items" do
|
45
|
+
@obj << {:name => 'test2'}
|
46
|
+
@obj.last.class.should eql(submodel)
|
47
|
+
@obj.first.name.should eql('test')
|
48
|
+
@obj.last.name.should eql('test2')
|
49
|
+
|
50
|
+
@obj.last.casted_by.should eql(owner)
|
51
|
+
@obj.last.casted_by_property.should eql(@prop)
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hashme::Properties do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
@aux_model = Class.new do
|
7
|
+
include Hashme
|
8
|
+
property :age, Fixnum
|
9
|
+
end
|
10
|
+
Kernel.const_set("Aux", @aux_model)
|
11
|
+
end
|
12
|
+
|
13
|
+
before :each do
|
14
|
+
@model = Class.new do
|
15
|
+
include Hashme
|
16
|
+
property :name, String
|
17
|
+
end
|
18
|
+
|
19
|
+
@obj = @model.new
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
describe "#get_attribute" do
|
24
|
+
it "should provide object in model" do
|
25
|
+
@obj[:key1] = 'value'
|
26
|
+
@obj.get_attribute(:key1).should eql('value')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#set_attribute" do
|
31
|
+
it "should be posible to set attribute not defined as property" do
|
32
|
+
@obj.set_attribute('key1', 'value1')
|
33
|
+
@obj.set_attribute(:key2, 'value2')
|
34
|
+
@obj[:key1].should eql('value1')
|
35
|
+
@obj[:key2].should eql('value2')
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should set and cast attribute with property" do
|
39
|
+
property = @model.send(:properties)[:name]
|
40
|
+
name = "Fred Flinstone"
|
41
|
+
property.should_receive(:cast).with(@obj, name).and_return(name)
|
42
|
+
@obj.set_attribute(:name, name)
|
43
|
+
@obj[:name].should eql(name)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe ".properties" do
|
48
|
+
|
49
|
+
it "should be instantiated after property set" do
|
50
|
+
@model.properties.should_not be_nil
|
51
|
+
@model.properties.class.should eql(Hash)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should be null if no properties" do
|
55
|
+
model = Class.new do
|
56
|
+
include Hashme
|
57
|
+
end
|
58
|
+
model.properties.should be_nil
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
describe ".property" do
|
64
|
+
|
65
|
+
it "should fail if no type is defined" do
|
66
|
+
@model.properties.length.should eql(1)
|
67
|
+
expect {
|
68
|
+
@model.property :foobar
|
69
|
+
}.to raise_error(ArgumentError)
|
70
|
+
@model.properties.length.should eql(1)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should create a new property with helper methods" do
|
74
|
+
@model.properties.length.should eql(1)
|
75
|
+
@model.property :desc, String
|
76
|
+
@model.properties.length.should eql(2)
|
77
|
+
|
78
|
+
prop = @model.properties[:desc]
|
79
|
+
prop.class.should eql(Hashme::Property)
|
80
|
+
|
81
|
+
@obj.should respond_to(:desc)
|
82
|
+
@obj.should respond_to(:desc=)
|
83
|
+
|
84
|
+
@obj.desc = "test"
|
85
|
+
@obj.desc.should eql("test")
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should return nil on property with no default" do
|
89
|
+
@model.property :nickname, String
|
90
|
+
@obj.nickname.should be_nil
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should create helper method with support for default values" do
|
94
|
+
@model.property :name, String, :default => "Sam"
|
95
|
+
@obj.name.should eql("Sam")
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hashme::Property do
|
4
|
+
|
5
|
+
let :owner do
|
6
|
+
double()
|
7
|
+
end
|
8
|
+
|
9
|
+
let :klass do
|
10
|
+
Hashme::Property
|
11
|
+
end
|
12
|
+
|
13
|
+
let :submodel do
|
14
|
+
Class.new do
|
15
|
+
include Hashme
|
16
|
+
property :name, String
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#initialize" do
|
21
|
+
|
22
|
+
it "should copy name and type" do
|
23
|
+
prop = klass.new("name", String)
|
24
|
+
prop.name.should eql(:name)
|
25
|
+
prop.type.should eql(String)
|
26
|
+
prop.use_casted_array?.should be_false
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should convert Array to CastedArray type" do
|
30
|
+
prop = klass.new("names", [String])
|
31
|
+
prop.name.should eql(:names)
|
32
|
+
prop.type.should eql(String)
|
33
|
+
prop.use_casted_array?.should be_true
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should accept a default option" do
|
37
|
+
prop = klass.new(:name, String, :default => "Freddo")
|
38
|
+
prop.default.should eql("Freddo")
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#to_s" do
|
44
|
+
it "should use property's name" do
|
45
|
+
prop = klass.new(:name, String)
|
46
|
+
prop.to_s.should eql("name")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#to_sym" do
|
51
|
+
it "should return the name" do
|
52
|
+
prop = klass.new(:name, String)
|
53
|
+
prop.to_sym.should eql (:name)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#cast" do
|
58
|
+
context "without an array" do
|
59
|
+
it "should build a new object" do
|
60
|
+
prop = klass.new(:date, Time)
|
61
|
+
obj = prop.cast(owner, "2013-06-02T12:00:00")
|
62
|
+
obj.class.should eql(Time)
|
63
|
+
obj.should eql(Time.new("2013-06-02T12:00:00"))
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should assign casted by and property" do
|
67
|
+
prop = klass.new(:item, submodel)
|
68
|
+
obj = prop.cast(owner, {:name => 'test'})
|
69
|
+
obj.casted_by.should eql(owner)
|
70
|
+
obj.casted_by_property.should eql(prop)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "with an array" do
|
75
|
+
it "should convert regular array to casted array" do
|
76
|
+
prop = klass.new(:dates, [Time])
|
77
|
+
obj = prop.cast(owner, ["2013-06-02T12:00:00"])
|
78
|
+
obj.class.should eql(Hashme::CastedArray)
|
79
|
+
obj.first.class.should eql(Time)
|
80
|
+
end
|
81
|
+
it "should handle complex objects" do
|
82
|
+
prop = klass.new(:items, [submodel])
|
83
|
+
obj = prop.cast(owner, [{:name => 'test'}])
|
84
|
+
obj.class.should eql(Hashme::CastedArray)
|
85
|
+
obj.first.class.should eql(submodel)
|
86
|
+
obj.first.name.should eql('test')
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hashme
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sam Lown
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-01-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activemodel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Modeling with Hashes made easy.
|
70
|
+
email:
|
71
|
+
- me@samlown.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- Gemfile
|
78
|
+
- LICENSE.txt
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- hashme.gemspec
|
82
|
+
- lib/hashme.rb
|
83
|
+
- lib/hashme/attributes.rb
|
84
|
+
- lib/hashme/castable.rb
|
85
|
+
- lib/hashme/casted_array.rb
|
86
|
+
- lib/hashme/properties.rb
|
87
|
+
- lib/hashme/property.rb
|
88
|
+
- lib/hashme/version.rb
|
89
|
+
- spec/hashme/attributes_spec.rb
|
90
|
+
- spec/hashme/base_spec.rb
|
91
|
+
- spec/hashme/casted_array_spec.rb
|
92
|
+
- spec/hashme/properties_spec.rb
|
93
|
+
- spec/hashme/property_spec.rb
|
94
|
+
- spec/spec_helper.rb
|
95
|
+
homepage: ''
|
96
|
+
licenses:
|
97
|
+
- MIT
|
98
|
+
metadata: {}
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options: []
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - '>='
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
requirements: []
|
114
|
+
rubyforge_project:
|
115
|
+
rubygems_version: 2.0.3
|
116
|
+
signing_key:
|
117
|
+
specification_version: 4
|
118
|
+
summary: Easily define simple models that can be easily serialized and de-serialized.
|
119
|
+
test_files:
|
120
|
+
- spec/hashme/attributes_spec.rb
|
121
|
+
- spec/hashme/base_spec.rb
|
122
|
+
- spec/hashme/casted_array_spec.rb
|
123
|
+
- spec/hashme/properties_spec.rb
|
124
|
+
- spec/hashme/property_spec.rb
|
125
|
+
- spec/spec_helper.rb
|