[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-7819":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":16,"stars7d":17,"stars30d":17,"stars90d":16,"forks30d":16,"starsTrendScore":17,"compositeScore":18,"rankGlobal":10,"rankLanguage":10,"license":19,"archived":20,"fork":20,"defaultBranch":21,"hasWiki":22,"hasPages":20,"topics":23,"createdAt":10,"pushedAt":10,"updatedAt":24,"readmeContent":25,"aiSummary":26,"trendingCount":16,"starSnapshotCount":16,"syncStatus":27,"lastSyncTime":28,"discoverSource":29},7819,"virtus","solnic\u002Fvirtus","solnic","[DISCONTINUED ] Attributes on Steroids for Plain Old Ruby Objects","",null,"Ruby",3747,228,59,49,0,1,29.08,"MIT License",false,"master",true,[],"2026-06-12 02:01:45","[gem]: https:\u002F\u002Frubygems.org\u002Fgems\u002Fvirtus\n[travis]: https:\u002F\u002Ftravis-ci.org\u002Fsolnic\u002Fvirtus\n[codeclimate]: https:\u002F\u002Fcodeclimate.com\u002Fgithub\u002Fsolnic\u002Fvirtus\n[coveralls]: https:\u002F\u002Fcoveralls.io\u002Fr\u002Fsolnic\u002Fvirtus\n[inchpages]: http:\u002F\u002Finch-ci.org\u002Fgithub\u002Fsolnic\u002Fvirtus\u002F\n\nDISCONTINUED\n------------\n\n> 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](https:\u002F\u002Fgithub.com\u002Fdry-rb\u002Fdry-types), [dry-struct](https:\u002F\u002Fgithub.com\u002Fdry-rb\u002Fdry-struct) and [dry-schema](https:\u002F\u002Fgithub.com\u002Fdry-rb\u002Fdry-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!\n>\n> @solnic\n\nVirtus\n======\n\n[![Gem Version](https:\u002F\u002Fbadge.fury.io\u002Frb\u002Fvirtus.svg)][gem]\n[![Build Status](https:\u002F\u002Ftravis-ci.org\u002Fsolnic\u002Fvirtus.svg?branch=master)][travis]\n[![Code Climate](https:\u002F\u002Fcodeclimate.com\u002Fgithub\u002Fsolnic\u002Fvirtus\u002Fbadges\u002Fgpa.svg)][codeclimate]\n[![Test Coverage](https:\u002F\u002Fcodeclimate.com\u002Fgithub\u002Fsolnic\u002Fvirtus\u002Fbadges\u002Fcoverage.svg)][codeclimate]\n[![Inline docs](http:\u002F\u002Finch-ci.org\u002Fgithub\u002Fsolnic\u002Fvirtus.svg?branch=master)][inchpages]\n\nVirtus allows you to define attributes on classes, modules or class instances with\noptional information about types, reader\u002Fwriter method visibility and coercion\nbehavior. It supports a lot of coercions and advanced mapping of embedded objects\nand collections.\n\nYou can use it in many different contexts like:\n\n* Input parameter sanitization and coercion in web applications\n* Mapping JSON to domain objects\n* Encapsulating data-access in Value Objects\n* Domain model prototyping\n\nAnd probably more.\n\nInstallation\n------------\n\n``` terminal\n$ gem install virtus\n```\n\nor in your **Gemfile**\n\n``` ruby\ngem 'virtus'\n```\n\nExamples\n--------\n\n### Using Virtus with Classes\n\nYou can create classes extended with Virtus and define attributes:\n\n``` ruby\nclass User\n  include Virtus.model\n\n  attribute :name, String\n  attribute :age, Integer\n  attribute :birthday, DateTime\nend\n\nuser = User.new(:name => 'Piotr', :age => 31)\nuser.attributes # => { :name => \"Piotr\", :age => 31, :birthday => nil }\n\nuser.name # => \"Piotr\"\n\nuser.age = '31' # => 31\nuser.age.class # => Fixnum\n\nuser.birthday = 'November 18th, 1983' # => #\u003CDateTime: 1983-11-18T00:00:00+00:00 (4891313\u002F2,0\u002F1,2299161)>\n\n# mass-assignment\nuser.attributes = { :name => 'Jane', :age => 21 }\nuser.name # => \"Jane\"\nuser.age  # => 21\n```\n\n### Cherry-picking extensions\n\n``` ruby\n# include attribute DSL + constructor + mass-assignment\nclass User\n  include Virtus.model\n\n  attribute :name, String\nend\n\nuser = User.new(:name => 'Piotr')\nuser.attributes = { :name => 'John' }\nuser.attributes\n# => {:name => 'John'}\n\n# include attribute DSL + constructor\nclass User\n  include Virtus.model(:mass_assignment => false)\n\n  attribute :name, String\nend\n\nUser.new(:name => 'Piotr')\n\n# include just the attribute DSL\nclass User\n  include Virtus.model(:constructor => false, :mass_assignment => false)\n\n  attribute :name, String\nend\n\nuser = User.new\nuser.name = 'Piotr'\n```\n\n### Using Virtus with Modules\n\nYou can create modules extended with Virtus and define attributes for later\ninclusion in your classes:\n\n```ruby\nmodule Name\n  include Virtus.module\n\n  attribute :name, String\nend\n\nmodule Age\n  include Virtus.module(:coerce => false)\n\n  attribute :age, Integer\nend\n\nclass User\n  include Name, Age\nend\n\nuser = User.new(:name => 'John', :age => 30)\n```\n\n### Dynamically Extending Instances\n\nIt's also possible to dynamically extend an object with Virtus:\n\n```ruby\nclass User\n  # nothing here\nend\n\nuser = User.new\nuser.extend(Virtus.model)\nuser.attribute :name, String\nuser.name = 'John'\nuser.name # => 'John'\n```\n\n### Default Values\n\n``` ruby\nclass Page\n  include Virtus.model\n\n  attribute :title, String\n\n  # default from a singleton value (integer in this case)\n  attribute :views, Integer, :default => 0\n\n  # default from a singleton value (boolean in this case)\n  attribute :published, Boolean, :default => false\n\n  # default from a callable object (proc in this case)\n  attribute :slug, String, :default => lambda { |page, attribute| page.title.downcase.gsub(' ', '-') }\n\n  # default from a method name as symbol\n  attribute :editor_title, String,  :default => :default_editor_title\n\n  def default_editor_title\n    published? ? title : \"UNPUBLISHED: #{title}\"\n  end\nend\n\npage = Page.new(:title => 'Virtus README')\npage.slug         # => 'virtus-readme'\npage.views        # => 0\npage.published    # => false\npage.editor_title # => \"UNPUBLISHED: Virtus README\"\n\npage.views = 10\npage.views                    # => 10\npage.reset_attribute(:views)  # => 0\npage.views                    # => 0\n```\n\n### Default values on dynamically extended instances\n\nThis requires you to set `:lazy` option because default values are set in the\nconstructor if it's set to false (which is the default setting):\n\n``` ruby\nUser = Class.new\nuser = User.new\nuser.extend(Virtus.model)\nuser.attribute :name, String, default: 'jane', lazy: true\nuser.name # => \"jane\"\n```\n\n### Embedded Value\n\n``` ruby\nclass City\n  include Virtus.model\n\n  attribute :name, String\nend\n\nclass Address\n  include Virtus.model\n\n  attribute :street,  String\n  attribute :zipcode, String\n  attribute :city,    City\nend\n\nclass User\n  include Virtus.model\n\n  attribute :name,    String\n  attribute :address, Address\nend\n\nuser = User.new(:address => {\n  :street => 'Street 1\u002F2', :zipcode => '12345', :city => { :name => 'NYC' } })\n\nuser.address.street # => \"Street 1\u002F2\"\nuser.address.city.name # => \"NYC\"\n```\n\n### Collection Member Coercions\n\n``` ruby\n# Support \"primitive\" classes\nclass Book\n  include Virtus.model\n\n  attribute :page_numbers, Array[Integer]\nend\n\nbook = Book.new(:page_numbers => %w[1 2 3])\nbook.page_numbers # => [1, 2, 3]\n\n# Support EmbeddedValues, too!\nclass Address\n  include Virtus.model\n\n  attribute :address,     String\n  attribute :locality,    String\n  attribute :region,      String\n  attribute :postal_code, String\nend\n\nclass PhoneNumber\n  include Virtus.model\n\n  attribute :number, String\nend\n\nclass User\n  include Virtus.model\n\n  attribute :phone_numbers, Array[PhoneNumber]\n  attribute :addresses,     Set[Address]\nend\n\nuser = User.new(\n  :phone_numbers => [\n    { :number => '212-555-1212' },\n    { :number => '919-444-3265' } ],\n  :addresses => [\n    { :address => '1234 Any St.', :locality => 'Anytown', :region => \"DC\", :postal_code => \"21234\" } ])\n\nuser.phone_numbers # => [#\u003CPhoneNumber:0x007fdb2d3bef88 @number=\"212-555-1212\">, #\u003CPhoneNumber:0x007fdb2d3beb00 @number=\"919-444-3265\">]\n\nuser.addresses # => #\u003CSet: {#\u003CAddress:0x007fdb2d3be448 @address=\"1234 Any St.\", @locality=\"Anytown\", @region=\"DC\", @postal_code=\"21234\">}>\n```\n\n### Hash attributes coercion\n\n``` ruby\nclass Package\n  include Virtus.model\n\n  attribute :dimensions, Hash[Symbol => Float]\nend\n\npackage = Package.new(:dimensions => { 'width' => \"2.2\", :height => 2, \"length\" => 4.5 })\npackage.dimensions # => { :width => 2.2, :height => 2.0, :length => 4.5 }\n```\n\n### IMPORTANT note about Boolean type\n\nBe aware that some libraries may do a terrible thing and define a global Boolean\nconstant which breaks virtus' constant type lookup, if you see issues with the\nboolean type you can workaround it like that:\n\n``` ruby\nclass User\n  include Virtus.model\n\n  attribute :admin, Axiom::Types::Boolean\nend\n```\n\nThis will be improved in Virtus 2.0.\n\n### IMPORTANT note about member coercions\n\nVirtus performs coercions only when a value is being assigned. If you mutate the value later on using its own\ninterfaces then coercion won't be triggered.\n\nHere's an example:\n\n``` ruby\nclass Book\n  include Virtus.model\n\n  attribute :title, String\nend\n\nclass Library\n  include Virtus.model\n\n  attribute :books, Array[Book]\nend\n\nlibrary = Library.new\n\n# This will coerce Hash to a Book instance\nlibrary.books = [ { :title => 'Introduction to Virtus' } ]\n\n# This WILL NOT COERCE the value because you mutate the books array with Array#\u003C\u003C\nlibrary.books \u003C\u003C { :title => 'Another Introduction to Virtus' }\n```\n\nA suggested solution to this problem would be to introduce your own class instead of using Array and implement\nmutation methods that perform coercions. For example:\n\n``` ruby\nclass Book\n  include Virtus.model\n\n  attribute :title, String\nend\n\nclass BookCollection \u003C Array\n  def \u003C\u003C(book)\n   if book.kind_of?(Hash)\n    super(Book.new(book))\n   else\n     super\n   end\n  end\nend\n\nclass Library\n  include Virtus.model\n\n  attribute :books, BookCollection[Book]\nend\n\nlibrary = Library.new\nlibrary.books \u003C\u003C { :title => 'Another Introduction to Virtus' }\n```\n\n### Value Objects\n\n``` ruby\nclass GeoLocation\n  include Virtus.value_object\n\n  values do\n    attribute :latitude,  Float\n    attribute :longitude, Float\n  end\nend\n\nclass Venue\n  include Virtus.value_object\n\n  values do\n    attribute :name,     String\n    attribute :location, GeoLocation\n  end\nend\n\nvenue = Venue.new(\n  :name     => 'Pub',\n  :location => { :latitude => 37.160317, :longitude => -98.437500 })\n\nvenue.location.latitude # => 37.160317\nvenue.location.longitude # => -98.4375\n\n# Supports object's equality\n\nvenue_other = Venue.new(\n  :name     => 'Other Pub',\n  :location => { :latitude => 37.160317, :longitude => -98.437500 })\n\nvenue.location === venue_other.location # => true\n```\n\n### Custom Coercions\n\n``` ruby\nrequire 'json'\n\nclass Json \u003C Virtus::Attribute\n  def coerce(value)\n    value.is_a?(::Hash) ? value : JSON.parse(value)\n  end\nend\n\nclass User\n  include Virtus.model\n\n  attribute :info, Json, default: {}\nend\n\nuser = User.new\nuser.info = '{\"email\":\"john@domain.com\"}' # => {\"email\"=>\"john@domain.com\"}\nuser.info.class # => Hash\n\n# With a custom attribute encapsulating coercion-specific configuration\nclass NoisyString \u003C Virtus::Attribute\n  def coerce(value)\n    value.to_s.upcase\n  end\nend\n\nclass User\n  include Virtus.model\n\n  attribute :scream, NoisyString\nend\n\nuser = User.new(:scream => 'hello world!')\nuser.scream # => \"HELLO WORLD!\"\n```\n\n### Private Attributes\n\n``` ruby\nclass User\n  include Virtus.model\n\n  attribute :unique_id, String, :writer => :private\n\n  def set_unique_id(id)\n    self.unique_id = id\n  end\nend\n\nuser = User.new(:unique_id => '1234-1234')\nuser.unique_id # => nil\n\nuser.unique_id = '1234-1234' # => NoMethodError: private method `unique_id='\n\nuser.set_unique_id('1234-1234')\nuser.unique_id # => '1234-1234'\n```\n\n### Overriding setters\n\n``` ruby\nclass User\n  include Virtus.model\n\n  attribute :name, String\n\n  def name=(new_name)\n    custom_name = nil\n    if new_name == \"Godzilla\"\n      custom_name = \"Can't tell\"\n    end\n    super custom_name || new_name\n  end\nend\n\nuser = User.new(name: \"Frank\")\nuser.name # => 'Frank'\n\nuser = User.new(name: \"Godzilla\")\nuser.name # => 'Can't tell'\n\n```\n\n## Strict Coercion Mode\n\nBy default Virtus returns the input value even when it couldn't coerce it to the expected type.\nIf you want to catch such cases in a noisy way you can use the strict mode in which\nVirtus raises an exception when it failed to coerce an input value.\n\n``` ruby\nclass User\n  include Virtus.model(:strict => true)\n\n  attribute :admin, Boolean\nend\n\n# this will raise an error\nUser.new :admin => \"can't really say if true or false\"\n```\n\n## Nullify Blank Strings Mode\n\nIf you want to replace empty Strings with `nil` values (since they can't be\ncoerced into the expected type), you can use the `:nullify_blank` option.\n\n``` ruby\nclass User\n  include Virtus.model(:nullify_blank => true)\n\n  attribute :birthday, Date\nend\n\nUser.new(:birthday => \"\").birthday # => nil\n```\n\n\n## Building modules with custom configuration\n\nYou can also build Virtus modules that contain their own configuration.\n\n```ruby\nYupNopeBooleans = Virtus.model { |mod|\n  mod.coerce = true\n  mod.coercer.config.string.boolean_map = { 'nope' => false, 'yup' => true }\n}\n\nclass User\n  include YupNopeBooleans\n\n  attribute :name, String\n  attribute :admin, Boolean\nend\n\n# Or just include the module straight away ...\nclass User\n  include Virtus.model(:coerce => false)\n\n  attribute :name, String\n  attribute :admin, Boolean\nend\n```\n\n## Attribute Finalization and Circular Dependencies\n\nIf a type references another type which happens to not be available yet you need\nto use lazy-finalization of attributes and finalize virtus manually after all\ntypes have been already loaded:\n\n``` ruby\n# in blog.rb\nclass Blog\n  include Virtus.model(:finalize => false)\n\n  attribute :posts, Array['Post']\nend\n\n# in post.rb\nclass Post\n  include Virtus.model(:finalize => false)\n\n  attribute :blog, 'Blog'\nend\n\n# after loading both files just do:\nVirtus.finalize\n\n# constants will be resolved:\nBlog.attribute_set[:posts].member_type.primitive # => Post\nPost.attribute_set[:blog].type.primitive # => Blog\n```\n\n## Plugins \u002F Extensions\n\nList of plugins\u002Fextensions that add features to Virtus:\n\n* [virtus-localized](https:\u002F\u002Fgithub.com\u002FXescuGC\u002Fvirtus-localized): Localize the attributes\n* [virtus-relations](https:\u002F\u002Fgithub.com\u002Fsmanolloff\u002Fvirtus-relations): Add relations to Virtus objects\n\nRuby version support\n--------------------\n\nVirtus is known to work correctly with the following rubies:\n\n* 1.9.3\n* 2.0.0\n* 2.1.2\n* jruby\n* (probably) rbx\n\nCredits\n-------\n\n* Dan Kubb ([dkubb](https:\u002F\u002Fgithub.com\u002Fdkubb))\n* Chris Corbyn ([d11wtq](https:\u002F\u002Fgithub.com\u002Fd11wtq))\n* Emmanuel Gomez ([emmanuel](https:\u002F\u002Fgithub.com\u002Femmanuel))\n* Fabio Rehm ([fgrehm](https:\u002F\u002Fgithub.com\u002Ffgrehm))\n* Ryan Closner ([rclosner](https:\u002F\u002Fgithub.com\u002Frclosner))\n* Markus Schirp ([mbj](https:\u002F\u002Fgithub.com\u002Fmbj))\n* Yves Senn ([senny](https:\u002F\u002Fgithub.com\u002Fsenny))\n\nContributing\n-------------\n\n* Fork the project.\n* Make your feature addition or bug fix.\n* Add tests for it. This is important so I don't break it in a\n  future version unintentionally.\n* Commit, do not mess with Rakefile or version\n  (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)\n* Send me a pull request. Bonus points for topic branches.\n","Virtus 是一个用于增强普通 Ruby 对象属性功能的库。它允许开发者在类、模块或类实例上定义具有类型信息、读写方法可见性和强制转换行为的属性，并支持多种数据类型的强制转换及嵌入对象和集合的高级映射。Virtus 适用于需要对输入参数进行清理与类型转换的Web应用、将JSON数据映射到领域模型对象、封装值对象中的数据访问逻辑以及快速原型化领域模型等场景。尽管该项目已经停止维护，但其设计理念和技术实现为后续开发如dry-types、dry-struct 和 dry-schema 等项目奠定了基础。",2,"2026-06-11 03:14:34","top_language"]