Git Product home page Git Product logo

virtus's Introduction

DISCONTINUED

Working on virtus taught me a lot about handling data in Ruby, which involves coercions, type safety and validation (amongst other things). Even though the project has been successful, and serving well for many people, I decided to build something better. As a result, dry-types, dry-struct and dry-schema were born. These projects should be considered as virtus' successors, with better separation of concerns and better features. If you're interested in a modern take on same problems that virtus tried to solve, please check out these projects!

@solnic

Virtus

Gem Version Build Status Code Climate Test Coverage Inline docs

Virtus allows you to define attributes on classes, modules or class instances with optional information about types, reader/writer method visibility and coercion behavior. It supports a lot of coercions and advanced mapping of embedded objects and collections.

You can use it in many different contexts like:

  • Input parameter sanitization and coercion in web applications
  • Mapping JSON to domain objects
  • Encapsulating data-access in Value Objects
  • Domain model prototyping

And probably more.

Installation

$ gem install virtus

or in your Gemfile

gem 'virtus'

Examples

Using Virtus with Classes

You can create classes extended with Virtus and define attributes:

class User
  include Virtus.model

  attribute :name, String
  attribute :age, Integer
  attribute :birthday, DateTime
end

user = User.new(:name => 'Piotr', :age => 31)
user.attributes # => { :name => "Piotr", :age => 31, :birthday => nil }

user.name # => "Piotr"

user.age = '31' # => 31
user.age.class # => Fixnum

user.birthday = 'November 18th, 1983' # => #<DateTime: 1983-11-18T00:00:00+00:00 (4891313/2,0/1,2299161)>

# mass-assignment
user.attributes = { :name => 'Jane', :age => 21 }
user.name # => "Jane"
user.age  # => 21

Cherry-picking extensions

# include attribute DSL + constructor + mass-assignment
class User
  include Virtus.model

  attribute :name, String
end

user = User.new(:name => 'Piotr')
user.attributes = { :name => 'John' }
user.attributes
# => {:name => 'John'}

# include attribute DSL + constructor
class User
  include Virtus.model(:mass_assignment => false)

  attribute :name, String
end

User.new(:name => 'Piotr')

# include just the attribute DSL
class User
  include Virtus.model(:constructor => false, :mass_assignment => false)

  attribute :name, String
end

user = User.new
user.name = 'Piotr'

Using Virtus with Modules

You can create modules extended with Virtus and define attributes for later inclusion in your classes:

module Name
  include Virtus.module

  attribute :name, String
end

module Age
  include Virtus.module(:coerce => false)

  attribute :age, Integer
end

class User
  include Name, Age
end

user = User.new(:name => 'John', :age => 30)

Dynamically Extending Instances

It's also possible to dynamically extend an object with Virtus:

class User
  # nothing here
end

user = User.new
user.extend(Virtus.model)
user.attribute :name, String
user.name = 'John'
user.name # => 'John'

Default Values

class Page
  include Virtus.model

  attribute :title, String

  # default from a singleton value (integer in this case)
  attribute :views, Integer, :default => 0

  # default from a singleton value (boolean in this case)
  attribute :published, Boolean, :default => false

  # 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 README')
page.slug         # => 'virtus-readme'
page.views        # => 0
page.published    # => false
page.editor_title # => "UNPUBLISHED: Virtus README"

page.views = 10
page.views                    # => 10
page.reset_attribute(:views)  # => 0
page.views                    # => 0

Default values on dynamically extended instances

This requires you to set :lazy option because default values are set in the constructor if it's set to false (which is the default setting):

User = Class.new
user = User.new
user.extend(Virtus.model)
user.attribute :name, String, default: 'jane', lazy: true
user.name # => "jane"

Embedded Value

class City
  include Virtus.model

  attribute :name, String
end

class Address
  include Virtus.model

  attribute :street,  String
  attribute :zipcode, String
  attribute :city,    City
end

class User
  include Virtus.model

  attribute :name,    String
  attribute :address, Address
end

user = User.new(:address => {
  :street => 'Street 1/2', :zipcode => '12345', :city => { :name => 'NYC' } })

user.address.street # => "Street 1/2"
user.address.city.name # => "NYC"

Collection Member Coercions

# Support "primitive" classes
class Book
  include Virtus.model

  attribute :page_numbers, Array[Integer]
end

book = Book.new(:page_numbers => %w[1 2 3])
book.page_numbers # => [1, 2, 3]

# Support EmbeddedValues, too!
class Address
  include Virtus.model

  attribute :address,     String
  attribute :locality,    String
  attribute :region,      String
  attribute :postal_code, String
end

class PhoneNumber
  include Virtus.model

  attribute :number, String
end

class User
  include Virtus.model

  attribute :phone_numbers, Array[PhoneNumber]
  attribute :addresses,     Set[Address]
end

user = User.new(
  :phone_numbers => [
    { :number => '212-555-1212' },
    { :number => '919-444-3265' } ],
  :addresses => [
    { :address => '1234 Any St.', :locality => 'Anytown', :region => "DC", :postal_code => "21234" } ])

user.phone_numbers # => [#<PhoneNumber:0x007fdb2d3bef88 @number="212-555-1212">, #<PhoneNumber:0x007fdb2d3beb00 @number="919-444-3265">]

user.addresses # => #<Set: {#<Address:0x007fdb2d3be448 @address="1234 Any St.", @locality="Anytown", @region="DC", @postal_code="21234">}>

Hash attributes coercion

class Package
  include Virtus.model

  attribute :dimensions, Hash[Symbol => Float]
end

package = Package.new(:dimensions => { 'width' => "2.2", :height => 2, "length" => 4.5 })
package.dimensions # => { :width => 2.2, :height => 2.0, :length => 4.5 }

IMPORTANT note about Boolean type

Be aware that some libraries may do a terrible thing and define a global Boolean constant which breaks virtus' constant type lookup, if you see issues with the boolean type you can workaround it like that:

class User
  include Virtus.model

  attribute :admin, Axiom::Types::Boolean
end

This will be improved in Virtus 2.0.

IMPORTANT note about member coercions

Virtus performs coercions only when a value is being assigned. If you mutate the value later on using its own interfaces then coercion won't be triggered.

Here's an example:

class Book
  include Virtus.model

  attribute :title, String
end

class Library
  include Virtus.model

  attribute :books, Array[Book]
end

library = Library.new

# This will coerce Hash to a Book instance
library.books = [ { :title => 'Introduction to Virtus' } ]

# This WILL NOT COERCE the value because you mutate the books array with Array#<<
library.books << { :title => 'Another Introduction to Virtus' }

A suggested solution to this problem would be to introduce your own class instead of using Array and implement mutation methods that perform coercions. For example:

class Book
  include Virtus.model

  attribute :title, String
end

class BookCollection < Array
  def <<(book)
   if book.kind_of?(Hash)
    super(Book.new(book))
   else
     super
   end
  end
end

class Library
  include Virtus.model

  attribute :books, BookCollection[Book]
end

library = Library.new
library.books << { :title => 'Another Introduction to Virtus' }

Value Objects

class GeoLocation
  include Virtus.value_object

  values do
    attribute :latitude,  Float
    attribute :longitude, Float
  end
end

class Venue
  include Virtus.value_object

  values do
    attribute :name,     String
    attribute :location, GeoLocation
  end
end

venue = Venue.new(
  :name     => 'Pub',
  :location => { :latitude => 37.160317, :longitude => -98.437500 })

venue.location.latitude # => 37.160317
venue.location.longitude # => -98.4375

# Supports object's equality

venue_other = Venue.new(
  :name     => 'Other Pub',
  :location => { :latitude => 37.160317, :longitude => -98.437500 })

venue.location === venue_other.location # => true

Custom Coercions

require 'json'

class Json < Virtus::Attribute
  def coerce(value)
    value.is_a?(::Hash) ? value : JSON.parse(value)
  end
end

class User
  include Virtus.model

  attribute :info, Json, default: {}
end

user = User.new
user.info = '{"email":"[email protected]"}' # => {"email"=>"[email protected]"}
user.info.class # => Hash

# With a custom attribute encapsulating coercion-specific configuration
class NoisyString < Virtus::Attribute
  def coerce(value)
    value.to_s.upcase
  end
end

class User
  include Virtus.model

  attribute :scream, NoisyString
end

user = User.new(:scream => 'hello world!')
user.scream # => "HELLO WORLD!"

Private Attributes

class User
  include Virtus.model

  attribute :unique_id, String, :writer => :private

  def set_unique_id(id)
    self.unique_id = id
  end
end

user = User.new(:unique_id => '1234-1234')
user.unique_id # => nil

user.unique_id = '1234-1234' # => NoMethodError: private method `unique_id='

user.set_unique_id('1234-1234')
user.unique_id # => '1234-1234'

Overriding setters

class User
  include Virtus.model

  attribute :name, String

  def name=(new_name)
    custom_name = nil
    if new_name == "Godzilla"
      custom_name = "Can't tell"
    end
    super custom_name || new_name
  end
end

user = User.new(name: "Frank")
user.name # => 'Frank'

user = User.new(name: "Godzilla")
user.name # => 'Can't tell'

Strict Coercion Mode

By default Virtus returns the input value even when it couldn't coerce it to the expected type. If you want to catch such cases in a noisy way you can use the strict mode in which Virtus raises an exception when it failed to coerce an input value.

class User
  include Virtus.model(:strict => true)

  attribute :admin, Boolean
end

# this will raise an error
User.new :admin => "can't really say if true or false"

Nullify Blank Strings Mode

If you want to replace empty Strings with nil values (since they can't be coerced into the expected type), you can use the :nullify_blank option.

class User
  include Virtus.model(:nullify_blank => true)

  attribute :birthday, Date
end

User.new(:birthday => "").birthday # => nil

Building modules with custom configuration

You can also build Virtus modules that contain their own configuration.

YupNopeBooleans = Virtus.model { |mod|
  mod.coerce = true
  mod.coercer.config.string.boolean_map = { 'nope' => false, 'yup' => true }
}

class User
  include YupNopeBooleans

  attribute :name, String
  attribute :admin, Boolean
end

# Or just include the module straight away ...
class User
  include Virtus.model(:coerce => false)

  attribute :name, String
  attribute :admin, Boolean
end

Attribute Finalization and Circular Dependencies

If a type references another type which happens to not be available yet you need to use lazy-finalization of attributes and finalize virtus manually after all types have been already loaded:

# in blog.rb
class Blog
  include Virtus.model(:finalize => false)

  attribute :posts, Array['Post']
end

# in post.rb
class Post
  include Virtus.model(:finalize => false)

  attribute :blog, 'Blog'
end

# after loading both files just do:
Virtus.finalize

# constants will be resolved:
Blog.attribute_set[:posts].member_type.primitive # => Post
Post.attribute_set[:blog].type.primitive # => Blog

Plugins / Extensions

List of plugins/extensions that add features to Virtus:

Ruby version support

Virtus is known to work correctly with the following rubies:

  • 1.9.3
  • 2.0.0
  • 2.1.2
  • jruby
  • (probably) rbx

Credits

Contributing

  • Fork the project.
  • Make your feature addition or bug fix.
  • Add tests for it. This is important so I don't break it in a future version unintentionally.
  • Commit, do not mess with Rakefile or version (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
  • Send me a pull request. Bonus points for topic branches.

virtus's People

Contributors

amarshall avatar apotonick avatar aptinio avatar blashca avatar booch avatar brutuscat avatar chastell avatar cored avatar dkubb avatar elskwid avatar emmanuel avatar envygeeks avatar fgrehm avatar fnando avatar greyblake avatar indrekj avatar jgaskins avatar ku1ik avatar lfender6445 avatar mduvall avatar novikserg avatar nunommc avatar pewniak747 avatar postmodern avatar rrrene avatar senny avatar shingara avatar snusnu avatar solnic avatar trptcolin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

virtus's Issues

ValueObjects are only needed once in memory

The discussion in #61 brought up, that ValueObjects should not be duped or cloned.

the current Virtus behavior is to create a new object. See the following irb session for an example:

ruby-1.9.2-p290 :004 >  class Example
ruby-1.9.2-p290 :005?>   include Virtus::ValueObject
ruby-1.9.2-p290 :006?> end
 => Example 
ruby-1.9.2-p290 :007 > e = Example.new
 => #<Example:0x007fdc24081dc8> 
ruby-1.9.2-p290 :008 > e.object_id
 => 70291737022180 
ruby-1.9.2-p290 :009 > e.dup.object_id
 => 70291736987800 

Virtus coercion and RSpec doubles

Hi,

I run into a minor issue with Virtus when trying to write some specs of a Virtus Object.

Here is a spec that describe the problem.

require 'spec_helper'

class Poro
  include Virtus

  attribute :id,   Integer
  attribute :name, String
end

describe Poro do
  subject { Poro.new }

  # This spec fails
  it 'accepts RSpec String doubles' do
    name         = double
    subject.name = name

    subject.name.should == name
  end

  # Those pass
  it 'accepts RSpec Integer doubles' do
    id         = double
    subject.id = id

    subject.id.should == id
  end

  it 'accepts integer attributes' do
    subject.id = 1

    subject.id.should == 1
  end

  it 'accepts string attributes' do
    subject.name = 'foo bar'

    subject.name.should == 'foo bar'
  end
end

The first spec failed with this message

Failures:

  1) Poro accepts RSpec String doubles
     Failure/Error: subject.name.should == name
       expected: #<RSpec::Mocks::Mock:0x3fe5188fb58c @name=nil>
            got: "#[RSpec::Mocks::Mock:0x3fe5188fb58c @name=nil]" (using ==)
       Diff:
       @@ -1,2 +1,2 @@
       -#<RSpec::Mocks::Mock:0x3fe5188fb58c @name=nil>
       +"#[RSpec::Mocks::Mock:0x3fe5188fb58c @name=nil]"
     # ./spec/integration/bug_spec.rb:25:in `block (2 levels) in <top (required)>'

Finished in 0.00176 seconds
1 example, 1 failure

It is pretty clear that the double is converted into a String but that is not what I expect.

To fix this issue, I add a custom coercion object.

module Virtus
  class Coercion
    class Mock < Virtus::Coercion::Object
      primitive ::RSpec::Mocks::Mock

      def self.to_string(value)
        value
      end
    end
  end
end

But, I'm pretty sure that it is not the right way to fix the problem.

What do you think of this issue ?

EDIT : BTW, this happend only in virtus 0.3 and not 0.2.

Allow attribute DSL method to be used in a module

In DataMapper one thing I had wished to do with the DataMapper::Resource API is allow the property and relationship DSL to be used within modules, rather than just in classes.

This would allow the user to specify attributes within modules and then compose objects from those modules without having to do anything special with the included hooks. I think it would make for an improved experience since it might encourage people to break things down into smaller components rather than trying to do everything in a single classs.

@solnic what do you think?

Custom coercion for an EmbeddedValue?

At the moment, if you have a Virtus object acting as an Embedded Value, you can't do any kind of custom coercion. Or even invoke the constructor, unless you pass in a hash. For example:

require 'virtus'

class Ability
  include Virtus

  attribute :rating, Integer, :default => 0
  attribute :favoured, Boolean, :default => false

  def initialize(rating_or_hash)
    if Integer === rating_or_hash
      self.rating = rating_or_hash
    else
      super
    end
  end
end

p Ability.new(1)
#=> #<Ability:0x1f01760 @rating=1>
p Ability.new(:rating => 1, :favoured => true)
#=> #<Ability:0x1f09380 @rating=1, @favoured=true>

class Character
  include Virtus

  attribute :driving, Ability
end

c = Character.new(:driving => 1)
p c
#=> #<Character:0x1c8c540 @driving=1>

c = Character.new(:driving => {:rating => 1})
p c
#=> #<Character:0x1c8b2b0 @driving=#<Ability:0x1c8b190 @rating=1>>

It would be helpful if in the Character.new(:driving => 1) case, the one could still be cast to an Ability. Perhaps via a custom coercion method? I'm not immediately sure how the best way to go about this would be.

Initializing attributes by default

Is it possible to get virtus to do something like:

require 'virtus'                                                                                    

class Address                                                                                       
  include Virtus                                                                                    
  attribute :street,      String                                                                    
  attribute :city,  String                                                                          
end                                                                                                 

class User                                                                                          
  include Virtus                                                                                    
  attribute :name, String                                                                           
  attribute :address, Address                                                                       
end                                                                                                 

u = User.new                                                                                        
fail unless "" == u.name                                                                            
fail unless "" == u.address.city

I'm building a form handling library based on virtus, and not having to deal with nil's would make this a lot easier.

Cannot include Virtus enabled Modules into other Modules

module Mixin
  include Virtus
end

module Mixin2
  include Mixin

  attribute :foo, String
end
NoMethodError: undefined method `superclass' for Mixin2:Module
    from /home/hal/.rvm/gems/ruby-1.9.3-p194/gems/virtus-0.5.0/lib/virtus/class_methods.rb:43:in `attribute_set'
    from /home/hal/.rvm/gems/ruby-1.9.3-p194/gems/virtus-0.5.0/lib/virtus/extensions.rb:89:in `virtus_add_attribute'
    from /home/hal/.rvm/gems/ruby-1.9.3-p194/gems/virtus-0.5.0/lib/virtus/class_methods.rb:110:in `virtus_add_attribute'
    from /home/hal/.rvm/gems/ruby-1.9.3-p194/gems/virtus-0.5.0/lib/virtus/extensions.rb:53:in `attribute'
    from (irb):7:in `'
    from (irb):5
    from /home/hal/.rvm/rubies/ruby-1.9.3-p194/bin/irb:16:in `'

Alias attribute_get() w/ [] and attribute_set() w/ []=

I don't know if this is seen as just syntactic sugar, but it could be quite nice to have Hash style read/write for Virtus objects. I used Virtus in a project today, just as an intermediate for creating a Hash (from an XML document from Quova.com) with each element cast to the correct type. I just defined a Virtus class with the respective properties, then parsed the XML, calling #attribute_set for each element I was interested it, before finally exporting #attributes to get a plain Hash back out. I was thinking the whole time, it would be nice if Virtus added [] and []= so the object could behave somewhat like a Hash.

DM does this already.

offer json + xml de/serialization

recently I played around with virtus and de/serialization. since virtus already offers a to_hash method with a deep copy if the data things were pretty straight forward. I wonder if this something for virtus itself or for another gem like dm-serialization ? I could setup a project in case nothing is there already !!

what happens when data-type conversions fail

the current behaviour seems to just assign the set value. This could be dangerous when you expect the attributes to contain data of the right type.

class Example
  include Virtus
  attribute :my_date, Date
end

e = Example.new
e.my_date = 'test'
e.my_date # => 'test'

key not found: :coercion_method when using Object type

Just updated to 0.0.6 and starting getting this error on classes where I'm using Object as an attribute type.

class Example
  include Virtus

  attribute :foo, Object
end
KeyError: key not found: :coercion_method
    from /home/chris/.rvm/gems/ruby-1.9.2-p290@flippa/gems/virtus-0.0.6/lib/virtus/attribute.rb:98:in `fetch'
    from /home/chris/.rvm/gems/ruby-1.9.2-p290@flippa/gems/virtus-0.0.6/lib/virtus/attribute.rb:98:in `initialize'
    from /home/chris/.rvm/gems/ruby-1.9.2-p290@flippa/gems/virtus-0.0.6/lib/virtus/class_methods.rb:46:in `new'
    from /home/chris/.rvm/gems/ruby-1.9.2-p290@flippa/gems/virtus-0.0.6/lib/virtus/class_methods.rb:46:in `attribute'
    from (irb):4:in `<class:Example>'
    from (irb):2
    from /home/chris/.rvm/rubies/ruby-1.9.2-p180/bin/irb:16:in `<main>'

I think I'll just use attr_accessor for these attributes for now, but it's nice to be consistent when there are a list of related attributes and some of those are just Object types.

special treatment for ValueObjects

It would be nice if virtus had like a submodule for building ValueObjects. On top of my head this could look something like:

class GeoLocation
  include Virtus::ValueObject

  attribute :latitude, Float
  attribute :longitude, Float
end

The Virtus::ValueObject module would set some sane defaults for building ValueObjects like:

  • They should be immutable so the setters are private
  • They should be == when all the properties are equal
  • We need to overwrite the #hash method since we defined a new == method
  • the constructor should verify that all the properties have been set since the setters are not available. (not completely sure about that one. There might be cases where optional attributes are welcome)
place = GeoLocation.new(:latitute => 40.73216945026674, :longitude => -74.091796875)
the_same_place = GeoLocation.new(:latitute => 40.73216945026674, :longitude => -74.091796875)
place == the_same_place # => true

hash = {palce => 1}
hash[the_same_palce] # => 1

place.latitude = -74.091796875 # => ERROR: Attempt to call private method

Tracking changed attributes

Would it make sense for virtus to track changed attributes so that the mapper can choose what fields to update?

u = User.new
u.name = 'foo'
u.changed_attributes # {:name => 'foo'}

Plans for dirty tracking?

Are there any plans to implement dirty tracking support in Virtus? I can do what I want without it, but it would be handy in some circumstances.

I believe the plan with DM2 is to manage dirty tracking in a UnitOfWork/Session object, but I'd like it for non-persistent objects.

Force to array coercion

It'd be great if an attribute of type Array could be automatically converted to an array.

Example use case:
In a Sinatra app, an URL formatted like ?key=value results in params['key'] = 'value', while ?key[]=value1&key[]=value2 results in params['key'] = ['value1', 'value2'].
I'd like to deal with an array all the time. i.e. VirtusObject.new(:key => 1).key # => [1].

On #datamapper, Solnic suggested something like:

class Virtus::Coercion::Object; def self.to_array(value); Array(value); end; end

Best,
Vivien.

Virtus cannot be used in Modules

I wanted to try replacing parameters with Virtus. One of the features that Parameters has is being able to be used in Mixin modules, so that when the Mixin is included the parameters are added to the class. Virtus appears to only work with Classes.

module Test
  include Virtus

  attribute :name, String
end
NoMethodError: undefined method `superclass' for Test:Module
    /home/hal/.rvm/gems/ruby-1.9.3-p0/gems/virtus-0.0.9/lib/virtus/class_methods.rb:71:in `attributes'
    /home/hal/.rvm/gems/ruby-1.9.3-p0/gems/virtus-0.0.9/lib/virtus/class_methods.rb:115:in `virtus_add_attribute'
    /home/hal/.rvm/gems/ruby-1.9.3-p0/gems/virtus-0.0.9/lib/virtus/class_methods.rb:48:in `attribute'
    (ripl):7:in `'

Not an issue; Just curiosity subclassing a Module? erh

AttributeSet And Equalizer are subclass of Module I have looked online as much as I can; but I have no idea why you have done this; must be cool reason;

if its not too much trouble can tell why you use this approach and what it achieves in the library as a whole. as I have not seen this technique before; I am curious;

playing around; when you have a subclass of a module and you include the methods they are not available to the includer which is puzzling;

Cheers

Richard

unify integration specs

I've noticed that there are multiple places where integration specs are placed:

  • /examples
  • /spec/integration

I think it would be a good idea to unify these specs and group them together. Currently the example specs also differ in writing style from the spec/integration specs. I would also suggest to pursue one writing style for integration specs. I also think these specs are a good place to "document" the different features of virtus, which are not all visible in the README. I like projects which explanatory integration specs to demonstrate the features.

What do you think? Is there a reason for these specs to coexist? What style of writing do you prefere?

Type conversions

I was just playing around with the gem and stumbled on this:

require 'virtus'

class Book
  include Virtus

  attribute :page_numbers, Array[Integer]
end

book = Book.new(:page_numbers => %w[dummy 2 3])
book.page_numbers # => ["dummy", 2, 3]

I would expect [0, 2, 3] or maybe [nil, 2, 3] or even an error.
Is it the wanted behavior ?

Default type for attribute

What about using Virtus::Attribute::Object as a default type if it is not specified in attribute, for example these would be equivalent:

attribute :obj, Object

attribute :obj

This would also have to handle cases where options are passed in, which shouldn't be too difficult to implement.

Ways to update an existing record

Hi guys,

Thanks again for an awesome helping tool. It really does save a ton of time, and works perfectly fine.

I'm still uncertain about the issues I reported before. I decided to keep developing mongo repository, and have opensourced a smallish proof of concept, but it's still to raw, and has only integration tests so far. But, hopefully, it will get moving.

I'm trying to understand, what's the best way to update an existing record? I can see attributes= method, but I'm not sure if it's a promoted way to do so. It works perfectly fine, and as I see from here: https://github.com/solnic/virtus/blob/master/lib/virtus/instance_methods.rb#L14 it seems to be exactly same mechanism to record creation.

Is it safe to use it? Is there any need to secure myself and use something else?

standalone coercion

Hi,
I really like this library but already got some cases where only the coercion part is needed, my problem is coming up with the shortest path to do this.
Currently I have this:

require 'virtus'

def coerce_value(type, ret)
  converter = Virtus::Attribute.build(:a, type)
  converter.coerce(ret)
end

p coerce_value(Array[String], [45, 56, "a", 4.56])

Is there a better way ?

Writing private attributes from the constructor

I am finding that a very common use-case for me is that I want to use a virtus object to wrap another object or objects.

I'd like to be able to do this:

class PersonPage
  attribute :person, Person, accessor: private

  def age
    "#{person.age} years old"
  end
end

I make the person attribute private because I don't want it to be part of the public interface of this object, but I do want to be able pass it to the PersonPage's constructor as a named argument. However I've found that when I pass the Person instance that I want to wrap into the PersonPage constructor, it isn't set. I think this is because the accessor is private.

I could use the options to set the writer to public, but I don't want to expose a setter as part of the public interface - I just want to be able to set it from the constructor.

Am I using the library wrongly, or should we change it so that even attributes with private setters can be set from the constructor?

Virtus::Attribute#value_coerced? not being used

Today I found out about this method and by doing a git grep "value_coerced?" I couldn't spot any use of it. Is it for some future feature or has it been forgotten?

On top of that, using the Custom Attributes example from README, is this code really expected to raise an error?

user = MyAppClass::User.new
user.info = {"email" => "[email protected]"}

TypeError: can't convert Hash into String
    from /home/fabio/.rvm/gems/ruby-1.8.7-p334@virtus/gems/json-1.6.5/lib/json/common.rb:148:in `initialize'
    from /home/fabio/.rvm/gems/ruby-1.8.7-p334@virtus/gems/json-1.6.5/lib/json/common.rb:148:in `new'
    from /home/fabio/.rvm/gems/ruby-1.8.7-p334@virtus/gems/json-1.6.5/lib/json/common.rb:148:in `parse'
    from (irb):11:in `coerce'
    from ./virtus/attribute.rb:191:in `set'
    from ./virtus/attributes_accessor.rb:60:in `info='
    from (irb):25

I mean, should we check if the value is an instance of the target class on custom attributes #coerce method (MyAppClass::Attributes::JSON in this case) or should Virtus bypass value coercion and just set the value? If the check is needed, the example should probably be updated to something like:

module MyAppClass
  module Attributes
    class JSON < Virtus::Attribute::Object
      primitive Hash

      def coerce(value)
        if value.is_a? Hash
          value
        else
          ::JSON.parse value
        end
      end
    end
  end
end

I'm asking this because I noticed that on my current project we have some custom attributes that have that kind of checking on #coerce method in order to use the coerced value right away on specs.

Page#published? undefined in Readme

In default values example, #default_editor_title calls #published?, but as far as I can tell #published? isn't defined. I'm guessing its just a Readme error?

Dealing with circular dependencies

For instance, I have a Person model that has many Article entities related to it:

class Person
  include Virtus

  attribute :name,     String
  attribute :gender,   String

  attribute :articles, Array[Article]
end

On the other hand, I have an Article object that is related to Person:

class Article
  include Virtus

  attribute :title,  String
  attribute :text,   String
  attribute :person_id, BSON::ObjectId
  attribute :person, Person
end

What is the suggested way to handle such things?
Usually, wrapping in lambda for lazy evaluation works fine for such cases (e.q.)

  attribute :person, lambda { Person } 

or turning Object to Symbol:

  attribute :person, :Person

Maybe current example is not quite exhaustive, but there are many cases when two objects have an equal power, first may be initialized via attributes of second one or vice versa.

Both approaches allow following trivial instantiation.

virtus does not work in anonymous classes

I just tried to write an integration spec. Since I don't want to pollute the namespace I wanted to define the virtus classes like:

let(:person_class) do
  Class.new do
    include Virtus

    attribute :name, String
    attribute :age, Integer
    attribute :doctor, Boolean
  end
end

This results in the following error:

     Failure/Error: attribute :age, Boolean
     NameError:
       uninitialized constant Class::Boolean

I investigated the error and found out, that the problem is the const_missing definition in Virtus. In the block passed to Class.new constant lookup is performed on the top-level so it never reaches the const-missing defined by virtus. Following an example to illustrate the problem:

class ThisWorks
  def self.const_missing(*args)
    p "yay!"
  end

  NonExistingConstant
end

this_does_not_work = Class.new do
  def self.const_missing(*args)
    p "yay!"
  end

  NonExistingConstant
end

Recursive to_hash

I've been working on Monger, Clojure Mongodb Driver. It's no surprise we've been always working with hashes there.

When you go down to the driver level, you always end up working with language primitives.

Right now I'm working on a prototype / proof of concept of Virtus + MongoDB driver, kind of DAO thing, which uses pretty much same API we expose in Monger.

Right now #to_hash is not recursive, therefore it won't handle embedded Mongodb models.

I realize that this is mostly applicable for the document databases, and way less applicable for Object Relational ones.

Do you think it would be useful for Virtus to have recursive implementation of #to_hash, and would consider making it a default implementation?

Thanks

Encrypted attribute(s)

Hi there,

I'm facing a use case where virtus may be suitable. I need to generate test data (user with id and password) which needs to be stored in YAML files. The passwords have to be store encrypted within that YAML file. When reading it, it needs to be decrypted.

Now comming to my question:

Can I use virtus for that use case? I think about building a custom attribute, which overwrites (coerce or set) and get.

require 'encryptor'

module MyApp

  # Defining the custom attribute(s)
  module Attributes
    class EncryptedString < Virtus::Attribute::Object
      primitive String

      def coerce(key, value)
       Encryptor.encrypt key, value
      end

      def get(key)
        [...]
        Encryptor.decrypt key, value
      end

    end
  end

  class User
    include Virtus

    attribute :name, String
    attribute :password, Attributes::EncryptedString
  end
end

Thank you very much.

Cheers,
MaxMeyer

how can default values be set based on private attributes

I noticed that the current implementation of default values passes in self to give the Proc access to it's attributes. Since it is possible to have private attributes, how would you make defaults based on these attributes without having to reach out to send?

It would be an option to evaluate the Block in the context of the record to get direct access to the attributes.

reject_if support for collection member coercions with embedded values

Folks, another feature I just remembered that would be good to have for the project I'm currently working on is a way to reject hash values that comes from a form before they get assigned / coerced to the main object, something along the lines of ActiveRecord's accepts_nested_attributes_for :reject_if parameter and I wanted to know your thoughts on this.

Where do you guys think would be the best place to have that? Is it something that Virtus should take care of?

Cheers!

No support for proc defaults

The present :default implementation doesn't allow for proc/lamba based defaults. This is important for (to give an example), Time based properties. Otherwise the default will be statically evaluated at class definition time.

class VirtusTime
  include Virtus

  attribute :time, DateTime, :default => DateTime.now
end

@old = VirtusTime.new

# many years pass

@new = VirtusTime.new

@old.time == @new.time

What would it take to allow Virtus classes as attributes?

what I'd like to be able to do is something like

class Address
  include Virtus
  # include .... something else, too, to make it a property as well?
  attribute :line1, String
  # ... others
end

class Person
  include Virtus
  attribute :name, String
  attribute :address, Address
end

Preferably without needing to manually set up a whole slew of property subclasses. This would be useful for using Virtus to back a system for interacting with e.g. CouchDB where embedded values are not uncommon and they sometimes need more complex behaviour than a hash provides

private method `inherited' called for Object:Class (NoMethodError)

Just updated from 0.0.3 to 0.0.4 and getting this error when my app starts up. I'll take a closer look tomorrow as I'm doing something else now and it's nearly 3am ;)

/home/chris/.rvm/gems/ruby-1.9.2-p180@flippa/gems/virtus-0.0.4/lib/virtus/support/descendants_tracker.rb:15:in `inherited': private method `inherited' called for Object:Class (NoMethodError)
    from /home/chris/.rvm/gems/ruby-1.9.2-p180@flippa/gems/virtus-0.0.4/lib/virtus/attribute.rb:118:in `inherited'
    from /home/chris/.rvm/gems/ruby-1.9.2-p180@flippa/gems/virtus-0.0.4/lib/virtus/attribute/object.rb:5:in `<class:Attribute>'
    from /home/chris/.rvm/gems/ruby-1.9.2-p180@flippa/gems/virtus-0.0.4/lib/virtus/attribute/object.rb:2:in `<module:Virtus>'
    from /home/chris/.rvm/gems/ruby-1.9.2-p180@flippa/gems/virtus-0.0.4/lib/virtus/attribute/object.rb:1:in `<top (required)>'
    from /home/chris/.rvm/gems/ruby-1.9.2-p180@flippa/gems/virtus-0.0.4/lib/virtus.rb:66:in `require'
    from /home/chris/.rvm/gems/ruby-1.9.2-p180@flippa/gems/virtus-0.0.4/lib/virtus.rb:66:in `<top (required)>'

Add coercion for Array and Hash to Virtus::Coercion::Object

Add #to_array and #to_hash methods to Virtus::Coercion::Object so that they coerce any value that responds to #to_ary (and #to_a), or #to_hash respectively. If the value does not respond to one of those methods then it should be returned directly as-is.

Consider similar coercions for objects into String using #to_str and #to_s, and Integers using #to_int and #to_i.

Virtus hogs initialize()

If I include Virtus in my class, it adds a method initialize to do it's magic. This is mostly a good thing, but it means that I cannot compute and set non-Virtus instance variables at object creation time. I'd like to be able to do this.

const_missing should be public

In Ruby 1.9.3 (and I assume others, although I haven't checked), Module defines const_missing to be a public method. Virtus::ClassMethods however, redefines const_missing to be private. Ultimately, there are cases when the following exception occurs:

NoMethodError in MyController#show

private method `const_missing' called for My::Klass:Class

As a test, I simply moved the method definition until the public space of ClassMethods and everything works as expected.

:default => nil for Object attribute creates instance of Object

I have a model, Person that has a Company attrbiute:

class Person
  include Virtus
  attribute :company, Company, :default => nil
  # rest omitted
end

Company also includes Virtus.

if I create a Person with

p = Person.new(:company => nil, :last_name => 'Confused')

and inspect p.attributes it has a Company instance. I don't think it should have a Company instance if I instantiate it with a nil. Of note: it did not exhibit this behavior in the previous version.

Virtus cannot define attributes on Objects

I was trying to replace Parameters with Virtus. One of the features of Parameters, is being able to define parameters on Objects as well as Classes. Virtus does not want to be extended into an Object.

class Test
end
obj = Test.new
obj.extend Virtus::ClassMethods

NoMethodError: undefined method `const_set' for #<Test:0x00000002aeb878>
    /home/hal/.rvm/gems/ruby-1.9.3-p0/gems/virtus-0.0.9/lib/virtus/class_methods.rb:16:in `extended'

Question about inheritance

How does Virtus handle class inheritance? More specifically how are attribute options accessed/collated through the inheritance chain.

constructor to perform mass-assignment

I want to integrate virtus into our existing application as a replacement for ActiveModel form backends. Since we use Rails it would be convenient to have a mass-assignment functionality backed into virtus. Is there anything planned in that direction?

Spec failure on RBX with 1.9 mode

Using worker: ruby3.worker.travis-ci.org:worker-5
2
3$ git clone --depth=1000 --quiet git://github.com/solnic/virtus.git solnic/virtus
4$ git checkout -qf af6a7cd38294e40306ff1e09980b00f6ffa85803
5$ rvm use rbx
6Using /home/vagrant/.rvm/gems/rbx-head
7$ export BUNDLE_GEMFILE=/home/vagrant/builds/solnic/virtus/Gemfile
8$ export RBXOPT="-X19"
9$ bundle install --without guard metrics
10Fetching git://github.com/dkubb/reek.git
11remote: Counting objects: 7204, done.
12remote: Compressing objects: 100% (1811/1811), done.[K
13remote: Total 7204 (delta 5288), reused 7167 (delta 5255)[K
14Receiving objects: 100% (7204/7204), 1006.40 KiB | 564 KiB/s, done.
15Resolving deltas: 100% (5288/5288), done.
16Fetching source index for http://rubygems.org/
17Using rake (0.9.2.2)
18Installing backports (2.3.0)
19Installing diff-lcs (1.1.3)
20Installing rspec-core (2.6.4)
21Installing rspec-expectations (2.6.0)
22Installing rspec-mocks (2.6.0)
23Installing rspec (2.6.0)
24Using virtus (0.10.0) from source at /home/vagrant/builds/solnic/virtus
25Using bundler (1.0.21)
26Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.
27$ bundle exec rake spec
28/home/vagrant/.rvm/rubies/rbx-head/bin/rbx -S bundle exec rspec ./spec/integration/virtus/class_methods/attributes_spec.rb ./spec/integration/virtus/class_methods/const_missing_spec.rb ./spec/integration/virtus/class_methods/attribute_spec.rb ./spec/integration/virtus/attributes/attribute/set_spec.rb ./spec/unit/virtus/attribute/decimal_spec.rb ./spec/unit/virtus/attribute/date_time_spec.rb ./spec/unit/virtus/attribute/time_spec.rb ./spec/unit/virtus/attribute/boolean_spec.rb ./spec/unit/virtus/attribute/float_spec.rb ./spec/unit/virtus/attribute/date_spec.rb ./spec/unit/virtus/attribute/class_spec.rb ./spec/unit/virtus/attribute/hash_spec.rb ./spec/unit/virtus/attribute/string_spec.rb ./spec/unit/virtus/attribute/array_spec.rb ./spec/unit/virtus/attribute/integer_spec.rb ./spec/unit/virtus/type_lookup/determine_type_spec.rb ./spec/unit/virtus/type_lookup/primitive_spec.rb ./spec/unit/virtus/class_methods/attributes_spec.rb ./spec/unit/virtus/class_methods/new_spec.rb ./spec/unit/virtus/class_methods/attribute_spec.rb ./spec/unit/virtus/descendants_tracker/add_descendant_spec.rb ./spec/unit/virtus/descendants_tracker/descendants_spec.rb ./spec/unit/virtus/attribute_set/each_spec.rb ./spec/unit/virtus/attribute_set/merge_spec.rb ./spec/unit/virtus/attribute_set/reset_spec.rb ./spec/unit/virtus/attribute_set/parent_spec.rb ./spec/unit/virtus/attribute_set/append_spec.rb ./spec/unit/virtus/attribute_set/element_set_spec.rb ./spec/unit/virtus/attribute_set/element_reference_spec.rb ./spec/unit/virtus/coercion/class_name_reference_spec.rb ./spec/unit/virtus/instance_methods/attributes_spec.rb ./spec/unit/virtus/instance_methods/element_set_spec.rb ./spec/unit/virtus/instance_methods/element_reference_spec.rb ./spec/unit/virtus/options/options_spec.rb ./spec/unit/virtus/options/accepted_options_spec.rb ./spec/unit/virtus/options/accept_options_spec.rb ./spec/unit/virtus/coercion/hash/class_methods/to_date_spec.rb ./spec/unit/virtus/coercion/hash/class_methods/to_datetime_spec.rb ./spec/unit/virtus/coercion/hash/class_methods/to_time_spec.rb ./spec/unit/virtus/coercion/hash/class_methods/to_array_spec.rb ./spec/unit/virtus/coercion/float/class_methods/to_decimal_spec.rb ./spec/unit/virtus/coercion/float/class_methods/to_integer_spec.rb ./spec/unit/virtus/coercion/float/class_methods/to_string_spec.rb ./spec/unit/virtus/coercion/object/class_methods/method_missing_spec.rb ./spec/unit/virtus/coercion/false_class/class_methods/to_string_spec.rb ./spec/unit/virtus/coercion/date/class_methods/to_datetime_spec.rb ./spec/unit/virtus/coercion/date/class_methods/to_time_spec.rb ./spec/unit/virtus/coercion/date/class_methods/to_string_spec.rb ./spec/unit/virtus/coercion/decimal/class_methods/to_float_spec.rb ./spec/unit/virtus/coercion/decimal/class_methods/to_integer_spec.rb ./spec/unit/virtus/coercion/decimal/class_methods/to_string_spec.rb ./spec/unit/virtus/coercion/integer/class_methods/to_decimal_spec.rb ./spec/unit/virtus/coercion/integer/class_methods/to_float_spec.rb ./spec/unit/virtus/coercion/integer/class_methods/to_string_spec.rb ./spec/unit/virtus/coercion/integer/class_methods/to_boolean_spec.rb ./spec/unit/virtus/coercion/symbol/class_methods/to_string_spec.rb ./spec/unit/virtus/coercion/true_class/class_methods/to_string_spec.rb ./spec/unit/virtus/coercion/string/class_methods/to_date_spec.rb ./spec/unit/virtus/coercion/string/class_methods/to_decimal_spec.rb ./spec/unit/virtus/coercion/string/class_methods/to_datetime_spec.rb ./spec/unit/virtus/coercion/string/class_methods/to_time_spec.rb ./spec/unit/virtus/coercion/string/class_methods/to_float_spec.rb ./spec/unit/virtus/coercion/string/class_methods/to_integer_spec.rb ./spec/unit/virtus/coercion/string/class_methods/to_constant_spec.rb ./spec/unit/virtus/coercion/string/class_methods/to_boolean_spec.rb ./spec/unit/virtus/coercion/date_time/class_methods/to_date_spec.rb ./spec/unit/virtus/coercion/date_time/class_methods/to_time_spec.rb ./spec/unit/virtus/coercion/date_time/class_methods/to_string_spec.rb ./spec/unit/virtus/attribute/class_methods/determine_type_spec.rb ./spec/unit/virtus/attribute/default_value/class_methods/new_spec.rb ./spec/unit/virtus/attribute/default_value/instance_methods/evaluate_spec.rb ./spec/unit/virtus/attribute/object/class_methods/descendants_spec.rb ./spec/unit/virtus/attribute/numeric/class_methods/descendants_spec.rb
29An exception occurred running at_exit handlers
30undefined method `empty?' on nil:NilClass. (NoMethodError)
31
32Backtrace:
33  Kernel(NilClass)#empty? (method_missing) at kernel/delta/kernel.rb:79
34RSpec::Core::Configuration#configure_group at /home/vagrant/.rvm/gems
35/rbx-head/gems
36/rspec-core-2.6.4/lib
37/rspec/core
38/configuration.rb:403
39                            Array#each at kernel/bootstrap/array.rb:59
40RSpec::Core::Configuration#configure_group at /home/vagrant/.rvm/gems/rbx-head
41/gems/rspec-core-2.6.4/lib/rspec
42/core/configuration.rb:402
43RSpec::Core::World#configure_group at /home/vagrant/.rvm/gems/rbx-head/gems
44/rspec-core-2.6.4/lib/rspec/core
45/world.rb:62
46RSpec::Core::ExampleGroup.set_it_up at /home/vagrant/.rvm/gems/rbx-head/gems
47/rspec-core-2.6.4/lib/rspec/core
48/example_group.rb:184
49RSpec::Core::ExampleGroup.subclass at /home/vagrant/.rvm/gems/rbx-head/gems
50/rspec-core-2.6.4/lib/rspec/core
51/example_group.rb:141
52RSpec::Core::ExampleGroup.describe at /home/vagrant/.rvm/gems/rbx-head/gems
53/rspec-core-2.6.4/lib/rspec/core
54/example_group.rb:129
55RSpec::Core::DSL(Object)#describe at /home/vagrant/.rvm/gems/rbx-head/gems
56/rspec-core-2.6.4/lib/rspec/core
57/dsl.rb:5
58Object#__script__ at spec/unit/virtus/attribute
59/integer_spec.rb:3
60  Kernel(RSpec::Core::Configuration)#load at kernel/common/kernel.rb:621
61{ } in RSpec::Core::Configuration#load_spec_files at /home/vagrant/.rvm/gems
62/rbx-head/gems
63/rspec-core-2.6.4/lib
64/rspec/core
65/configuration.rb:419
66                             Array#map at kernel/bootstrap/array19.rb:16
67RSpec::Core::Configuration#load_spec_files at /home/vagrant/.rvm/gems/rbx-head
68/gems/rspec-core-2.6.4/lib/rspec
69/core/configuration.rb:419
70RSpec::Core::CommandLine#run at /home/vagrant/.rvm/gems/rbx-head/gems
71/rspec-core-2.6.4/lib/rspec/core
72/command_line.rb:18
73RSpec::Core::Runner.run_in_process at /home/vagrant/.rvm/gems/rbx-head/gems
74/rspec-core-2.6.4/lib/rspec/core
75/runner.rb:80
76RSpec::Core::Runner.run at /home/vagrant/.rvm/gems/rbx-head/gems
77/rspec-core-2.6.4/lib/rspec/core
78/runner.rb:69
79{ } in RSpec::Core::Runner.autorun at /home/vagrant/.rvm/gems/rbx-head/gems
80/rspec-core-2.6.4/lib/rspec/core
81/runner.rb:11
82         Rubinius::Loader#run_at_exits at kernel/loader.rb:659
83             Rubinius::Loader#epilogue at kernel/loader.rb:679
84                 Rubinius::Loader#main at kernel/loader.rb:809
85rake aborted!
86ruby -S bundle exec rspec ./spec/integration/virtus/class_methods/attributes_spec.rb ./spec/integration/virtus/class_methods/const_missing_spec.rb ./spec/integration/virtus/class_methods/attribute_spec.rb ./spec/integration/virtus/attributes/attribute/set_spec.rb ./spec/unit/virtus/attribute/decimal_spec.rb ./spec/unit/virtus/attribute/date_time_spec.rb ./spec/unit/virtus/attribute/time_spec.rb ./spec/unit/virtus/attribute/boolean_spec.rb ./spec/unit/virtus/attribute/float_spec.rb ./spec/unit/virtus/attribute/date_spec.rb ./spec/unit/virtus/attribute/class_spec.rb ./spec/unit/virtus/attribute/hash_spec.rb ./spec/unit/virtus/attribute/string_spec.rb ./spec/unit/virtus/attribute/array_spec.rb ./spec/unit/virtus/attribute/integer_spec.rb ./spec/unit/virtus/type_lookup/determine_type_spec.rb ./spec/unit/virtus/type_lookup/primitive_spec.rb ./spec/unit/virtus/class_methods/attributes_spec.rb ./spec/unit/virtus/class_methods/new_spec.rb ./spec/unit/virtus/class_methods/attribute_spec.rb ./spec/unit/virtus/descendants_tracker/add_descendant_spec.rb ./spec/unit/virtus/descendants_tracker/descendants_spec.rb ./spec/unit/virtus/attribute_set/each_spec.rb ./spec/unit/virtus/attribute_set/merge_spec.rb ./spec/unit/virtus/attribute_set/reset_spec.rb ./spec/unit/virtus/attribute_set/parent_spec.rb ./spec/unit/virtus/attribute_set/append_spec.rb ./spec/unit/virtus/attribute_set/element_set_spec.rb ./spec/unit/virtus/attribute_set/element_reference_spec.rb ./spec/unit/virtus/coercion/class_name_reference_spec.rb ./spec/unit/virtus/instance_methods/attributes_spec.rb ./spec/unit/virtus/instance_methods/element_set_spec.rb ./spec/unit/virtus/instance_methods/element_reference_spec.rb ./spec/unit/virtus/options/options_spec.rb ./spec/unit/virtus/options/accepted_options_spec.rb ./spec/unit/virtus/options/accept_options_spec.rb ./spec/unit/virtus/coercion/hash/class_methods/to_date_spec.rb ./spec/unit/virtus/coercion/hash/class_methods/to_datetime_spec.rb ./spec/unit/virtus/coercion/hash/class_methods/to_time_spec.rb ./spec/unit/virtus/coercion/hash/class_methods/to_array_spec.rb ./spec/unit/virtus/coercion/float/class_methods/to_decimal_spec.rb ./spec/unit/virtus/coercion/float/class_methods/to_integer_spec.rb ./spec/unit/virtus/coercion/float/class_methods/to_string_spec.rb ./spec/unit/virtus/coercion/object/class_methods/method_missing_spec.rb ./spec/unit/virtus/coercion/false_class/class_methods/to_string_spec.rb ./spec/unit/virtus/coercion/date/class_methods/to_datetime_spec.rb ./spec/unit/virtus/coercion/date/class_methods/to_time_spec.rb ./spec/unit/virtus/coercion/date/class_methods/to_string_spec.rb ./spec/unit/virtus/coercion/decimal/class_methods/to_float_spec.rb ./spec/unit/virtus/coercion/decimal/class_methods/to_integer_spec.rb ./spec/unit/virtus/coercion/decimal/class_methods/to_string_spec.rb ./spec/unit/virtus/coercion/integer/class_methods/to_decimal_spec.rb ./spec/unit/virtus/coercion/integer/class_methods/to_float_spec.rb ./spec/unit/virtus/coercion/integer/class_methods/to_string_spec.rb ./spec/unit/virtus/coercion/integer/class_methods/to_boolean_spec.rb ./spec/unit/virtus/coercion/symbol/class_methods/to_string_spec.rb ./spec/unit/virtus/coercion/true_class/class_methods/to_string_spec.rb ./spec/unit/virtus/coercion/string/class_methods/to_date_spec.rb ./spec/unit/virtus/coercion/string/class_methods/to_decimal_spec.rb ./spec/unit/virtus/coercion/string/class_methods/to_datetime_spec.rb ./spec/unit/virtus/coercion/string/class_methods/to_time_spec.rb ./spec/unit/virtus/coercion/string/class_methods/to_float_spec.rb ./spec/unit/virtus/coercion/string/class_methods/to_integer_spec.rb ./spec/unit/virtus/coercion/string/class_methods/to_constant_spec.rb ./spec/unit/virtus/coercion/string/class_methods/to_boolean_spec.rb ./spec/unit/virtus/coercion/date_time/class_methods/to_date_spec.rb ./spec/unit/virtus/coercion/date_time/class_methods/to_time_spec.rb ./spec/unit/virtus/coercion/date_time/class_methods/to_string_spec.rb ./spec/unit/virtus/attribute/class_methods/determine_type_spec.rb ./spec/unit/virtus/attribute/default_value/class_methods/new_spec.rb ./spec/unit/virtus/attribute/default_value/instance_methods/evaluate_spec.rb ./spec/unit/virtus/attribute/object/class_methods/descendants_spec.rb ./spec/unit/virtus/attribute/numeric/class_methods/descendants_spec.rb failed
87
88Tasks: TOP => spec
89(See full trace by running task with --trace)
90
91Done. Build script exited with: 1

Encrypted attributes

Hi there,

I'm facing a use case where @virtus@ may be suitable. I need to generate test data (user with id and password) which needs to be stored in YAML files. The passwords have to be store encrypted within that YAML file. When reading it, it needs to be decrypted. I hope the use case is clear. Now to my question:

Bring back support for mass assignment using accessors?

Hey folks, we are trying to upgrade virtus from 0.0.9 to 0.2.0 and because of the changes made to mass assignment it actually broke some of our specs as we didn't have some of our attributes declared as a virtus attribute, we have our own logic on getters and setters that we think would not fit into an attribute type kinda thing.

To give you some context, we are using virtus for dealing with API resources and we use attributes= to build the object from a JSON response. The reason we believe those accessors wouldn't fit into a attribute type / class is that they are like "virtual properties", used to extract and reconstruct values based on the API data.

The code would be something like this:

class Movie
  # ... a bunch of virtus attributes ...
  attribute :main_category, String
  attribute :subcategory, String

  def categories
    [self.main_category, self.subcategory]
  end

  def categories=(all_categories)
    self.main_category = all_categories.first
    self.subcategory = all_categories.last
  end
end

With that we can get extract the "composite" attribute when fetching from the API and simplify our use on the context of our application and rebuild the values when sending data back to the API.

Does it make sense to you guys? If it does, what do you think about it?

Keep up with the good work!
Cheers!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.