rom-model 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 Version](https://badge.fury.io/rb/rom-model.svg)][gem]
|
10
|
+
[![Build Status](https://travis-ci.org/rom-rb/rom-model.svg?branch=master)][travis]
|
11
|
+
[![Dependency Status](https://gemnasium.com/rom-rb/rom-model.png)][gemnasium]
|
12
|
+
[![Code Climate](https://codeclimate.com/github/rom-rb/rom-model/badges/gpa.svg)][codeclimate]
|
13
|
+
[![Test Coverage](https://codeclimate.com/github/rom-rb/rom-model/badges/coverage.svg)][codeclimate]
|
14
|
+
[![Inline docs](http://inch-ci.org/github/rom-rb/rom-model.svg?branch=master)][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
|