attrio 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/.gitignore +17 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +8 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +49 -0
  7. data/Rakefile +8 -0
  8. data/attrio.gemspec +27 -0
  9. data/lib/attrio.rb +62 -0
  10. data/lib/attrio/attribute.rb +68 -0
  11. data/lib/attrio/attributes_parser.rb +56 -0
  12. data/lib/attrio/builders/accessor_builder.rb +24 -0
  13. data/lib/attrio/builders/reader_builder.rb +23 -0
  14. data/lib/attrio/builders/writer_builder.rb +24 -0
  15. data/lib/attrio/core_ext/array.rb +23 -0
  16. data/lib/attrio/core_ext/class.rb +55 -0
  17. data/lib/attrio/core_ext/hash.rb +31 -0
  18. data/lib/attrio/core_ext/object.rb +11 -0
  19. data/lib/attrio/core_ext/string.rb +15 -0
  20. data/lib/attrio/initialize.rb +29 -0
  21. data/lib/attrio/inspect.rb +33 -0
  22. data/lib/attrio/reset.rb +21 -0
  23. data/lib/attrio/types/base.rb +29 -0
  24. data/lib/attrio/types/boolean.rb +26 -0
  25. data/lib/attrio/types/date.rb +19 -0
  26. data/lib/attrio/types/date_time.rb +19 -0
  27. data/lib/attrio/types/float.rb +19 -0
  28. data/lib/attrio/types/integer.rb +19 -0
  29. data/lib/attrio/types/symbol.rb +19 -0
  30. data/lib/attrio/types/time.rb +19 -0
  31. data/lib/attrio/version.rb +12 -0
  32. data/spec/attrio/initialize_spec.rb +29 -0
  33. data/spec/attrio/types/boolean_spec.rb +101 -0
  34. data/spec/attrio/types/date_spec.rb +80 -0
  35. data/spec/attrio/types/date_time_spec.rb +80 -0
  36. data/spec/attrio/types/float_spec.rb +38 -0
  37. data/spec/attrio/types/integer_spec.rb +38 -0
  38. data/spec/attrio/types/symbol_spec.rb +41 -0
  39. data/spec/attrio/types/time_spec.rb +80 -0
  40. data/spec/spec_helper.rb +31 -0
  41. metadata +177 -0
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/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format nested
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - ruby-head
7
+ - rbx-19mode
8
+ - jruby-19mode
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Julia Egorova
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,49 @@
1
+ [![Build Status](https://travis-ci.org/jetrockets/attrio.png)](https://travis-ci.org/jetrockets/attrio)
2
+ [![Code Climate](https://codeclimate.com/github/jetrockets/attrio.png)](https://codeclimate.com/github/jetrockets/attrio)
3
+ [![Coverage Status](https://coveralls.io/repos/jetrockets/attrio/badge.png)](https://coveralls.io/r/jetrockets/attrio)
4
+ [![Dependency Status](https://gemnasium.com/jetrockets/attrio.png)](https://gemnasium.com/jetrockets/attrio)
5
+ [![Gem Version](https://badge.fury.io/rb/attrio.png)](http://badge.fury.io/rb/attrio)
6
+
7
+ # Attrio
8
+
9
+ Attributes for plain Ruby objects. The goal is to provide an ability to define attributes for your models without reinventing the wheel all over again. Attrio doesn't have any third-party dependencies like Virtus or ActiveAttr and does not redefine any methods inside your class, unless you want it to.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'attrio'
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install attrio
24
+
25
+ ## Note on Patches / Pull Requests
26
+
27
+ * Fork the project.
28
+ * Make your feature addition or bug fix.
29
+ * Add tests for it. This is important so I don't break it in a
30
+ future version unintentionally.
31
+ * Commit, do not mess with rakefile, version, or history.
32
+ (if you want to have your own version, that is fine but
33
+ bump version in a commit by itself I can ignore when I pull)
34
+ * Send me a pull request. Bonus points for topic branches.
35
+
36
+ ## Credits
37
+
38
+ ![JetRockets](http://www.jetrockets.ru/public/logo.png)
39
+
40
+ Webmaster is maintained by [JetRockets](http://www.jetrockets.ru/en).
41
+
42
+ Contributors:
43
+
44
+ * [Igor Alexandrov](http://igor-alexandrov.github.com/)
45
+ * [Julia Egorova](https://github.com/vankiru)
46
+
47
+ ## License
48
+
49
+ It is free software, and may be redistributed under the terms specified in the LICENSE file.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new('spec')
7
+
8
+ task :default => :spec
data/attrio.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'attrio/version'
6
+
7
+ Gem::Specification.new do |gem|
8
+ gem.name = "attrio"
9
+ gem.version = Attrio::Version::STRING
10
+ gem.authors = ['Igor Alexandrov', 'Julia Egorova']
11
+ gem.email = 'hello@jetrockets.ru'
12
+ gem.summary = "Attributes for Plain Old Ruby Objects"
13
+ gem.homepage = "https://github.com/jetrockets/attrio"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_development_dependency 'rspec'
21
+ gem.add_development_dependency 'webmock', '~> 1.9.0'
22
+ gem.add_development_dependency 'simplecov'
23
+ gem.add_development_dependency 'coveralls'
24
+ gem.add_development_dependency 'rake'
25
+ gem.add_development_dependency 'bundler'
26
+ gem.add_development_dependency "rake"
27
+ end
data/lib/attrio.rb ADDED
@@ -0,0 +1,62 @@
1
+ # encoding: utf-8
2
+
3
+ require 'attrio/version'
4
+
5
+ require 'attrio/core_ext/array'
6
+ require 'attrio/core_ext/class'
7
+ require 'attrio/core_ext/hash'
8
+ require 'attrio/core_ext/object'
9
+ require 'attrio/core_ext/string'
10
+
11
+ module Attrio
12
+ autoload :AttributesParser, 'attrio/attributes_parser'
13
+ autoload :Attribute, 'attrio/attribute'
14
+ autoload :Initialize, 'attrio/initialize'
15
+ autoload :Inspect, 'attrio/inspect'
16
+ autoload :Reset, 'attrio/reset'
17
+
18
+ def self.included(base)
19
+ base.send(:include, Attrio::Initialize)
20
+ base.send(:include, Attrio::Inspect)
21
+ base.send(:include, Attrio::Reset)
22
+
23
+ base.send(:extend, Attrio::ClassMethods)
24
+ end
25
+
26
+ module ClassMethods
27
+ def define_attributes(options = {}, &block)
28
+ options[:as] ||= :attributes
29
+
30
+ cattr_accessor options[:as].to_sym
31
+ class_eval(<<-EOS)
32
+ @@#{options[:as].to_s} ||= {}
33
+ EOS
34
+
35
+ self.define_attrio_new(options[:as])
36
+ self.define_attrio_inspect(options[:as]) unless options[:inspect] == false
37
+ self.define_attrio_reset(options[:as]) unless options[:reset] == false
38
+
39
+ Attrio::AttributesParser.new(self, options, &block)
40
+ end
41
+
42
+ def const_missing(name)
43
+ Attrio::AttributesParser.cast_type(name) || super
44
+ end
45
+ end
46
+
47
+ module Builders
48
+ autoload :ReaderBuilder, 'attrio/builders/reader_builder'
49
+ autoload :WriterBuilder, 'attrio/builders/writer_builder'
50
+ end
51
+
52
+ module Types
53
+ autoload :Base, 'attrio/types/base'
54
+ autoload :Boolean, 'attrio/types/boolean'
55
+ autoload :Date, 'attrio/types/date'
56
+ autoload :DateTime, 'attrio/types/date_time'
57
+ autoload :Float, 'attrio/types/float'
58
+ autoload :Integer, 'attrio/types/integer'
59
+ autoload :Symbol, 'attrio/types/symbol'
60
+ autoload :Time, 'attrio/types/time'
61
+ end
62
+ end
@@ -0,0 +1,68 @@
1
+ # encoding: utf-8
2
+
3
+ module Attrio
4
+ class Attribute
5
+ attr_reader :object, :name, :type, :options
6
+
7
+ def initialize(object, name, type, options)
8
+ @object = object; @name = name; @type = type; @options = options.symbolize_keys
9
+ end
10
+
11
+ def reader_method_name
12
+ @reader_method_name ||= self.accessor_name_from_options(:reader) || self.name
13
+ end
14
+
15
+ def writer_method_name
16
+ @writer_method_name ||= self.accessor_name_from_options(:writer) || "#{self.name}="
17
+ end
18
+
19
+ def reader_visibility
20
+ @reader_visibility ||= self.accessor_visibility_from_options(:reader) || :public
21
+ end
22
+
23
+ def writer_visibility
24
+ @writer_visibility ||= self.accessor_name_from_options(:writer) || :public
25
+ end
26
+
27
+ def instance_variable_name
28
+ @instance_variable_name ||= self.options[:instance_variable_name] || "@#{self.name}"
29
+ end
30
+
31
+ def default_value
32
+ self.options[:default] || self.options[:default_value]
33
+ end
34
+
35
+ def define_writer
36
+ Attrio::Builders::WriterBuilder.define(self.object, self.type,
37
+ self.options.merge({
38
+ :method_name => self.writer_method_name,
39
+ :method_visibility => self.writer_visibility,
40
+ :instance_variable_name => self.instance_variable_name
41
+ })
42
+ )
43
+ self
44
+ end
45
+
46
+ def define_reader
47
+ Attrio::Builders::ReaderBuilder.define(self.object, self.type,
48
+ self.options.merge({
49
+ :method_name => self.reader_method_name,
50
+ :method_visibility => self.reader_visibility,
51
+ :instance_variable_name => self.instance_variable_name
52
+ })
53
+ )
54
+ self
55
+ end
56
+
57
+ protected
58
+
59
+ def accessor_name_from_options(accessor)
60
+ (self.options[accessor.to_sym].is_a?(Hash) && self.options[accessor.to_sym][:name]) || self.options["#{accessor.to_s}_name".to_sym]
61
+ end
62
+
63
+ def accessor_visibility_from_options(accessor)
64
+ return self.options[accessor] if self.options[accessor].present? && [:public, :protected, :private].include?(self.options[accessor])
65
+ (self.options[accessor].is_a?(Hash) && self.options[accessor][:visibility]) || self.options["#{accessor.to_s}_visibility".to_sym]
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+
3
+ module Attrio
4
+ class AttributesParser
5
+ attr_reader :klass, :options
6
+
7
+ def initialize(object, options, &block)
8
+ @object = object
9
+ @options = options
10
+
11
+ raise ArgumentError.new('Missing options[:as] value' ) if @options[:as].blank?
12
+
13
+ self.instance_eval(&block)
14
+ end
15
+
16
+ def attr(*args)
17
+ attribute_options = args.extract_options!
18
+ attribute_name = args[0].to_s
19
+
20
+ type = self.class.cast_type(attribute_options.delete(:type) || args[1])
21
+ self.class.const_missing(attribute_options.delete(:type).to_s || args[1].to_s) if type.blank?
22
+
23
+ attribute = Attrio::Attribute.new(@object, attribute_name, type, attribute_options).define_writer.define_reader
24
+ self.add_attribute(attribute_name, attribute)
25
+ end
26
+
27
+ def self.cast_type(constant)
28
+ return constant if constant.is_a?(Class) && !!(constant < Attrio::Types::Base)
29
+
30
+ string = constant.to_s
31
+ string = string.camelize if (string =~ /\w_\w/ || string.chars.first.downcase == string[0])
32
+
33
+ begin
34
+ if Attrio::Types.const_defined?(string)
35
+ return Attrio::Types.const_get(string)
36
+ elsif Object.const_defined?(string)
37
+ return Object.const_get(string)
38
+ else
39
+ return nil
40
+ end
41
+ rescue
42
+ return constant
43
+ end
44
+ end
45
+
46
+ protected
47
+
48
+ def as
49
+ self.options[:as]
50
+ end
51
+
52
+ def add_attribute(name, attribute)
53
+ @object.send(self.as)[name.to_sym] = attribute
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ module Attrio
4
+ module Builders
5
+ class AccessorBuilder
6
+ def self.accessor
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def self.define(object, type, options)
11
+ self.define_accessor(object, type, options)
12
+ self.define_aliases(object, type, options)
13
+ end
14
+
15
+ def self.define_aliases(object, type, options)
16
+ if type.respond_to?("default_#{self.accessor.to_s}_aliases")
17
+ type.send("default_#{self.accessor.to_s}_aliases", options[:method_name]).each do |alias_method_name|
18
+ object.send(:alias_method, alias_method_name, options[:method_name])
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ require 'attrio/builders/accessor_builder'
4
+
5
+ module Attrio
6
+ module Builders
7
+ class ReaderBuilder < AccessorBuilder
8
+ def self.accessor
9
+ :reader
10
+ end
11
+
12
+ def self.define_accessor(object, type, options)
13
+ unless object.method_defined?(options[:method_name])
14
+ object.send :define_method, options[:method_name] do
15
+ self.instance_variable_get(options[:instance_variable_name])
16
+ end
17
+
18
+ object.send options[:method_visibility], options[:method_name]
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ require 'attrio/builders/accessor_builder'
4
+
5
+ module Attrio
6
+ module Builders
7
+ class WriterBuilder < AccessorBuilder
8
+ def self.accessor
9
+ :writer
10
+ end
11
+
12
+ def self.define_accessor(object, type, options)
13
+ unless object.method_defined?(options[:method_name])
14
+ object.send :define_method, options[:method_name] do |value|
15
+ value = type.respond_to?(:typecast) ? type._typecast(*[value, options]) : type.new(value) if !value.nil?
16
+ self.instance_variable_set(options[:instance_variable_name], value)
17
+ end
18
+
19
+ object.send options[:method_visibility], options[:method_name]
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ class Array # :nodoc:
4
+ def extract_options!
5
+ if last.is_a?(Hash) && last.extractable_options?
6
+ pop
7
+ else
8
+ {}
9
+ end
10
+ end unless method_defined?(:extract_options!)
11
+
12
+ class << self
13
+ def wrap(object)
14
+ if object.nil?
15
+ []
16
+ elsif object.respond_to?(:to_ary)
17
+ object.to_ary || [object]
18
+ else
19
+ [object]
20
+ end
21
+ end unless method_defined?(:wrap)
22
+ end
23
+ end
@@ -0,0 +1,55 @@
1
+ # encoding: utf-8
2
+
3
+ class Class
4
+ def cattr_reader(*syms)
5
+ options = syms.extract_options!
6
+ syms.each do |sym|
7
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
8
+ unless defined? @@#{sym}
9
+ @@#{sym} = nil
10
+ end
11
+
12
+ def self.#{sym}
13
+ @@#{sym}
14
+ end
15
+ EOS
16
+
17
+ unless options[:instance_reader] == false || options[:instance_accessor] == false
18
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
19
+ def #{sym}
20
+ @@#{sym}
21
+ end
22
+ EOS
23
+ end
24
+ end
25
+ end unless method_defined?(:cattr_reader)
26
+
27
+ def cattr_writer(*syms)
28
+ options = syms.extract_options!
29
+ syms.each do |sym|
30
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
31
+ unless defined? @@#{sym}
32
+ @@#{sym} = nil
33
+ end
34
+
35
+ def self.#{sym}=(obj)
36
+ @@#{sym} = obj
37
+ end
38
+ EOS
39
+
40
+ unless options[:instance_writer] == false || options[:instance_accessor] == false
41
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
42
+ def #{sym}=(obj)
43
+ @@#{sym} = obj
44
+ end
45
+ EOS
46
+ end
47
+ self.send("#{sym}=", yield) if block_given?
48
+ end
49
+ end unless method_defined?(:cattr_writer)
50
+
51
+ def cattr_accessor(*syms, &blk)
52
+ cattr_reader(*syms)
53
+ cattr_writer(*syms, &blk)
54
+ end unless method_defined?(:cattr_accessor)
55
+ end