rom-rails 0.3.0.beta1 → 0.3.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -1
- data/lib/generators/rom/form/templates/edit_form.rb.erb +1 -1
- data/lib/generators/rom/form/templates/new_form.rb.erb +1 -1
- data/lib/generators/rom/mapper/templates/mapper.rb.erb +2 -2
- data/lib/rom/model.rb +1 -1
- data/lib/rom/rails/controller_extension.rb +0 -35
- data/lib/rom/rails/model/attributes.rb +133 -0
- data/lib/rom/rails/model/form.rb +96 -5
- data/lib/rom/rails/model/form/class_interface.rb +401 -0
- data/lib/rom/rails/model/validator.rb +62 -15
- data/lib/rom/rails/model/validator/uniqueness_validator.rb +37 -1
- data/lib/rom/rails/version.rb +1 -1
- data/rom-rails.gemspec +2 -2
- data/spec/dummy/app/controllers/users_controller.rb +7 -8
- data/spec/dummy/app/forms/new_user_form.rb +2 -2
- data/spec/dummy/app/forms/update_user_form.rb +2 -2
- data/spec/dummy/spec/integration/{user_params_spec.rb → user_attributes_spec.rb} +11 -11
- data/spec/dummy/spec/integration/user_model_mapping_spec.rb +1 -1
- data/spec/lib/generators/form_generator_spec.rb +2 -2
- data/spec/lib/generators/mapper_generator_spec.rb +2 -2
- data/spec/unit/form_spec.rb +20 -16
- data/spec/unit/validator_spec.rb +9 -9
- metadata +11 -16
- data/lib/rom/rails/model/form/dsl.rb +0 -173
- data/lib/rom/rails/model/params.rb +0 -72
- data/spec/dummy/spec/controllers/users_controller_spec.rb +0 -29
- data/spec/unit/params_spec.rb +0 -23
@@ -7,8 +7,8 @@ module ROM
|
|
7
7
|
# @example
|
8
8
|
#
|
9
9
|
#
|
10
|
-
# class
|
11
|
-
# include ROM::Model::
|
10
|
+
# class UserAttributes
|
11
|
+
# include ROM::Model::Attributes
|
12
12
|
#
|
13
13
|
# attribute :name
|
14
14
|
#
|
@@ -19,53 +19,100 @@ module ROM
|
|
19
19
|
# include ROM::Model::Validator
|
20
20
|
# end
|
21
21
|
#
|
22
|
-
#
|
23
|
-
# UserValidator.call(
|
22
|
+
# attrs = UserAttributes.new(name: '')
|
23
|
+
# UserValidator.call(attrs) # raises ValidationError
|
24
24
|
#
|
25
25
|
# @api public
|
26
26
|
module Validator
|
27
|
+
# Inclusion hook that extends a class with required interfaces
|
28
|
+
#
|
29
|
+
# @api private
|
27
30
|
def self.included(base)
|
28
31
|
base.class_eval do
|
29
32
|
extend ClassMethods
|
30
33
|
include ActiveModel::Validations
|
31
|
-
include Equalizer.new(:
|
34
|
+
include Equalizer.new(:attributes, :errors)
|
32
35
|
end
|
33
36
|
end
|
34
37
|
|
35
|
-
|
36
|
-
|
38
|
+
# @return [Model::Attributes]
|
39
|
+
#
|
40
|
+
# @api private
|
41
|
+
attr_reader :attributes
|
37
42
|
|
38
|
-
|
39
|
-
|
43
|
+
delegate :model_name, to: :attributes
|
44
|
+
|
45
|
+
# @api private
|
46
|
+
def initialize(attributes)
|
47
|
+
@attributes = attributes
|
40
48
|
end
|
41
49
|
|
50
|
+
# @return [Model::Attributes]
|
51
|
+
#
|
52
|
+
# @api public
|
42
53
|
def to_model
|
43
|
-
|
54
|
+
attributes
|
44
55
|
end
|
45
56
|
|
57
|
+
# Trigger validations and return attributes on success
|
58
|
+
#
|
59
|
+
# @raises ValidationError
|
60
|
+
#
|
61
|
+
# @return [Model::Attributes]
|
62
|
+
#
|
63
|
+
# @api public
|
46
64
|
def call
|
47
65
|
raise ValidationError, errors unless valid?
|
48
|
-
|
66
|
+
attributes
|
49
67
|
end
|
50
68
|
|
51
69
|
private
|
52
70
|
|
71
|
+
# This is needed for ActiveModel::Validations to work properly
|
72
|
+
# as it expects the object to provide attribute values. Meh.
|
73
|
+
#
|
74
|
+
# @api private
|
53
75
|
def method_missing(name)
|
54
|
-
|
76
|
+
attributes[name]
|
55
77
|
end
|
56
78
|
|
57
79
|
module ClassMethods
|
80
|
+
# Set relation name for a validator
|
81
|
+
#
|
82
|
+
# This is needed for validators that require database access
|
83
|
+
#
|
84
|
+
# @example
|
85
|
+
#
|
86
|
+
# class UserValidator
|
87
|
+
# include ROM::Model::Validator
|
88
|
+
#
|
89
|
+
# relation :users
|
90
|
+
#
|
91
|
+
# validates :name, uniqueness: true
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# @return [Symbol]
|
95
|
+
#
|
96
|
+
# @api public
|
58
97
|
def relation(name = nil)
|
59
98
|
@relation = name if name
|
60
99
|
@relation
|
61
100
|
end
|
62
101
|
|
102
|
+
# FIXME: this looks like not needed
|
63
103
|
def model_name
|
64
|
-
|
104
|
+
attributes.model_name
|
65
105
|
end
|
66
106
|
|
67
|
-
|
68
|
-
|
107
|
+
# Trigger validation for specific attributes
|
108
|
+
#
|
109
|
+
# @param [Model::Attributes] attributes The attributes for validation
|
110
|
+
#
|
111
|
+
# @raises [ValidationError]
|
112
|
+
#
|
113
|
+
# @return [Model::Attributes]
|
114
|
+
def call(attributes)
|
115
|
+
validator = new(attributes)
|
69
116
|
validator.call
|
70
117
|
end
|
71
118
|
end
|
@@ -3,33 +3,69 @@ require 'active_model/validator'
|
|
3
3
|
module ROM
|
4
4
|
module Model
|
5
5
|
module Validator
|
6
|
+
# Uniqueness validation
|
7
|
+
#
|
8
|
+
# @api public
|
6
9
|
class UniquenessValidator < ActiveModel::EachValidator
|
7
|
-
|
10
|
+
# Relation validator class
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
attr_reader :klass
|
8
14
|
|
15
|
+
# error message
|
16
|
+
#
|
17
|
+
# @return [String, Symbol]
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
attr_reader :message
|
21
|
+
|
22
|
+
# @api private
|
9
23
|
def initialize(options)
|
10
24
|
super
|
11
25
|
@klass = options.fetch(:class)
|
12
26
|
@message = options.fetch(:message) { :taken }
|
13
27
|
end
|
14
28
|
|
29
|
+
# Hook called by ActiveModel internally
|
30
|
+
#
|
31
|
+
# @api private
|
15
32
|
def validate_each(validator, name, value)
|
16
33
|
validator.errors.add(name, message) unless unique?(name, value)
|
17
34
|
end
|
18
35
|
|
19
36
|
private
|
20
37
|
|
38
|
+
# Get relation object from the rom env
|
39
|
+
#
|
40
|
+
# @api private
|
21
41
|
def relation
|
22
42
|
rom.relations[relation_name]
|
23
43
|
end
|
24
44
|
|
45
|
+
# Relation name defined on the validator class
|
46
|
+
#
|
47
|
+
# @api private
|
25
48
|
def relation_name
|
26
49
|
klass.relation
|
27
50
|
end
|
28
51
|
|
52
|
+
# Shortcut to access global rom env
|
53
|
+
#
|
54
|
+
# @return [ROM::Env]
|
55
|
+
#
|
56
|
+
# @api private
|
29
57
|
def rom
|
30
58
|
ROM.env
|
31
59
|
end
|
32
60
|
|
61
|
+
# Ask relation if a given attribute value is unique
|
62
|
+
#
|
63
|
+
# This uses `Relation#unique?` interface that not all adapters can
|
64
|
+
# implement.
|
65
|
+
#
|
66
|
+
# @return [TrueClass,FalseClass]
|
67
|
+
#
|
68
|
+
# @api private
|
33
69
|
def unique?(name, value)
|
34
70
|
relation.unique?(name => value)
|
35
71
|
end
|
data/lib/rom/rails/version.rb
CHANGED
data/rom-rails.gemspec
CHANGED
@@ -17,10 +17,10 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
|
-
spec.add_runtime_dependency 'rom', '~> 0.6.0.
|
20
|
+
spec.add_runtime_dependency 'rom', '~> 0.6.0.rc1'
|
21
21
|
spec.add_runtime_dependency 'addressable', '~> 2.3'
|
22
22
|
spec.add_runtime_dependency 'charlatan', '~> 0.1'
|
23
|
-
spec.add_runtime_dependency 'virtus', '~> 1.0', '>= 1.0.
|
23
|
+
spec.add_runtime_dependency 'virtus', '~> 1.0', '>= 1.0.5'
|
24
24
|
spec.add_runtime_dependency 'railties', ['>= 3.0', '< 5.0']
|
25
25
|
|
26
26
|
spec.add_development_dependency "bundler"
|
@@ -3,11 +3,14 @@ class UsersController < ApplicationController
|
|
3
3
|
head :bad_request
|
4
4
|
end
|
5
5
|
|
6
|
-
relation 'users.index', only: :index
|
7
|
-
relation 'users.by_name', only: :search, requires: :name
|
8
|
-
|
9
6
|
def index
|
10
|
-
render
|
7
|
+
render :index, locals: { users: rom.relation(:users).as(:users) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def search
|
11
|
+
render :index, locals: {
|
12
|
+
users: rom.relation(:users).as(:users).by_name(params[:name])
|
13
|
+
}
|
11
14
|
end
|
12
15
|
|
13
16
|
def new
|
@@ -40,10 +43,6 @@ class UsersController < ApplicationController
|
|
40
43
|
end
|
41
44
|
end
|
42
45
|
|
43
|
-
def search
|
44
|
-
render :index
|
45
|
-
end
|
46
|
-
|
47
46
|
def ping
|
48
47
|
head :ok
|
49
48
|
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
class UpdateUserForm < UserForm
|
2
2
|
commands users: :update
|
3
3
|
|
4
|
-
|
4
|
+
attributes.timestamps(:updated_at)
|
5
5
|
|
6
6
|
def commit!
|
7
|
-
users.try { users.update.by_id(id).set(
|
7
|
+
users.try { users.update.by_id(id).set(attributes) }
|
8
8
|
end
|
9
9
|
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe ROM::Model::
|
4
|
-
let(:
|
3
|
+
describe ROM::Model::Attributes do
|
4
|
+
let(:attributes) do
|
5
5
|
Class.new do
|
6
|
-
include ROM::Model::
|
6
|
+
include ROM::Model::Attributes
|
7
7
|
|
8
8
|
attribute :name, String
|
9
9
|
|
@@ -13,32 +13,32 @@ describe ROM::Model::Params do
|
|
13
13
|
|
14
14
|
describe '.timestamps' do
|
15
15
|
it 'provides a way to specify timestamps with default values' do
|
16
|
-
expect(
|
17
|
-
expect(
|
16
|
+
expect(attributes.new.created_at).to be_a(DateTime)
|
17
|
+
expect(attributes.new.updated_at).to be_a(DateTime)
|
18
18
|
end
|
19
19
|
|
20
20
|
context 'passing in arbritrary names' do
|
21
21
|
it 'excludes :created_at when passing in :updated_at' do
|
22
|
-
|
23
|
-
include ROM::Model::
|
22
|
+
attributes = Class.new {
|
23
|
+
include ROM::Model::Attributes
|
24
24
|
|
25
25
|
timestamps(:updated_at)
|
26
26
|
}
|
27
27
|
|
28
|
-
model =
|
28
|
+
model = attributes.new
|
29
29
|
|
30
30
|
expect(model).not_to respond_to(:created_at)
|
31
31
|
expect(model).to respond_to(:updated_at)
|
32
32
|
end
|
33
33
|
|
34
34
|
it 'accepts multiple timestamp attribute names' do
|
35
|
-
|
36
|
-
include ROM::Model::
|
35
|
+
attributes = Class.new {
|
36
|
+
include ROM::Model::Attributes
|
37
37
|
|
38
38
|
timestamps(:published_at, :revised_at)
|
39
39
|
}
|
40
40
|
|
41
|
-
model =
|
41
|
+
model = attributes.new
|
42
42
|
|
43
43
|
expect(model).to respond_to(:published_at)
|
44
44
|
expect(model).to respond_to(:revised_at)
|
@@ -37,7 +37,7 @@ describe ROM::Generators::FormGenerator do
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def commit!
|
40
|
-
users.try { users.create.call(
|
40
|
+
users.try { users.create.call(attributes) }
|
41
41
|
end
|
42
42
|
|
43
43
|
end
|
@@ -76,7 +76,7 @@ describe ROM::Generators::FormGenerator do
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def commit!
|
79
|
-
users.try { users.update.by_id(id).set(
|
79
|
+
users.try { users.update.by_id(id).set(attributes) }
|
80
80
|
end
|
81
81
|
|
82
82
|
end
|
data/spec/unit/form_spec.rb
CHANGED
@@ -25,17 +25,21 @@ describe 'Form' do
|
|
25
25
|
|
26
26
|
describe '.build' do
|
27
27
|
it 'rejects blank strings from params' do
|
28
|
-
input = {
|
29
|
-
'name' => 'Jane',
|
30
|
-
'hash' => { 'one' => '', 'two' => 2 },
|
31
|
-
'array' => [{ 'three' => '', 'four' => 4 }, 5]
|
32
|
-
}
|
28
|
+
input = { 'name' => '' }
|
33
29
|
|
34
30
|
form_object = form.build(input)
|
35
31
|
|
36
|
-
expect(form_object.
|
37
|
-
|
38
|
-
|
32
|
+
expect(form_object.attributes.to_h).to eql(email: nil)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'exposes param values' do
|
36
|
+
params = { 'email' => 'jane@doe.org' }
|
37
|
+
form_object = form.build(params)
|
38
|
+
expect(form_object.email).to eql('jane@doe.org')
|
39
|
+
|
40
|
+
params = { email: 'jane@doe.org' }
|
41
|
+
form_object = form.build(params)
|
42
|
+
expect(form_object.email).to eql('jane@doe.org')
|
39
43
|
end
|
40
44
|
end
|
41
45
|
|
@@ -48,7 +52,7 @@ describe 'Form' do
|
|
48
52
|
validations { validates :name, presence: true }
|
49
53
|
|
50
54
|
def commit!
|
51
|
-
users.try { users.create.call(
|
55
|
+
users.try { users.create.call(attributes) }
|
52
56
|
end
|
53
57
|
}
|
54
58
|
|
@@ -94,16 +98,16 @@ describe 'Form' do
|
|
94
98
|
end
|
95
99
|
|
96
100
|
describe '.model_name' do
|
97
|
-
it 'delegates to
|
98
|
-
expect(form.model_name).to be(form.
|
101
|
+
it 'delegates to Attributes.model_name' do
|
102
|
+
expect(form.model_name).to be(form.attributes.model_name)
|
99
103
|
end
|
100
104
|
end
|
101
105
|
|
102
106
|
describe 'input DSL' do
|
103
107
|
it 'defines params handler' do
|
104
|
-
expect(form.const_defined?(:
|
105
|
-
expect(form.
|
106
|
-
expect(form.
|
108
|
+
expect(form.const_defined?(:Attributes)).to be(true)
|
109
|
+
expect(form.attributes.attribute_set.map(&:name)).to eql([:email])
|
110
|
+
expect(form.attributes.model_name).to eql('User')
|
107
111
|
end
|
108
112
|
|
109
113
|
it 'defines a model' do
|
@@ -278,8 +282,8 @@ describe 'Form' do
|
|
278
282
|
end
|
279
283
|
|
280
284
|
it 'copies input' do
|
281
|
-
expect(child_form.
|
282
|
-
expect(child_form.
|
285
|
+
expect(child_form.attributes.attribute_set[:email]).to_not be(nil)
|
286
|
+
expect(child_form.attributes).to_not be(form.attributes)
|
283
287
|
end
|
284
288
|
|
285
289
|
it 'copies model' do
|
data/spec/unit/validator_spec.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe 'Validation' do
|
4
|
-
subject(:validator) { user_validator.new(
|
4
|
+
subject(:validator) { user_validator.new(attributes) }
|
5
5
|
|
6
|
-
let(:
|
6
|
+
let(:user_attrs) do
|
7
7
|
Class.new {
|
8
|
-
include ROM::Model::
|
8
|
+
include ROM::Model::Attributes
|
9
9
|
|
10
10
|
attribute :name, String
|
11
11
|
attribute :email, String
|
@@ -28,24 +28,24 @@ describe 'Validation' do
|
|
28
28
|
end
|
29
29
|
|
30
30
|
describe '#call' do
|
31
|
-
let(:
|
31
|
+
let(:attributes) { {} }
|
32
32
|
|
33
|
-
it 'raises validation error when
|
33
|
+
it 'raises validation error when attributes are not valid' do
|
34
34
|
expect { validator.call }.to raise_error(ROM::Model::ValidationError)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
38
|
describe "#validate" do
|
39
|
-
let(:
|
39
|
+
let(:attributes) { {} }
|
40
40
|
|
41
|
-
it "sets errors when
|
41
|
+
it "sets errors when attributes are not valid" do
|
42
42
|
validator.validate
|
43
43
|
expect(validator.errors[:name]).to eql(["can't be blank"])
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
47
|
describe ':presence' do
|
48
|
-
let(:
|
48
|
+
let(:attributes) { user_attrs.new(name: '') }
|
49
49
|
|
50
50
|
it 'sets error messages' do
|
51
51
|
expect(validator).to_not be_valid
|
@@ -54,7 +54,7 @@ describe 'Validation' do
|
|
54
54
|
end
|
55
55
|
|
56
56
|
describe ':uniqueness' do
|
57
|
-
let(:
|
57
|
+
let(:attributes) { user_attrs.new(name: 'Jane', email: 'jane@doe.org') }
|
58
58
|
|
59
59
|
before do
|
60
60
|
rom.relations.users.insert(name: 'Jane', email: 'jane@doe.org')
|