virtus 0.2.0 → 0.3.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/.pelusa.yml +7 -0
- data/.travis.yml +5 -3
- data/Changelog.md +17 -0
- data/Gemfile +4 -0
- data/README.md +35 -39
- data/TODO +12 -7
- data/config/flay.yml +1 -1
- data/config/flog.yml +1 -1
- data/lib/virtus.rb +8 -0
- data/lib/virtus/attribute.rb +8 -29
- data/lib/virtus/attribute/boolean.rb +1 -1
- data/lib/virtus/attribute/default_value.rb +15 -45
- data/lib/virtus/attribute/default_value/from_callable.rb +33 -0
- data/lib/virtus/attribute/default_value/from_clonable.rb +40 -0
- data/lib/virtus/attribute/default_value/from_symbol.rb +35 -0
- data/lib/virtus/attribute/embedded_value.rb +3 -14
- data/lib/virtus/class_methods.rb +17 -0
- data/lib/virtus/coercion/hash.rb +0 -11
- data/lib/virtus/coercion/object.rb +98 -0
- data/lib/virtus/coercion/string.rb +9 -2
- data/lib/virtus/coercion/time_coercions.rb +2 -5
- data/lib/virtus/instance_methods.rb +16 -37
- data/lib/virtus/support/type_lookup.rb +1 -2
- data/lib/virtus/value_object.rb +31 -8
- data/lib/virtus/value_object/equalizer.rb +21 -26
- data/lib/virtus/version.rb +1 -1
- data/spec/integration/custom_attributes_spec.rb +1 -1
- data/spec/integration/default_values_spec.rb +15 -3
- data/spec/integration/defining_attributes_spec.rb +1 -1
- data/spec/integration/mass_assignment_with_accessors_spec.rb +44 -0
- data/spec/integration/virtus/value_object_spec.rb +2 -2
- data/spec/spec_helper.rb +8 -1
- data/spec/unit/virtus/attribute/class_methods/determine_type_spec.rb +1 -1
- data/spec/unit/virtus/attribute/default_spec.rb +1 -1
- data/spec/unit/virtus/attribute/default_value/evaluate_spec.rb +25 -11
- data/spec/unit/virtus/attribute/embedded_value/class_methods/merge_options_spec.rb +1 -1
- data/spec/unit/virtus/attribute/embedded_value/coerce_spec.rb +1 -1
- data/spec/unit/virtus/class_methods/allowed_writer_methods_spec.rb +25 -0
- data/spec/unit/virtus/coercion/object/class_methods/to_array_spec.rb +51 -0
- data/spec/unit/virtus/coercion/object/class_methods/to_hash_spec.rb +22 -0
- data/spec/unit/virtus/coercion/object/class_methods/to_integer_spec.rb +22 -0
- data/spec/unit/virtus/coercion/object/class_methods/to_string_spec.rb +22 -0
- data/spec/unit/virtus/coercion/string/class_methods/to_constant_spec.rb +37 -1
- data/spec/unit/virtus/instance_methods/attributes_spec.rb +14 -2
- data/spec/unit/virtus/value_object/class_methods/allowed_writer_methods_spec.rb +15 -0
- data/spec/unit/virtus/value_object/class_methods/equalizer_spec.rb +1 -1
- data/spec/unit/virtus/value_object/initialize_spec.rb +1 -1
- data/spec/unit/virtus/value_object/with_spec.rb +1 -1
- metadata +17 -10
- data/spec/unit/virtus/attribute/default_value/instance_methods/evaluate_spec.rb +0 -30
- data/spec/unit/virtus/attribute/instance_variable_name_spec.rb +0 -12
- data/spec/unit/virtus/attribute/reader_visibility_spec.rb +0 -24
- data/spec/unit/virtus/attribute/writer_visibility_spec.rb +0 -24
- data/spec/unit/virtus/coercion/hash/class_methods/to_array_spec.rb +0 -12
@@ -1,20 +1,8 @@
|
|
1
1
|
module Virtus
|
2
2
|
module ValueObject
|
3
|
+
|
3
4
|
# A type of Module for dynamically defining and hosting equality methods
|
4
5
|
class Equalizer < Module
|
5
|
-
# Name of hosting Class or Module that will be used for #inspect
|
6
|
-
#
|
7
|
-
# @return [String]
|
8
|
-
#
|
9
|
-
# @api private
|
10
|
-
attr_reader :host_name
|
11
|
-
|
12
|
-
# List of methods that will be used to define equality methods
|
13
|
-
#
|
14
|
-
# @return [Array(Symbol)]
|
15
|
-
#
|
16
|
-
# @api private
|
17
|
-
attr_reader :keys
|
18
6
|
|
19
7
|
# Initialize an Equalizer with the given keys
|
20
8
|
#
|
@@ -23,8 +11,7 @@ module Virtus
|
|
23
11
|
#
|
24
12
|
# @api private
|
25
13
|
def initialize(host_name, keys = [])
|
26
|
-
@host_name = host_name
|
27
|
-
@keys = keys
|
14
|
+
@host_name, @keys = host_name, keys
|
28
15
|
end
|
29
16
|
|
30
17
|
# Append a key and compile the equality methods
|
@@ -35,8 +22,6 @@ module Virtus
|
|
35
22
|
def <<(key)
|
36
23
|
@keys << key
|
37
24
|
compile
|
38
|
-
|
39
|
-
self
|
40
25
|
end
|
41
26
|
|
42
27
|
# Compile the equalizer methods based on #keys
|
@@ -49,7 +34,6 @@ module Virtus
|
|
49
34
|
define_eql_method
|
50
35
|
define_equivalent_method
|
51
36
|
define_hash_method
|
52
|
-
|
53
37
|
self
|
54
38
|
end
|
55
39
|
|
@@ -57,20 +41,20 @@ module Virtus
|
|
57
41
|
|
58
42
|
# Define an inspect method that reports the values of the instance's keys
|
59
43
|
#
|
60
|
-
# @return [
|
44
|
+
# @return [undefined]
|
61
45
|
#
|
62
46
|
# @api private
|
63
47
|
def define_inspect_method
|
64
48
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
65
49
|
def inspect
|
66
|
-
"#<#{host_name} #{
|
50
|
+
"#<#{@host_name} #{compile_keys { |key| "#{key}=\#{#{key}.inspect}" }}>"
|
67
51
|
end
|
68
52
|
RUBY
|
69
53
|
end
|
70
54
|
|
71
55
|
# Define an #eql? method based on the instance's values identified by #keys
|
72
56
|
#
|
73
|
-
# @return [
|
57
|
+
# @return [undefined]
|
74
58
|
#
|
75
59
|
# @api private
|
76
60
|
def define_eql_method
|
@@ -78,14 +62,14 @@ module Virtus
|
|
78
62
|
def eql?(other)
|
79
63
|
return true if equal?(other)
|
80
64
|
instance_of?(other.class) &&
|
81
|
-
#{
|
65
|
+
#{compile_keys(' && ') { |key| "#{key}.eql?(other.#{key})" }}
|
82
66
|
end
|
83
67
|
RUBY
|
84
68
|
end
|
85
69
|
|
86
70
|
# Define an #== method based on the instance's values identified by #keys
|
87
71
|
#
|
88
|
-
# @return [
|
72
|
+
# @return [undefined]
|
89
73
|
#
|
90
74
|
# @api private
|
91
75
|
def define_equivalent_method
|
@@ -101,29 +85,40 @@ module Virtus
|
|
101
85
|
|
102
86
|
# Define a #hash method based on the instance's values identified by #keys
|
103
87
|
#
|
104
|
-
# @return [
|
88
|
+
# @return [undefined]
|
105
89
|
#
|
106
90
|
# @api private
|
107
91
|
def define_hash_method
|
108
92
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
109
93
|
def hash
|
110
|
-
self.class.hash ^ #{
|
94
|
+
self.class.hash ^ #{compile_keys(' ^ ') { |key| "#{key}.hash" }}
|
111
95
|
end
|
112
96
|
RUBY
|
113
97
|
end
|
114
98
|
|
99
|
+
# Return a list of strings containing ruby code for method generation
|
100
|
+
#
|
101
|
+
# @return [Array(Array<String>, Array<String>)]
|
102
|
+
#
|
115
103
|
# @api private
|
116
104
|
def compile_strings_for_equivalent_method
|
117
105
|
respond_to = []
|
118
106
|
equivalent = []
|
119
107
|
|
120
|
-
keys.each do |key|
|
108
|
+
@keys.each do |key|
|
121
109
|
respond_to << "other.respond_to?(#{key.inspect})"
|
122
110
|
equivalent << "#{key} == other.#{key}"
|
123
111
|
end
|
124
112
|
|
125
113
|
[ respond_to, equivalent ]
|
126
114
|
end
|
115
|
+
|
116
|
+
# @api private
|
117
|
+
def compile_keys(separator = ' ', &block)
|
118
|
+
keys_map = @keys.map { |key| yield(key) }
|
119
|
+
keys_map.join(separator)
|
120
|
+
end
|
121
|
+
|
127
122
|
end # class Equalizer
|
128
123
|
end # module ValueObject
|
129
124
|
end # module Virtus
|
data/lib/virtus/version.rb
CHANGED
@@ -7,10 +7,17 @@ describe "default values" do
|
|
7
7
|
class Page
|
8
8
|
include Virtus
|
9
9
|
|
10
|
-
attribute :title,
|
11
|
-
attribute :slug,
|
12
|
-
attribute :view_count,
|
10
|
+
attribute :title, String
|
11
|
+
attribute :slug, String, :default => lambda { |post, attribute| post.title.downcase.gsub(' ', '-') }
|
12
|
+
attribute :view_count, Integer, :default => 0
|
13
|
+
attribute :published, Boolean, :default => false, :accessor => :private
|
14
|
+
attribute :editor_title, String, :default => :default_editor_title
|
15
|
+
|
16
|
+
def default_editor_title
|
17
|
+
published? ? title : "UNPUBLISHED: #{title}"
|
18
|
+
end
|
13
19
|
end
|
20
|
+
|
14
21
|
end
|
15
22
|
end
|
16
23
|
|
@@ -29,4 +36,9 @@ describe "default values" do
|
|
29
36
|
subject.slug.should == 'example-blog-post'
|
30
37
|
end
|
31
38
|
|
39
|
+
specify 'you can set defaults for private attributes' do
|
40
|
+
subject.title = 'Top Secret'
|
41
|
+
subject.editor_title.should == 'UNPUBLISHED: Top Secret'
|
42
|
+
end
|
43
|
+
|
32
44
|
end
|
@@ -52,7 +52,7 @@ describe "virtus attribute definitions" do
|
|
52
52
|
end
|
53
53
|
|
54
54
|
context 'inheritance' do
|
55
|
-
specify 'inherits all the
|
55
|
+
specify 'inherits all the attributes from the base class' do
|
56
56
|
fred = Examples::Manager.new(:name => 'Fred', :age => 29)
|
57
57
|
fred.name.should == 'Fred'
|
58
58
|
fred.age.should == 29
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "mass assignment with accessors" do
|
4
|
+
|
5
|
+
before do
|
6
|
+
module Examples
|
7
|
+
class Product
|
8
|
+
include Virtus
|
9
|
+
|
10
|
+
attribute :id, Integer
|
11
|
+
attribute :category, String
|
12
|
+
attribute :subcategory, String
|
13
|
+
|
14
|
+
def categories=(categories)
|
15
|
+
self.category = categories.first
|
16
|
+
self.subcategory = categories.last
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def _id=(value)
|
22
|
+
self.id = value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
subject { Examples::Product.new(:categories => ['Office', 'Printers'], :_id => 100) }
|
29
|
+
|
30
|
+
specify 'works uppon instantiation' do
|
31
|
+
subject.category.should == 'Office'
|
32
|
+
subject.subcategory.should == 'Printers'
|
33
|
+
end
|
34
|
+
|
35
|
+
specify 'can be set with #attributes=' do
|
36
|
+
subject.attributes = {:categories => ['Home', 'Furniture']}
|
37
|
+
subject.category.should == 'Home'
|
38
|
+
subject.subcategory.should == 'Furniture'
|
39
|
+
end
|
40
|
+
|
41
|
+
specify 'respects accessor visibility' do
|
42
|
+
subject.id.should_not == 100
|
43
|
+
end
|
44
|
+
end
|
@@ -29,8 +29,8 @@ describe Virtus::ValueObject do
|
|
29
29
|
|
30
30
|
describe 'writer visibility' do
|
31
31
|
it 'attributes are configured for private writers' do
|
32
|
-
class_under_test.attributes[:latitude].
|
33
|
-
class_under_test.attributes[:longitude].
|
32
|
+
class_under_test.attributes[:latitude].public_reader?.should be(true)
|
33
|
+
class_under_test.attributes[:longitude].public_writer?.should be(false)
|
34
34
|
end
|
35
35
|
|
36
36
|
it 'writer methods are set to private' do
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
require 'backports'
|
2
1
|
require 'rubygems'
|
2
|
+
require 'backports'
|
3
3
|
|
4
4
|
begin
|
5
5
|
require 'rspec' # try for RSpec 2
|
@@ -38,3 +38,10 @@ RSpec.configure do |config|
|
|
38
38
|
end
|
39
39
|
|
40
40
|
end
|
41
|
+
|
42
|
+
# change the heckle timeout to be 5 seconds
|
43
|
+
if defined?(::Heckle)
|
44
|
+
class ::Heckle
|
45
|
+
@@timeout = 5
|
46
|
+
end
|
47
|
+
end
|
@@ -17,7 +17,7 @@ describe Virtus::Attribute, '.determine_type' do
|
|
17
17
|
subject { object.determine_type(primitive) }
|
18
18
|
|
19
19
|
before do
|
20
|
-
if [Virtus::Attribute::
|
20
|
+
if [Virtus::Attribute::Collection].include? attribute_class
|
21
21
|
pending
|
22
22
|
end
|
23
23
|
end
|
@@ -23,7 +23,7 @@ describe Virtus::Attribute, '#default' do
|
|
23
23
|
options.update(:default => default)
|
24
24
|
end
|
25
25
|
|
26
|
-
it { should be_instance_of(Virtus::Attribute::DefaultValue) }
|
26
|
+
it { should be_instance_of(Virtus::Attribute::DefaultValue::FromClonable) }
|
27
27
|
|
28
28
|
its(:attribute) { should be(object) }
|
29
29
|
|
@@ -3,17 +3,16 @@ require 'spec_helper'
|
|
3
3
|
describe Virtus::Attribute::DefaultValue, '#evaluate' do
|
4
4
|
subject { object.evaluate(instance) }
|
5
5
|
|
6
|
-
let(:object) { described_class.
|
7
|
-
let(:attribute) { mock('attribute')
|
8
|
-
let(:value) { mock('value')
|
9
|
-
let(:instance) { mock('instance')
|
6
|
+
let(:object) { described_class.build(attribute, value) }
|
7
|
+
let(:attribute) { mock('attribute') }
|
8
|
+
let(:value) { mock('value') }
|
9
|
+
let(:instance) { mock('instance') }
|
10
|
+
let(:response) { stub('response') }
|
10
11
|
|
11
12
|
context 'when the value is callable' do
|
12
|
-
|
13
|
+
before { value.stub(:call => response) }
|
13
14
|
|
14
|
-
|
15
|
-
value.stub(:call => response)
|
16
|
-
end
|
15
|
+
specify { object.should be_instance_of(Virtus::Attribute::DefaultValue::FromCallable) }
|
17
16
|
|
18
17
|
it { should be(response) }
|
19
18
|
|
@@ -26,9 +25,9 @@ describe Virtus::Attribute::DefaultValue, '#evaluate' do
|
|
26
25
|
context 'when the value is cloneable' do
|
27
26
|
let(:clone) { stub('clone') }
|
28
27
|
|
29
|
-
before
|
30
|
-
|
31
|
-
|
28
|
+
before { value.stub(:clone => clone) }
|
29
|
+
|
30
|
+
specify { object.should be_instance_of(Virtus::Attribute::DefaultValue::FromClonable) }
|
32
31
|
|
33
32
|
it { should be(clone) }
|
34
33
|
|
@@ -38,6 +37,21 @@ describe Virtus::Attribute::DefaultValue, '#evaluate' do
|
|
38
37
|
end
|
39
38
|
end
|
40
39
|
|
40
|
+
context 'when the value is a method name' do
|
41
|
+
let(:instance) { mock('instance', value => retval) }
|
42
|
+
let(:value) { :set_default }
|
43
|
+
let(:retval) { stub('retval') }
|
44
|
+
|
45
|
+
specify { object.should be_instance_of(Virtus::Attribute::DefaultValue::FromSymbol) }
|
46
|
+
|
47
|
+
it { should be(retval) }
|
48
|
+
|
49
|
+
it 'calls the method' do
|
50
|
+
instance.should_receive(value).with(no_args)
|
51
|
+
subject
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
41
55
|
# smallest number that is Bignum across major ruby impls
|
42
56
|
bignum = 0x7fffffffffffffff + 1
|
43
57
|
|
@@ -11,7 +11,7 @@ describe Virtus::Attribute::EmbeddedValue, '#coerce' do
|
|
11
11
|
let(:value) { Hash[:foo => 'bar'] }
|
12
12
|
|
13
13
|
context 'when the options include the model' do
|
14
|
-
let(:object) { described_class.new(attribute_name, :
|
14
|
+
let(:object) { described_class.new(attribute_name, :primitive => model) }
|
15
15
|
let(:model) { mock('model') }
|
16
16
|
let(:model_instance) { mock('model_instance') }
|
17
17
|
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::ClassMethods, '#allowed_writer_methods' do
|
4
|
+
subject { object.allowed_writer_methods }
|
5
|
+
|
6
|
+
let(:object) do
|
7
|
+
Class.new do
|
8
|
+
include Virtus
|
9
|
+
attribute :virtus_attribute, String
|
10
|
+
attr_accessor :some_other_attribute
|
11
|
+
private
|
12
|
+
attr_accessor :private_attribute
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it { should include('virtus_attribute=') }
|
17
|
+
it { should include('some_other_attribute=') }
|
18
|
+
it { should_not include('private_attribute=') }
|
19
|
+
|
20
|
+
%w[ == != === []= attributes= ].each do |invalid_method|
|
21
|
+
it { should_not include(invalid_method) }
|
22
|
+
end
|
23
|
+
|
24
|
+
it { should be_frozen }
|
25
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::Coercion::Object, '.to_array' do
|
4
|
+
subject { object.to_array(value) }
|
5
|
+
|
6
|
+
let(:object) { described_class }
|
7
|
+
let(:value) { Object.new }
|
8
|
+
let(:coerced) { [ value ] }
|
9
|
+
|
10
|
+
context 'when the value responds to #to_ary' do
|
11
|
+
before do
|
12
|
+
value.should_receive(:to_ary).with().and_return(coerced)
|
13
|
+
end
|
14
|
+
|
15
|
+
it { should be(coerced) }
|
16
|
+
|
17
|
+
it 'does not call #to_a if #to_ary is available' do
|
18
|
+
value.should_not_receive(:to_a)
|
19
|
+
subject
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'when the value responds to #to_a but not #to_ary' do
|
24
|
+
before do
|
25
|
+
value.should_receive(:to_a).with().and_return(coerced)
|
26
|
+
end
|
27
|
+
|
28
|
+
it { should be(coerced) }
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'when the value does not respond to #to_ary or #to_a' do
|
32
|
+
it { should be_instance_of(Array) }
|
33
|
+
|
34
|
+
it { should == coerced }
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when the value returns nil from #to_ary' do
|
38
|
+
before do
|
39
|
+
value.should_receive(:to_ary).with().and_return(nil)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'calls #to_a as a fallback' do
|
43
|
+
value.should_receive(:to_a).with().and_return(coerced)
|
44
|
+
should be(coerced)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'wraps the value in an Array if #to_a is not available' do
|
48
|
+
should == coerced
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::Coercion::Object, '.to_hash' do
|
4
|
+
subject { object.to_hash(value) }
|
5
|
+
|
6
|
+
let(:object) { described_class }
|
7
|
+
let(:value) { stub('value') }
|
8
|
+
|
9
|
+
context 'when the value responds to #to_hash' do
|
10
|
+
let(:coerced) { stub('coerced') }
|
11
|
+
|
12
|
+
before do
|
13
|
+
value.should_receive(:to_hash).with().and_return(coerced)
|
14
|
+
end
|
15
|
+
|
16
|
+
it { should be(coerced) }
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when the value does not respond to #to_hash' do
|
20
|
+
it { should be(value) }
|
21
|
+
end
|
22
|
+
end
|