fast_attributes 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4c7f1055c9b3373f670d0a951802c291da7e64d3
4
- data.tar.gz: c07c8f114c8dedaa380ff9a17901fa1f6b928f87
3
+ metadata.gz: 77f2e59818faf79d91ebf8f90f76cebc2bab978b
4
+ data.tar.gz: bb9905bc7899f5c2d4d4e69c8f9c42760e28be3e
5
5
  SHA512:
6
- metadata.gz: 1490d8927c991b691150524b0febe8abb69f554bc113d4b19c17318b180a37c724b07ab98be843c0aee452462449b69cfac4852b76eb4353c881b354f4beb2c5
7
- data.tar.gz: 6d27f9bce17691ecd4bfe988598876a27632abe9b24f52b52d2f5d585195e515a1fc572c3dcde75390379cd0a0fb5ea75194f5559977c07f0a56b649a0166491
6
+ metadata.gz: 0c6a59bfb0c1a73be212f8c2f9485186aaf3282d90d09633aac841fffd083645d4c12c9afc144781c38f850aa30362661710d1833886a834b544a9d568ae7410
7
+ data.tar.gz: 009c11fbd431c352d1c8fe72502851a14a1eaeb55c73184c3b2585ccf2db1b6dbdebff910bc27a05cc5e78e503f82398a95d4a719f44ea28600b191aad7bf848
data/.coveralls.yml ADDED
@@ -0,0 +1 @@
1
+ service_name: travis-ci
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format d
data/.travis.yml ADDED
@@ -0,0 +1,2 @@
1
+ language: ruby
2
+ script: bundle exec rspec spec
data/CHANGELOG.md CHANGED
@@ -1,2 +1,5 @@
1
+ **0.1.0 (June 26, 2014)**
2
+ * Support `Integer`, `String`, `Array`, `Date`, `Time` and `DateTime` attribute types
3
+
1
4
  **0.0.1 (June 20, 2014)**
2
5
  * Initial commit
data/README.md CHANGED
@@ -1,6 +1,64 @@
1
1
  # FastAttributes
2
+ [![Gem Version](http://img.shields.io/gem/v/fast_attributes.svg)](http://rubygems.org/gems/fast_attributes)
3
+ [![Build Status](http://img.shields.io/travis/applift/fast_attributes.svg)](https://travis-ci.org/applift/fast_attributes)
4
+ [![Coverage Status](http://img.shields.io/coveralls/applift/fast_attributes.svg)](https://coveralls.io/r/applift/fast_attributes?branch=master)
2
5
 
3
- TODO: Write a gem description
6
+ ## Motivation
7
+ There are already a lot of good and flexible gems which solve the similar problem, allow to define attributes with their types, for example: [virtus](https://github.com/solnic/virtus) or [attrio](https://github.com/jetrockets/attrio). But the disadvantage of existing gems is performance. So, the goal of `fast_attributes` is to make a simple solution which is fast, understandable and easy extendable.
8
+
9
+ This is [performance benchmark](https://github.com/applift/fast_attributes/blob/master/benchmarks/comparison.rb) of `fast_attributes` and other popular gems.
10
+
11
+ ```
12
+ Calculating -------------------------------------
13
+ FastAttributes: without values
14
+ 71131 i/100ms
15
+ FastAttributes: integer values for integer attributes
16
+ 8410 i/100ms
17
+ FastAttributes: string values for integer attributes
18
+ 6869 i/100ms
19
+ Attrio: without values
20
+ 700 i/100ms
21
+ Attrio: integer values for integer attributes
22
+ 1165 i/100ms
23
+ Attrio: string values for integer attributes
24
+ 1073 i/100ms
25
+ Virtus: without values
26
+ 898 i/100ms
27
+ Virtus: integer values for integer attributes
28
+ 2123 i/100ms
29
+ Virtus: string values for integer attributes
30
+ 306 i/100ms
31
+ -------------------------------------------------
32
+ FastAttributes: without values
33
+ 1528209.4 (±16.2%) i/s - 1564882 in 1.045527s
34
+ FastAttributes: integer values for integer attributes
35
+ 88794.2 (±16.0%) i/s - 92510 in 1.078912s
36
+ FastAttributes: string values for integer attributes
37
+ 77673.3 (±4.7%) i/s - 82428 in 1.063921s
38
+ Attrio: without values
39
+ 7164.3 (±2.6%) i/s - 7700 in 1.075560s
40
+ Attrio: integer values for integer attributes
41
+ 11932.2 (±2.1%) i/s - 12815 in 1.074474s
42
+ Attrio: string values for integer attributes
43
+ 11007.2 (±0.9%) i/s - 11803 in 1.072382s
44
+ Virtus: without values
45
+ 10151.0 (±5.5%) i/s - 10776 in 1.065475s
46
+ Virtus: integer values for integer attributes
47
+ 21104.7 (±10.4%) i/s - 21230 in 1.019115s
48
+ Virtus: string values for integer attributes
49
+ 3195.6 (±1.7%) i/s - 3366 in 1.053637s
50
+
51
+ Comparison:
52
+ FastAttributes: without values : 1528209.4 i/s
53
+ FastAttributes: integer values for integer attributes: 88794.2 i/s - 17.21x slower
54
+ FastAttributes: string values for integer attributes : 77673.3 i/s - 19.67x slower
55
+ Virtus: integer values for integer attributes : 21104.7 i/s - 72.41x slower
56
+ Attrio: integer values for integer attributes : 11932.2 i/s - 128.07x slower
57
+ Attrio: string values for integer attributes : 11007.2 i/s - 138.84x slower
58
+ Virtus: without values : 10151.0 i/s - 150.55x slower
59
+ Attrio: without values : 7164.3 i/s - 213.31x slower
60
+ Virtus: string values for integer attributes : 3195.6 i/s - 478.22x slower
61
+ ```
4
62
 
5
63
  ## Installation
6
64
 
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ ruby '2.1.2'
4
+
5
+ gem 'fast_attributes', '0.0.1'
6
+ gem 'virtus', '1.0.2'
7
+ gem 'attrio', '0.7.3'
8
+ gem 'benchmark-ips', '2.0.0'
@@ -0,0 +1,58 @@
1
+ require 'benchmark/ips'
2
+ require 'fast_attributes'
3
+ require 'virtus'
4
+ require 'attrio'
5
+
6
+ ATTR_NAMES = [:attr0, :attr1, :attr2, :attr3, :attr4, :attr5, :attr6, :attr7, :attr8, :attr9]
7
+
8
+ module Initializer
9
+ def initialize(attributes = {})
10
+ attributes.each do |attribute, value|
11
+ public_send("#{attribute}=", value)
12
+ end
13
+ end
14
+ end
15
+
16
+ class FastIntegers
17
+ extend FastAttributes
18
+ include Initializer
19
+ attribute *ATTR_NAMES, Integer
20
+ end
21
+
22
+ class AttrioIntegers
23
+ include Attrio
24
+ include Initializer
25
+ define_attributes do
26
+ ATTR_NAMES.each do |name|
27
+ attr name, Integer
28
+ end
29
+ end
30
+ end
31
+
32
+ class VirtusIntegers
33
+ include Virtus.model
34
+ ATTR_NAMES.each do |name|
35
+ attribute name, Integer
36
+ end
37
+ end
38
+
39
+ Benchmark.ips do |x|
40
+ x.config(time: 1, warmup: 1)
41
+
42
+ integers = {attr0: 0, attr1: 1, attr2: 2, attr3: 3, attr4: 4, attr5: 5, attr6: 6, attr7: 7, attr8: 8, attr9: 9}
43
+ strings = {attr0: '0', attr1: '1', attr2: '2', attr3: '3', attr4: '4', attr5: '5', attr6: '6', attr7: '7', attr8: '8', attr9: '9'}
44
+
45
+ x.report('FastAttributes: without values ') { FastIntegers.new }
46
+ x.report('FastAttributes: integer values for integer attributes') { FastIntegers.new(integers) }
47
+ x.report('FastAttributes: string values for integer attributes ') { FastIntegers.new(strings) }
48
+
49
+ x.report('Attrio: without values ') { AttrioIntegers.new }
50
+ x.report('Attrio: integer values for integer attributes ') { AttrioIntegers.new(integers) }
51
+ x.report('Attrio: string values for integer attributes ') { AttrioIntegers.new(strings) }
52
+
53
+ x.report('Virtus: without values ') { VirtusIntegers.new }
54
+ x.report('Virtus: integer values for integer attributes ') { VirtusIntegers.new(integers) }
55
+ x.report('Virtus: string values for integer attributes ') { VirtusIntegers.new(strings) }
56
+
57
+ x.compare!
58
+ end
@@ -18,6 +18,8 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_development_dependency 'bundler', '~> 1.5'
21
+ spec.add_development_dependency 'bundler', '~> 1.5'
22
22
  spec.add_development_dependency 'rake'
23
+ spec.add_development_dependency 'rspec', '~> 3.0.0'
24
+ spec.add_development_dependency 'coveralls', '~> 0.7.0'
23
25
  end
@@ -1,3 +1,3 @@
1
1
  module FastAttributes
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -6,21 +6,15 @@ module FastAttributes
6
6
  @type_casting ||= {
7
7
  'String' => 'String(%s)',
8
8
  'Integer' => 'Integer(%s)',
9
+ 'Array' => 'Array(%s)',
9
10
  'Date' => 'Date.parse(%s)',
10
11
  'Time' => 'Time.parse(%s)',
11
- 'DateTime' => 'DateTime.parse(%s)',
12
- 'Array' => 'Array(%s)'
12
+ 'DateTime' => 'DateTime.parse(%s)'
13
13
  }
14
14
  end
15
15
 
16
- def default_type_casting
17
- '%s'
18
- end
19
-
20
16
  def get_type_casting(klass)
21
- @type_casting.fetch(type_from_class(klass)) do
22
- default_type_casting
23
- end
17
+ @type_casting[type_from_class(klass)]
24
18
  end
25
19
 
26
20
  def add_type_casting(klass, casting)
@@ -41,11 +35,15 @@ module FastAttributes
41
35
  end
42
36
 
43
37
  def attribute(*attributes, klass)
38
+ unless FastAttributes.type_exists?(klass)
39
+ raise %(Unsupported attribute type "#{FastAttributes.type_from_class(klass)}")
40
+ end
41
+
44
42
  @fast_attributes ||= []
45
43
  attributes.each do |attribute|
46
44
  @fast_attributes << attribute
47
45
 
48
- type_matching = FastAttributes.type_exists?(klass) ? "when #{FastAttributes.type_from_class(klass)} then value" : nil
46
+ type_matching = "when #{FastAttributes.type_from_class(klass)} then value"
49
47
  type_casting = FastAttributes.get_type_casting(klass) % 'value'
50
48
  all_attributes = @fast_attributes.map do |attr|
51
49
  "'#{attr}'=>@#{attr}"
@@ -0,0 +1,198 @@
1
+ require 'spec_helper'
2
+
3
+ describe FastAttributes do
4
+ describe '.type_casting' do
5
+ it 'returns predefined type casting rules' do
6
+ expect(FastAttributes.type_casting).to eq({
7
+ 'String' => 'String(%s)',
8
+ 'Integer' => 'Integer(%s)',
9
+ 'Array' => 'Array(%s)',
10
+ 'Date' => 'Date.parse(%s)',
11
+ 'Time' => 'Time.parse(%s)',
12
+ 'DateTime' => 'DateTime.parse(%s)'
13
+ })
14
+ end
15
+ end
16
+
17
+ describe '.get_type_casting' do
18
+ it 'returns type casting function' do
19
+ expect(FastAttributes.get_type_casting(String)).to eq('String(%s)')
20
+ expect(FastAttributes.get_type_casting(Time)).to eq('Time.parse(%s)')
21
+ end
22
+ end
23
+
24
+ describe '.add_type_casting' do
25
+ after do
26
+ FastAttributes.remove_type_casting(OpenStruct)
27
+ end
28
+
29
+ it 'adds type to supported type casting list' do
30
+ FastAttributes.add_type_casting(OpenStruct, 'OpenStruct.new(a: %s)')
31
+ expect(FastAttributes.get_type_casting(OpenStruct)).to eq('OpenStruct.new(a: %s)')
32
+ end
33
+ end
34
+
35
+ describe '.remove_type_casting' do
36
+ before do
37
+ FastAttributes.add_type_casting(OpenStruct, 'OpenStruct.new(a: %s)')
38
+ end
39
+
40
+ it 'removes type casting function from supported list' do
41
+ FastAttributes.remove_type_casting(OpenStruct)
42
+ expect(FastAttributes.get_type_casting(OpenStruct)).to be(nil)
43
+ end
44
+ end
45
+
46
+ describe '.type_exists?' do
47
+ it 'checks if type is registered' do
48
+ expect(FastAttributes.type_exists?(DateTime)).to be(true)
49
+ expect(FastAttributes.type_exists?(OpenStruct)).to be(false)
50
+ end
51
+ end
52
+
53
+ describe '.type_from_class' do
54
+ it 'converts class into type name' do
55
+ expect(FastAttributes.type_from_class(DateTime)).to eq('DateTime')
56
+ expect(FastAttributes.type_from_class(OpenStruct)).to eq('OpenStruct')
57
+ end
58
+ end
59
+
60
+ describe '#attribute' do
61
+ it 'generates getter methods' do
62
+ book = Book.new
63
+ expect(book.respond_to?(:title)).to be(true)
64
+ expect(book.respond_to?(:name)).to be(true)
65
+ expect(book.respond_to?(:pages)).to be(true)
66
+ expect(book.respond_to?(:authors)).to be(true)
67
+ expect(book.respond_to?(:published)).to be(true)
68
+ expect(book.respond_to?(:sold)).to be(true)
69
+ expect(book.respond_to?(:finished)).to be(true)
70
+ end
71
+
72
+ it 'generates setter methods' do
73
+ book = Book.new
74
+ expect(book.respond_to?(:title=)).to be(true)
75
+ expect(book.respond_to?(:name=)).to be(true)
76
+ expect(book.respond_to?(:pages=)).to be(true)
77
+ expect(book.respond_to?(:authors=)).to be(true)
78
+ expect(book.respond_to?(:published=)).to be(true)
79
+ expect(book.respond_to?(:sold=)).to be(true)
80
+ expect(book.respond_to?(:finished=)).to be(true)
81
+ end
82
+
83
+ it 'generates attributes method' do
84
+ book = Book.new
85
+ expect(book.respond_to?(:attributes)).to be(true)
86
+ end
87
+
88
+ it 'setter methods convert values to correct datatype' do
89
+ book = Book.new
90
+ book.title = 123
91
+ book.name = 456
92
+ book.pages = '250'
93
+ book.authors = 'Jobs'
94
+ book.published = '2014-06-21'
95
+ book.sold = '2014-06-21 20:45:15'
96
+ book.finished = '2014-05-20 21:35:20'
97
+
98
+ expect(book.title).to eq('123')
99
+ expect(book.name).to eq('456')
100
+ expect(book.pages).to be(250)
101
+ expect(book.authors).to eq(%w[Jobs])
102
+ expect(book.published).to eq(Date.new(2014, 6, 21))
103
+ expect(book.sold).to eq(Time.new(2014, 6, 21, 20, 45, 15))
104
+ expect(book.finished).to eq(DateTime.new(2014, 5, 20, 21, 35, 20))
105
+ end
106
+
107
+ it 'setter methods accept values which are already in a proper type' do
108
+ book = Book.new
109
+ book.title = title = 'One'
110
+ book.name = name = 'Two'
111
+ book.pages = pages = 250
112
+ book.authors = authors = %w[Jobs]
113
+ book.published = published = Date.new(2014, 06, 21)
114
+ book.sold = sold = Time.new(2014, 6, 21, 20, 45, 15)
115
+ book.finished = finished = DateTime.new(2014, 05, 20, 21, 35, 20)
116
+
117
+ expect(book.title).to be(title)
118
+ expect(book.name).to be(name)
119
+ expect(book.pages).to be(pages)
120
+ expect(book.authors).to be(authors)
121
+ expect(book.published).to be(published)
122
+ expect(book.sold).to be(sold)
123
+ expect(book.finished).to be(finished)
124
+ end
125
+
126
+ it 'setter methods accept nil values' do
127
+ book = Book.new
128
+ book.title = 'One'
129
+ book.name = 'Two'
130
+ book.pages = 250
131
+ book.authors = %w[Jobs]
132
+ book.published = Date.new(2014, 06, 21)
133
+ book.sold = Time.new(2014, 6, 21, 20, 45, 15)
134
+ book.finished = DateTime.new(2014, 05, 20, 21, 35, 20)
135
+
136
+ book.title = nil
137
+ book.name = nil
138
+ book.pages = nil
139
+ book.authors = nil
140
+ book.published = nil
141
+ book.sold = nil
142
+ book.finished = nil
143
+
144
+ expect(book.title).to be(nil)
145
+ expect(book.name).to be(nil)
146
+ expect(book.pages).to be(nil)
147
+ expect(book.authors).to be(nil)
148
+ expect(book.published).to be(nil)
149
+ expect(book.sold).to be(nil)
150
+ expect(book.finished).to be(nil)
151
+ end
152
+
153
+ it 'setter methods raise an exception when cannot parse values' do
154
+ book = Book.new
155
+
156
+ expect{ book.title = BasicObject.new }.to raise_error(TypeError)
157
+ expect{ book.name = BasicObject.new }.to raise_error(TypeError)
158
+ expect{ book.pages = 'number' }.to raise_error(ArgumentError)
159
+ expect{ book.published = 'date' }.to raise_error(ArgumentError)
160
+ expect{ book.sold = 'time' }.to raise_error(ArgumentError)
161
+ expect{ book.finished = 'datetime' }.to raise_error(ArgumentError)
162
+ end
163
+
164
+ it 'attributes method return all attributes with nil values by default' do
165
+ book = Book.new
166
+ expect(book.attributes).to eq({
167
+ 'title' => nil,
168
+ 'name' => nil,
169
+ 'pages' => nil,
170
+ 'authors' => nil,
171
+ 'published' => nil,
172
+ 'sold' => nil,
173
+ 'finished' => nil,
174
+ })
175
+ end
176
+
177
+ it 'attributes method return all attributes with their values' do
178
+ book = Book.new
179
+ book.title = 'One'
180
+ book.name = 'Two'
181
+ book.pages = 250
182
+ book.authors = %w[Jobs]
183
+ book.published = Date.new(2014, 06, 21)
184
+ book.sold = Time.new(2014, 6, 21, 20, 45, 15)
185
+ book.finished = DateTime.new(2014, 05, 20, 21, 35, 20)
186
+
187
+ expect(book.attributes).to eq({
188
+ 'title' => 'One',
189
+ 'name' => 'Two',
190
+ 'pages' => 250,
191
+ 'authors' => %w[Jobs],
192
+ 'published' => Date.new(2014, 06, 21),
193
+ 'sold' => Time.new(2014, 6, 21, 20, 45, 15),
194
+ 'finished' => DateTime.new(2014, 05, 20, 21, 35, 20),
195
+ })
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,10 @@
1
+ class Book
2
+ extend FastAttributes
3
+
4
+ attribute :title, :name, String
5
+ attribute :pages, Integer
6
+ attribute :authors, Array
7
+ attribute :published, Date
8
+ attribute :sold, Time
9
+ attribute :finished, DateTime
10
+ end
@@ -0,0 +1,8 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+
4
+ require 'fast_attributes'
5
+ require 'ostruct'
6
+ require 'date'
7
+ require 'time'
8
+ require 'fixtures/book'
metadata CHANGED
@@ -1,43 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fast_attributes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kostiantyn Stepaniuk
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-20 00:00:00.000000000 Z
11
+ date: 2014-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.5'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.5'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 3.0.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 3.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: coveralls
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.7.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.7.0
41
69
  description: Fast attributes with data types
42
70
  email:
43
71
  - ks@applift.com
@@ -45,15 +73,23 @@ executables: []
45
73
  extensions: []
46
74
  extra_rdoc_files: []
47
75
  files:
48
- - .gitignore
76
+ - ".coveralls.yml"
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".travis.yml"
49
80
  - CHANGELOG.md
50
81
  - Gemfile
51
82
  - LICENSE.txt
52
83
  - README.md
53
84
  - Rakefile
85
+ - benchmarks/Gemfile
86
+ - benchmarks/comparison.rb
54
87
  - fast_attributes.gemspec
55
88
  - lib/fast_attributes.rb
56
89
  - lib/fast_attributes/version.rb
90
+ - spec/fast_attributes_spec.rb
91
+ - spec/fixtures/book.rb
92
+ - spec/spec_helper.rb
57
93
  homepage: https://github.com/applift/fast_attributes
58
94
  licenses:
59
95
  - MIT
@@ -64,18 +100,21 @@ require_paths:
64
100
  - lib
65
101
  required_ruby_version: !ruby/object:Gem::Requirement
66
102
  requirements:
67
- - - '>='
103
+ - - ">="
68
104
  - !ruby/object:Gem::Version
69
105
  version: '0'
70
106
  required_rubygems_version: !ruby/object:Gem::Requirement
71
107
  requirements:
72
- - - '>='
108
+ - - ">="
73
109
  - !ruby/object:Gem::Version
74
110
  version: '0'
75
111
  requirements: []
76
112
  rubyforge_project:
77
- rubygems_version: 2.0.3
113
+ rubygems_version: 2.2.2
78
114
  signing_key:
79
115
  specification_version: 4
80
116
  summary: Fast attributes with data types
81
- test_files: []
117
+ test_files:
118
+ - spec/fast_attributes_spec.rb
119
+ - spec/fixtures/book.rb
120
+ - spec/spec_helper.rb