array_model 1.0.1
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 +9 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +27 -0
- data/Rakefile +7 -0
- data/array_model.gemspec +24 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/array_model/version.rb +3 -0
- data/lib/array_model.rb +168 -0
- data/test/array_model_test.rb +80 -0
- metadata +85 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e8154f70dc3b07a528b040dc52dffc2338ac6674
|
4
|
+
data.tar.gz: 7846ddf345a00a22c32a230e130c58a25dd1037b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 27a9274b534907d5410266ed4b02649dd4962cea3a4008bf6690554d483145afae3d9efb9faed9de354a67b2a293d96223054c8f8e7ee32aed61a2a8740dff16
|
7
|
+
data.tar.gz: a63d838fbf6997d6f632755aaeaf0e6c9f0742a22b378ff42b78b93b3a43cab5728a814e7fe912407e25351aa32400e3f9c2160409bc09965b4e6c967b06cd64
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Nathan Reed
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# ArrayModel
|
2
|
+
|
3
|
+
ArrayModel is a class to create ActiveRecord / Sequel style models from simple arrays and hashes. This is useful for integrating simple reference data into an application without having to create many small tables that will never change.
|
4
|
+
|
5
|
+
As data should be never change while the application is running, the model objects that are created are read only. The data can come either from a constant in the ruby script itself, or from the filesystem as a YAML or JSON file.
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
Example:
|
10
|
+
|
11
|
+
USERS = [
|
12
|
+
{ name: 'Nathan', year: 1984 },
|
13
|
+
{ name: 'Dave', year: 1987 }
|
14
|
+
]
|
15
|
+
|
16
|
+
class User < ArrayModel
|
17
|
+
model_data USERS
|
18
|
+
attr_model_reader :name
|
19
|
+
attr_model_reader :year
|
20
|
+
|
21
|
+
def age
|
22
|
+
Time.now.year - year
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
User[0].age # => 32
|
27
|
+
User[1].name # => "Dave"
|
data/Rakefile
ADDED
data/array_model.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'array_model/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "array_model"
|
8
|
+
spec.version = ArrayModel::VERSION
|
9
|
+
spec.authors = ["Nathan Reed"]
|
10
|
+
spec.email = ["reednj@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{an ActiveRecord/Sequel style class for static reference data}
|
13
|
+
spec.homepage = "https://github.com/reednj/array_model"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.9"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "array_model"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/lib/array_model.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
require "array_model/version"
|
2
|
+
|
3
|
+
#
|
4
|
+
# ArrayModel
|
5
|
+
#
|
6
|
+
# This is used as a base class to create ActiveRecord / Sequel style models from
|
7
|
+
# simple arrays and hashes. This is useful for integrating simple reference data
|
8
|
+
# into an ActiveRecord model without having to create many small tables that will never
|
9
|
+
# change.
|
10
|
+
#
|
11
|
+
# This data should be never change while the application is running, so the model objects
|
12
|
+
# that are created are read only. The data can come either from a constant in the ruby
|
13
|
+
# script itself, or from the filesystem as a YAML or JSON file.
|
14
|
+
#
|
15
|
+
# Example:
|
16
|
+
#
|
17
|
+
# USERS = [
|
18
|
+
# { name: 'Nathan', year: 1984 },
|
19
|
+
# { name: 'Dave', year: 1987 }
|
20
|
+
# ]
|
21
|
+
#
|
22
|
+
# class User < ArrayModel
|
23
|
+
# model_data USERS
|
24
|
+
# attr_model_reader :name
|
25
|
+
# attr_model_reader :year
|
26
|
+
#
|
27
|
+
# def age
|
28
|
+
# Time.now.year - year
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# User[0].age # => 32
|
33
|
+
# User[1].name # => "Dave"
|
34
|
+
#
|
35
|
+
class ArrayModel
|
36
|
+
# get the object with the key :k:
|
37
|
+
#
|
38
|
+
# By default :k: will be a simple array index, but it can be changed to
|
39
|
+
# any field by using the :primary_key option when calling :model_data:
|
40
|
+
# when the class is defined
|
41
|
+
#
|
42
|
+
# Users['reednj'] # => #<Users:0x007fe693866808>
|
43
|
+
#
|
44
|
+
def self.[](k)
|
45
|
+
if @data_key.nil?
|
46
|
+
item_data = @data[k.to_i]
|
47
|
+
else
|
48
|
+
# if a key has been specified, then find the first matching
|
49
|
+
# record. It would be faster to convert it to a hash, but since the
|
50
|
+
# number of records should be small, this should do for now
|
51
|
+
item_data = @data.select{|a| a[@data_key] == k }.first
|
52
|
+
end
|
53
|
+
|
54
|
+
return nil if item_data.nil?
|
55
|
+
return self.new(item_data)
|
56
|
+
end
|
57
|
+
|
58
|
+
# returns an array of all the data in the model. The array will contain
|
59
|
+
# objects of the appropriate type (not raw hashes)
|
60
|
+
def self.all
|
61
|
+
if @data.is_a? Array
|
62
|
+
@all_records ||= @data.map { |v| self.new(v) }
|
63
|
+
else
|
64
|
+
raise "ArrayModel does not support #{@data.class} as data source"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# an alias for the values method to return a raw value from the data
|
69
|
+
# hash for a given model object. The attr_reader methods should be
|
70
|
+
# prefered to accessing the hash directly, but this can be useful
|
71
|
+
# in certain cases
|
72
|
+
#
|
73
|
+
# u = Users['reednj']
|
74
|
+
# u.username # => 'reednj'
|
75
|
+
# u.values(:username) # => 'reednj'
|
76
|
+
# u[:username] # => 'reednj'
|
77
|
+
#
|
78
|
+
def [](k)
|
79
|
+
values[k]
|
80
|
+
end
|
81
|
+
|
82
|
+
# returns the raw Hash that provides the data for the model
|
83
|
+
# object
|
84
|
+
#
|
85
|
+
# Users['reednj'].values # => {:username => 'reednj', ...}
|
86
|
+
#
|
87
|
+
def values
|
88
|
+
@item_data
|
89
|
+
end
|
90
|
+
|
91
|
+
# create a new model object from a given hash. This should never
|
92
|
+
# need to be called directly - the class methods should be used
|
93
|
+
# to get model objects from the dataset
|
94
|
+
def initialize(item_data)
|
95
|
+
item_data.is_a! Hash, 'item_data'
|
96
|
+
@item_data = item_data
|
97
|
+
end
|
98
|
+
|
99
|
+
# Adds attr_reader methods to the class for a given field
|
100
|
+
# in the data hash. The :key: option can be used to set the name
|
101
|
+
# of the key in the hash, if it doesn't have the same name as the
|
102
|
+
# method
|
103
|
+
#
|
104
|
+
# class Users < ArrayModel
|
105
|
+
# ...
|
106
|
+
# attr_model_reader :username
|
107
|
+
# attr_model_reader :user_id, :key => :userId
|
108
|
+
# ...
|
109
|
+
# end
|
110
|
+
#
|
111
|
+
def self.attr_model_reader(name, options = {})
|
112
|
+
define_method name.to_sym do
|
113
|
+
values[(options[:key] || name).to_sym]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# like :attr_model_reader:, but mulitple readers can be added at
|
118
|
+
# once. No options can be passed when using this method to add
|
119
|
+
# the readers
|
120
|
+
#
|
121
|
+
# class Users < ArrayModel
|
122
|
+
# ...
|
123
|
+
# attr_model_readers [:username, :user_id]
|
124
|
+
# ...
|
125
|
+
# end
|
126
|
+
#
|
127
|
+
def self.attr_model_readers(keys)
|
128
|
+
keys.each {|k| attr_model_reader k }
|
129
|
+
end
|
130
|
+
|
131
|
+
# add the model data to the model class. This data should be
|
132
|
+
# in the from of an array of hashes.
|
133
|
+
#
|
134
|
+
# The array model is designed to handle static reference data, so the data
|
135
|
+
# should not change while the application is running. It can come either from
|
136
|
+
# a constant hard coded into the ruby file, or from a json or yaml file on
|
137
|
+
# the filesystem.
|
138
|
+
#
|
139
|
+
# The :primary_key option can be used to index the data by a particular field
|
140
|
+
# in the hash when it is accessed later via the subscript operator. If this option
|
141
|
+
# is ommited then the data will be accessable simply by the array index
|
142
|
+
#
|
143
|
+
# class Users < ArrayModel
|
144
|
+
# # USER_LIST is a const containing an array of hashes
|
145
|
+
# model_data USER_LIST, :primary_key => :username
|
146
|
+
# ...
|
147
|
+
# end
|
148
|
+
#
|
149
|
+
def self.model_data(data, options = nil)
|
150
|
+
options ||= {}
|
151
|
+
data.is_a! Array, 'data'
|
152
|
+
|
153
|
+
@data_key = options[:primary_key]
|
154
|
+
@data = data
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class Object
|
159
|
+
def is_a!(t, name = nil)
|
160
|
+
if !is_a? t
|
161
|
+
if name.nil?
|
162
|
+
raise "expected #{t} but got #{self.class}"
|
163
|
+
else
|
164
|
+
raise "#{name} requires #{t} but got #{self.class}"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'array_model'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
|
4
|
+
USER_DATA = [
|
5
|
+
{ :username => 'reednj', :name => 'Nathan', :age => 31 },
|
6
|
+
{ :username => 'dmate', :name => 'Dave', :age => 29 },
|
7
|
+
{ :username => 'rkon', :name => 'Rachel', :age => 25 },
|
8
|
+
{ :username => 'zhena', :name => 'Lucy', :age => 19 },
|
9
|
+
{ :username => 'j77', :name => 'Peter', :age => 16 }
|
10
|
+
]
|
11
|
+
|
12
|
+
class Users < ArrayModel
|
13
|
+
model_data USER_DATA
|
14
|
+
attr_model_reader :username
|
15
|
+
attr_model_reader :age
|
16
|
+
|
17
|
+
def adult?
|
18
|
+
self.age >= 18
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class KeyUsers < ArrayModel
|
23
|
+
model_data USER_DATA, :primary_key => :username
|
24
|
+
attr_model_reader :username
|
25
|
+
attr_model_reader :age
|
26
|
+
end
|
27
|
+
|
28
|
+
class AttrUsers < ArrayModel
|
29
|
+
model_data USER_DATA, :primary_key => :username
|
30
|
+
attr_model_reader :first_name, :key => :name
|
31
|
+
attr_model_readers [:username, :age]
|
32
|
+
end
|
33
|
+
|
34
|
+
class ArrayModelTest < Minitest::Test
|
35
|
+
def test_can_select_by_index
|
36
|
+
assert USER_DATA.first == Users.all.first.values, 'first item does not match'
|
37
|
+
assert USER_DATA[1] == Users[1].values, 'second item does not match'
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_can_access_attributes_by_name
|
41
|
+
u = Users.all.first
|
42
|
+
assert u.username.is_a?(String), 'could not get value from model instance'
|
43
|
+
assert u.username == u[:username], 'subscript does not match method value'
|
44
|
+
assert u.username == USER_DATA[0][:username], 'field value does not match'
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_extension_methods_work
|
48
|
+
u = Users.all.select{|u| u.username == 'j77' }.first
|
49
|
+
assert u.adult? == u.age >= 18, 'extension method gave unexpected result'
|
50
|
+
|
51
|
+
u = Users.all.first
|
52
|
+
assert u.adult? == u.age >= 18, 'extension method gave unexpected result'
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_index_by_key_works
|
56
|
+
username = 'rkon'
|
57
|
+
u = KeyUsers['rkon']
|
58
|
+
t = USER_DATA.select{|a| a[:username] == username }.first
|
59
|
+
|
60
|
+
assert !u.nil?, 'expected record, but got nil'
|
61
|
+
assert u.username == t[:username], 'incorrect record selected, field does not match'
|
62
|
+
assert KeyUsers['randommm'].nil?, 'non-existant key should return nil'
|
63
|
+
assert KeyUsers[0].nil?, 'non-existant integer key should return nil'
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_mulitple_attrs_created
|
67
|
+
u = AttrUsers['zhena']
|
68
|
+
assert !u.nil?, 'unexpected nil record'
|
69
|
+
assert !u.username.nil?, 'unexpected nil value (username)'
|
70
|
+
assert u.age.is_a?(Fixnum), 'unexpected value type (age)'
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_alternative_attr_name
|
74
|
+
u = AttrUsers['zhena']
|
75
|
+
assert !u.nil?, 'unexpected nil record'
|
76
|
+
assert !u.first_name.nil?, 'unexpected nil value (first_name)'
|
77
|
+
assert u.first_name == u[:name], 'method does not match backing field'
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: array_model
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nathan Reed
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-07-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.9'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.9'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
description:
|
42
|
+
email:
|
43
|
+
- reednj@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- ".travis.yml"
|
50
|
+
- Gemfile
|
51
|
+
- LICENSE.txt
|
52
|
+
- README.md
|
53
|
+
- Rakefile
|
54
|
+
- array_model.gemspec
|
55
|
+
- bin/console
|
56
|
+
- bin/setup
|
57
|
+
- lib/array_model.rb
|
58
|
+
- lib/array_model/version.rb
|
59
|
+
- test/array_model_test.rb
|
60
|
+
homepage: https://github.com/reednj/array_model
|
61
|
+
licenses:
|
62
|
+
- MIT
|
63
|
+
metadata: {}
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options: []
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
requirements: []
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 2.4.5
|
81
|
+
signing_key:
|
82
|
+
specification_version: 4
|
83
|
+
summary: an ActiveRecord/Sequel style class for static reference data
|
84
|
+
test_files:
|
85
|
+
- test/array_model_test.rb
|