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 ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3-p429
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ script: "bundle exec thor spec"
2
+ language: ruby
3
+ rvm:
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - 2.0.0
7
+ - jruby-19mode
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
+ [![Gem Version](https://badge.fury.io/rb/varia_model.png)](http://badge.fury.io/rb/varia_model)
3
+ [![Build Status](https://secure.travis-ci.org/RiotGames/varia_model.png?branch=master)](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
@@ -0,0 +1,3 @@
1
+ module VariaModel
2
+ VERSION = "0.1.0"
3
+ end
@@ -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
@@ -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