Permalink
Browse files

Deprecate Virtus::ValueObject in favor of new syntax

  * Use extracted Equalizer gem
  * Introduce values { .. } block for defining value object attributes
  * Deprecate including Virtus::ValueObject

There are two major reasons for doing that. First of all we wanted to
use extracted Equalizer which disallow mutating it once initialized and
ValueObject relies on that unfortunately. Secondly with new syntax you
can use customized Virtus modules via Virtus.module { } which would be
harder to achieve using previous Virtus::ValueObject inclusion.

Besides this feels more coherent as now you *always* just include Virtus
module
  • Loading branch information...
1 parent 2191b26 commit 6ea32edcd270d228e41459211bf43fc42997d96d @solnic committed Sep 16, 2013
Showing with 122 additions and 29 deletions.
  1. +2 −1 lib/virtus.rb
  2. +1 −1 lib/virtus/attribute.rb
  3. +19 −0 lib/virtus/extensions.rb
  4. +22 −27 lib/virtus/value_object.rb
  5. +77 −0 spec/unit/virtus/value_object_spec.rb
  6. +1 −0 virtus.gemspec
View
@@ -128,13 +128,14 @@ def self.configuration
require 'abstract_type'
require 'descendants_tracker'
+require 'equalizer'
require 'adamantium'
require 'axiom-types'
require 'coercible'
+require 'virtus/support/equalizer'
require 'virtus/support/options'
require 'virtus/support/type_lookup'
-require 'virtus/support/equalizer'
require 'virtus/extensions'
require 'virtus/const_missing_extensions'
View
@@ -3,7 +3,7 @@ module Virtus
class Attribute
extend DescendantsTracker, Options, TypeLookup
- include Equalizer.new(inspect) << :type << :options
+ include ::Equalizer.new(:type, :options)
accept_options :primitive, :accessor, :default, :lazy
View
@@ -54,6 +54,25 @@ def attribute(name, type, options = {})
self
end
+ # @api public
+ def values(&block)
+ instance_eval do
+ private :attributes=
+
+ def attribute(name, type, options = {})
+ super(name, type, options.merge(:writer => :private))
+ end
+ end
+
+ yield
+
+ extend(ValueObject::AllowedWriterMethods)
+ include(ValueObject::InstanceMethods)
+ include(::Equalizer.new(*attribute_set.map(&:name)))
+
+ self
+ end
+
# The list of writer methods that can be mass-assigned to in #attributes=
#
# @return [Set]
View
@@ -31,10 +31,13 @@ module ValueObject
#
# @api private
def self.included(base)
+ warn "Virtus::ValueObject is deprecated and will be removed in 1.0.0 #{caller.first}"
+
base.instance_eval do
include ::Virtus
include InstanceMethods
extend ClassMethods
+ extend AllowedWriterMethods
private :attributes=
end
end
@@ -43,18 +46,6 @@ def self.included(base)
module InstanceMethods
- # the #get_attributes method accept a Proc object that will filter
- # out an attribute when the block returns false. the ValueObject
- # needs all the attributes, so we allow every attribute.
- FILTER_NONE = proc { true }
-
- # @api private
- def with(attribute_updates)
- self.class.new(
- attribute_set.get(self, &FILTER_NONE).merge(attribute_updates)
- )
- end
-
# ValueObjects are immutable and can't be cloned
#
# They always represent the same value
@@ -73,6 +64,22 @@ def clone
end
+ module AllowedWriterMethods
+ # The list of writer methods that can be mass-assigned to in #attributes=
+ #
+ # @return [Set]
+ #
+ # @api private
+ def allowed_writer_methods
+ @allowed_writer_methods ||=
+ begin
+ allowed_writer_methods = super
+ allowed_writer_methods += attribute_set.map{|attr| "#{attr.name}="}
+ allowed_writer_methods.to_set.freeze
+ end
+ end
+ end
+
module ClassMethods
# Define an attribute on the receiver
@@ -114,26 +121,14 @@ def attribute(name, type, options = {})
def equalizer
@equalizer ||=
begin
- equalizer = Equalizer.new(name || inspect)
+ equalizer = Virtus::Equalizer.new(name || inspect)
include equalizer
equalizer
end
end
- # The list of writer methods that can be mass-assigned to in #attributes=
- #
- # @return [Set]
- #
- # @api private
- def allowed_writer_methods
- @allowed_writer_methods ||=
- begin
- allowed_writer_methods = super
- allowed_writer_methods += attribute_set.map{|attr| "#{attr.name}="}
- allowed_writer_methods.to_set.freeze
- end
- end
-
end # module ClassMethods
+
end # module ValueObject
+
end # module Virtus
@@ -0,0 +1,77 @@
+require 'spec_helper'
+
+describe Virtus::ValueObject do
+ subject { model.new(attributes) }
+
+ let(:attributes) { Hash[:id => 1, :name => 'Jane Doe'] }
+
+ share_examples_for 'a valid value object' do
+ its(:id) { should be(1) }
+ its(:name) { should eql('Jane Doe') }
+
+ it 'sets private writers' do
+ expect(model.attribute_set[:id]).to_not be_public_writer
+ expect(model.attribute_set[:name]).to_not be_public_writer
+ end
+
+ it 'disallows mass-assignment' do
+ expect(subject.private_methods).to include(:attributes=)
+ end
+
+ it 'disallows cloning' do
+ expect(subject.clone).to be(subject)
+ end
+
+ it 'defines #eql?' do
+ expect(subject).to eql(model.new(attributes))
+ end
+
+ it 'defines #==' do
+ expect(subject == model.new(attributes)).to be(true)
+ end
+
+ it 'defines #hash' do
+ expect(subject.hash).to eql(model.new(attributes).hash)
+ end
+
+ it 'defines #inspect' do
+ expect(subject.inspect).to eql('#<Model id=1 name="Jane Doe">')
+ end
+ end
+
+ context 'using new values {} block' do
+ let(:model) {
+ Class.new {
+ include Virtus
+
+ def self.name
+ 'Model'
+ end
+
+ values do
+ attribute :id, Integer
+ attribute :name, String
+ end
+ }
+ }
+
+ it_behaves_like 'a valid value object'
+ end
+
+ context 'using deprecated inclusion' do
+ let(:model) {
+ Class.new {
+ include Virtus::ValueObject
+
+ def self.name
+ 'Model'
+ end
+
+ attribute :id, Integer
+ attribute :name, String
+ }
+ }
+
+ it_behaves_like 'a valid value object'
+ end
+end
View
@@ -19,5 +19,6 @@ Gem::Specification.new do |gem|
gem.add_dependency('abstract_type', '~> 0.0.5')
gem.add_dependency('descendants_tracker', '~> 0.0.1')
gem.add_dependency('adamantium', '~> 0.1')
+ gem.add_dependency('equalizer', '~> 0.0.7')
gem.add_dependency('coercible', '~> 0.2')
end

0 comments on commit 6ea32ed

Please sign in to comment.