rom-model 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.rspec +2 -0
- data/.travis.yml +30 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +17 -0
- data/LICENSE +20 -0
- data/README.md +65 -0
- data/Rakefile +8 -0
- data/config/locales/en.yml +8 -0
- data/lib/rom-model.rb +1 -0
- data/lib/rom/model.rb +2 -0
- data/lib/rom/model/attributes.rb +133 -0
- data/lib/rom/model/validator.rb +201 -0
- data/lib/rom/model/validator/uniqueness_validator.rb +83 -0
- data/lib/rom/model/version.rb +5 -0
- data/rakelib/mutant.rake +16 -0
- data/rakelib/rubocop.rake +18 -0
- data/rom-model.gemspec +29 -0
- data/spec/shared/database.rb +32 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/unit/attributes_spec.rb +46 -0
- data/spec/unit/validator/embedded_spec.rb +116 -0
- data/spec/unit/validator_spec.rb +165 -0
- metadata +196 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8dae3cb7ed3616ec2fb6dab8815cdcfcbcc5d4ee
|
4
|
+
data.tar.gz: dd5df2f70e0b945ec0a42e4ce02bbac700ca13ef
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d9e16eedc459dfda4e4ff7d641179a23266781cbd646fedc10234386dfda320487dd30f2a81bf6134cca7b17ffa93c35a92e694872f91e1a91f2f4b6e924703f
|
7
|
+
data.tar.gz: 416b99d8c8bdcc6be0377706976e3d42b725f3b3f3136ffcc9f555fef2a34989fbe20a3a340cce8e980c43fc1e3e54b65ecf4a555f6ced6ba37bdfd1d1efcc6b
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Gemfile.lock
|
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
language: ruby
|
2
|
+
sudo: false
|
3
|
+
cache: bundler
|
4
|
+
bundler_args: --without yard guard benchmarks tools
|
5
|
+
before_script:
|
6
|
+
- psql -c 'create database rom_model' -U postgres
|
7
|
+
script: "bundle exec rake ci"
|
8
|
+
rvm:
|
9
|
+
- 2.0
|
10
|
+
- 2.1
|
11
|
+
- 2.2
|
12
|
+
- rbx-2
|
13
|
+
- jruby
|
14
|
+
- jruby-head
|
15
|
+
- ruby-head
|
16
|
+
env:
|
17
|
+
global:
|
18
|
+
- CODECLIMATE_REPO_TOKEN=TODO
|
19
|
+
- JRUBY_OPTS='--dev -J-Xmx1024M'
|
20
|
+
matrix:
|
21
|
+
allow_failures:
|
22
|
+
- rvm: ruby-head
|
23
|
+
- rvm: jruby-head
|
24
|
+
notifications:
|
25
|
+
webhooks:
|
26
|
+
urls:
|
27
|
+
- https://webhooks.gitter.im/e/39e1225f489f38b0bd09
|
28
|
+
on_success: change
|
29
|
+
on_failure: always
|
30
|
+
on_start: false
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
gem 'rom', github: 'rom-rb/rom', branch: 'master'
|
6
|
+
gem 'rom-sql', github: 'rom-rb/rom-sql', branch: 'master'
|
7
|
+
gem 'pg', platforms: [:mri, :rbx]
|
8
|
+
gem 'pg_jruby', platforms: :jruby
|
9
|
+
|
10
|
+
group :tools do
|
11
|
+
gem 'byebug', platforms: :mri
|
12
|
+
end
|
13
|
+
|
14
|
+
group :test do
|
15
|
+
gem 'rspec'
|
16
|
+
gem 'codeclimate-test-reporter', require: nil, platform: :rbx
|
17
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014-2015 Ruby Object Mapper
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
[gem]: https://rubygems.org/gems/rom-model
|
2
|
+
[travis]: https://travis-ci.org/rom-rb/rom-model
|
3
|
+
[gemnasium]: https://gemnasium.com/rom-rb/rom-model
|
4
|
+
[codeclimate]: https://codeclimate.com/github/rom-rb/rom-model
|
5
|
+
[inchpages]: http://inch-ci.org/github/rom-rb/rom-model
|
6
|
+
|
7
|
+
# ROM::Model
|
8
|
+
|
9
|
+
[][gem]
|
10
|
+
[][travis]
|
11
|
+
[][gemnasium]
|
12
|
+
[][codeclimate]
|
13
|
+
[][codeclimate]
|
14
|
+
[][inchpages]
|
15
|
+
|
16
|
+
This is a set of extensions for PORO objects to help in data coercion and validation.
|
17
|
+
It was extracted from rom-rails and for now it uses Virtus and ActiveModel.
|
18
|
+
|
19
|
+
The package includes:
|
20
|
+
|
21
|
+
- `ROM::Attributes` for defining input attributes with types and coercion rules
|
22
|
+
- `ROM::Validator` a standalone validator object extension built on top of
|
23
|
+
`ActiveModel::Validations` with additional features like nested validators
|
24
|
+
|
25
|
+
## The Plan™
|
26
|
+
|
27
|
+
This gem is built on top of existing 3rd party gems that have proven to be stable
|
28
|
+
and good-enough. Unfortunatelly neither Virtus nor ActiveModel do not meet certain
|
29
|
+
design requirements to be a good fit in the long term.
|
30
|
+
|
31
|
+
For that reason we're exploring how to build a better foundation for rom-model.
|
32
|
+
Specifically following initiatives are taking place:
|
33
|
+
|
34
|
+
- Exploring a lower-level validation library with great composability features
|
35
|
+
and simple interface
|
36
|
+
- Investigating a lower-level input data sanitization/coercion library that would
|
37
|
+
be a perfect fit for handling web forms and json input
|
38
|
+
|
39
|
+
Furthermore rom-model will be soon extended with a third extension for defining
|
40
|
+
[Algebraic Data Types](https://en.wikipedia.org/wiki/Algebraic_data_type) which
|
41
|
+
will work remarkably well with [rom-repository](https://github.com/rom-rb/rom-repository)
|
42
|
+
and its auto-mapping features.
|
43
|
+
|
44
|
+
This project will provide convenient interfaces on top of robust lower-level tools
|
45
|
+
and if it turns out to be too big we'll split it into smaller gems.
|
46
|
+
|
47
|
+
## Installation
|
48
|
+
|
49
|
+
Add this line to your application's Gemfile:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
gem 'rom-model'
|
53
|
+
```
|
54
|
+
|
55
|
+
And then execute:
|
56
|
+
|
57
|
+
$ bundle
|
58
|
+
|
59
|
+
Or install it yourself as:
|
60
|
+
|
61
|
+
$ gem install rom-model
|
62
|
+
|
63
|
+
## License
|
64
|
+
|
65
|
+
See `LICENSE` file.
|
data/Rakefile
ADDED
data/lib/rom-model.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'rom/model'
|
data/lib/rom/model.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'virtus'
|
2
|
+
require 'active_model' # can't cherry-pick conversion :(
|
3
|
+
|
4
|
+
module ROM
|
5
|
+
module Model
|
6
|
+
# Mixin for validatable and coercible parameters
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# class UserAttributes
|
11
|
+
# include ROM::Model::Attributes
|
12
|
+
#
|
13
|
+
# attribute :email, String
|
14
|
+
# attribute :age, Integer
|
15
|
+
#
|
16
|
+
# validates :email, :age, presence: true
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# user_attrs = UserAttributes.new(email: '', age: '18')
|
20
|
+
#
|
21
|
+
# user_attrs.email # => ''
|
22
|
+
# user_attrs.age # => 18
|
23
|
+
#
|
24
|
+
# user_attrs.valid? # => false
|
25
|
+
# user_attrs.errors # => #<ActiveModel::Errors:0x007fd2423fadb0 ...>
|
26
|
+
#
|
27
|
+
# @api public
|
28
|
+
module Attributes
|
29
|
+
VirtusModel = Virtus.model(nullify_blank: true)
|
30
|
+
|
31
|
+
# Inclusion hook used to extend a class with required interfaces
|
32
|
+
#
|
33
|
+
# @api private
|
34
|
+
def self.included(base)
|
35
|
+
base.class_eval do
|
36
|
+
include VirtusModel
|
37
|
+
include ActiveModel::Conversion
|
38
|
+
end
|
39
|
+
base.extend(ClassMethods)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return model name for the attributes class
|
43
|
+
#
|
44
|
+
# The model name object is configurable using `set_model_name` macro
|
45
|
+
#
|
46
|
+
# @see ClassMethods#set_model_name
|
47
|
+
#
|
48
|
+
# @return [ActiveModel::Name]
|
49
|
+
#
|
50
|
+
# @api public
|
51
|
+
def model_name
|
52
|
+
self.class.model_name
|
53
|
+
end
|
54
|
+
|
55
|
+
# Class extensions for an attributes class
|
56
|
+
#
|
57
|
+
# @api public
|
58
|
+
module ClassMethods
|
59
|
+
# Default timestamp attribute names used by `timestamps` method
|
60
|
+
DEFAULT_TIMESTAMPS = [:created_at, :updated_at].freeze
|
61
|
+
|
62
|
+
# Process input and return attributes instance
|
63
|
+
#
|
64
|
+
# @example
|
65
|
+
# class UserAttributes
|
66
|
+
# include ROM::Model::Attributes
|
67
|
+
#
|
68
|
+
# attribute :name, String
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# UserAttributes[name: 'Jane']
|
72
|
+
#
|
73
|
+
# @param [Hash,#to_hash] input The input params
|
74
|
+
#
|
75
|
+
# @return [Attributes]
|
76
|
+
#
|
77
|
+
# @api public
|
78
|
+
def [](input)
|
79
|
+
input.is_a?(self) ? input : new(input)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Macro for defining ActiveModel::Name object on the attributes class
|
83
|
+
#
|
84
|
+
# This is essential for rails helpers to work properly when generating
|
85
|
+
# form input names etc.
|
86
|
+
#
|
87
|
+
# @example
|
88
|
+
# class UserAttributes
|
89
|
+
# include ROM::Model::Attributes
|
90
|
+
#
|
91
|
+
# set_model_name 'User'
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# @return [undefined]
|
95
|
+
#
|
96
|
+
# @api public
|
97
|
+
def set_model_name(name)
|
98
|
+
class_eval <<-RUBY
|
99
|
+
def self.model_name
|
100
|
+
@model_name ||= ActiveModel::Name.new(self, nil, #{name.inspect})
|
101
|
+
end
|
102
|
+
RUBY
|
103
|
+
end
|
104
|
+
|
105
|
+
# Shortcut for defining timestamp attributes like created_at etc.
|
106
|
+
#
|
107
|
+
# @example
|
108
|
+
# class NewPostAttributes
|
109
|
+
# include ROM::Model::Attributes
|
110
|
+
#
|
111
|
+
# # provide name(s) explicitly
|
112
|
+
# timestamps :published_at
|
113
|
+
#
|
114
|
+
# # defaults to :created_at, :updated_at without args
|
115
|
+
# timestamps
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
# @api public
|
119
|
+
def timestamps(*attrs)
|
120
|
+
if attrs.empty?
|
121
|
+
DEFAULT_TIMESTAMPS.each do |t|
|
122
|
+
attribute t, DateTime, default: proc { DateTime.now }
|
123
|
+
end
|
124
|
+
else
|
125
|
+
attrs.each do |attr|
|
126
|
+
attribute attr, DateTime, default: proc { DateTime.now }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
require 'charlatan'
|
2
|
+
|
3
|
+
require 'rom/support/constants'
|
4
|
+
require 'rom/constants'
|
5
|
+
|
6
|
+
require 'rom/model/validator/uniqueness_validator'
|
7
|
+
require 'rom/support/class_macros'
|
8
|
+
|
9
|
+
module ROM
|
10
|
+
module Model
|
11
|
+
class ValidationError < CommandError
|
12
|
+
include Charlatan.new(:errors)
|
13
|
+
include Equalizer.new(:errors)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Mixin for ROM-compliant validator objects
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
#
|
20
|
+
#
|
21
|
+
# class UserAttributes
|
22
|
+
# include ROM::Model::Attributes
|
23
|
+
#
|
24
|
+
# attribute :name
|
25
|
+
#
|
26
|
+
# validates :name, presence: true
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# class UserValidator
|
30
|
+
# include ROM::Model::Validator
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# attrs = UserAttributes.new(name: '')
|
34
|
+
# UserValidator.call(attrs) # raises ValidationError
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
module Validator
|
38
|
+
# Inclusion hook that extends a class with required interfaces
|
39
|
+
#
|
40
|
+
# @api private
|
41
|
+
def self.included(base)
|
42
|
+
base.class_eval do
|
43
|
+
extend ClassMethods
|
44
|
+
extend ROM::ClassMacros
|
45
|
+
|
46
|
+
include ActiveModel::Validations
|
47
|
+
include Equalizer.new(:attributes, :errors)
|
48
|
+
|
49
|
+
base.defines :embedded_validators
|
50
|
+
|
51
|
+
embedded_validators({})
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [Model::Attributes]
|
56
|
+
#
|
57
|
+
# @api private
|
58
|
+
attr_reader :attributes
|
59
|
+
|
60
|
+
# @api private
|
61
|
+
attr_reader :attr_names
|
62
|
+
|
63
|
+
delegate :model_name, to: :attributes
|
64
|
+
|
65
|
+
# @api private
|
66
|
+
def initialize(attributes)
|
67
|
+
@attributes = attributes
|
68
|
+
@attr_names = self.class.validators.map(&:attributes).flatten.uniq
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [Model::Attributes]
|
72
|
+
#
|
73
|
+
# @api public
|
74
|
+
def to_model
|
75
|
+
attributes
|
76
|
+
end
|
77
|
+
|
78
|
+
# Trigger validations and return attributes on success
|
79
|
+
#
|
80
|
+
# @raises ValidationError
|
81
|
+
#
|
82
|
+
# @return [Model::Attributes]
|
83
|
+
#
|
84
|
+
# @api public
|
85
|
+
def call
|
86
|
+
raise ValidationError, errors unless valid?
|
87
|
+
attributes
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# This is needed for ActiveModel::Validations to work properly
|
93
|
+
# as it expects the object to provide attribute values. Meh.
|
94
|
+
#
|
95
|
+
# @api private
|
96
|
+
def method_missing(name, *args, &block)
|
97
|
+
if attr_names.include?(name)
|
98
|
+
attributes[name]
|
99
|
+
else
|
100
|
+
super
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
module ClassMethods
|
105
|
+
# Set relation name for a validator
|
106
|
+
#
|
107
|
+
# This is needed for validators that require database access
|
108
|
+
#
|
109
|
+
# @example
|
110
|
+
#
|
111
|
+
# class UserValidator
|
112
|
+
# include ROM::Model::Validator
|
113
|
+
#
|
114
|
+
# relation :users
|
115
|
+
#
|
116
|
+
# validates :name, uniqueness: true
|
117
|
+
# end
|
118
|
+
#
|
119
|
+
# @return [Symbol]
|
120
|
+
#
|
121
|
+
# @api public
|
122
|
+
def relation(name = nil)
|
123
|
+
@relation = name if name
|
124
|
+
@relation
|
125
|
+
end
|
126
|
+
|
127
|
+
# @api private
|
128
|
+
def set_model_name(name)
|
129
|
+
class_eval <<-RUBY
|
130
|
+
def self.model_name
|
131
|
+
@model_name ||= ActiveModel::Name.new(self, nil, #{name.inspect})
|
132
|
+
end
|
133
|
+
RUBY
|
134
|
+
end
|
135
|
+
|
136
|
+
# Trigger validation for specific attributes
|
137
|
+
#
|
138
|
+
# @param [Model::Attributes] attributes The attributes for validation
|
139
|
+
#
|
140
|
+
# @raises [ValidationError]
|
141
|
+
#
|
142
|
+
# @return [Model::Attributes]
|
143
|
+
def call(attributes)
|
144
|
+
validator = new(attributes)
|
145
|
+
validator.call
|
146
|
+
end
|
147
|
+
|
148
|
+
# Specify an embedded validator for nested structures
|
149
|
+
#
|
150
|
+
# @example
|
151
|
+
# class UserValidator
|
152
|
+
# include ROM::Model::Validator
|
153
|
+
#
|
154
|
+
# set_model_name 'User'
|
155
|
+
#
|
156
|
+
# embedded :address do
|
157
|
+
# validates :city, :street, :zipcode, presence: true
|
158
|
+
# end
|
159
|
+
#
|
160
|
+
# emebdded :tasks do
|
161
|
+
# validates :title, presence: true
|
162
|
+
# end
|
163
|
+
# end
|
164
|
+
#
|
165
|
+
# validator = UserAttributes.new(address: {}, tasks: {})
|
166
|
+
#
|
167
|
+
# validator.valid? # false
|
168
|
+
# validator.errors[:address].first # errors for address
|
169
|
+
# validator.errors[:tasks] # errors for tasks
|
170
|
+
#
|
171
|
+
# @api public
|
172
|
+
def embedded(name, &block)
|
173
|
+
validator_class = Class.new { include ROM::Model::Validator }
|
174
|
+
validator_class.class_eval(&block)
|
175
|
+
validator_class.set_model_name(name.to_s.classify)
|
176
|
+
|
177
|
+
embedded_validators[name] = validator_class
|
178
|
+
|
179
|
+
validates name, presence: true
|
180
|
+
|
181
|
+
validate do
|
182
|
+
value = attributes[name]
|
183
|
+
|
184
|
+
if value.present?
|
185
|
+
Array([value]).flatten.each do |object|
|
186
|
+
validator = validator_class.new(object)
|
187
|
+
validator.validate
|
188
|
+
|
189
|
+
if validator.errors.any?
|
190
|
+
errors.add(name, validator.errors)
|
191
|
+
else
|
192
|
+
errors.add(name, [])
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'active_model/validator'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
module Model
|
5
|
+
module Validator
|
6
|
+
# Uniqueness validation
|
7
|
+
#
|
8
|
+
# @api public
|
9
|
+
class UniquenessValidator < ActiveModel::EachValidator
|
10
|
+
# Relation validator class
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
attr_reader :klass
|
14
|
+
|
15
|
+
# error message
|
16
|
+
#
|
17
|
+
# @return [String, Symbol]
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
attr_reader :message
|
21
|
+
|
22
|
+
# @api private
|
23
|
+
def initialize(options)
|
24
|
+
super
|
25
|
+
@klass = options.fetch(:class)
|
26
|
+
@message = options.fetch(:message) { :taken }
|
27
|
+
@scope_keys = options[:scope]
|
28
|
+
end
|
29
|
+
|
30
|
+
# Hook called by ActiveModel internally
|
31
|
+
#
|
32
|
+
# @api private
|
33
|
+
def validate_each(validator, name, value)
|
34
|
+
scope = Array(@scope_keys).each_with_object({}) do |key, scope|
|
35
|
+
scope[key] = validator.to_model[key]
|
36
|
+
end
|
37
|
+
validator.errors.add(name, message) unless unique?(name, value, scope)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Get relation object from the rom env
|
43
|
+
#
|
44
|
+
# @api private
|
45
|
+
def relation
|
46
|
+
if relation_name
|
47
|
+
rom.relations[relation_name]
|
48
|
+
else
|
49
|
+
raise "relation must be specified to use uniqueness validation"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Relation name defined on the validator class
|
54
|
+
#
|
55
|
+
# @api private
|
56
|
+
def relation_name
|
57
|
+
klass.relation
|
58
|
+
end
|
59
|
+
|
60
|
+
# Shortcut to access global rom env
|
61
|
+
#
|
62
|
+
# @return [ROM::Env]
|
63
|
+
#
|
64
|
+
# @api private
|
65
|
+
def rom
|
66
|
+
ROM.env
|
67
|
+
end
|
68
|
+
|
69
|
+
# Ask relation if a given attribute value is unique
|
70
|
+
#
|
71
|
+
# This uses `Relation#unique?` interface that not all adapters can
|
72
|
+
# implement.
|
73
|
+
#
|
74
|
+
# @return [TrueClass,FalseClass]
|
75
|
+
#
|
76
|
+
# @api private
|
77
|
+
def unique?(name, value, scope)
|
78
|
+
relation.unique?({name => value}.merge(scope))
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/rakelib/mutant.rake
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
desc "Run mutant against a specific subject"
|
2
|
+
task :mutant do
|
3
|
+
subject = ARGV.last
|
4
|
+
if subject == 'mutant'
|
5
|
+
abort "usage: rake mutant SUBJECT\nexample: rake mutant ROM::Header"
|
6
|
+
else
|
7
|
+
opts = {
|
8
|
+
'include' => 'lib',
|
9
|
+
'require' => 'rom',
|
10
|
+
'use' => 'rspec',
|
11
|
+
'ignore-subject' => "#{subject}#respond_to_missing?"
|
12
|
+
}.to_a.map { |k, v| "--#{k} #{v}" }.join(' ')
|
13
|
+
|
14
|
+
exec("bundle exec mutant #{opts} #{subject}")
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
begin
|
2
|
+
require "rubocop/rake_task"
|
3
|
+
|
4
|
+
Rake::Task[:default].enhance [:rubocop]
|
5
|
+
|
6
|
+
RuboCop::RakeTask.new do |task|
|
7
|
+
task.options << "--display-cop-names"
|
8
|
+
end
|
9
|
+
|
10
|
+
namespace :rubocop do
|
11
|
+
desc 'Generate a configuration file acting as a TODO list.'
|
12
|
+
task :auto_gen_config do
|
13
|
+
exec "bundle exec rubocop --auto-gen-config"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
rescue LoadError
|
18
|
+
end
|
data/rom-model.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rom/model/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "rom-model"
|
8
|
+
spec.version = ROM::Model::VERSION.dup
|
9
|
+
spec.authors = ["Piotr Solnica"]
|
10
|
+
spec.email = ["piotr.solnica@gmail.com"]
|
11
|
+
spec.summary = 'A small collection of extensions useful for data coercion and validation'
|
12
|
+
spec.homepage = "http://rom-rb.org"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_runtime_dependency 'rom-support', '~> 0.1'
|
21
|
+
spec.add_runtime_dependency 'charlatan', '~> 0.1'
|
22
|
+
spec.add_runtime_dependency 'virtus', '~> 1.0', '>= 1.0.5'
|
23
|
+
spec.add_runtime_dependency 'activemodel', '>= 3.0', '< 5.0'
|
24
|
+
spec.add_runtime_dependency 'i18n'
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler"
|
27
|
+
spec.add_development_dependency "rake"
|
28
|
+
spec.add_development_dependency "rubocop", "~> 0.28.0"
|
29
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
shared_context 'database' do
|
2
|
+
let(:rom) { ROM.env }
|
3
|
+
let(:uri) { DB_URI }
|
4
|
+
let(:conn) { Sequel.connect(uri) }
|
5
|
+
|
6
|
+
def drop_tables
|
7
|
+
[:users].each do |name|
|
8
|
+
conn.drop_table?(name)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
before do
|
13
|
+
setup = ROM.setup(:sql, conn)
|
14
|
+
|
15
|
+
drop_tables
|
16
|
+
|
17
|
+
conn.create_table :users do
|
18
|
+
primary_key :id
|
19
|
+
column :name, String, null: false
|
20
|
+
column :email, String, null: false
|
21
|
+
column :birthday, Date
|
22
|
+
index :name, unique: true
|
23
|
+
check { char_length(name) > 2 }
|
24
|
+
end
|
25
|
+
|
26
|
+
setup.relation(:users)
|
27
|
+
end
|
28
|
+
|
29
|
+
after do
|
30
|
+
conn.disconnect
|
31
|
+
end
|
32
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
if RUBY_ENGINE == 'rbx'
|
2
|
+
require "codeclimate-test-reporter"
|
3
|
+
CodeClimate::TestReporter.start
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'rom-model'
|
7
|
+
require 'rom-sql'
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'byebug'
|
11
|
+
rescue LoadError
|
12
|
+
end
|
13
|
+
|
14
|
+
DB_URI = ENV.fetch('DB_URI', 'postgres://localhost/rom_model')
|
15
|
+
|
16
|
+
root = Pathname(__FILE__).dirname
|
17
|
+
|
18
|
+
Dir[root.join('support/*.rb').to_s].each do |f|
|
19
|
+
require f
|
20
|
+
end
|
21
|
+
|
22
|
+
Dir[root.join('shared/*.rb').to_s].each do |f|
|
23
|
+
require f
|
24
|
+
end
|
25
|
+
|
26
|
+
I18n.load_path << [root.join('../config/locales/en.yml').realpath]
|
27
|
+
|
28
|
+
RSpec.configure do |config|
|
29
|
+
config.order = "random"
|
30
|
+
end
|
31
|
+
|
32
|
+
ROM.use :auto_registration
|
@@ -0,0 +1,46 @@
|
|
1
|
+
describe ROM::Model::Attributes do
|
2
|
+
let(:attributes) do
|
3
|
+
Class.new do
|
4
|
+
include ROM::Model::Attributes
|
5
|
+
|
6
|
+
attribute :name, String
|
7
|
+
|
8
|
+
timestamps
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '.timestamps' do
|
13
|
+
it 'provides a way to specify timestamps with default values' do
|
14
|
+
expect(attributes.new.created_at).to be_a(DateTime)
|
15
|
+
expect(attributes.new.updated_at).to be_a(DateTime)
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'passing in arbritrary names' do
|
19
|
+
it 'excludes :created_at when passing in :updated_at' do
|
20
|
+
attributes = Class.new {
|
21
|
+
include ROM::Model::Attributes
|
22
|
+
|
23
|
+
timestamps(:updated_at)
|
24
|
+
}
|
25
|
+
|
26
|
+
model = attributes.new
|
27
|
+
|
28
|
+
expect(model).not_to respond_to(:created_at)
|
29
|
+
expect(model).to respond_to(:updated_at)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'accepts multiple timestamp attribute names' do
|
33
|
+
attributes = Class.new {
|
34
|
+
include ROM::Model::Attributes
|
35
|
+
|
36
|
+
timestamps(:published_at, :revised_at)
|
37
|
+
}
|
38
|
+
|
39
|
+
model = attributes.new
|
40
|
+
|
41
|
+
expect(model).to respond_to(:published_at)
|
42
|
+
expect(model).to respond_to(:revised_at)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
describe 'Embedded validators' do
|
2
|
+
it 'allows defining a validator for a nested hash' do
|
3
|
+
user_validator = Class.new do
|
4
|
+
include ROM::Model::Validator
|
5
|
+
|
6
|
+
set_model_name 'User'
|
7
|
+
|
8
|
+
validates :name, presence: true
|
9
|
+
|
10
|
+
embedded :address do
|
11
|
+
set_model_name 'Address'
|
12
|
+
|
13
|
+
validates :street, :city, :zipcode, presence: true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
attributes = { name: '', address: { street: '', city: '', zipcode: '' } }
|
18
|
+
|
19
|
+
expect { user_validator.call(attributes) }.to raise_error(
|
20
|
+
ROM::Model::ValidationError)
|
21
|
+
|
22
|
+
validator = user_validator.new(attributes)
|
23
|
+
|
24
|
+
expect(validator).to_not be_valid
|
25
|
+
|
26
|
+
expect(validator.errors[:name]).to include("can't be blank")
|
27
|
+
|
28
|
+
address_errors = validator.errors[:address].first
|
29
|
+
|
30
|
+
expect(address_errors).to_not be_empty
|
31
|
+
|
32
|
+
expect(address_errors[:street]).to include("can't be blank")
|
33
|
+
expect(address_errors[:city]).to include("can't be blank")
|
34
|
+
expect(address_errors[:zipcode]).to include("can't be blank")
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'allows defining a validator for a nested array' do
|
38
|
+
user_validator = Class.new do
|
39
|
+
include ROM::Model::Validator
|
40
|
+
|
41
|
+
set_model_name 'User'
|
42
|
+
|
43
|
+
validates :name, presence: true
|
44
|
+
|
45
|
+
embedded :tasks do
|
46
|
+
set_model_name 'Task'
|
47
|
+
|
48
|
+
validates :title, presence: true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
attributes = {
|
53
|
+
name: '',
|
54
|
+
tasks: [
|
55
|
+
{ title: '' },
|
56
|
+
{ title: 'Two' }
|
57
|
+
]
|
58
|
+
}
|
59
|
+
|
60
|
+
expect { user_validator.call(attributes) }.to raise_error(
|
61
|
+
ROM::Model::ValidationError)
|
62
|
+
|
63
|
+
validator = user_validator.new(attributes)
|
64
|
+
|
65
|
+
expect(validator).to_not be_valid
|
66
|
+
|
67
|
+
expect(validator.errors[:name]).to include("can't be blank")
|
68
|
+
|
69
|
+
task_errors = validator.errors[:tasks]
|
70
|
+
|
71
|
+
expect(task_errors).to_not be_empty
|
72
|
+
|
73
|
+
expect(task_errors[0][:title]).to include("can't be blank")
|
74
|
+
expect(task_errors[1]).to be_empty
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'validates presence of the nested structure' do
|
78
|
+
user_validator = Class.new do
|
79
|
+
include ROM::Model::Validator
|
80
|
+
|
81
|
+
set_model_name 'User'
|
82
|
+
|
83
|
+
validates :name, presence: true
|
84
|
+
|
85
|
+
embedded :tasks do
|
86
|
+
set_model_name 'Task'
|
87
|
+
|
88
|
+
validates :title, presence: true
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
validator = user_validator.new(name: '')
|
93
|
+
validator.validate
|
94
|
+
|
95
|
+
expect(validator.errors[:name]).to include("can't be blank")
|
96
|
+
expect(validator.errors[:tasks]).to include("can't be blank")
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'exposes registered validators in embedded_validators hash' do
|
100
|
+
user_validator = Class.new do
|
101
|
+
include ROM::Model::Validator
|
102
|
+
|
103
|
+
set_model_name 'User'
|
104
|
+
|
105
|
+
validates :name, presence: true
|
106
|
+
|
107
|
+
embedded :tasks do
|
108
|
+
set_model_name 'Task'
|
109
|
+
|
110
|
+
validates :title, presence: true
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
expect(user_validator.embedded_validators[:tasks]).to be_present
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
describe 'Validation' do
|
2
|
+
include_context 'database'
|
3
|
+
|
4
|
+
subject(:validator) { user_validator.new(attributes) }
|
5
|
+
|
6
|
+
let(:user_attrs) do
|
7
|
+
Class.new {
|
8
|
+
include ROM::Model::Attributes
|
9
|
+
|
10
|
+
set_model_name 'User'
|
11
|
+
|
12
|
+
attribute :name, String
|
13
|
+
attribute :email, String
|
14
|
+
attribute :birthday, Date
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:user_validator) do
|
19
|
+
Class.new {
|
20
|
+
include ROM::Model::Validator
|
21
|
+
|
22
|
+
relation :users
|
23
|
+
|
24
|
+
validates :name, presence: true, uniqueness: { message: 'TAKEN!' }
|
25
|
+
validates :email, uniqueness: true
|
26
|
+
|
27
|
+
def self.name
|
28
|
+
'User'
|
29
|
+
end
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
before { ROM.finalize }
|
34
|
+
|
35
|
+
describe '#call' do
|
36
|
+
let(:attributes) { user_attrs.new }
|
37
|
+
|
38
|
+
it 'raises validation error when attributes are not valid' do
|
39
|
+
expect { validator.call }.to raise_error(ROM::Model::ValidationError)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#validate" do
|
44
|
+
let(:attributes) { user_attrs.new }
|
45
|
+
|
46
|
+
it "sets errors when attributes are not valid" do
|
47
|
+
validator.validate
|
48
|
+
expect(validator.errors[:name]).to eql(["can't be blank"])
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe ':presence' do
|
53
|
+
let(:attributes) { user_attrs.new(name: '') }
|
54
|
+
|
55
|
+
it 'sets error messages' do
|
56
|
+
expect(validator).to_not be_valid
|
57
|
+
expect(validator.errors[:name]).to eql(["can't be blank"])
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe ':uniqueness' do
|
62
|
+
let(:attributes) { user_attrs.new(name: 'Jane', email: 'jane@doe.org') }
|
63
|
+
|
64
|
+
it 'sets default error messages' do
|
65
|
+
rom.relations.users.insert(name: 'Jane', email: 'jane@doe.org')
|
66
|
+
|
67
|
+
expect(validator).to_not be_valid
|
68
|
+
expect(validator.errors[:email]).to eql(['has already been taken'])
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'sets custom error messages' do
|
72
|
+
rom.relations.users.insert(name: 'Jane', email: 'jane@doe.org')
|
73
|
+
|
74
|
+
expect(validator).to_not be_valid
|
75
|
+
expect(validator.errors[:name]).to eql(['TAKEN!'])
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'with unique attributes within a scope' do
|
79
|
+
let(:user_validator) do
|
80
|
+
Class.new {
|
81
|
+
include ROM::Model::Validator
|
82
|
+
|
83
|
+
relation :users
|
84
|
+
|
85
|
+
validates :email, uniqueness: {scope: :name}
|
86
|
+
|
87
|
+
def self.name
|
88
|
+
'User'
|
89
|
+
end
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
let(:doubly_scoped_validator) do
|
94
|
+
Class.new {
|
95
|
+
include ROM::Model::Validator
|
96
|
+
|
97
|
+
relation :users
|
98
|
+
|
99
|
+
validates :email, uniqueness: {scope: [:name, :birthday]}
|
100
|
+
|
101
|
+
def self.name
|
102
|
+
'User'
|
103
|
+
end
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'does not add errors' do
|
108
|
+
rom.relations.users.insert(name: 'Jane', email: 'jane+doe@doe.org')
|
109
|
+
attributes = user_attrs.new(name: 'Jane', email: 'jane@doe.org', birthday: Date.parse('2014-12-12'))
|
110
|
+
validator = user_validator.new(attributes)
|
111
|
+
expect(validator).to be_valid
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'adds an error when the doubly scoped validation fails' do
|
115
|
+
attributes = user_attrs.new(name: 'Jane', email: 'jane@doe.org', birthday: Date.parse('2014-12-12'))
|
116
|
+
validator = doubly_scoped_validator.new(attributes)
|
117
|
+
expect(validator).to be_valid
|
118
|
+
|
119
|
+
rom.relations.users.insert(attributes.attributes)
|
120
|
+
expect(validator).to_not be_valid
|
121
|
+
|
122
|
+
attributes = user_attrs.new(name: 'Jane', email: 'jane+doe@doe.org', birthday: Date.parse('2014-12-12'))
|
123
|
+
validator = doubly_scoped_validator.new(attributes)
|
124
|
+
expect(validator).to be_valid
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe 'with missing relation' do
|
129
|
+
let(:user_validator) do
|
130
|
+
Class.new {
|
131
|
+
include ROM::Model::Validator
|
132
|
+
|
133
|
+
validates :email, uniqueness: true
|
134
|
+
|
135
|
+
def self.name
|
136
|
+
'User'
|
137
|
+
end
|
138
|
+
}
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'raises a helpful error' do
|
142
|
+
validator = user_validator.new(user_attrs.new)
|
143
|
+
expect {
|
144
|
+
validator.valid?
|
145
|
+
}.to raise_error(/relation must be specified/)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe '#method_missing' do
|
151
|
+
let(:attributes) { { name: 'Jane' } }
|
152
|
+
|
153
|
+
it 'returns attribute value if present' do
|
154
|
+
expect(validator.name).to eql('Jane')
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'returns nil if attribute is not present' do
|
158
|
+
expect(validator.email).to be(nil)
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'raises error when name does not match any of the attributes' do
|
162
|
+
expect { validator.foobar }.to raise_error(NoMethodError, /foobar/)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
metadata
ADDED
@@ -0,0 +1,196 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rom-model
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Piotr Solnica
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rom-support
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: charlatan
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: virtus
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.0'
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 1.0.5
|
51
|
+
type: :runtime
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - "~>"
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '1.0'
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 1.0.5
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: activemodel
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '3.0'
|
68
|
+
- - "<"
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '5.0'
|
71
|
+
type: :runtime
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '3.0'
|
78
|
+
- - "<"
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '5.0'
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: i18n
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :runtime
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: bundler
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
- !ruby/object:Gem::Dependency
|
110
|
+
name: rake
|
111
|
+
requirement: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
type: :development
|
117
|
+
prerelease: false
|
118
|
+
version_requirements: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
- !ruby/object:Gem::Dependency
|
124
|
+
name: rubocop
|
125
|
+
requirement: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - "~>"
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: 0.28.0
|
130
|
+
type: :development
|
131
|
+
prerelease: false
|
132
|
+
version_requirements: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - "~>"
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: 0.28.0
|
137
|
+
description:
|
138
|
+
email:
|
139
|
+
- piotr.solnica@gmail.com
|
140
|
+
executables: []
|
141
|
+
extensions: []
|
142
|
+
extra_rdoc_files: []
|
143
|
+
files:
|
144
|
+
- ".gitignore"
|
145
|
+
- ".rspec"
|
146
|
+
- ".travis.yml"
|
147
|
+
- CHANGELOG.md
|
148
|
+
- Gemfile
|
149
|
+
- LICENSE
|
150
|
+
- README.md
|
151
|
+
- Rakefile
|
152
|
+
- config/locales/en.yml
|
153
|
+
- lib/rom-model.rb
|
154
|
+
- lib/rom/model.rb
|
155
|
+
- lib/rom/model/attributes.rb
|
156
|
+
- lib/rom/model/validator.rb
|
157
|
+
- lib/rom/model/validator/uniqueness_validator.rb
|
158
|
+
- lib/rom/model/version.rb
|
159
|
+
- rakelib/mutant.rake
|
160
|
+
- rakelib/rubocop.rake
|
161
|
+
- rom-model.gemspec
|
162
|
+
- spec/shared/database.rb
|
163
|
+
- spec/spec_helper.rb
|
164
|
+
- spec/unit/attributes_spec.rb
|
165
|
+
- spec/unit/validator/embedded_spec.rb
|
166
|
+
- spec/unit/validator_spec.rb
|
167
|
+
homepage: http://rom-rb.org
|
168
|
+
licenses:
|
169
|
+
- MIT
|
170
|
+
metadata: {}
|
171
|
+
post_install_message:
|
172
|
+
rdoc_options: []
|
173
|
+
require_paths:
|
174
|
+
- lib
|
175
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - ">="
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0'
|
180
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
181
|
+
requirements:
|
182
|
+
- - ">="
|
183
|
+
- !ruby/object:Gem::Version
|
184
|
+
version: '0'
|
185
|
+
requirements: []
|
186
|
+
rubyforge_project:
|
187
|
+
rubygems_version: 2.4.5
|
188
|
+
signing_key:
|
189
|
+
specification_version: 4
|
190
|
+
summary: A small collection of extensions useful for data coercion and validation
|
191
|
+
test_files:
|
192
|
+
- spec/shared/database.rb
|
193
|
+
- spec/spec_helper.rb
|
194
|
+
- spec/unit/attributes_spec.rb
|
195
|
+
- spec/unit/validator/embedded_spec.rb
|
196
|
+
- spec/unit/validator_spec.rb
|