varia_model 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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