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 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
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in array_model.gemspec
4
+ gemspec
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
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ t.pattern = "test/*_test.rb"
7
+ end
@@ -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
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,3 @@
1
+ class ArrayModel
2
+ VERSION = "1.0.1"
3
+ end
@@ -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