Repository: yammer/model_attribute
Branch: master
Commit: 4bc9b26bdbe9
Files: 17
Total size: 48.3 KB
Directory structure:
gitextract__nywioe0/
├── .gitignore
├── .rspec
├── .travis.yml
├── CHANGELOG.md
├── Gemfile
├── Guardfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── lib/
│ ├── model_attribute/
│ │ ├── casts.rb
│ │ ├── errors.rb
│ │ └── version.rb
│ └── model_attribute.rb
├── model_attribute.gemspec
├── performance_comparison.rb
└── spec/
├── model_attributes_spec.rb
└── spec_helper.rb
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
/.bundle/
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
*.bundle
*.so
*.o
*.a
*.gem
mkmf.log
================================================
FILE: .rspec
================================================
--color
--require spec_helper
--warnings
================================================
FILE: .travis.yml
================================================
language: ruby
install:
- bundle install --without interactive
rvm:
- "2.4.10"
- "2.5.8"
- "2.6.6"
- "2.7.1"
- "jruby-9.1.17.0"
- "jruby-9.2.11.1"
================================================
FILE: CHANGELOG.md
================================================
# Change Log
All notable changes to this project will be documented in this file.
## 3.2.0
- Support `Float` type with `:float`.
- Tested with Ruby 2.4.
## 3.1.0
- Allow strings 'true' and 'false' to be assigned to boolean attributes and be
cast as expected.
## 3.0.0
- **Breaking change**: All casting errors raise `ArgumentError`. Previously some
errors during casting would raise `RuntimeError`.
Thanks to [@gotascii](https://github.com/gotascii) for the report.
## 2.1.0
- **New feature**: default values. Allows you to specify a default value like
so:
```
class User
attribute :name, :string, default: 'Michelle'
end
User.new.name
# => 'Michelle'
```
## 2.0.0
- **Breaking change**: Rename to `ModelAttribute` (no trailing 's') to avoid name
clash with another gem.
## 1.4.0
- **New method**: #changes_for_json Returns a hash from attribute name to its
new value, suitable for serialization to a JSON string. Easily generate the
payload to send in an HTTP PUT to a web service.
- **New attribute type: json** Store an array/hash/etc. built using the basic
JSON data types: nil, numeric, string, boolean, hash and array.
## 1.3.0
- **Breaking change**: Parsing an integer to a time attribute, the integer is
treated as the number of milliseconds since the epoch (not the number of
seconds). `attributes_as_json` emits integers for time attributes.
## 1.2.0
- **Breaking change**: `attributes_as_json` removed; replaced with
`attributes_for_json`. You will have to serialize this yourself:
`Oj.dump(attributes_for_json, mode: :strict)`. This allows you to modify the
returned hash before serializing it.
## 1.1.0
- Initial release
================================================
FILE: Gemfile
================================================
source 'https://rubygems.org'
# Specify your gem's dependencies in model_attribute.gemspec
gemspec
group 'interactive' do
gem 'rspec-nc'
gem 'guard'
gem 'guard-rspec'
end
================================================
FILE: Guardfile
================================================
# A sample Guardfile
# More info at https://github.com/guard/guard#readme
## Uncomment and set this to only include directories you want to watch
# directories %w(app lib config test spec feature)
## Uncomment to clear the screen before every task
# clearing :on
## Make Guard exit when config is changed so it can be restarted
#
## Note: if you want Guard to automatically start up again, run guard in a
## shell loop, e.g.:
#
# $ while bundle exec guard; do echo "Restarting Guard..."; done
#
## Note: if you are using the `directories` clause above and you are not
## watching the project directory ('.'), the you will want to move the Guardfile
## to a watched dir and symlink it back, e.g.
#
# $ mkdir config
# $ mv Guardfile config/
# $ ln -s config/Guardfile .
#
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
#
watch ("Guardfile") do
UI.info "Exiting because Guard must be restarted for changes to take effect"
exit 0
end
guard :rspec, cmd: "bundle exec rspec --format=Nc --format=documentation", all_on_start: true do
require "guard/rspec/dsl"
dsl = Guard::RSpec::Dsl.new(self)
# RSpec files
rspec = dsl.rspec
watch(rspec.spec_helper) { rspec.spec_dir }
watch(rspec.spec_support) { rspec.spec_dir }
watch(rspec.spec_files)
# Ruby files
ruby = dsl.ruby
dsl.watch_spec_files_for(ruby.lib_files)
watch(%r{lib/*}) { 'spec' }
end
================================================
FILE: LICENSE.txt
================================================
ModelAttribute
Copyright (c) 2015 Microsoft Corporation
All rights reserved.
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# ModelAttribute [](http://badge.fury.io/rb/model_attribute) [](https://travis-ci.org/yammer/model_attribute)
Simple attributes for a non-ActiveRecord model.
- Stores attributes in instance variables.
- Type casting and checking.
- Dirty tracking.
- List attribute names and values.
- Default values for attributes
- Handles integers, floats, booleans, strings and times - a set of types that are very
easy to persist to and parse from JSON.
- Supports efficient serialization of attributes to JSON.
- Mass assignment - handy for initializers.
Why not [Virtus][virtus-gem]? Virtus doesn't provide dirty tracking, and
doesn't integrate with [ActiveModel::Dirty][am-dirty]. So if you're not using
ActiveRecord, but you need attributes with dirty tracking, ModelAttribute may be
what you're after. For example, it works very well for a model that fronts an
HTTP web service, and you want dirty tracking so you can PATCH appropriately.
Also in favor of ModelAttribute:
- It's simple - less than [200 lines of code][source].
- It supports efficient serialization and deserialization to/from JSON.
[virtus-gem]:https://github.com/solnic/virtus
[am-dirty]:https://github.com/rails/rails/blob/master/activemodel/lib/active_model/dirty.rb
[source]:https://github.com/yammer/model_attribute/blob/master/lib/model_attribute.rb
## Integrating with Rails
If you're using ModelAttribute in a Rails application, you will probably want to
augment your model with other methods to make it behave more like
`ActiveRecord`. `ActiveModel` provides a very useful set of mixins,
described in the [Rails guide][active-model-guide]. You can also see an example
of the methods we found useful at Yammer described in [this blog
post][yammer-blog-post], with full source in [this Gist][active-record-mimic].
[active-model-guide]:https://guides.rubyonrails.org/active_model_basics.html
[yammer-blog-post]:https://medium.com/yammer-engineering/activerecord-stole-my-data-and-now-i-want-it-back-3041ac4eb163
[active-record-mimic]:https://gist.github.com/dwaller/5474304cfea354a9701d
## Usage
```ruby
require 'model_attribute'
class User
extend ModelAttribute
attribute :id, :integer
attribute :paid, :boolean
attribute :name, :string
attribute :created_at, :time
attribute :grades, :json
def initialize(attributes = {})
set_attributes(attributes)
end
end
User.attributes # => [:id, :paid, :name, :created_at, :grades]
user = User.new
user.attributes # => {:id=>nil, :paid=>nil, :name=>nil, :created_at=>nil, :grades=>nil}
# An integer attribute
user.id # => nil
user.id = 3
user.id # => 3
# Stores values that convert cleanly to an integer
user.id = '5'
user.id # => 5
# Protects you against nonsense assignment
user.id = '5error'
ArgumentError: invalid value for Integer(): "5error"
# A boolean attribute
user.paid # => nil
user.paid = true
# Booleans also define a predicate method (ending in '?')
user.paid? # => true
# Conversion from strings used by databases.
user.paid = 'f'
user.paid # => false
user.paid = 't'
user.paid # => true
user.paid = 'false'
user.paid # => false
user.paid = 'true'
user.paid # => true
# A :time attribute
user.created_at = Time.now
user.created_at # => 2015-01-08 15:57:05 +0000
# Also converts from other reasonable time formats
user.created_at = "2014-12-25 14:00:00 +0100"
user.created_at # => 2014-12-25 13:00:00 +0000
user.created_at = Date.parse('2014-01-08')
user.created_at # => 2014-01-08 00:00:00 +0000
user.created_at = DateTime.parse("2014-12-25 13:00:45")
user.created_at # => 2014-12-25 13:00:45 +0000
# Convert from seconds since the epoch
user.created_at = Time.now.to_f
user.created_at # => 2015-01-08 16:23:02 +0000
# Or milliseconds since the epoch
user.created_at = 1420734182000
user.created_at # => 2015-01-08 16:23:02 +0000
# A :json attribute is schemaless and accepts the basic JSON types - hash,
# array, nil, numeric, string and boolean.
user.grades = {'maths' => 'A', 'history' => 'C'}
user.grades # => {"maths"=>"A", "history"=>"C"}
user.grades = ['A', 'A*', 'C']
user.grades # => ["A", "A*", "C"]
user.grades = 'AAB'
user.grades # => "AAB"
user.grades = Time.now
# => ArgumentError: JSON only supports nil, numeric, string, boolean and arrays and hashes of those.
# read_attribute and write_attribute methods
user.read_attribute(:created_at)
user.write_attribute(:name, 'Fred')
# View attributes
user.attributes # => {:id=>5, :paid=>true, :name=>"Fred", :created_at=>2015-01-08 15:57:05 +0000, :grades=>{"maths"=>"A", "history"=>"C"}}
user.inspect # => "#<User id: 5, paid: true, name: \"Fred\", created_at: 2015-01-08 15:57:05 +0000, grades: {\"maths\"=>\"A\", \"history\"=>\"C\"}>"
# Mass assignment
user.set_attributes(name: "Sally", paid: false)
user.attributes # => {:id=>5, :paid=>false, :name=>"Sally", :created_at=>2015-01-08 15:57:05 +0000}
# Efficient JSON serialization and deserialization.
# Attributes with nil values are omitted.
user.attributes_for_json
# => {"id"=>5, "paid"=>true, "name"=>"Fred", "created_at"=>1421171317762}
require 'oj'
Oj.dump(user.attributes_for_json, mode: :strict)
# => "{\"id\":5,\"paid\":true,\"name\":\"Fred\",\"created_at\":1421171317762}"
user2 = User.new(Oj.load(json, strict: true))
# Change tracking. A much smaller set of functions than that provided by
# ActiveModel::Dirty.
user.changes # => {:id=>[nil, 5], :paid=>[nil, true], :created_at=>[nil, 2015-01-08 15:57:05 +0000], :name=>[nil, "Fred"]}
user.name_changed? # => true
# If you need the new values to send as a PUT to a web service
user.changes_for_json # => {"id"=>5, "paid"=>true, "name"=>"Fred", "created_at"=>1421171317762}
# If you're imitating ActiveRecord behaviour, changes are cleared after
# after_save callbacks, but before after_commit callbacks.
user.changes.clear
user.changes # => {}
# Equality if all the attribute values match
another = User.new
another.id = 5
another.paid = true
another.created_at = user.created_at
another.name = 'Fred'
user == another # => true
user === another # => true
user.eql? another # => true
# Making some attributes private
class User
extend ModelAttribute
attribute :events, :string
private :events=
def initialize(attributes)
# Pass flag to set_attributes to allow setting attributes with private writers
set_attributes(attributes, true)
end
def add_event(new_event)
events ||= ""
events += new_event
end
end
# Supporting default attributes
class UserWithDefaults
extend ModelAttribute
attribute :name, :string, default: 'Charlie'
end
UserWithDefaults.attribute_defaults # => {:name=>"Charlie"}
user = UserWithDefaults.new
user.name # => "Charlie"
user.read_attribute(:name) # => "Charlie"
user.attributes # => {:name=>"Charlie"}
# attributes_for_json omits defaults to keep the JSON compact
user.attributes_for_json # => {}
# You can add them back in if you need them
user.attributes_for_json.merge(user.class.attribute_defaults) # => {:name=>"Charlie"}
# A default isn't a change
user.changes # => {}
user.changes_for_json # => {}
user.name = 'Bob'
user.attributes # => {:name=>"Bob"}
```
## Installation
Add this line to your application's Gemfile:
```ruby
gem 'model_attribute'
```
And then execute:
$ bundle
Or install it yourself as:
$ gem install model_attribute
## Testing
Running specs:
$ rspec
## Contributing
1. [Fork it](https://github.com/yammer/model_attribute/fork)
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request
## Code of Conduct
This project has adopted the [Microsoft Open Source Code of
Conduct](https://opensource.microsoft.com/codeofconduct/). For more information
see the [Code of Conduct
FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact
[opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional
questions or comments.
================================================
FILE: Rakefile
================================================
require 'bundler/gem_tasks'
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec) do |t|
t.pattern = Dir.glob('spec/**/*_spec.rb')
t.rspec_opts = '--format documentation'
end
task :default => :spec
================================================
FILE: lib/model_attribute/casts.rb
================================================
module ModelAttribute
module Casts
class << self
def cast(value, type)
return nil if value.nil?
case type
when :integer
int = Integer(value)
float = Float(value)
raise ArgumentError, "Can't cast #{value.inspect} to an integer without loss of precision" unless int == float
int
when :float
Float(value)
when :boolean
if !!value == value
value
elsif value == 't' || value == 'true'
true
elsif value == 'f' || value == 'false'
false
else
raise ArgumentError, "Can't cast #{value.inspect} to boolean"
end
when :time
case value
when Time
value
when Date, DateTime
value.to_time
when Integer
# Assume milliseconds since epoch.
Time.at(value / 1000.0)
when Numeric
# Numeric, but not an integer. Assume seconds since epoch.
Time.at(value)
else
Time.parse(value)
end
when :string
String(value)
when :json
if valid_json?(value)
value
else
raise ArgumentError, "JSON only supports nil, numeric, string, boolean and arrays and hashes of those."
end
else
raise UnsupportedTypeError.new(type)
end
end
private
def valid_json?(value)
(value == nil ||
value == true ||
value == false ||
value.is_a?(Numeric) ||
value.is_a?(String) ||
(value.is_a?(Array) && valid_json_array?(value)) ||
(value.is_a?(Hash) && valid_json_hash?(value) ))
end
def valid_json_array?(array)
array.all? { |value| valid_json?(value) }
end
def valid_json_hash?(hash)
hash.all? do |key, value|
key.is_a?(String) && valid_json?(value)
end
end
end
end
end
================================================
FILE: lib/model_attribute/errors.rb
================================================
module ModelAttribute
class InvalidAttributeNameError < StandardError
def initialize(attribute_name)
super "Invalid attribute name #{attribute_name.inspect}"
end
end
class UnsupportedTypeError < StandardError
def initialize(type)
types_list = ModelAttribute::SUPPORTED_TYPES.map(&:inspect).join(', ')
super "Unsupported type #{type.inspect}. Must be one of #{types_list}."
end
end
end
================================================
FILE: lib/model_attribute/version.rb
================================================
module ModelAttribute
VERSION = "3.2.0"
end
================================================
FILE: lib/model_attribute.rb
================================================
require "model_attribute/version"
require "model_attribute/casts"
require "model_attribute/errors"
require "time"
module ModelAttribute
SUPPORTED_TYPES = [:integer, :float, :boolean, :string, :time, :json]
def self.extended(base)
base.send(:include, InstanceMethods)
base.instance_variable_set('@attribute_names', [])
base.instance_variable_set('@attribute_types', {})
base.instance_variable_set('@attribute_defaults', {})
end
def attribute(name, type, opts = {})
name = name.to_sym
type = type.to_sym
raise UnsupportedTypeError.new(type) unless SUPPORTED_TYPES.include?(type)
@attribute_names << name
@attribute_types[name] = type
@attribute_defaults[name] = opts[:default] if opts.key?(:default)
self.class_eval(<<-CODE, __FILE__, __LINE__ + 1)
def #{name}=(value)
write_attribute(#{name.inspect}, value, #{type.inspect})
end
def #{name}
read_attribute(#{name.inspect})
end
def #{name}_changed?
!!changes[#{name.inspect}]
end
CODE
if type == :boolean
self.class_eval(<<-CODE, __FILE__, __LINE__ + 1)
def #{name}?
!!read_attribute(#{name.inspect})
end
CODE
end
end
def attributes
@attribute_names
end
def attribute_defaults
@attribute_defaults
end
module InstanceMethods
def write_attribute(name, value, type = nil)
name = name.to_sym
# Don't want to expose attribute types as a method on the class, so access
# via a back door.
type ||= self.class.instance_variable_get('@attribute_types')[name]
raise InvalidAttributeNameError.new(name) unless type
value = Casts.cast(value, type)
return if value == read_attribute(name)
if changes.has_key? name
original = changes[name].first
else
original = read_attribute(name)
end
if original == value
changes.delete(name)
else
changes[name] = [original, value]
end
instance_variable_set("@#{name}", value)
end
def read_attribute(name)
ivar_name = "@#{name}"
if instance_variable_defined?(ivar_name)
instance_variable_get(ivar_name)
elsif !self.class.attributes.include?(name.to_sym)
raise InvalidAttributeNameError.new(name)
else
self.class.attribute_defaults[name.to_sym]
end
end
def attributes
self.class.attributes.each_with_object({}) do |name, attributes|
attributes[name] = read_attribute(name)
end
end
def set_attributes(attributes, can_set_private_attrs = false)
attributes.each do |key, value|
send("#{key}=", value) if respond_to?("#{key}=", can_set_private_attrs)
end
end
def ==(other)
return true if equal?(other)
if respond_to?(:id)
other.kind_of?(self.class) && id == other.id
else
other.kind_of?(self.class) && attributes == other.attributes
end
end
alias_method :eql?, :==
def changes
@changes ||= {}
end
# Attributes suitable for serializing to a JSON string.
#
# - Attribute keys are strings (for 'strict' JSON dumping).
# - Attributes with a default or nil value are omitted to speed serialization.
# - :time attributes are serialized as an Integer giving the number of
# milliseconds since the epoch.
def attributes_for_json
self.class.attributes.each_with_object({}) do |name, attributes|
value = read_attribute(name)
if value != self.class.attribute_defaults[name.to_sym]
value = (value.to_f * 1000).to_i if value.is_a? Time
attributes[name.to_s] = value
end
end
end
# Changed attributes suitable for serializing to a JSON string. Returns a
# hash from attribute name (as a string) to the new value of that attribute,
# for attributes that have changed.
#
# - :time attributes are serialized as an Integer giving the number of
# milliseconds since the epoch.
# - Unlike attributes_for_json, attributes that have changed to a nil value
# *are* included.
def changes_for_json
hash = {}
changes.each do |attr_name, (_old_value, new_value)|
new_value = (new_value.to_f * 1000).to_i if new_value.is_a? Time
hash[attr_name.to_s] = new_value
end
hash
end
# Includes the class name and all the attributes and their values. e.g.
# "#<User id: 1, paid: true, name: \"Fred\", created_at: 2014-12-25 08:00:00 +0000>"
def inspect
attribute_string = self.class.attributes.map do |key|
"#{key}: #{read_attribute(key).inspect}"
end.join(', ')
"#<#{self.class} #{attribute_string}>"
end
end
end
================================================
FILE: model_attribute.gemspec
================================================
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'model_attribute/version'
Gem::Specification.new do |spec|
spec.name = "model_attribute"
spec.version = ModelAttribute::VERSION
spec.authors = ["David Waller"]
spec.email = ["dwaller@yammer-inc.com"]
spec.summary = %q{Attributes for non-ActiveRecord models}
spec.description = <<-EOF
Attributes for non-ActiveRecord models.
Smaller and simpler than Virtus, and adds dirty tracking.
EOF
spec.homepage = "https://github.com/yammer/model_attribute"
spec.license = "MIT"
spec.files = `git ls-files -z`.split("\x0")
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ["lib"]
spec.add_development_dependency "bundler"
spec.add_development_dependency "rake", ">= 12.3.3"
spec.add_development_dependency "rspec", "~> 3.1"
end
================================================
FILE: performance_comparison.rb
================================================
require 'benchmark'
$LOAD_PATH << "lib"
Benchmark.bm(41) do |bm|
bm.report("Virtus load") do
require 'virtus'
class VirtusUser
include Virtus.model
attribute :id, Integer
attribute :name, String
attribute :paid, Boolean
attribute :updated_at, DateTime
end
end
bm.report("ModelAttribute load") do
require_relative 'lib/model_attribute'
class ModelAttributeUser
extend ModelAttribute
attribute :id, :integer
attribute :name, :string
attribute :paid, :boolean
attribute :updated_at, :time
end
end
end
Benchmark.bm(41) do |bm|
vu = VirtusUser.new
mau = ModelAttributeUser.new
bm.report("Virtus assign integer") { 10_000.times { vu.id = rand(100_000) } }
bm.report("ModelAttribute assign integer") { 10_000.times { mau.id = rand(100_000) } }
bm.report("Virtus assign integer from string") { 10_000.times { vu.id = rand(100_000).to_s } }
bm.report("ModelAttribute assign integer from string") { 10_000.times { mau.id = rand(100_000).to_s } }
bm.report("Virtus assign time") { 10_000.times { vu.updated_at = Time.now } }
bm.report("ModelAttribute assign time") { 10_000.times { mau.updated_at = Time.now } }
bm.report("Virtus assign DateTime") { 10_000.times { vu.updated_at = DateTime.now } }
bm.report("ModelAttribute assign DateTime") { 10_000.times { mau.updated_at = DateTime.now } }
bm.report("Virtus assign time from epoch") { 10_000.times { vu.updated_at = Time.now.to_f } }
bm.report("ModelAttribute assign time from epoch") { 10_000.times { mau.updated_at = Time.now.to_f } }
bm.report("Virtus assign time from string") { 10_000.times { vu.updated_at = "2014-12-25 06:00:00" } }
bm.report("ModelAttribute assign time from string") { 10_000.times { mau.updated_at = "2014-12-25 06:00:00" } }
end
__END__
$ ruby -v
ruby 1.9.3p545 (2014-02-24 revision 45159) [x86_64-darwin13.3.0]
$ ruby performance_comparison.rb
user system total real
Virtus load 0.120000 0.040000 0.160000 ( 0.207931)
ModelAttribute load 0.020000 0.010000 0.030000 ( 0.027237)
user system total real
Virtus assign integer 0.010000 0.000000 0.010000 ( 0.013906)
ModelAttribute assign integer 0.030000 0.000000 0.030000 ( 0.033674)
Virtus assign integer from string 0.170000 0.000000 0.170000 ( 0.171892)
ModelAttribute assign integer from string 0.050000 0.000000 0.050000 ( 0.042726)
Virtus assign time 0.080000 0.000000 0.080000 ( 0.089792)
ModelAttribute assign time 0.060000 0.000000 0.060000 ( 0.057887)
Virtus assign DateTime 0.030000 0.000000 0.030000 ( 0.026447)
ModelAttribute assign DateTime 0.200000 0.000000 0.200000 ( 0.204524)
Virtus assign time from epoch 0.230000 0.010000 0.240000 ( 0.225557)
ModelAttribute assign time from epoch 0.110000 0.000000 0.110000 ( 0.113315)
Virtus assign time from string 0.260000 0.000000 0.260000 ( 0.264467)
ModelAttribute assign time from string 0.450000 0.000000 0.450000 ( 0.444686)
================================================
FILE: spec/model_attributes_spec.rb
================================================
class User
extend ModelAttribute
attribute :id, :integer
attribute :paid, :boolean
attribute :name, :string
attribute :created_at, :time
attribute :profile, :json
attribute :reward_points, :integer, default: 0
attribute :win_rate, :float
def initialize(attributes = {})
set_attributes(attributes)
end
end
class UserWithoutId
extend ModelAttribute
attribute :paid, :boolean
attribute :name, :string
attribute :created_at, :time
def initialize(attributes = {})
set_attributes(attributes)
end
end
RSpec.describe "a class using ModelAttribute" do
describe "class methods" do
describe ".attribute" do
context "passed an unrecognised type" do
it "raises an error" do
expect do
User.attribute :address, :custom_type
end.to raise_error(ModelAttribute::UnsupportedTypeError,
"Unsupported type :custom_type. " +
"Must be one of :integer, :float, :boolean, :string, :time, :json.")
end
end
end
describe ".attributes" do
it "returns an array of attribute names as symbols" do
expect(User.attributes).to eq([:id, :paid, :name, :created_at, :profile, :reward_points, :win_rate])
end
end
describe ".attribute_defaults" do
it "returns a hash of attributes that have non-nil defaults" do
expect(User.attribute_defaults).to eq({reward_points: 0})
end
end
end
describe "an instance of the class" do
let(:user) { User.new }
describe "an integer attribute (id)" do
it "is nil when unset" do
expect(user.id).to be_nil
end
it "stores an integer" do
user.id = 3
expect(user.id).to eq(3)
end
it "stores an integer passed as a float" do
user.id = 3.0
expect(user.id).to eq(3)
end
it "raises when passed a float with non-zero decimal part" do
expect { user.id = 3.3 }.to raise_error(ArgumentError)
end
it "parses an integer string" do
user.id = '3'
expect(user.id).to eq(3)
end
it "raises if passed a string it can't parse" do
expect { user.id = '3a' }.to raise_error(ArgumentError,
/invalid value for Integer.*: "3a"/)
end
it "stores nil" do
user.id = 3
user.id = nil
expect(user.id).to be_nil
end
it "does not provide an id? method" do
expect(user).to_not respond_to(:id?)
expect { user.id? }.to raise_error(NoMethodError)
end
end
describe "a float attribute (win_rate)" do
it "stores a float" do
user.win_rate = 35.62
expect(user.win_rate).to eq(35.62)
end
it "parses a float string" do
user.win_rate = 35.62
expect(user.win_rate).to eq(35.62)
end
end
describe "a boolean attribute (paid)" do
it "is nil when unset" do
expect(user.paid).to be_nil
end
it "stores true" do
user.paid = true
expect(user.paid).to eq(true)
end
it "stores false" do
user.paid = false
expect(user.paid).to eq(false)
end
it "parses 't' as true" do
user.paid = 't'
expect(user.paid).to eq(true)
end
it "parses 'f' as false" do
user.paid = 'f'
expect(user.paid).to eq(false)
end
it "parses 'false' to false" do
user.paid = 'false'
expect(user.paid).to eq(false)
end
it "parses 'true' to true" do
user.paid = 'true'
expect(user.paid).to eq(true)
end
it "raises if passed a string it can't parse" do
expect { user.paid = '3a' }.to raise_error(ArgumentError,
'Can\'t cast "3a" to boolean')
end
it "stores nil" do
user.paid = true
user.paid = nil
expect(user.paid).to be_nil
end
describe "#paid?" do
it "returns false when unset" do
expect(user.paid?).to eq(false)
end
it "returns false for false attributes" do
user.paid = false
expect(user.paid?).to eq(false)
end
it "returns true for true attributes" do
user.paid = true
expect(user.paid?).to eq(true)
end
end
end
describe "a string attribute (name)" do
it "is nil when unset" do
expect(user.name).to be_nil
end
it "stores a string" do
user.name = 'Fred'
expect(user.name).to eq('Fred')
end
it "casts an integer to a string" do
user.name = 3
expect(user.name).to eq('3')
end
it "stores nil" do
user.name = 'Fred'
user.name = nil
expect(user.name).to be_nil
end
it "does not provide a name? method" do
expect(user).to_not respond_to(:name?)
expect { user.name? }.to raise_error(NoMethodError)
end
end
describe "a time attribute (created_at)" do
let(:now_time) { Time.now }
it "is nil when unset" do
expect(user.created_at).to be_nil
end
it "stores a Time object" do
user.created_at = now_time
expect(user.created_at).to eq(now_time)
end
it "parses floats as seconds past the epoch" do
user.created_at = now_time.to_f
# Going via float loses precision, so use be_within
expect(user.created_at).to be_within(0.0001).of(now_time)
expect(user.created_at).to be_a_kind_of(Time)
end
it "parses integers as milliseconds past the epoch" do
user.created_at = (now_time.to_f * 1000).to_i
# Truncating to milliseconds loses precision, so use be_within
expect(user.created_at).to be_within(0.001).of(now_time)
expect(user.created_at).to be_a_kind_of(Time)
end
it "parses strings to date/times" do
user.created_at = "2014-12-25 14:00:00 +0100"
expect(user.created_at).to eq(Time.new(2014, 12, 25, 13, 00, 00, 0))
end
it "raises for unparseable strings" do
expect { user.created_at = "Today, innit?" }.to raise_error(ArgumentError,
'no time information in "Today, innit?"')
end
it "converts Dates to Time" do
user.created_at = Date.parse("2014-12-25")
expect(user.created_at).to eq(Time.new(2014, 12, 25, 00, 00, 00))
end
it "converts DateTime to Time" do
user.created_at = DateTime.parse("2014-12-25 13:00:45 +0000")
expect(user.created_at.utc).to eq(Time.new(2014, 12, 25, 13, 00, 45, 0))
end
it "stores nil" do
user.created_at = now_time
user.created_at = nil
expect(user.created_at).to be_nil
end
it "does not provide a created_at? method" do
expect(user).to_not respond_to(:created_at?)
expect { user.created_at? }.to raise_error(NoMethodError)
end
end
describe "a json attribute (profile)" do
it "is nil when unset" do
expect(user.profile).to be_nil
end
it "stores a string" do
user.profile = 'Incomplete'
expect(user.profile).to eq('Incomplete')
end
it "stores an integer" do
user.profile = 3
expect(user.profile).to eq(3)
end
it "stores true" do
user.profile = true
expect(user.profile).to eq(true)
end
it "stores false" do
user.profile = false
expect(user.profile).to eq(false)
end
it "stores an array" do
user.profile = [1, 2, 3]
expect(user.profile).to eq([1, 2, 3])
end
it "stores a hash" do
user.profile = {'skill' => 8}
expect(user.profile).to eq({'skill' => 8})
end
it "stores nested hashes and arrays" do
json = {'array' => [1,
2,
true,
{'inner' => true},
['inside', {}]
],
'hash' => {'getting' => {'nested' => 'yes'}},
'boolean' => true
}
user.profile = json
expect(user.profile).to eq(json)
end
it "raises when passed an object not supported by JSON" do
expect { user.profile = Object.new }.to raise_error(ArgumentError,
"JSON only supports nil, numeric, string, boolean and arrays and hashes of those.")
end
it "raises when passed a hash with a non-string key" do
expect { user.profile = {1 => 'first'} }.to raise_error(ArgumentError,
"JSON only supports nil, numeric, string, boolean and arrays and hashes of those.")
end
it "raises when passed a hash with an unsupported value" do
expect { user.profile = {'first' => :symbol} }.to raise_error(ArgumentError,
"JSON only supports nil, numeric, string, boolean and arrays and hashes of those.")
end
it "raises when passed an array with an unsupported value" do
expect { user.profile = [1, 2, nil, :symbol] }.to raise_error(ArgumentError,
"JSON only supports nil, numeric, string, boolean and arrays and hashes of those.")
end
it "stores nil" do
user.profile = {'foo' => 'bar'}
user.profile = nil
expect(user.profile).to be_nil
end
it "does not provide a profile? method" do
expect(user).to_not respond_to(:profile?)
expect { user.profile? }.to raise_error(NoMethodError)
end
end
describe 'a defaulted attribute (reward_points)' do
it "returns the default when unset" do
expect(user.reward_points).to eq(0)
end
end
describe "#write_attribute" do
it "does the same casting as using the writer method" do
user.write_attribute(:id, '3')
expect(user.id).to eq(3)
end
it "raises an error if passed an invalid attribute name" do
expect do
user.write_attribute(:spelling_mistake, '3')
end.to raise_error(ModelAttribute::InvalidAttributeNameError,
"Invalid attribute name :spelling_mistake")
end
end
describe "#read_attribute" do
it "returns the value of an attribute that has been set" do
user.write_attribute(:id, 3)
expect(user.read_attribute(:id)).to eq(user.id)
end
it "returns nil for an attribute that has not been set" do
expect(user.read_attribute(:id)).to be_nil
end
context "for an attribute with a default" do
it "returns the default if the attribute has not been set" do
expect(user.read_attribute(:reward_points)).to eq(0)
end
end
it "raises an error if passed an invalid attribute name" do
expect do
user.read_attribute(:spelling_mistake)
end.to raise_error(ModelAttribute::InvalidAttributeNameError,
"Invalid attribute name :spelling_mistake")
end
end
describe "#changes" do
let(:changes) { user.changes }
context "for a model instance created with no attributes except defaults" do
it "is empty" do
expect(changes).to be_empty
end
end
context "when an attribute is set via a writer method" do
before(:each) { user.id = 3 }
it "has an entry from attribute name to [old, new] pair" do
expect(changes).to include(:id => [nil, 3])
end
context "when an attribute is set again" do
before(:each) { user.id = 5 }
it "shows the latest value for the attribute" do
expect(changes).to include(:id => [nil, 5])
end
end
context "when an attribute is set back to its original value" do
before(:each) { user.id = nil }
it "does not have an entry for the attribute" do
expect(changes).to_not include(:id)
end
end
end
end
describe "#changes_for_json" do
let(:changes_for_json) { user.changes_for_json }
context "for a model instance created with no attributes" do
it "is empty" do
expect(changes_for_json).to be_empty
end
end
context "when an attribute is set via a writer method" do
before(:each) { user.id = 3 }
it "has an entry from attribute name (as a string) to the new value" do
expect(changes_for_json).to include('id' => 3)
end
context "when an attribute is set again" do
before(:each) { user.id = 5 }
it "shows the latest value for the attribute" do
expect(changes_for_json).to include('id' => 5)
end
end
context "when an attribute is set back to its original value" do
before(:each) { user.id = nil }
it "does not have an entry for the attribute" do
expect(changes_for_json).to_not include('id')
end
end
context "if the returned hash is modified" do
before(:each) { user.changes_for_json.clear }
it "does not affect subsequent results from changes_for_json" do
expect(changes_for_json).to include('id' => 3)
end
end
end
it "serializes time attributes as JSON integer" do
user.created_at = Time.now
expect(changes_for_json).to include("created_at" => kind_of(Integer))
end
end
describe "#id_changed?" do
context "with no changes" do
it "returns false" do
expect(user.id_changed?).to eq(false)
end
end
context "with changes" do
before(:each) { user.id = 3 }
it "returns true" do
expect(user.id_changed?).to eq(true)
end
end
end
describe "#attributes" do
let(:time_now) { Time.now }
before(:each) do
user.id = 1
user.paid = true
user.created_at = time_now
end
it "returns a hash including each set attribute" do
expect(user.attributes).to include(id: 1, paid: true, created_at: time_now)
end
it "returns a hash with a nil value for each unset attribute" do
expect(user.attributes).to include(name: nil)
end
end
describe "#attributes_for_json" do
let(:time_now) { Time.now }
before(:each) do
user.id = 1
user.paid = true
user.created_at = time_now
end
it "serializes integer attributes as JSON integer" do
expect(user.attributes_for_json).to include("id" => 1)
end
it "serializes time attributes as JSON integer" do
expect(user.attributes_for_json).to include("created_at" => kind_of(Integer))
end
it "serializes string attributes as JSON string" do
user.name = 'Fred'
expect(user.attributes_for_json).to include("name" => "Fred")
end
it "leaves JSON attributes unchanged" do
json = {'interests' => ['coding', 'social networks'], 'rank' => 15}
user.profile = json
expect(user.attributes_for_json).to include("profile" => json)
end
it "omits attributes still set to the default value" do
expect(user.attributes_for_json).to_not include("name", "reward_points")
end
it "includes an attribute changed from its default value" do
user.name = "Fred"
expect(user.attributes_for_json).to include("name" => "Fred")
end
it "includes an attribute changed from its default value to nil" do
user.reward_points = nil
expect(user.attributes_for_json).to include("reward_points" => nil)
end
end
describe "#set_attributes" do
it "allows mass assignment of attributes" do
user.set_attributes(id: 5, name: "Sally")
expect(user.attributes).to include(id: 5, name: "Sally")
end
it "ignores keys that have no writer method" do
user.set_attributes(id: 5, species: "Human")
expect(user.attributes).to_not include(species: "Human")
end
context "for an attribute with a private writer method" do
before(:all) { User.send(:private, :name=) }
after(:all) { User.send(:public, :name=) }
it "does not set the attribute" do
user.set_attributes(id: 5, name: "Sally")
expect(user.attributes).to_not include(name: "Sally")
end
it "sets the attribute if the flag is passed" do
user.set_attributes({id: 5, name: "Sally"}, true)
expect(user.attributes).to include(name: "Sally")
end
end
end
describe "#inspect" do
let(:user) do
User.new(id: 1,
name: "Fred",
created_at: "2014-12-25 08:00 +0000",
paid: true,
profile: {'interests' => ['coding', 'social networks'], 'rank' => 15},
win_rate: 35.62)
end
it "includes integer attributes as 'name: value'" do
expect(user.inspect).to include("id: 1")
end
it "includes boolean attributes as 'name: true/false'" do
expect(user.inspect).to include("paid: true")
end
it "includes string attributes as 'name: \"string\"'" do
expect(user.inspect).to include('name: "Fred"')
end
it "includes time attributes as 'name: <ISO 8601>'" do
expect(user.inspect).to include("created_at: 2014-12-25 08:00:00 +0000")
end
it "includes json attributes as 'name: inspected_json'" do
expect(user.inspect).to include('profile: {"interests"=>["coding", "social networks"], "rank"=>15}')
end
it "includes defaulted attributes" do
expect(user.inspect).to include('reward_points: 0')
end
it "includes the class name" do
expect(user.inspect).to include("User")
end
it "looks like '#<User id: 1, paid: true, name: ..., created_at: ...>'" do
expect(user.inspect).to eq("#<User id: 1, paid: true, name: \"Fred\", created_at: 2014-12-25 08:00:00 +0000, profile: {\"interests\"=>[\"coding\", \"social networks\"], \"rank\"=>15}, reward_points: 0, win_rate: 35.62>")
end
end
describe 'equality with :id field' do
let(:u1) { User.new(id: 1, name: 'David') }
context '#==' do
it 'returns true when ids match, regardless of other attributes' do
u2 = User.new(id: 1, name: 'Dave')
expect(u1).to eq(u2)
end
it 'returns false when ids do not match' do
u2 = User.new(id: 2, name: 'David')
expect(u1).to_not eq(u2)
end
end
context '#eql?' do
it 'returns true when ids match, regardless of other attributes' do
u2 = User.new(id: 1, name: 'Dave')
expect(u1).to eql(u2)
end
it 'returns false when ids do not match' do
u2 = User.new(id: 2, name: 'David')
expect(u1).to_not eql(u2)
end
end
end
describe 'equality without :id field' do
let(:u1) { UserWithoutId.new(name: 'David') }
context "for models with different attribute values" do
let(:u2) { UserWithoutId.new(name: 'Dave') }
it "#== returns false" do
expect(u1).to_not eq(u2)
end
it "#eql? returns false" do
expect(u1).to_not eql(u2)
end
end
context "for models with different attributes set" do
let(:u2) { UserWithoutId.new }
it "#== returns false" do
expect(u1).to_not eq(u2)
end
it "#eql? returns false" do
expect(u1).to_not eql(u2)
end
end
context "for models with the same attributes set to the same values" do
let(:u2) { UserWithoutId.new(name: 'David') }
it "#== returns true" do
expect(u1).to eq(u2)
end
it "#eql? returns true" do
expect(u1).to eql(u2)
end
end
end
end
end
================================================
FILE: spec/spec_helper.rb
================================================
require 'model_attribute'
# This file was generated by the `rspec --init` command. Conventionally, all
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
# The generated `.rspec` file contains `--require spec_helper` which will cause this
# file to always be loaded, without a need to explicitly require it in any files.
#
# Given that it is always loaded, you are encouraged to keep this file as
# light-weight as possible. Requiring heavyweight dependencies from this file
# will add to the boot time of your test suite on EVERY test run, even for an
# individual file that may not need all of that loaded. Instead, consider making
# a separate helper file that requires the additional dependencies and performs
# the additional setup, and require it from the spec files that actually need it.
#
# The `.rspec` file also contains a few flags that are not defaults but that
# users commonly want.
#
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
RSpec.configure do |config|
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
# assertions if you prefer.
config.expect_with :rspec do |expectations|
# This option will default to `true` in RSpec 4. It makes the `description`
# and `failure_message` of custom matchers include text for helper methods
# defined using `chain`, e.g.:
# be_bigger_than(2).and_smaller_than(4).description
# # => "be bigger than 2 and smaller than 4"
# ...rather than:
# # => "be bigger than 2"
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
# rspec-mocks config goes here. You can use an alternate test double
# library (such as bogus or mocha) by changing the `mock_with` option here.
config.mock_with :rspec do |mocks|
# Prevents you from mocking or stubbing a method that does not exist on
# a real object. This is generally recommended, and will default to
# `true` in RSpec 4.
mocks.verify_partial_doubles = true
end
# Limits the available syntax to the non-monkey patched syntax that is recommended.
# For more details, see:
# - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
# - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
# - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
config.disable_monkey_patching!
# This setting enables warnings. It's recommended, but in some cases may
# be too noisy due to issues in dependencies.
config.warnings = true
# The settings below are suggested to provide a good initial experience
# with RSpec, but feel free to customize to your heart's content.
=begin
# These two settings work together to allow you to limit a spec run
# to individual examples or groups you care about by tagging them with
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
# get run.
config.filter_run :focus
config.run_all_when_everything_filtered = true
# Many RSpec users commonly either run the entire suite or an individual
# file, and it's useful to allow more verbose output when running an
# individual spec file.
if config.files_to_run.one?
# Use the documentation formatter for detailed output,
# unless a formatter has already been configured
# (e.g. via a command-line flag).
config.default_formatter = 'doc'
end
# Print the 10 slowest examples and example groups at the
# end of the spec run, to help surface which specs are running
# particularly slow.
config.profile_examples = 10
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = :random
# Seed global randomization in this process using the `--seed` CLI option.
# Setting this allows you to use `--seed` to deterministically reproduce
# test failures related to randomization by passing the same `--seed` value
# as the one that triggered the failure.
Kernel.srand config.seed
=end
end
gitextract__nywioe0/
├── .gitignore
├── .rspec
├── .travis.yml
├── CHANGELOG.md
├── Gemfile
├── Guardfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── lib/
│ ├── model_attribute/
│ │ ├── casts.rb
│ │ ├── errors.rb
│ │ └── version.rb
│ └── model_attribute.rb
├── model_attribute.gemspec
├── performance_comparison.rb
└── spec/
├── model_attributes_spec.rb
└── spec_helper.rb
SYMBOL INDEX (33 symbols across 6 files)
FILE: lib/model_attribute.rb
type ModelAttribute (line 6) | module ModelAttribute
function extended (line 9) | def self.extended(base)
function attribute (line 16) | def attribute(name, type, opts = {})
function attributes (line 48) | def attributes
function attribute_defaults (line 52) | def attribute_defaults
type InstanceMethods (line 56) | module InstanceMethods
function write_attribute (line 57) | def write_attribute(name, value, type = nil)
function read_attribute (line 83) | def read_attribute(name)
function attributes (line 94) | def attributes
function set_attributes (line 100) | def set_attributes(attributes, can_set_private_attrs = false)
function == (line 106) | def ==(other)
function changes (line 116) | def changes
function attributes_for_json (line 126) | def attributes_for_json
function changes_for_json (line 144) | def changes_for_json
function inspect (line 156) | def inspect
FILE: lib/model_attribute/casts.rb
type ModelAttribute (line 1) | module ModelAttribute
type Casts (line 2) | module Casts
function cast (line 4) | def cast(value, type)
function valid_json? (line 55) | def valid_json?(value)
function valid_json_array? (line 65) | def valid_json_array?(array)
function valid_json_hash? (line 69) | def valid_json_hash?(hash)
FILE: lib/model_attribute/errors.rb
type ModelAttribute (line 1) | module ModelAttribute
class InvalidAttributeNameError (line 2) | class InvalidAttributeNameError < StandardError
method initialize (line 3) | def initialize(attribute_name)
class UnsupportedTypeError (line 8) | class UnsupportedTypeError < StandardError
method initialize (line 9) | def initialize(type)
FILE: lib/model_attribute/version.rb
type ModelAttribute (line 1) | module ModelAttribute
FILE: performance_comparison.rb
class VirtusUser (line 8) | class VirtusUser
class ModelAttributeUser (line 20) | class ModelAttributeUser
FILE: spec/model_attributes_spec.rb
class User (line 1) | class User
method initialize (line 11) | def initialize(attributes = {})
class UserWithoutId (line 16) | class UserWithoutId
method initialize (line 22) | def initialize(attributes = {})
Condensed preview — 17 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (52K chars).
[
{
"path": ".gitignore",
"chars": 124,
"preview": "/.bundle/\n/.yardoc\n/Gemfile.lock\n/_yardoc/\n/coverage/\n/doc/\n/pkg/\n/spec/reports/\n/tmp/\n*.bundle\n*.so\n*.o\n*.a\n*.gem\nmkmf."
},
{
"path": ".rspec",
"chars": 41,
"preview": "--color\n--require spec_helper\n--warnings\n"
},
{
"path": ".travis.yml",
"chars": 159,
"preview": "language: ruby\ninstall:\n- bundle install --without interactive\nrvm:\n - \"2.4.10\"\n - \"2.5.8\"\n - \"2.6.6\"\n - \"2.7.1\"\n -"
},
{
"path": "CHANGELOG.md",
"chars": 1690,
"preview": "# Change Log\n\nAll notable changes to this project will be documented in this file.\n\n## 3.2.0\n\n- Support `Float` type wit"
},
{
"path": "Gemfile",
"chars": 179,
"preview": "source 'https://rubygems.org'\n\n# Specify your gem's dependencies in model_attribute.gemspec\ngemspec\n\ngroup 'interactive'"
},
{
"path": "Guardfile",
"chars": 1394,
"preview": "# A sample Guardfile\n# More info at https://github.com/guard/guard#readme\n\n## Uncomment and set this to only include dir"
},
{
"path": "LICENSE.txt",
"chars": 1116,
"preview": "ModelAttribute\n\nCopyright (c) 2015 Microsoft Corporation\n\nAll rights reserved.\n\nMIT License\n\nPermission is hereby grante"
},
{
"path": "README.md",
"chars": 8144,
"preview": "# ModelAttribute [](http://badge.fury.io/rb/model_attribute)"
},
{
"path": "Rakefile",
"chars": 213,
"preview": "require 'bundler/gem_tasks'\nrequire 'rspec/core/rake_task'\nRSpec::Core::RakeTask.new(:spec) do |t|\n t.pattern = Dir.glo"
},
{
"path": "lib/model_attribute/casts.rb",
"chars": 2057,
"preview": "module ModelAttribute\n module Casts\n class << self\n def cast(value, type)\n return nil if value.nil?\n\n "
},
{
"path": "lib/model_attribute/errors.rb",
"chars": 428,
"preview": "module ModelAttribute\n class InvalidAttributeNameError < StandardError\n def initialize(attribute_name)\n super \""
},
{
"path": "lib/model_attribute/version.rb",
"chars": 46,
"preview": "module ModelAttribute\n VERSION = \"3.2.0\"\nend\n"
},
{
"path": "lib/model_attribute.rb",
"chars": 4802,
"preview": "require \"model_attribute/version\"\nrequire \"model_attribute/casts\"\nrequire \"model_attribute/errors\"\nrequire \"time\"\n\nmodul"
},
{
"path": "model_attribute.gemspec",
"chars": 1053,
"preview": "# coding: utf-8\nlib = File.expand_path('../lib', __FILE__)\n$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)\nrequi"
},
{
"path": "performance_comparison.rb",
"chars": 3517,
"preview": "require 'benchmark'\n$LOAD_PATH << \"lib\"\n\nBenchmark.bm(41) do |bm|\n bm.report(\"Virtus load\") do\n require 'virtus'\n\n "
},
{
"path": "spec/model_attributes_spec.rb",
"chars": 20197,
"preview": "class User\n extend ModelAttribute\n attribute :id, :integer\n attribute :paid, :boolean\n attribute"
},
{
"path": "spec/spec_helper.rb",
"chars": 4252,
"preview": "require 'model_attribute'\n\n# This file was generated by the `rspec --init` command. Conventionally, all\n# specs live und"
}
]
About this extraction
This page contains the full source code of the yammer/model_attribute GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 17 files (48.3 KB), approximately 13.3k tokens, and a symbol index with 33 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.