Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: solnic/virtus
base: b8d8960b4d
...
head fork: solnic/virtus
compare: 53f8edbd3e
  • 12 commits
  • 14 files changed
  • 1 commit comment
  • 1 contributor
View
7 Changelog.md
@@ -1,7 +1,6 @@
-# v0.3.0 to-be-released
-
-[Compare v0.2.0..master](https://github.com/solnic/virtus/compare/v0.2.0...master)
+# v0.3.0 2012-02-25
+* [feature] Support for default values from a symbol (which can be a method name) (solnic)
* [feature] Support for mass-assignment via custom setters not generated with attribute (fgrehm)
* [feature] Virtus::Coercion::String.to_constant handles namespaced names (dkubb)
* [feature] New coercion: Virtus::Coercion::Object.to_array (dkubb)
@@ -14,6 +13,8 @@
* [BREAKING CHANGE] Removed Attribute#instance_variable_name - this is a private ivar (solnic)
* [BREAKING CHANGE] Removed Equalizer#host_name and Equalizer#keys (solnic)
+[Compare v0.2.0..v0.3.0](https://github.com/solnic/virtus/compare/v0.2.0...v0.3.0)
+
# v0.2.0 2012-02-08
* [feature] Support for Value Objects (emmanuel)
View
17 README.md
@@ -61,13 +61,25 @@ class Page
include Virtus
attribute :title, String
+
+ # default from a singleton value (integer in this case)
attribute :views, Integer, :default => 0
+
+ # default from a callable object (proc in this case)
attribute :slug, String, :default => lambda { |page, attribute| page.title.downcase.gsub(' ', '-') }
+
+ # default from a method name as symbol
+ attribute :editor_title, String, :default => :default_editor_title
+
+ def default_editor_title
+ published? ? title : "UNPUBLISHED: #{title}"
+ end
end
-page = Page.new(:title => 'Virtus Is Awesome')
+page = Page.new(:title => 'Virtus Is Awesome', :editor_title => 'Virtus README')
page.slug # => 'virtus-is-awesome'
page.views # => 0
+page.editor_title # => "UNPUBSLISHED: Virtus README"
```
**Embedded Value**
@@ -258,10 +270,11 @@ Credits
* Dan Kubb ([dkubb](https://github.com/dkubb))
* Chris Corbyn ([d11wtq](https://github.com/d11wtq))
* Emmanuel Gomez ([emmanuel](https://github.com/emmanuel))
+* Fabio Rehm ([fgrehm](https://github.com/fgrehm))
* Ryan Closner ([rclosner](https://github.com/rclosner))
+* Markus Schirp ([mbj](https://github.com/mbj))
* Yves Senn ([senny](https://github.com/senny))
-
Contributing
-------------
View
19 TODO
@@ -1,31 +1,36 @@
* Add missing specs:
* 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
- * Add spec file spec/unit/virtus/attribute/collection/coerce_and_append_member_spec.rb for Virtus::Attribute::Collection#coerce_and_append_member
* Add spec file spec/unit/virtus/attribute/collection/member_type_spec.rb for Virtus::Attribute::Collection#member_type
* Add spec file spec/unit/virtus/attribute/collection/new_collection_spec.rb for Virtus::Attribute::Collection#new_collection
+ * Add spec file spec/unit/virtus/attribute/collection/coerce_and_append_member_spec.rb for Virtus::Attribute::Collection#coerce_and_append_member
+ * Add spec file spec/unit/virtus/attribute/default_value/from_symbol/class_methods/handle_spec.rb for Virtus::Attribute::DefaultValue::FromSymbol.handle?
+ * Add spec file spec/unit/virtus/attribute/default_value/from_symbol/evaluate_spec.rb for Virtus::Attribute::DefaultValue::FromSymbol#evaluate
+ * Add spec file spec/unit/virtus/attribute/default_value/from_callable/class_methods/handle_spec.rb for Virtus::Attribute::DefaultValue::FromCallable.handle?
+ * Add spec file spec/unit/virtus/attribute/default_value/from_callable/evaluate_spec.rb for Virtus::Attribute::DefaultValue::FromCallable#evaluate
+ * Add spec file spec/unit/virtus/attribute/default_value/from_clonable/class_methods/handle_spec.rb for Virtus::Attribute::DefaultValue::FromClonable.handle?
+ * Add spec file spec/unit/virtus/attribute/default_value/from_clonable/evaluate_spec.rb for Virtus::Attribute::DefaultValue::FromClonable#evaluate
+ * Add spec file spec/unit/virtus/attribute/default_value/class_methods/build_spec.rb for Virtus::Attribute::DefaultValue.build
* Add spec file spec/unit/virtus/coercion/string/class_methods/to_symbol_spec.rb for Virtus::Coercion::String.to_symbol
* Add spec file spec/unit/virtus/coercion/time/class_methods/to_integer_spec.rb for Virtus::Coercion::Time.to_integer
- * Add spec file spec/unit/virtus/coercion/time_coercions/to_time_spec.rb for Virtus::Coercion::TimeCoercions#to_time
* Add spec file spec/unit/virtus/coercion/time_coercions/to_datetime_spec.rb for Virtus::Coercion::TimeCoercions#to_datetime
+ * Add spec file spec/unit/virtus/coercion/time_coercions/to_time_spec.rb for Virtus::Coercion::TimeCoercions#to_time
* Add spec file spec/unit/virtus/coercion/time_coercions/to_date_spec.rb for Virtus::Coercion::TimeCoercions#to_date
* Add spec file spec/unit/virtus/coercion/time_coercions/to_string_spec.rb for Virtus::Coercion::TimeCoercions#to_string
* Add spec file spec/unit/virtus/coercion/array/class_methods/to_set_spec.rb for Virtus::Coercion::Array.to_set
* Add spec file spec/unit/virtus/coercion/decimal/class_methods/to_decimal_spec.rb for Virtus::Coercion::Decimal.to_decimal
* Add spec file spec/unit/virtus/coercion/float/class_methods/to_float_spec.rb for Virtus::Coercion::Float.to_float
* Add spec file spec/unit/virtus/coercion/integer/class_methods/to_integer_spec.rb for Virtus::Coercion::Integer.to_integer
+ * Add spec file spec/unit/virtus/coercion/numeric/class_methods/to_decimal_spec.rb for Virtus::Coercion::Numeric.to_decimal
* Add spec file spec/unit/virtus/coercion/numeric/class_methods/to_float_spec.rb for Virtus::Coercion::Numeric.to_float
* Add spec file spec/unit/virtus/coercion/numeric/class_methods/to_integer_spec.rb for Virtus::Coercion::Numeric.to_integer
* Add spec file spec/unit/virtus/coercion/numeric/class_methods/to_string_spec.rb for Virtus::Coercion::Numeric.to_string
- * Add spec file spec/unit/virtus/coercion/numeric/class_methods/to_decimal_spec.rb for Virtus::Coercion::Numeric.to_decimal
* Add spec file spec/unit/virtus/coercion/class_methods/element_reference_spec.rb for Virtus::Coercion.[]
* Add spec file spec/unit/virtus/coercion/class_methods/primitive_spec.rb for Virtus::Coercion.primitive
* Add spec file spec/unit/virtus/value_object/class_methods/attribute_spec.rb for Virtus::ValueObject::ClassMethods#attribute
* Add spec file spec/unit/virtus/value_object/instance_methods/with_spec.rb for Virtus::ValueObject::InstanceMethods#with
- * Add spec file spec/unit/virtus/value_object/equalizer/compile_spec.rb for Virtus::ValueObject::Equalizer#compile
* Add spec file spec/unit/virtus/value_object/equalizer/append_spec.rb for Virtus::ValueObject::Equalizer#<<
- * Add spec file spec/unit/virtus/value_object/equalizer/host_name_spec.rb for Virtus::ValueObject::Equalizer#host_name
- * Add spec file spec/unit/virtus/value_object/equalizer/keys_spec.rb for Virtus::ValueObject::Equalizer#keys
- * Add spec file spec/unit/virtus/attributes_accessor/define_writer_method_spec.rb for Virtus::AttributesAccessor#define_writer_method
+ * Add spec file spec/unit/virtus/value_object/equalizer/compile_spec.rb for Virtus::ValueObject::Equalizer#compile
* Add spec file spec/unit/virtus/attributes_accessor/define_reader_method_spec.rb for Virtus::AttributesAccessor#define_reader_method
+ * Add spec file spec/unit/virtus/attributes_accessor/define_writer_method_spec.rb for Virtus::AttributesAccessor#define_writer_method
* Make #to_time #to_date and #to_datetime work on Ruby 1.8.7 instead of typecasting to string and parsing the value
View
2  config/flay.yml
@@ -1,3 +1,3 @@
---
threshold: 19
-total_score: 342
+total_score: 326
View
4 lib/virtus.rb
@@ -61,6 +61,10 @@ def self.included(descendant)
require 'virtus/coercion/symbol'
require 'virtus/attribute/default_value'
+require 'virtus/attribute/default_value/from_clonable'
+require 'virtus/attribute/default_value/from_callable'
+require 'virtus/attribute/default_value/from_symbol'
+
require 'virtus/attribute'
require 'virtus/attribute/object'
require 'virtus/attribute/class'
View
2  lib/virtus/attribute.rb
@@ -127,7 +127,7 @@ def initialize(name, options = {})
@instance_variable_name = "@#{@name}".to_sym
@primitive = @options.fetch(:primitive)
@coercion_method = @options.fetch(:coercion_method)
- @default = DefaultValue.new(self, @options[:default])
+ @default = DefaultValue.build(self, @options[:default])
initialize_visibility
end
View
60 lib/virtus/attribute/default_value.rb
@@ -2,9 +2,20 @@ module Virtus
class Attribute
# Class representing the default value option
+ #
+ # @api private
class DefaultValue
- SINGLETON_CLASSES = [ ::NilClass, ::TrueClass, ::FalseClass,
- ::Numeric, ::Symbol ].freeze
+ extend DescendantsTracker
+
+ # Builds a default value instance
+ #
+ # @return [Virtus::Attribute::DefaultValue]
+ #
+ # @api private
+ def self.build(*args)
+ klass = descendants.detect { |descendant| descendant.handle?(*args) } || self
+ klass.new(*args)
+ end
# Returns the attribute associated with this default value instance
#
@@ -40,50 +51,9 @@ def initialize(attribute, value)
#
# @api private
def evaluate(instance)
- if callable?
- call(instance)
- elsif cloneable?
- value.clone
- else
- value
- end
- end
-
- private
-
- # Evaluates a proc value
- #
- # @param [Object]
- #
- # @return [Object] evaluated value
- #
- # @api private
- def call(instance)
- value.call(instance, attribute)
+ value
end
-
- # Returns if the value is callable
- #
- # @return [TrueClass,FalseClass]
- #
- # @api private
- def callable?
- value.respond_to?(:call)
- end
-
- # Returns whether or not the value is cloneable
- #
- # # return [TrueClass, FalseClass]
- #
- # @api private
- def cloneable?
- case value
- when *SINGLETON_CLASSES then false
- else
- true
- end
- end
-
end # class DefaultValue
+
end # class Attribute
end # module Virtus
View
33 lib/virtus/attribute/default_value/from_callable.rb
@@ -0,0 +1,33 @@
+module Virtus
+ class Attribute
+ class DefaultValue
+
+ # Represents default value evaluated via a callable object
+ #
+ # @api private
+ class FromCallable < DefaultValue
+
+ # Return if the class can handle the value
+ #
+ # @return [Boolean]
+ #
+ # @api private
+ def self.handle?(attribute, value)
+ value.respond_to?(:call)
+ end
+
+ # Evaluates the value via value#call
+ #
+ # @param [Object]
+ #
+ # @return [Object] evaluated value
+ #
+ # @api private
+ def evaluate(instance)
+ @value.call(instance, @attribute)
+ end
+
+ end # class FromCallable
+ end # class DefaultValue
+ end # class Attribute
+end # module Virtus
View
40 lib/virtus/attribute/default_value/from_clonable.rb
@@ -0,0 +1,40 @@
+module Virtus
+ class Attribute
+ class DefaultValue
+
+ # Represents default value evaluated via a clonable object
+ #
+ # @api private
+ class FromClonable < DefaultValue
+ SINGLETON_CLASSES = [
+ ::NilClass, ::TrueClass, ::FalseClass, ::Numeric, ::Symbol ].freeze
+
+ # Return if the class can handle the value
+ #
+ # @return [Boolean]
+ #
+ # @api private
+ def self.handle?(attribute, value)
+ case value
+ when *SINGLETON_CLASSES
+ false
+ else
+ true
+ end
+ end
+
+ # Evaluates the value via value#clone
+ #
+ # @param [Object]
+ #
+ # @return [Object] evaluated value
+ #
+ # @api private
+ def evaluate(instance)
+ @value.clone
+ end
+
+ end # class FromClonable
+ end # class DefaultValue
+ end # class Attribute
+end # module Virtus
View
35 lib/virtus/attribute/default_value/from_symbol.rb
@@ -0,0 +1,35 @@
+module Virtus
+ class Attribute
+ class DefaultValue
+
+ # Represents default value evaluated via a symbol
+ #
+ # @api private
+ class FromSymbol < DefaultValue
+
+ # Return if the class can handle the value
+ #
+ # @return [Boolean]
+ #
+ # @api private
+ def self.handle?(attribute, value)
+ value.is_a?(::Symbol)
+ end
+
+ # Evaluates the value via instance#__send__(value)
+ #
+ # Symbol value is returned if the instance doesn't respond to value
+ #
+ # @param [Object]
+ #
+ # @return [Object] evaluated value
+ #
+ # @api private
+ def evaluate(instance)
+ instance.respond_to?(@value) ? instance.__send__(@value) : @value
+ end
+
+ end # class FromSymbol
+ end # class DefaultValue
+ end # class Attribute
+end # module Virtus
View
18 spec/integration/default_values_spec.rb
@@ -7,14 +7,17 @@ module Examples
class Page
include Virtus
- attribute :title, String
- attribute :slug, String, :default => lambda { |post, attribute| post.title.downcase.gsub(' ', '-') }
- attribute :view_count, Integer, :default => 0
- attribute :published, Boolean, :accessor => :private, :default => false
- attribute :editor_title, String, :default => lambda { |post, attribute|
- post.published? ? post.title : "UNPUBLISHED: #{post.title}"
- }
+ attribute :title, String
+ attribute :slug, String, :default => lambda { |post, attribute| post.title.downcase.gsub(' ', '-') }
+ attribute :view_count, Integer, :default => 0
+ attribute :published, Boolean, :default => false, :accessor => :private
+ attribute :editor_title, String, :default => :default_editor_title
+
+ def default_editor_title
+ published? ? title : "UNPUBLISHED: #{title}"
+ end
end
+
end
end
@@ -34,7 +37,6 @@ class Page
end
specify 'you can set defaults for private attributes' do
- pending "can't call private methods in a proc for the default value"
subject.title = 'Top Secret'
subject.editor_title.should == 'UNPUBLISHED: Top Secret'
end
View
2  spec/unit/virtus/attribute/default_spec.rb
@@ -23,7 +23,7 @@
options.update(:default => default)
end
- it { should be_instance_of(Virtus::Attribute::DefaultValue) }
+ it { should be_instance_of(Virtus::Attribute::DefaultValue::FromClonable) }
its(:attribute) { should be(object) }
View
36 spec/unit/virtus/attribute/default_value/evaluate_spec.rb
@@ -3,17 +3,16 @@
describe Virtus::Attribute::DefaultValue, '#evaluate' do
subject { object.evaluate(instance) }
- let(:object) { described_class.new(attribute, value) }
- let(:attribute) { mock('attribute') }
- let(:value) { mock('value') }
- let(:instance) { mock('instance') }
+ let(:object) { described_class.build(attribute, value) }
+ let(:attribute) { mock('attribute') }
+ let(:value) { mock('value') }
+ let(:instance) { mock('instance') }
+ let(:response) { stub('response') }
context 'when the value is callable' do
- let(:response) { stub('response') }
+ before { value.stub(:call => response) }
- before do
- value.stub(:call => response)
- end
+ specify { object.should be_instance_of(Virtus::Attribute::DefaultValue::FromCallable) }
it { should be(response) }
@@ -26,9 +25,9 @@
context 'when the value is cloneable' do
let(:clone) { stub('clone') }
- before do
- value.stub(:clone => clone)
- end
+ before { value.stub(:clone => clone) }
+
+ specify { object.should be_instance_of(Virtus::Attribute::DefaultValue::FromClonable) }
it { should be(clone) }
@@ -38,6 +37,21 @@
end
end
+ context 'when the value is a method name' do
+ let(:instance) { mock('instance', value => retval) }
+ let(:value) { :set_default }
+ let(:retval) { stub('retval') }
+
+ specify { object.should be_instance_of(Virtus::Attribute::DefaultValue::FromSymbol) }
+
+ it { should be(retval) }
+
+ it 'calls the method' do
+ instance.should_receive(value).with(no_args)
+ subject
+ end
+ end
+
# smallest number that is Bignum across major ruby impls
bignum = 0x7fffffffffffffff + 1
View
30 spec/unit/virtus/attribute/default_value/instance_methods/evaluate_spec.rb
@@ -1,30 +0,0 @@
-require 'spec_helper'
-
-describe Virtus::Attribute::DefaultValue, '#evaluate' do
- subject { object.evaluate(instance) }
-
- let(:object) { described_class.new(attribute, value) }
- let(:attribute) { Virtus::Attribute::String.new(:title) }
- let(:instance) { Class.new }
-
- context 'with a non-callable value' do
- context 'with a non-cloneable value' do
- let(:value) { nil }
-
- it { should eql(value) }
- end
-
- context 'with a cloneable value' do
- let(:value) { 'something' }
-
- it { should eql(value) }
- it { should_not equal(value) }
- end
- end
-
- context 'with a callable value' do
- let(:value) { lambda { |instance, attribute| attribute.name } }
-
- it { should be(:title) }
- end
-end

Showing you all comments on commits in this comparison.

@dkubb
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.