Skip to content
This repository
  • 12 commits
  • 14 files changed
  • 1 comment
  • 1 contributor
7 Changelog.md
Source Rendered
... ... @@ -1,7 +1,6 @@
1   -# v0.3.0 to-be-released
2   -
3   -[Compare v0.2.0..master](https://github.com/solnic/virtus/compare/v0.2.0...master)
  1 +# v0.3.0 2012-02-25
4 2
  3 +* [feature] Support for default values from a symbol (which can be a method name) (solnic)
5 4 * [feature] Support for mass-assignment via custom setters not generated with attribute (fgrehm)
6 5 * [feature] Virtus::Coercion::String.to_constant handles namespaced names (dkubb)
7 6 * [feature] New coercion: Virtus::Coercion::Object.to_array (dkubb)
@@ -14,6 +13,8 @@
14 13 * [BREAKING CHANGE] Removed Attribute#instance_variable_name - this is a private ivar (solnic)
15 14 * [BREAKING CHANGE] Removed Equalizer#host_name and Equalizer#keys (solnic)
16 15
  16 +[Compare v0.2.0..v0.3.0](https://github.com/solnic/virtus/compare/v0.2.0...v0.3.0)
  17 +
17 18 # v0.2.0 2012-02-08
18 19
19 20 * [feature] Support for Value Objects (emmanuel)
17 README.md
Source Rendered
@@ -61,13 +61,25 @@ class Page
61 61 include Virtus
62 62
63 63 attribute :title, String
  64 +
  65 + # default from a singleton value (integer in this case)
64 66 attribute :views, Integer, :default => 0
  67 +
  68 + # default from a callable object (proc in this case)
65 69 attribute :slug, String, :default => lambda { |page, attribute| page.title.downcase.gsub(' ', '-') }
  70 +
  71 + # default from a method name as symbol
  72 + attribute :editor_title, String, :default => :default_editor_title
  73 +
  74 + def default_editor_title
  75 + published? ? title : "UNPUBLISHED: #{title}"
  76 + end
66 77 end
67 78
68   -page = Page.new(:title => 'Virtus Is Awesome')
  79 +page = Page.new(:title => 'Virtus Is Awesome', :editor_title => 'Virtus README')
69 80 page.slug # => 'virtus-is-awesome'
70 81 page.views # => 0
  82 +page.editor_title # => "UNPUBSLISHED: Virtus README"
71 83 ```
72 84
73 85 **Embedded Value**
@@ -258,10 +270,11 @@ Credits
258 270 * Dan Kubb ([dkubb](https://github.com/dkubb))
259 271 * Chris Corbyn ([d11wtq](https://github.com/d11wtq))
260 272 * Emmanuel Gomez ([emmanuel](https://github.com/emmanuel))
  273 +* Fabio Rehm ([fgrehm](https://github.com/fgrehm))
261 274 * Ryan Closner ([rclosner](https://github.com/rclosner))
  275 +* Markus Schirp ([mbj](https://github.com/mbj))
262 276 * Yves Senn ([senny](https://github.com/senny))
263 277
264   -
265 278 Contributing
266 279 -------------
267 280
19 TODO
... ... @@ -1,31 +1,36 @@
1 1 * Add missing specs:
2 2 * Add spec file spec/unit/virtus/attribute/collection/member_coercion/coerce_and_append_member_spec.rb for Virtus::Attribute::Collection::MemberCoercion#coerce_and_append_member
3   - * Add spec file spec/unit/virtus/attribute/collection/coerce_and_append_member_spec.rb for Virtus::Attribute::Collection#coerce_and_append_member
4 3 * Add spec file spec/unit/virtus/attribute/collection/member_type_spec.rb for Virtus::Attribute::Collection#member_type
5 4 * Add spec file spec/unit/virtus/attribute/collection/new_collection_spec.rb for Virtus::Attribute::Collection#new_collection
  5 + * Add spec file spec/unit/virtus/attribute/collection/coerce_and_append_member_spec.rb for Virtus::Attribute::Collection#coerce_and_append_member
  6 + * Add spec file spec/unit/virtus/attribute/default_value/from_symbol/class_methods/handle_spec.rb for Virtus::Attribute::DefaultValue::FromSymbol.handle?
  7 + * Add spec file spec/unit/virtus/attribute/default_value/from_symbol/evaluate_spec.rb for Virtus::Attribute::DefaultValue::FromSymbol#evaluate
  8 + * Add spec file spec/unit/virtus/attribute/default_value/from_callable/class_methods/handle_spec.rb for Virtus::Attribute::DefaultValue::FromCallable.handle?
  9 + * Add spec file spec/unit/virtus/attribute/default_value/from_callable/evaluate_spec.rb for Virtus::Attribute::DefaultValue::FromCallable#evaluate
  10 + * Add spec file spec/unit/virtus/attribute/default_value/from_clonable/class_methods/handle_spec.rb for Virtus::Attribute::DefaultValue::FromClonable.handle?
  11 + * Add spec file spec/unit/virtus/attribute/default_value/from_clonable/evaluate_spec.rb for Virtus::Attribute::DefaultValue::FromClonable#evaluate
  12 + * Add spec file spec/unit/virtus/attribute/default_value/class_methods/build_spec.rb for Virtus::Attribute::DefaultValue.build
6 13 * Add spec file spec/unit/virtus/coercion/string/class_methods/to_symbol_spec.rb for Virtus::Coercion::String.to_symbol
7 14 * Add spec file spec/unit/virtus/coercion/time/class_methods/to_integer_spec.rb for Virtus::Coercion::Time.to_integer
8   - * Add spec file spec/unit/virtus/coercion/time_coercions/to_time_spec.rb for Virtus::Coercion::TimeCoercions#to_time
9 15 * Add spec file spec/unit/virtus/coercion/time_coercions/to_datetime_spec.rb for Virtus::Coercion::TimeCoercions#to_datetime
  16 + * Add spec file spec/unit/virtus/coercion/time_coercions/to_time_spec.rb for Virtus::Coercion::TimeCoercions#to_time
10 17 * Add spec file spec/unit/virtus/coercion/time_coercions/to_date_spec.rb for Virtus::Coercion::TimeCoercions#to_date
11 18 * Add spec file spec/unit/virtus/coercion/time_coercions/to_string_spec.rb for Virtus::Coercion::TimeCoercions#to_string
12 19 * Add spec file spec/unit/virtus/coercion/array/class_methods/to_set_spec.rb for Virtus::Coercion::Array.to_set
13 20 * Add spec file spec/unit/virtus/coercion/decimal/class_methods/to_decimal_spec.rb for Virtus::Coercion::Decimal.to_decimal
14 21 * Add spec file spec/unit/virtus/coercion/float/class_methods/to_float_spec.rb for Virtus::Coercion::Float.to_float
15 22 * Add spec file spec/unit/virtus/coercion/integer/class_methods/to_integer_spec.rb for Virtus::Coercion::Integer.to_integer
  23 + * Add spec file spec/unit/virtus/coercion/numeric/class_methods/to_decimal_spec.rb for Virtus::Coercion::Numeric.to_decimal
16 24 * Add spec file spec/unit/virtus/coercion/numeric/class_methods/to_float_spec.rb for Virtus::Coercion::Numeric.to_float
17 25 * Add spec file spec/unit/virtus/coercion/numeric/class_methods/to_integer_spec.rb for Virtus::Coercion::Numeric.to_integer
18 26 * Add spec file spec/unit/virtus/coercion/numeric/class_methods/to_string_spec.rb for Virtus::Coercion::Numeric.to_string
19   - * Add spec file spec/unit/virtus/coercion/numeric/class_methods/to_decimal_spec.rb for Virtus::Coercion::Numeric.to_decimal
20 27 * Add spec file spec/unit/virtus/coercion/class_methods/element_reference_spec.rb for Virtus::Coercion.[]
21 28 * Add spec file spec/unit/virtus/coercion/class_methods/primitive_spec.rb for Virtus::Coercion.primitive
22 29 * Add spec file spec/unit/virtus/value_object/class_methods/attribute_spec.rb for Virtus::ValueObject::ClassMethods#attribute
23 30 * Add spec file spec/unit/virtus/value_object/instance_methods/with_spec.rb for Virtus::ValueObject::InstanceMethods#with
24   - * Add spec file spec/unit/virtus/value_object/equalizer/compile_spec.rb for Virtus::ValueObject::Equalizer#compile
25 31 * Add spec file spec/unit/virtus/value_object/equalizer/append_spec.rb for Virtus::ValueObject::Equalizer#<<
26   - * Add spec file spec/unit/virtus/value_object/equalizer/host_name_spec.rb for Virtus::ValueObject::Equalizer#host_name
27   - * Add spec file spec/unit/virtus/value_object/equalizer/keys_spec.rb for Virtus::ValueObject::Equalizer#keys
28   - * Add spec file spec/unit/virtus/attributes_accessor/define_writer_method_spec.rb for Virtus::AttributesAccessor#define_writer_method
  32 + * Add spec file spec/unit/virtus/value_object/equalizer/compile_spec.rb for Virtus::ValueObject::Equalizer#compile
29 33 * Add spec file spec/unit/virtus/attributes_accessor/define_reader_method_spec.rb for Virtus::AttributesAccessor#define_reader_method
  34 + * Add spec file spec/unit/virtus/attributes_accessor/define_writer_method_spec.rb for Virtus::AttributesAccessor#define_writer_method
30 35
31 36 * Make #to_time #to_date and #to_datetime work on Ruby 1.8.7 instead of typecasting to string and parsing the value
2  config/flay.yml
... ... @@ -1,3 +1,3 @@
1 1 ---
2 2 threshold: 19
3   -total_score: 342
  3 +total_score: 326
4 lib/virtus.rb
@@ -61,6 +61,10 @@ def self.included(descendant)
61 61 require 'virtus/coercion/symbol'
62 62
63 63 require 'virtus/attribute/default_value'
  64 +require 'virtus/attribute/default_value/from_clonable'
  65 +require 'virtus/attribute/default_value/from_callable'
  66 +require 'virtus/attribute/default_value/from_symbol'
  67 +
64 68 require 'virtus/attribute'
65 69 require 'virtus/attribute/object'
66 70 require 'virtus/attribute/class'
2  lib/virtus/attribute.rb
@@ -127,7 +127,7 @@ def initialize(name, options = {})
127 127 @instance_variable_name = "@#{@name}".to_sym
128 128 @primitive = @options.fetch(:primitive)
129 129 @coercion_method = @options.fetch(:coercion_method)
130   - @default = DefaultValue.new(self, @options[:default])
  130 + @default = DefaultValue.build(self, @options[:default])
131 131 initialize_visibility
132 132 end
133 133
60 lib/virtus/attribute/default_value.rb
@@ -2,9 +2,20 @@ module Virtus
2 2 class Attribute
3 3
4 4 # Class representing the default value option
  5 + #
  6 + # @api private
5 7 class DefaultValue
6   - SINGLETON_CLASSES = [ ::NilClass, ::TrueClass, ::FalseClass,
7   - ::Numeric, ::Symbol ].freeze
  8 + extend DescendantsTracker
  9 +
  10 + # Builds a default value instance
  11 + #
  12 + # @return [Virtus::Attribute::DefaultValue]
  13 + #
  14 + # @api private
  15 + def self.build(*args)
  16 + klass = descendants.detect { |descendant| descendant.handle?(*args) } || self
  17 + klass.new(*args)
  18 + end
8 19
9 20 # Returns the attribute associated with this default value instance
10 21 #
@@ -40,50 +51,9 @@ def initialize(attribute, value)
40 51 #
41 52 # @api private
42 53 def evaluate(instance)
43   - if callable?
44   - call(instance)
45   - elsif cloneable?
46   - value.clone
47   - else
48   - value
49   - end
50   - end
51   -
52   - private
53   -
54   - # Evaluates a proc value
55   - #
56   - # @param [Object]
57   - #
58   - # @return [Object] evaluated value
59   - #
60   - # @api private
61   - def call(instance)
62   - value.call(instance, attribute)
  54 + value
63 55 end
64   -
65   - # Returns if the value is callable
66   - #
67   - # @return [TrueClass,FalseClass]
68   - #
69   - # @api private
70   - def callable?
71   - value.respond_to?(:call)
72   - end
73   -
74   - # Returns whether or not the value is cloneable
75   - #
76   - # # return [TrueClass, FalseClass]
77   - #
78   - # @api private
79   - def cloneable?
80   - case value
81   - when *SINGLETON_CLASSES then false
82   - else
83   - true
84   - end
85   - end
86   -
87 56 end # class DefaultValue
  57 +
88 58 end # class Attribute
89 59 end # module Virtus
33 lib/virtus/attribute/default_value/from_callable.rb
... ... @@ -0,0 +1,33 @@
  1 +module Virtus
  2 + class Attribute
  3 + class DefaultValue
  4 +
  5 + # Represents default value evaluated via a callable object
  6 + #
  7 + # @api private
  8 + class FromCallable < DefaultValue
  9 +
  10 + # Return if the class can handle the value
  11 + #
  12 + # @return [Boolean]
  13 + #
  14 + # @api private
  15 + def self.handle?(attribute, value)
  16 + value.respond_to?(:call)
  17 + end
  18 +
  19 + # Evaluates the value via value#call
  20 + #
  21 + # @param [Object]
  22 + #
  23 + # @return [Object] evaluated value
  24 + #
  25 + # @api private
  26 + def evaluate(instance)
  27 + @value.call(instance, @attribute)
  28 + end
  29 +
  30 + end # class FromCallable
  31 + end # class DefaultValue
  32 + end # class Attribute
  33 +end # module Virtus
40 lib/virtus/attribute/default_value/from_clonable.rb
... ... @@ -0,0 +1,40 @@
  1 +module Virtus
  2 + class Attribute
  3 + class DefaultValue
  4 +
  5 + # Represents default value evaluated via a clonable object
  6 + #
  7 + # @api private
  8 + class FromClonable < DefaultValue
  9 + SINGLETON_CLASSES = [
  10 + ::NilClass, ::TrueClass, ::FalseClass, ::Numeric, ::Symbol ].freeze
  11 +
  12 + # Return if the class can handle the value
  13 + #
  14 + # @return [Boolean]
  15 + #
  16 + # @api private
  17 + def self.handle?(attribute, value)
  18 + case value
  19 + when *SINGLETON_CLASSES
  20 + false
  21 + else
  22 + true
  23 + end
  24 + end
  25 +
  26 + # Evaluates the value via value#clone
  27 + #
  28 + # @param [Object]
  29 + #
  30 + # @return [Object] evaluated value
  31 + #
  32 + # @api private
  33 + def evaluate(instance)
  34 + @value.clone
  35 + end
  36 +
  37 + end # class FromClonable
  38 + end # class DefaultValue
  39 + end # class Attribute
  40 +end # module Virtus
35 lib/virtus/attribute/default_value/from_symbol.rb
... ... @@ -0,0 +1,35 @@
  1 +module Virtus
  2 + class Attribute
  3 + class DefaultValue
  4 +
  5 + # Represents default value evaluated via a symbol
  6 + #
  7 + # @api private
  8 + class FromSymbol < DefaultValue
  9 +
  10 + # Return if the class can handle the value
  11 + #
  12 + # @return [Boolean]
  13 + #
  14 + # @api private
  15 + def self.handle?(attribute, value)
  16 + value.is_a?(::Symbol)
  17 + end
  18 +
  19 + # Evaluates the value via instance#__send__(value)
  20 + #
  21 + # Symbol value is returned if the instance doesn't respond to value
  22 + #
  23 + # @param [Object]
  24 + #
  25 + # @return [Object] evaluated value
  26 + #
  27 + # @api private
  28 + def evaluate(instance)
  29 + instance.respond_to?(@value) ? instance.__send__(@value) : @value
  30 + end
  31 +
  32 + end # class FromSymbol
  33 + end # class DefaultValue
  34 + end # class Attribute
  35 +end # module Virtus
18 spec/integration/default_values_spec.rb
@@ -7,14 +7,17 @@ module Examples
7 7 class Page
8 8 include Virtus
9 9
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, :accessor => :private, :default => false
14   - attribute :editor_title, String, :default => lambda { |post, attribute|
15   - post.published? ? post.title : "UNPUBLISHED: #{post.title}"
16   - }
  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
17 19 end
  20 +
18 21 end
19 22 end
20 23
@@ -34,7 +37,6 @@ class Page
34 37 end
35 38
36 39 specify 'you can set defaults for private attributes' do
37   - pending "can't call private methods in a proc for the default value"
38 40 subject.title = 'Top Secret'
39 41 subject.editor_title.should == 'UNPUBLISHED: Top Secret'
40 42 end
2  spec/unit/virtus/attribute/default_spec.rb
@@ -23,7 +23,7 @@
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
36 spec/unit/virtus/attribute/default_value/evaluate_spec.rb
@@ -3,17 +3,16 @@
3 3 describe Virtus::Attribute::DefaultValue, '#evaluate' do
4 4 subject { object.evaluate(instance) }
5 5
6   - let(:object) { described_class.new(attribute, value) }
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   - let(:response) { stub('response') }
  13 + before { value.stub(:call => response) }
13 14
14   - before do
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 @@
26 25 context 'when the value is cloneable' do
27 26 let(:clone) { stub('clone') }
28 27
29   - before do
30   - value.stub(:clone => clone)
31   - end
  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 @@
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
30 spec/unit/virtus/attribute/default_value/instance_methods/evaluate_spec.rb
... ... @@ -1,30 +0,0 @@
1   -require 'spec_helper'
2   -
3   -describe Virtus::Attribute::DefaultValue, '#evaluate' do
4   - subject { object.evaluate(instance) }
5   -
6   - let(:object) { described_class.new(attribute, value) }
7   - let(:attribute) { Virtus::Attribute::String.new(:title) }
8   - let(:instance) { Class.new }
9   -
10   - context 'with a non-callable value' do
11   - context 'with a non-cloneable value' do
12   - let(:value) { nil }
13   -
14   - it { should eql(value) }
15   - end
16   -
17   - context 'with a cloneable value' do
18   - let(:value) { 'something' }
19   -
20   - it { should eql(value) }
21   - it { should_not equal(value) }
22   - end
23   - end
24   -
25   - context 'with a callable value' do
26   - let(:value) { lambda { |instance, attribute| attribute.name } }
27   -
28   - it { should be(:title) }
29   - end
30   -end

Showing you all comments on commits in this comparison.

Dan Kubb
Collaborator

@solnic what do you think about doing value.respond_to?(:to_sym) here, and then doing instance.public_send(value.to_sym) in the .call method?

Something went wrong with that request. Please try again.