varia_model 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.
- data/.gitignore +17 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +29 -0
- data/Guardfile +18 -0
- data/LICENSE +15 -0
- data/README.md +25 -0
- data/Thorfile +43 -0
- data/lib/varia_model/attributes.rb +52 -0
- data/lib/varia_model/version.rb +3 -0
- data/lib/varia_model.rb +284 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/support/matchers/each.rb +12 -0
- data/spec/unit/varia_model_spec.rb +610 -0
- data/varia_model.gemspec +33 -0
- metadata +259 -0
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.9.3-p429
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
gem 'buff-extensions', path: '~/code/buff-extensions'
|
6
|
+
|
7
|
+
group :development do
|
8
|
+
gem 'coolline'
|
9
|
+
|
10
|
+
require 'rbconfig'
|
11
|
+
|
12
|
+
if RbConfig::CONFIG['target_os'] =~ /darwin/i
|
13
|
+
gem 'growl', require: false
|
14
|
+
gem 'rb-fsevent', require: false
|
15
|
+
|
16
|
+
if `uname`.strip == 'Darwin' && `sw_vers -productVersion`.strip >= '10.8'
|
17
|
+
gem 'terminal-notifier-guard', '~> 1.5.3', require: false
|
18
|
+
end rescue Errno::ENOENT
|
19
|
+
|
20
|
+
elsif RbConfig::CONFIG['target_os'] =~ /linux/i
|
21
|
+
gem 'libnotify', '~> 0.8.0', require: false
|
22
|
+
gem 'rb-inotify', require: false
|
23
|
+
|
24
|
+
elsif RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
|
25
|
+
gem 'win32console', require: false
|
26
|
+
gem 'rb-notifu', '>= 0.0.4', require: false
|
27
|
+
gem 'wdm', require: false
|
28
|
+
end
|
29
|
+
end
|
data/Guardfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
notification :off
|
2
|
+
interactor :coolline
|
3
|
+
|
4
|
+
guard 'spork' do
|
5
|
+
watch('Gemfile')
|
6
|
+
watch('spec/spec_helper.rb') { :rspec }
|
7
|
+
watch(%r{^spec/support/.+\.rb$}) { :rspec }
|
8
|
+
end
|
9
|
+
|
10
|
+
guard 'rspec', version: 2, cli: "--color --drb --format Fuubar", all_on_start: false, all_after_pass: false do
|
11
|
+
watch(%r{^spec/unit/.+_spec\.rb$})
|
12
|
+
watch(%r{^spec/acceptance/.+_spec\.rb$})
|
13
|
+
|
14
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
|
15
|
+
watch('spec/spec_helper.rb') { "spec" }
|
16
|
+
watch(%r{^spec/support/.+\.rb$}) { "spec" }
|
17
|
+
watch(%r{^spec/shared_examples/(.*)\.rb$}) { |m| ["spec/unit/#{m[1]}_spec.rb", "spec/unit/#{m[1]}"] }
|
18
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Copyright 2012-2013 Riot Games
|
2
|
+
|
3
|
+
Jamie Winsor (<reset@riotgames.com>)
|
4
|
+
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
you may not use this file except in compliance with the License.
|
7
|
+
You may obtain a copy of the License at
|
8
|
+
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
See the License for the specific language governing permissions and
|
15
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# VariaModel
|
2
|
+
[](http://badge.fury.io/rb/varia_model)
|
3
|
+
[](http://travis-ci.org/RiotGames/varia_model)
|
4
|
+
|
5
|
+
A mixin to provide objects with magic attribute reading and writing
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
$ gem install varia_model
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
require 'varia_model'
|
14
|
+
|
15
|
+
module MyApp
|
16
|
+
class Config
|
17
|
+
include VariaModel
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Authors and Contributors
|
22
|
+
|
23
|
+
* Jamie Winsor (<reset@riotgames.com>)
|
24
|
+
|
25
|
+
Thank you to all of our [Contributors](https://github.com/RiotGames/buff-extensions/graphs/contributors), testers, and users.
|
data/Thorfile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
require 'bundler'
|
5
|
+
require 'bundler/setup'
|
6
|
+
require 'buff/ruby_engine'
|
7
|
+
require 'varia_model'
|
8
|
+
|
9
|
+
class Default < Thor
|
10
|
+
extend Buff::RubyEngine
|
11
|
+
|
12
|
+
unless jruby?
|
13
|
+
require 'thor/rake_compat'
|
14
|
+
|
15
|
+
include Thor::RakeCompat
|
16
|
+
Bundler::GemHelper.install_tasks
|
17
|
+
|
18
|
+
desc "build", "Build varia_model-#{VariaModel::VERSION}.gem into the pkg directory"
|
19
|
+
def build
|
20
|
+
Rake::Task["build"].execute
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "install", "Build and install varia_model-#{VariaModel::VERSION}.gem into system gems"
|
24
|
+
def install
|
25
|
+
Rake::Task["install"].execute
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "release", "Create tag v#{VariaModel::VERSION} and build and push varia_model-#{VariaModel::VERSION}.gem to Rubygems"
|
29
|
+
def release
|
30
|
+
Rake::Task["release"].execute
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Spec < Thor
|
35
|
+
namespace :spec
|
36
|
+
default_task :all
|
37
|
+
|
38
|
+
desc "all", "run all tests"
|
39
|
+
def all
|
40
|
+
exec "rspec --color --format=documentation spec"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'hashie'
|
2
|
+
require 'hashie/hash_extensions'
|
3
|
+
require 'hashie/mash'
|
4
|
+
|
5
|
+
module VariaModel
|
6
|
+
class Attributes < Hashie::Mash
|
7
|
+
alias_method :old_setter, :[]=
|
8
|
+
alias_method :old_dup, :dup
|
9
|
+
|
10
|
+
attr_writer :coercions
|
11
|
+
|
12
|
+
# @return [Hashie::Mash]
|
13
|
+
def coercions
|
14
|
+
@coercions ||= Hashie::Mash.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def coercion(key)
|
18
|
+
self.coercions[key]
|
19
|
+
end
|
20
|
+
|
21
|
+
def set_coercion(key, fun)
|
22
|
+
self.coercions[key] = fun
|
23
|
+
end
|
24
|
+
|
25
|
+
# Override setter to coerce the given value if a coercion is defined
|
26
|
+
def []=(key, value)
|
27
|
+
coerced_value = coercion(key).present? ? coercion(key).call(value) : value
|
28
|
+
old_setter(key, coerced_value)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Return the containing Hashie::Mash of the given dotted path
|
32
|
+
#
|
33
|
+
# @param [String] path
|
34
|
+
#
|
35
|
+
# @return [Hashie::Mash, nil]
|
36
|
+
def container(path)
|
37
|
+
parts = path.split('.', 2)
|
38
|
+
match = (self[parts[0].to_s] || self[parts[0].to_sym])
|
39
|
+
if !parts[1] or match.nil?
|
40
|
+
self
|
41
|
+
else
|
42
|
+
match.container(parts[1])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def dup
|
47
|
+
mash = old_dup
|
48
|
+
mash.coercions = self.coercions
|
49
|
+
mash
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/varia_model.rb
ADDED
@@ -0,0 +1,284 @@
|
|
1
|
+
require 'buff/extensions'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module VariaModel
|
5
|
+
require_relative 'varia_model/version'
|
6
|
+
require_relative 'varia_model/attributes'
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
ASSIGNMENT_MODES = [
|
10
|
+
:whitelist,
|
11
|
+
:carefree
|
12
|
+
]
|
13
|
+
|
14
|
+
# @return [VariaModel::Attributes]
|
15
|
+
def attributes
|
16
|
+
@attributes ||= Attributes.new
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Hashie::Mash]
|
20
|
+
def validations
|
21
|
+
@validations ||= Hashie::Mash.new
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [Symbol]
|
25
|
+
def assignment_mode
|
26
|
+
@assignment_mode ||= :whitelist
|
27
|
+
end
|
28
|
+
|
29
|
+
# Set the attribute mass assignment mode
|
30
|
+
# * :whitelist - only attributes defined on the class will have values set
|
31
|
+
# * :carefree - values will be set for attributes that are not explicitly defined
|
32
|
+
# in the class definition
|
33
|
+
#
|
34
|
+
# @param [Symbol] mode
|
35
|
+
# an assignment mode to use @see {ASSIGNMENT_MODES}
|
36
|
+
def set_assignment_mode(mode)
|
37
|
+
unless ASSIGNMENT_MODES.include?(mode)
|
38
|
+
raise ArgumentError, "unknown assignment mode: #{mode}"
|
39
|
+
end
|
40
|
+
|
41
|
+
@assignment_mode = mode
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param [#to_s] name
|
45
|
+
# @option options [Symbol, Array<Symbol>] :type
|
46
|
+
# @option options [Boolean] :required
|
47
|
+
# @option options [Object] :default
|
48
|
+
# @option options [Proc] :coerce
|
49
|
+
def attribute(name, options = {})
|
50
|
+
name = name.to_s
|
51
|
+
options[:type] = Array(options[:type])
|
52
|
+
options[:required] ||= false
|
53
|
+
|
54
|
+
register_attribute(name, options)
|
55
|
+
define_mimic_methods(name, options)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param [String] name
|
59
|
+
#
|
60
|
+
# @return [Array]
|
61
|
+
def validations_for(name)
|
62
|
+
self.validations[name] ||= Array.new
|
63
|
+
end
|
64
|
+
|
65
|
+
# @param [Constant, Array<Constant>] types
|
66
|
+
# @param [VariaModel] model
|
67
|
+
# @param [String] key
|
68
|
+
#
|
69
|
+
# @return [Array]
|
70
|
+
def validate_kind_of(types, model, key)
|
71
|
+
errors = Array.new
|
72
|
+
types = types.uniq
|
73
|
+
matches = false
|
74
|
+
|
75
|
+
types.each do |type|
|
76
|
+
if model.get_attribute(key).is_a?(type)
|
77
|
+
matches = true
|
78
|
+
break
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
if matches
|
83
|
+
[ :ok, "" ]
|
84
|
+
else
|
85
|
+
types_msg = types.collect { |type| "'#{type}'" }
|
86
|
+
[ :error, "Expected attribute: '#{key}' to be a type of: #{types_msg.join(', ')}" ]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Validate that the attribute on the given model has a non-nil value assigned
|
91
|
+
#
|
92
|
+
# @param [VariaModel] model
|
93
|
+
# @param [String] key
|
94
|
+
#
|
95
|
+
# @return [Array]
|
96
|
+
def validate_required(model, key)
|
97
|
+
if model.get_attribute(key).nil?
|
98
|
+
[ :error, "A value is required for attribute: '#{key}'" ]
|
99
|
+
else
|
100
|
+
[ :ok, "" ]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def register_attribute(name, options = {})
|
107
|
+
if options[:type] && options[:type].any?
|
108
|
+
unless options[:required]
|
109
|
+
options[:type] << NilClass
|
110
|
+
end
|
111
|
+
register_validation(name, lambda { |object, key| validate_kind_of(options[:type], object, key) })
|
112
|
+
end
|
113
|
+
|
114
|
+
if options[:required]
|
115
|
+
register_validation(name, lambda { |object, key| validate_required(object, key) })
|
116
|
+
end
|
117
|
+
|
118
|
+
class_eval do
|
119
|
+
new_attributes = Attributes.from_dotted_path(name, options[:default])
|
120
|
+
self.attributes.merge!(new_attributes)
|
121
|
+
|
122
|
+
if options[:coerce].is_a?(Proc)
|
123
|
+
register_coercion(name, options[:coerce])
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def register_validation(name, fun)
|
129
|
+
self.validations[name] = (self.validations_for(name) << fun)
|
130
|
+
end
|
131
|
+
|
132
|
+
def register_coercion(name, fun)
|
133
|
+
self.attributes.container(name).set_coercion(name.split('.').last, fun)
|
134
|
+
end
|
135
|
+
|
136
|
+
def define_mimic_methods(name, options = {})
|
137
|
+
fun_name = name.split('.').first
|
138
|
+
|
139
|
+
class_eval do
|
140
|
+
define_method(fun_name) do
|
141
|
+
_attributes_[fun_name]
|
142
|
+
end
|
143
|
+
|
144
|
+
define_method("#{fun_name}=") do |value|
|
145
|
+
value = if options[:coerce].is_a?(Proc)
|
146
|
+
options[:coerce].call(value)
|
147
|
+
else
|
148
|
+
value
|
149
|
+
end
|
150
|
+
|
151
|
+
_attributes_[fun_name] = value
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class << self
|
158
|
+
def included(base)
|
159
|
+
base.extend(ClassMethods)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# @return [Hashie::Mash]
|
164
|
+
def validate
|
165
|
+
self.class.validations.each do |attr_path, validations|
|
166
|
+
validations.each do |validation|
|
167
|
+
status, messages = validation.call(self, attr_path)
|
168
|
+
|
169
|
+
if status == :error
|
170
|
+
if messages.is_a?(Array)
|
171
|
+
messages.each do |message|
|
172
|
+
self.add_error(attr_path, message)
|
173
|
+
end
|
174
|
+
else
|
175
|
+
self.add_error(attr_path, messages)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
self.errors
|
182
|
+
end
|
183
|
+
|
184
|
+
# @return [Boolean]
|
185
|
+
def valid?
|
186
|
+
validate.empty?
|
187
|
+
end
|
188
|
+
|
189
|
+
# @return [Hashie::Mash]
|
190
|
+
def errors
|
191
|
+
@errors ||= Hashie::Mash.new
|
192
|
+
end
|
193
|
+
|
194
|
+
# Assigns the attributes of a model from a given hash of attributes.
|
195
|
+
#
|
196
|
+
# If the assignment mode is set to `:whitelist`, then only the values of keys which have a
|
197
|
+
# corresponding attribute definition on the model will be set. All other keys will have their
|
198
|
+
# values ignored.
|
199
|
+
#
|
200
|
+
# If the assignment mode is set to `:carefree`, then the attributes hash will be populated
|
201
|
+
# with any key/values that are provided.
|
202
|
+
#
|
203
|
+
# @param [Hash] new_attrs
|
204
|
+
def mass_assign(new_attrs = {})
|
205
|
+
case self.class.assignment_mode
|
206
|
+
when :whitelist
|
207
|
+
whitelist_assign(new_attrs)
|
208
|
+
when :carefree
|
209
|
+
carefree_assign(new_attrs)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# @param [#to_s] key
|
214
|
+
#
|
215
|
+
# @return [Object]
|
216
|
+
def get_attribute(key)
|
217
|
+
_attributes_.dig(key.to_s)
|
218
|
+
end
|
219
|
+
alias_method :[], :get_attribute
|
220
|
+
|
221
|
+
# @param [#to_s] key
|
222
|
+
# @param [Object] value
|
223
|
+
def set_attribute(key, value)
|
224
|
+
_attributes_.deep_merge!(Attributes.from_dotted_path(key.to_s, value))
|
225
|
+
end
|
226
|
+
alias_method :[]=, :set_attribute
|
227
|
+
|
228
|
+
# @param [#to_hash] hash
|
229
|
+
#
|
230
|
+
# @return [self]
|
231
|
+
def from_hash(hash)
|
232
|
+
mass_assign(hash.to_hash)
|
233
|
+
self
|
234
|
+
end
|
235
|
+
|
236
|
+
# @param [String] data
|
237
|
+
#
|
238
|
+
# @return [self]
|
239
|
+
def from_json(data)
|
240
|
+
mass_assign(JSON.load(data))
|
241
|
+
self
|
242
|
+
end
|
243
|
+
|
244
|
+
# The storage hash containing all of the key/values for this object's attributes
|
245
|
+
#
|
246
|
+
# @return [Hashie::Mash]
|
247
|
+
def _attributes_
|
248
|
+
@_attributes_ ||= self.class.attributes.dup
|
249
|
+
end
|
250
|
+
alias_method :to_hash, :_attributes_
|
251
|
+
|
252
|
+
# @option options [Boolean] :symbolize_keys
|
253
|
+
# @option options [Class, Symbol, String] :adapter
|
254
|
+
#
|
255
|
+
# @return [String]
|
256
|
+
def to_json(options = {})
|
257
|
+
JSON.generate(_attributes_, options)
|
258
|
+
end
|
259
|
+
alias_method :as_json, :to_json
|
260
|
+
|
261
|
+
protected
|
262
|
+
|
263
|
+
# @param [String] attribute
|
264
|
+
# @param [String] message
|
265
|
+
def add_error(attribute, message)
|
266
|
+
self.errors[attribute] ||= Array.new
|
267
|
+
self.errors[attribute] << message
|
268
|
+
end
|
269
|
+
|
270
|
+
private
|
271
|
+
|
272
|
+
def carefree_assign(new_attrs = {})
|
273
|
+
_attributes_.deep_merge!(new_attrs)
|
274
|
+
end
|
275
|
+
|
276
|
+
def whitelist_assign(new_attrs = {})
|
277
|
+
self.class.attributes.dotted_paths.each do |dotted_path|
|
278
|
+
value = new_attrs.dig(dotted_path)
|
279
|
+
next if value.nil?
|
280
|
+
|
281
|
+
set_attribute(dotted_path, value)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
require 'buff/ruby_engine'
|
5
|
+
|
6
|
+
def setup_rspec
|
7
|
+
Dir[File.join(File.expand_path("../../spec/support/**/*.rb", __FILE__))].each { |f| require f }
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
config.expect_with :rspec do |c|
|
11
|
+
c.syntax = :expect
|
12
|
+
end
|
13
|
+
|
14
|
+
config.mock_with :rspec
|
15
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
16
|
+
config.filter_run focus: true
|
17
|
+
config.run_all_when_everything_filtered = true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
if Buff::RubyEngine.jruby?
|
22
|
+
require 'varia_model'
|
23
|
+
setup_rspec
|
24
|
+
else
|
25
|
+
require 'spork'
|
26
|
+
|
27
|
+
Spork.prefork { setup_rspec }
|
28
|
+
Spork.each_run { require 'varia_model' }
|
29
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
RSpec::Matchers.define :each do |check|
|
2
|
+
match do |actual|
|
3
|
+
actual.each_with_index do |index, o|
|
4
|
+
@object = o
|
5
|
+
expect(index).to check
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
failure_message_for_should do |actual|
|
10
|
+
"at[#{@object}] #{check.failure_message_for_should}"
|
11
|
+
end
|
12
|
+
end
|