rom-rails 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/lib/generators/rom/relation/templates/relation.rb.erb +3 -3
  4. data/lib/generators/rom/relation_generator.rb +5 -5
  5. data/lib/rom/rails/active_record/configuration.rb +1 -1
  6. data/lib/rom/rails/configuration.rb +6 -1
  7. data/lib/rom/rails/inflections.rb +2 -1
  8. data/lib/rom/rails/model/form/class_interface.rb +2 -2
  9. data/lib/rom/rails/model/validator.rb +75 -5
  10. data/lib/rom/rails/model/validator/uniqueness_validator.rb +7 -3
  11. data/lib/rom/rails/railtie.rb +68 -64
  12. data/lib/rom/rails/tasks/db.rake +3 -7
  13. data/lib/rom/rails/version.rb +1 -1
  14. data/rom-rails.gemspec +1 -1
  15. data/spec/dummy/app/mappers/users.rb +1 -0
  16. data/spec/dummy/app/relations/dummy_relation.rb +1 -1
  17. data/spec/dummy/config/initializers/rom.rb +2 -2
  18. data/spec/dummy/db/migrate/20141110205016_add_users.rb +1 -0
  19. data/spec/dummy/lib/rom/test_adapter.rb +1 -1
  20. data/spec/{dummy/spec/features → features}/users_spec.rb +0 -0
  21. data/spec/{dummy/spec/integration → integration}/activerecord_setup.rb +1 -1
  22. data/spec/{dummy/spec/integration → integration}/form_with_injected_commands_spec.rb +0 -0
  23. data/spec/integration/initializer_spec.rb +11 -0
  24. data/spec/{dummy/spec/integration → integration}/logger_spec.rb +3 -3
  25. data/spec/{dummy/spec/integration → integration}/new_user_form_spec.rb +0 -0
  26. data/spec/{dummy/spec/integration → integration}/user_attributes_spec.rb +0 -0
  27. data/spec/{dummy/spec/integration → integration}/user_commands_spec.rb +0 -0
  28. data/spec/{dummy/spec/integration → integration}/user_model_mapping_spec.rb +0 -0
  29. data/spec/lib/generators/relation_generator_spec.rb +4 -4
  30. data/spec/spec_helper.rb +2 -2
  31. data/spec/unit/validator/embedded_spec.rb +118 -0
  32. data/spec/unit/validator_spec.rb +68 -3
  33. metadata +26 -24
  34. data/spec/dummy/spec/integration/initializer_spec.rb +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d53fad0999e88095261c12196674bf05ef9754eb
4
- data.tar.gz: 134a8100ee7b0b4967315b3ce342a1526b9280e8
3
+ metadata.gz: 3eb6dc7cbe13616ba42eb04dedbf46d454e5fee6
4
+ data.tar.gz: 3de6f1246346eda7234baec3ada3af5ee425603a
5
5
  SHA512:
6
- metadata.gz: ff2d1b07af8d3e2ad0263ea1f7fa1d433dc1f3da4c801710f51daa778dc9b903224747bdd55a9ca9bb232103a4561ef332161a6db12bd367a9ffe6e130fd127b
7
- data.tar.gz: ebf50d904cd43b7be7addd0c9167921e4ae9924250b8e69aea2bbd8f9f705020f80865fc5336b8a1ee67480b8b6833d241250cb3c732afc4ed2138d2bf67c571
6
+ metadata.gz: 7f4f2a71ecc81887fee3c3fd326119af378d3a633dcadcab03191b7410756bbcf39db3ba65d5e1b78e498469299c0de1511b340cd0af861a99ae76a8b3085b64
7
+ data.tar.gz: 89106263fa73c8346e2eb7b0062e1c7386c7995e0a9630ae490b40adf5f6e7fde3e6f943b1b1c07d36154469f9f8bf94d0b4004f5dd6e5b8f0e26487a2ab16b6
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## v0.4.0 2015-06-22
2
+
3
+ ### Added
4
+
5
+ * `embedded` validators allowing to nest validations for embedded values (solnic)
6
+ * Uniqueness validation supports `:scope` option (vrish88)
7
+
8
+ ### Changed
9
+
10
+ * `ROM.env` is lazy-finalized on first access of any of the components (solnic)
11
+ * `db:setup` provided by the railtie now loads `:environment` (solnic)
12
+ * `repositories` in railtie configuration deprecated in favor of `gateways` (solnic)
13
+
14
+ ### Fixed
15
+
16
+ * `method_missing` in validators behaves properly now (solnic)
17
+
18
+ [Compare v0.3.3...v0.4.0](https://github.com/rom-rb/rom-rails/compare/v0.3.3...v0.4.0)
19
+
1
20
  ## v0.3.3 2015-05-22
2
21
 
3
22
  ### Added
@@ -1,8 +1,8 @@
1
1
  class <%= class_name %>Relation < ROM::Relation<%= "[:#{adapter}]" %>
2
- <% if repository -%>
3
- repository :<%= repository %>
2
+ <% if gateway -%>
3
+ gateway :<%= gateway %>
4
4
  <% else -%>
5
- # repository :default
5
+ # gateway :default
6
6
  <% end -%>
7
7
 
8
8
  dataset :<%= dataset %>
@@ -8,9 +8,9 @@ module ROM
8
8
  desc: "specify an adapter to use", required: true,
9
9
  default: ROM.adapters.keys.first
10
10
 
11
- class_option :repository,
12
- banner: "--repository=repo",
13
- desc: "specify a repository to connect to",
11
+ class_option :gateway,
12
+ banner: "--gateway=repo",
13
+ desc: "specify a gateway to connect to",
14
14
  required: false
15
15
 
16
16
  class_option :register,
@@ -40,8 +40,8 @@ module ROM
40
40
  options[:register] || dataset
41
41
  end
42
42
 
43
- def repository
44
- options[:repository]
43
+ def gateway
44
+ options[:gateway]
45
45
  end
46
46
 
47
47
  end
@@ -17,7 +17,7 @@ module ROM
17
17
  :root
18
18
  ].freeze
19
19
 
20
- # Returns repository configuration for the current environment.
20
+ # Returns gateway configuration for the current environment.
21
21
  #
22
22
  # @note This relies on ActiveRecord being initialized already.
23
23
  # @param [Rails::Application]
@@ -1,11 +1,16 @@
1
1
  require 'virtus'
2
+ require 'rom/support/deprecations'
2
3
 
3
4
  module ROM
4
5
  module Rails
5
6
  class Configuration
7
+ extend ROM::Deprecations
8
+
6
9
  include Virtus.model(strict: true)
7
10
 
8
- attribute :repositories, Hash, default: {}
11
+ attribute :gateways, Hash, default: {}
12
+
13
+ deprecate :repositories, :gateways
9
14
  end
10
15
  end
11
16
  end
@@ -1,5 +1,6 @@
1
1
  require 'active_support/inflections'
2
2
 
3
3
  ActiveSupport::Inflector.inflections do |inflect|
4
- inflect.acronym 'ROM'
4
+ # `acronym` was added in ActiveSupport 3.2.5
5
+ inflect.acronym 'ROM' if inflect.respond_to?(:acronym)
5
6
  end
@@ -431,8 +431,8 @@ module ROM
431
431
  klass.validator @validator
432
432
 
433
433
  relation = rom.relations[rel_name]
434
- repository = rom.repositories[relation.repository]
435
- repository.extend_command_class(klass, relation.dataset)
434
+ gateway = rom.gateways[relation.gateway]
435
+ gateway.extend_command_class(klass, relation.dataset)
436
436
 
437
437
  klass.build(relation)
438
438
  end
@@ -1,4 +1,5 @@
1
1
  require 'rom/rails/model/validator/uniqueness_validator'
2
+ require 'rom/support/class_macros'
2
3
 
3
4
  module ROM
4
5
  module Model
@@ -30,8 +31,14 @@ module ROM
30
31
  def self.included(base)
31
32
  base.class_eval do
32
33
  extend ClassMethods
34
+ extend ROM::ClassMacros
35
+
33
36
  include ActiveModel::Validations
34
37
  include Equalizer.new(:attributes, :errors)
38
+
39
+ base.defines :embedded_validators
40
+
41
+ embedded_validators({})
35
42
  end
36
43
  end
37
44
 
@@ -40,11 +47,15 @@ module ROM
40
47
  # @api private
41
48
  attr_reader :attributes
42
49
 
50
+ # @api private
51
+ attr_reader :attr_names
52
+
43
53
  delegate :model_name, to: :attributes
44
54
 
45
55
  # @api private
46
56
  def initialize(attributes)
47
57
  @attributes = attributes
58
+ @attr_names = self.class.validators.map(&:attributes).flatten.uniq
48
59
  end
49
60
 
50
61
  # @return [Model::Attributes]
@@ -72,8 +83,12 @@ module ROM
72
83
  # as it expects the object to provide attribute values. Meh.
73
84
  #
74
85
  # @api private
75
- def method_missing(name)
76
- attributes[name]
86
+ def method_missing(name, *args, &block)
87
+ if attr_names.include?(name)
88
+ attributes[name]
89
+ else
90
+ super
91
+ end
77
92
  end
78
93
 
79
94
  module ClassMethods
@@ -99,9 +114,13 @@ module ROM
99
114
  @relation
100
115
  end
101
116
 
102
- # FIXME: this looks like not needed
103
- def model_name
104
- attributes.model_name
117
+ # @api private
118
+ def set_model_name(name)
119
+ class_eval <<-RUBY
120
+ def self.model_name
121
+ @model_name ||= ActiveModel::Name.new(self, nil, #{name.inspect})
122
+ end
123
+ RUBY
105
124
  end
106
125
 
107
126
  # Trigger validation for specific attributes
@@ -115,6 +134,57 @@ module ROM
115
134
  validator = new(attributes)
116
135
  validator.call
117
136
  end
137
+
138
+ # Specify an embedded validator for nested structures
139
+ #
140
+ # @example
141
+ # class UserValidator
142
+ # include ROM::Model::Validator
143
+ #
144
+ # set_model_name 'User'
145
+ #
146
+ # embedded :address do
147
+ # validates :city, :street, :zipcode, presence: true
148
+ # end
149
+ #
150
+ # emebdded :tasks do
151
+ # validates :title, presence: true
152
+ # end
153
+ # end
154
+ #
155
+ # validator = UserAttributes.new(address: {}, tasks: {})
156
+ #
157
+ # validator.valid? # false
158
+ # validator.errors[:address].first # errors for address
159
+ # validator.errors[:tasks] # errors for tasks
160
+ #
161
+ # @api public
162
+ def embedded(name, &block)
163
+ validator_class = Class.new { include ROM::Model::Validator }
164
+ validator_class.class_eval(&block)
165
+ validator_class.set_model_name(name.to_s.classify)
166
+
167
+ embedded_validators[name] = validator_class
168
+
169
+ validates name, presence: true
170
+
171
+ validate do
172
+ value = attributes[name]
173
+
174
+ if value.present?
175
+ Array([value]).flatten.each do |object|
176
+ validator = validator_class.new(object)
177
+ validator.validate
178
+
179
+ if validator.errors.any?
180
+ errors.add(name, validator.errors)
181
+ else
182
+ errors.add(name, [])
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
118
188
  end
119
189
  end
120
190
  end
@@ -24,13 +24,17 @@ module ROM
24
24
  super
25
25
  @klass = options.fetch(:class)
26
26
  @message = options.fetch(:message) { :taken }
27
+ @scope_keys = options[:scope]
27
28
  end
28
29
 
29
30
  # Hook called by ActiveModel internally
30
31
  #
31
32
  # @api private
32
33
  def validate_each(validator, name, value)
33
- validator.errors.add(name, message) unless unique?(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)
34
38
  end
35
39
 
36
40
  private
@@ -66,8 +70,8 @@ module ROM
66
70
  # @return [TrueClass,FalseClass]
67
71
  #
68
72
  # @api private
69
- def unique?(name, value)
70
- relation.unique?(name => value)
73
+ def unique?(name, value, scope)
74
+ relation.unique?({name => value}.merge(scope))
71
75
  end
72
76
  end
73
77
  end
@@ -12,42 +12,11 @@ module ROM
12
12
  class Railtie < ::Rails::Railtie
13
13
  COMPONENT_DIRS = %w(relations mappers commands).freeze
14
14
 
15
- MissingRepositoryConfigError = Class.new(StandardError)
15
+ MissingGatewayConfigError = Class.new(StandardError)
16
16
 
17
- # @api public
18
- def self.setup_repositories
19
- raise(
20
- MissingRepositoryConfigError,
21
- "seems like you didn't configure any repositories"
22
- ) unless config.rom.repositories.any?
23
-
24
- ROM.setup(config.rom.repositories)
25
- self
26
- end
27
-
28
- # @api public
29
- def self.finalize
30
- ROM.finalize
31
- self
32
- end
33
-
34
- # If there's no default repository configured, try to infer it from
35
- # other sources, e.g. ActiveRecord.
36
- #
37
- # @api private
38
- def self.infer_default_repository
39
- return unless active_record?
40
- spec = ROM::Rails::ActiveRecord::Configuration.call
41
- [:sql, spec[:uri], spec[:options]]
42
- end
43
-
44
- # @api private
45
- def self.active_record?
46
- defined?(::ActiveRecord)
47
- end
48
-
49
- # @api private
50
- def before_initialize
17
+ # Make `ROM::Rails::Configuration` instance available to the user via
18
+ # `Rails.application.config` before other initializers run.
19
+ config.before_initialize do |_app|
51
20
  config.rom = Configuration.new
52
21
  end
53
22
 
@@ -59,25 +28,19 @@ module ROM
59
28
 
60
29
  initializer 'rom.adjust_eager_load_paths' do |app|
61
30
  paths = COMPONENT_DIRS.map do |directory|
62
- ::Rails.root.join('app', directory).to_s
31
+ root.join('app', directory).to_s
63
32
  end
64
33
 
65
- app.config.eager_load_paths -= paths
34
+ app.config.eager_load_paths -= paths
66
35
  end
67
36
 
68
37
  rake_tasks do
69
- load "rom/rails/tasks/db.rake" unless self.class.active_record?
70
- end
71
-
72
- # Make `ROM::Rails::Configuration` instance available to the user via
73
- # `Rails.application.config` before other initializers run.
74
- config.before_initialize do |_app|
75
- before_initialize
38
+ load "rom/rails/tasks/db.rake" unless active_record?
76
39
  end
77
40
 
78
41
  # Reload ROM-related application code on each request.
79
42
  config.to_prepare do |_config|
80
- Railtie.setup
43
+ Railtie.finalize
81
44
  end
82
45
 
83
46
  # Behaves like `Railtie#configure` if the given block does not take any
@@ -85,11 +48,13 @@ module ROM
85
48
  #
86
49
  # @example
87
50
  # ROM::Rails::Railtie.configure do |config|
88
- # config.repositories[:default] = [:yaml, 'yaml:///data']
51
+ # config.gateways[:default] = [:yaml, 'yaml:///data']
89
52
  # end
90
53
  #
91
54
  # @api public
92
55
  def configure(&block)
56
+ config.rom = Configuration.new unless config.respond_to?(:rom)
57
+
93
58
  if block.arity == 1
94
59
  block.call(config.rom)
95
60
  else
@@ -97,40 +62,69 @@ module ROM
97
62
  end
98
63
  end
99
64
 
65
+ # @api private
66
+ def setup(gateways)
67
+ raise(
68
+ MissingGatewayConfigError,
69
+ "seems like you didn't configure any gateways"
70
+ ) unless gateways.any?
71
+
72
+ ROM.setup(gateways)
73
+ end
74
+
75
+ # @api private
76
+ def setup_gateways
77
+ gateways =
78
+ if env
79
+ env.gateways
80
+ else
81
+ prepare_gateways
82
+ end
83
+
84
+ setup(gateways)
85
+ end
86
+
87
+ # @api private
88
+ def finalize
89
+ setup_gateways
90
+ load_components
91
+ ROM.finalize
92
+ end
93
+
100
94
  # TODO: Add `ROM.env.disconnect` to core.
101
95
  #
102
96
  # @api private
103
97
  def disconnect
104
- ROM.env.repositories.each_value(&:disconnect)
98
+ ROM.gateways.each_key(&:disconnect)
105
99
  end
106
100
 
107
101
  # @api private
108
- def setup
109
- if ROM.env
110
- ROM.setup(ROM.env.repositories)
111
- else
112
- repositories = config.rom.repositories
102
+ def prepare_gateways
103
+ config.rom.gateways[:default] ||= infer_default_gateway if active_record?
104
+ config.rom.gateways
105
+ end
113
106
 
114
- if self.class.active_record?
115
- repositories[:default] ||= self.class.infer_default_repository
116
- end
107
+ # If there's no default gateway configured, try to infer it from
108
+ # other sources, e.g. ActiveRecord.
109
+ #
110
+ # @api private
111
+ def infer_default_gateway
112
+ spec = ROM::Rails::ActiveRecord::Configuration.call
113
+ [:sql, spec[:uri], spec[:options]]
114
+ end
117
115
 
118
- self.class.setup_repositories
119
- end
120
- load_all
121
- self.class.finalize
116
+ def load_initializer
117
+ load "#{root}/config/initializers/rom.rb" rescue LoadError
122
118
  end
123
119
 
124
120
  # @api private
125
- def load_all
126
- COMPONENT_DIRS.each do |type|
127
- load_files(type)
128
- end
121
+ def load_components
122
+ COMPONENT_DIRS.each { |type| load_files(type) }
129
123
  end
130
124
 
131
125
  # @api private
132
126
  def load_files(type)
133
- Dir[root.join("app/#{type}/**/*.rb").to_s].each do |path|
127
+ Dir[root.join("app/#{type}/**/*.rb")].each do |path|
134
128
  require_dependency(path)
135
129
  end
136
130
  end
@@ -139,6 +133,16 @@ module ROM
139
133
  def root
140
134
  ::Rails.root
141
135
  end
136
+
137
+ # @api private
138
+ def env
139
+ ROM.env
140
+ end
141
+
142
+ # @api private
143
+ def active_record?
144
+ defined?(::ActiveRecord)
145
+ end
142
146
  end
143
147
  end
144
148
  end
@@ -1,11 +1,7 @@
1
1
  namespace :db do
2
- desc 'Set up ROM repositories'
2
+ desc 'Set up ROM gateways'
3
3
  task :setup do
4
- railtie = ROM::Rails::Railtie
5
- railtie.before_initialize
6
-
7
- require "#{Rails.root}/config/initializers/rom"
8
-
9
- railtie.setup_repositories.finalize
4
+ ROM::Rails::Railtie.load_initializer
5
+ ROM::Rails::Railtie.setup_gateways
10
6
  end
11
7
  end
@@ -1,5 +1,5 @@
1
1
  module ROM
2
2
  module Rails
3
- VERSION = '0.3.3'.freeze
3
+ VERSION = '0.4.0'.freeze
4
4
  end
5
5
  end
data/rom-rails.gemspec CHANGED
@@ -17,7 +17,7 @@ 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.7', '>= 0.7.0'
20
+ spec.add_runtime_dependency 'rom', '~> 0.8', '>= 0.8.0'
21
21
  spec.add_runtime_dependency 'addressable', '~> 2.3'
22
22
  spec.add_runtime_dependency 'charlatan', '~> 0.1'
23
23
  spec.add_runtime_dependency 'virtus', '~> 1.0', '>= 1.0.5'
@@ -7,4 +7,5 @@ class UserMapper < ROM::Mapper
7
7
  attribute :id
8
8
  attribute :name
9
9
  attribute :email
10
+ attribute :birthday
10
11
  end
@@ -1,4 +1,4 @@
1
1
  class DummyRelation < ROM::Relation[:test_adapter]
2
2
  register_as :dummy
3
- repository :test
3
+ gateway :test
4
4
  end
@@ -1,5 +1,5 @@
1
1
  ROM::Rails::Railtie.configure do |config|
2
2
  scheme = RUBY_ENGINE == 'jruby' ? 'jdbc:sqlite' : 'sqlite'
3
- config.repositories[:default] = [:sql, "#{scheme}://#{Rails.root}/db/#{Rails.env}.sqlite3"]
4
- config.repositories[:test] = [:test_adapter, foo: :bar]
3
+ config.gateways[:default] = [:sql, "#{scheme}://#{Rails.root}/db/#{Rails.env}.sqlite3"]
4
+ config.gateways[:test] = [:test_adapter, foo: :bar]
5
5
  end
@@ -4,6 +4,7 @@ ROM::SQL.migration do
4
4
  primary_key :id
5
5
  String :name
6
6
  String :email
7
+ Date :birthday
7
8
  DateTime :created_at
8
9
  DateTime :updated_at
9
10
  end
@@ -3,7 +3,7 @@ module ROM
3
3
  class Relation < ROM::Relation
4
4
  end
5
5
 
6
- class Repository < ROM::Repository
6
+ class Gateway < ROM::Gateway
7
7
  include Equalizer.new(:args)
8
8
 
9
9
  attr_reader :args, :datasets
@@ -1,4 +1,4 @@
1
- describe 'Connects to repositories using database.yml' do
1
+ describe 'Connects to gateways using database.yml' do
2
2
  it 'works' do
3
3
  pending 'I have no idea how to load AR in isolation'
4
4
  raise 'database.yml must go'
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'ROM initializer' do
4
+ it 'allows setting up a custom gateway' do
5
+ gateway = ROM::TestAdapter::Gateway.new(foo: :bar)
6
+ relation = DummyRelation.new([])
7
+
8
+ expect(rom.gateways[:test]).to eql(gateway)
9
+ expect(rom.relations.dummy).to eql(relation)
10
+ end
11
+ end
@@ -3,12 +3,12 @@ require 'spec_helper'
3
3
  describe 'ROM logger' do
4
4
  let(:rom) { ROM.env }
5
5
 
6
- it 'sets up rails logger for all repositories' do
6
+ it 'sets up rails logger for all gateways' do
7
7
  pending 'this will be re-enabled once we have feature detection on ' \
8
8
  'adapters and in case of missing rails-log-subscriber support we ' \
9
9
  'will set logger to Rails.logger'
10
- rom.repositories.each_value do |repository|
11
- expect(repository.logger).to be(Rails.logger)
10
+ rom.gateways.each_value do |gateway|
11
+ expect(gateway.logger).to be(Rails.logger)
12
12
  end
13
13
  end
14
14
  end
@@ -19,7 +19,7 @@ describe ROM::Generators::RelationGenerator, type: :generator do
19
19
  file 'users_relation.rb' do
20
20
  contains <<-CONTENT.strip_heredoc
21
21
  class UsersRelation < ROM::Relation[:#{default_adapter}]
22
- # repository :default
22
+ # gateway :default
23
23
 
24
24
  dataset :users
25
25
 
@@ -45,11 +45,11 @@ describe ROM::Generators::RelationGenerator, type: :generator do
45
45
  expect(relation).to include("class UsersRelation < ROM::Relation[:memory]")
46
46
  end
47
47
 
48
- specify "with given repository" do
49
- run_generator ['users', '--repository=remote']
48
+ specify "with given gateway" do
49
+ run_generator ['users', '--gateway=remote']
50
50
 
51
51
  relation = File.read(File.join(destination_root, 'app', 'relations', 'users_relation.rb'))
52
- expect(relation).to include("repository :remote")
52
+ expect(relation).to include("gateway :remote")
53
53
  end
54
54
 
55
55
  specify "with given registration" do
data/spec/spec_helper.rb CHANGED
@@ -24,14 +24,14 @@ RSpec.configure do |config|
24
24
  config.order = "random"
25
25
 
26
26
  config.before(:suite) do
27
- conn = ROM.env.repositories[:default].connection
27
+ conn = ROM.env.gateways[:default].connection
28
28
 
29
29
  DatabaseCleaner[:sequel, connection: conn].strategy = :transaction
30
30
  DatabaseCleaner[:sequel, connection: conn].clean_with(:truncation)
31
31
  end
32
32
 
33
33
  config.around(:each) do |example|
34
- conn = ROM.env.repositories[:default].connection
34
+ conn = ROM.env.gateways[:default].connection
35
35
 
36
36
  DatabaseCleaner[:sequel, connection: conn].cleaning { example.run }
37
37
  end
@@ -0,0 +1,118 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Embedded validators' do
4
+ it 'allows defining a validator for a nested hash' do
5
+ user_validator = Class.new do
6
+ include ROM::Model::Validator
7
+
8
+ set_model_name 'User'
9
+
10
+ validates :name, presence: true
11
+
12
+ embedded :address do
13
+ set_model_name 'Address'
14
+
15
+ validates :street, :city, :zipcode, presence: true
16
+ end
17
+ end
18
+
19
+ attributes = { name: '', address: { street: '', city: '', zipcode: '' } }
20
+
21
+ expect { user_validator.call(attributes) }.to raise_error(
22
+ ROM::Model::ValidationError)
23
+
24
+ validator = user_validator.new(attributes)
25
+
26
+ expect(validator).to_not be_valid
27
+
28
+ expect(validator.errors[:name]).to include("can't be blank")
29
+
30
+ address_errors = validator.errors[:address].first
31
+
32
+ expect(address_errors).to_not be_empty
33
+
34
+ expect(address_errors[:street]).to include("can't be blank")
35
+ expect(address_errors[:city]).to include("can't be blank")
36
+ expect(address_errors[:zipcode]).to include("can't be blank")
37
+ end
38
+
39
+ it 'allows defining a validator for a nested array' do
40
+ user_validator = Class.new do
41
+ include ROM::Model::Validator
42
+
43
+ set_model_name 'User'
44
+
45
+ validates :name, presence: true
46
+
47
+ embedded :tasks do
48
+ set_model_name 'Task'
49
+
50
+ validates :title, presence: true
51
+ end
52
+ end
53
+
54
+ attributes = {
55
+ name: '',
56
+ tasks: [
57
+ { title: '' },
58
+ { title: 'Two' }
59
+ ]
60
+ }
61
+
62
+ expect { user_validator.call(attributes) }.to raise_error(
63
+ ROM::Model::ValidationError)
64
+
65
+ validator = user_validator.new(attributes)
66
+
67
+ expect(validator).to_not be_valid
68
+
69
+ expect(validator.errors[:name]).to include("can't be blank")
70
+
71
+ task_errors = validator.errors[:tasks]
72
+
73
+ expect(task_errors).to_not be_empty
74
+
75
+ expect(task_errors[0][:title]).to include("can't be blank")
76
+ expect(task_errors[1]).to be_empty
77
+ end
78
+
79
+ it 'validates presence of the nested structure' do
80
+ user_validator = Class.new do
81
+ include ROM::Model::Validator
82
+
83
+ set_model_name 'User'
84
+
85
+ validates :name, presence: true
86
+
87
+ embedded :tasks do
88
+ set_model_name 'Task'
89
+
90
+ validates :title, presence: true
91
+ end
92
+ end
93
+
94
+ validator = user_validator.new(name: '')
95
+ validator.validate
96
+
97
+ expect(validator.errors[:name]).to include("can't be blank")
98
+ expect(validator.errors[:tasks]).to include("can't be blank")
99
+ end
100
+
101
+ it 'exposes registered validators in embedded_validators hash' do
102
+ user_validator = Class.new do
103
+ include ROM::Model::Validator
104
+
105
+ set_model_name 'User'
106
+
107
+ validates :name, presence: true
108
+
109
+ embedded :tasks do
110
+ set_model_name 'Task'
111
+
112
+ validates :title, presence: true
113
+ end
114
+ end
115
+
116
+ expect(user_validator.embedded_validators[:tasks]).to be_present
117
+ end
118
+ end
@@ -11,6 +11,7 @@ describe 'Validation' do
11
11
 
12
12
  attribute :name, String
13
13
  attribute :email, String
14
+ attribute :birthday, Date
14
15
  }
15
16
  end
16
17
 
@@ -58,11 +59,9 @@ describe 'Validation' do
58
59
  describe ':uniqueness' do
59
60
  let(:attributes) { user_attrs.new(name: 'Jane', email: 'jane@doe.org') }
60
61
 
61
- before do
62
+ it 'sets default error messages' do
62
63
  rom.relations.users.insert(name: 'Jane', email: 'jane@doe.org')
63
- end
64
64
 
65
- it 'sets default error messages' do
66
65
  expect(validator).to_not be_valid
67
66
  expect(validator.errors[:email]).to eql(['has already been taken'])
68
67
  end
@@ -73,5 +72,71 @@ describe 'Validation' do
73
72
  expect(validator).to_not be_valid
74
73
  expect(validator.errors[:name]).to eql(['TAKEN!'])
75
74
  end
75
+
76
+ context 'with unique attributes within a scope' do
77
+ let(:user_validator) do
78
+ Class.new {
79
+ include ROM::Model::Validator
80
+
81
+ relation :users
82
+
83
+ validates :email, uniqueness: {scope: :name}
84
+
85
+ def self.name
86
+ 'User'
87
+ end
88
+ }
89
+ end
90
+
91
+ let(:doubly_scoped_validator) do
92
+ Class.new {
93
+ include ROM::Model::Validator
94
+
95
+ relation :users
96
+
97
+ validates :email, uniqueness: {scope: [:name, :birthday]}
98
+
99
+ def self.name
100
+ 'User'
101
+ end
102
+ }
103
+ end
104
+
105
+ it 'does not add errors' do
106
+ rom.relations.users.insert(name: 'Jane', email: 'jane+doe@doe.org')
107
+ attributes = user_attrs.new(name: 'Jane', email: 'jane@doe.org', birthday: Date.parse('2014-12-12'))
108
+ validator = user_validator.new(attributes)
109
+ expect(validator).to be_valid
110
+ end
111
+
112
+ it 'adds an error when the doubly scoped validation fails' do
113
+ attributes = user_attrs.new(name: 'Jane', email: 'jane@doe.org', birthday: Date.parse('2014-12-12'))
114
+ validator = doubly_scoped_validator.new(attributes)
115
+ expect(validator).to be_valid
116
+
117
+ rom.relations.users.insert(attributes.attributes)
118
+ expect(validator).to_not be_valid
119
+
120
+ attributes = user_attrs.new(name: 'Jane', email: 'jane+doe@doe.org', birthday: Date.parse('2014-12-12'))
121
+ validator = doubly_scoped_validator.new(attributes)
122
+ expect(validator).to be_valid
123
+ end
124
+ end
125
+ end
126
+
127
+ describe '#method_missing' do
128
+ let(:attributes) { { name: 'Jane' } }
129
+
130
+ it 'returns attribute value if present' do
131
+ expect(validator.name).to eql('Jane')
132
+ end
133
+
134
+ it 'returns nil if attribute is not present' do
135
+ expect(validator.email).to be(nil)
136
+ end
137
+
138
+ it 'raises error when name does not match any of the attributes' do
139
+ expect { validator.foobar }.to raise_error(NoMethodError, /foobar/)
140
+ end
76
141
  end
77
142
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rom-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-22 00:00:00.000000000 Z
11
+ date: 2015-06-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rom
@@ -16,20 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.7'
19
+ version: '0.8'
20
20
  - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: 0.7.0
22
+ version: 0.8.0
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - "~>"
28
28
  - !ruby/object:Gem::Version
29
- version: '0.7'
29
+ version: '0.8'
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 0.7.0
32
+ version: 0.8.0
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: addressable
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -241,17 +241,17 @@ files:
241
241
  - spec/dummy/public/500.html
242
242
  - spec/dummy/public/favicon.ico
243
243
  - spec/dummy/public/robots.txt
244
- - spec/dummy/spec/features/users_spec.rb
245
- - spec/dummy/spec/integration/activerecord_setup.rb
246
- - spec/dummy/spec/integration/form_with_injected_commands_spec.rb
247
- - spec/dummy/spec/integration/initializer_spec.rb
248
- - spec/dummy/spec/integration/logger_spec.rb
249
- - spec/dummy/spec/integration/new_user_form_spec.rb
250
- - spec/dummy/spec/integration/user_attributes_spec.rb
251
- - spec/dummy/spec/integration/user_commands_spec.rb
252
- - spec/dummy/spec/integration/user_model_mapping_spec.rb
253
244
  - spec/dummy/vendor/assets/javascripts/.keep
254
245
  - spec/dummy/vendor/assets/stylesheets/.keep
246
+ - spec/features/users_spec.rb
247
+ - spec/integration/activerecord_setup.rb
248
+ - spec/integration/form_with_injected_commands_spec.rb
249
+ - spec/integration/initializer_spec.rb
250
+ - spec/integration/logger_spec.rb
251
+ - spec/integration/new_user_form_spec.rb
252
+ - spec/integration/user_attributes_spec.rb
253
+ - spec/integration/user_commands_spec.rb
254
+ - spec/integration/user_model_mapping_spec.rb
255
255
  - spec/lib/active_record/configuration_spec.rb
256
256
  - spec/lib/generators/commands_generator_spec.rb
257
257
  - spec/lib/generators/form_generator_spec.rb
@@ -259,6 +259,7 @@ files:
259
259
  - spec/lib/generators/relation_generator_spec.rb
260
260
  - spec/spec_helper.rb
261
261
  - spec/unit/form_spec.rb
262
+ - spec/unit/validator/embedded_spec.rb
262
263
  - spec/unit/validator_spec.rb
263
264
  homepage: http://rom-rb.org
264
265
  licenses:
@@ -341,17 +342,17 @@ test_files:
341
342
  - spec/dummy/public/500.html
342
343
  - spec/dummy/public/favicon.ico
343
344
  - spec/dummy/public/robots.txt
344
- - spec/dummy/spec/features/users_spec.rb
345
- - spec/dummy/spec/integration/activerecord_setup.rb
346
- - spec/dummy/spec/integration/form_with_injected_commands_spec.rb
347
- - spec/dummy/spec/integration/initializer_spec.rb
348
- - spec/dummy/spec/integration/logger_spec.rb
349
- - spec/dummy/spec/integration/new_user_form_spec.rb
350
- - spec/dummy/spec/integration/user_attributes_spec.rb
351
- - spec/dummy/spec/integration/user_commands_spec.rb
352
- - spec/dummy/spec/integration/user_model_mapping_spec.rb
353
345
  - spec/dummy/vendor/assets/javascripts/.keep
354
346
  - spec/dummy/vendor/assets/stylesheets/.keep
347
+ - spec/features/users_spec.rb
348
+ - spec/integration/activerecord_setup.rb
349
+ - spec/integration/form_with_injected_commands_spec.rb
350
+ - spec/integration/initializer_spec.rb
351
+ - spec/integration/logger_spec.rb
352
+ - spec/integration/new_user_form_spec.rb
353
+ - spec/integration/user_attributes_spec.rb
354
+ - spec/integration/user_commands_spec.rb
355
+ - spec/integration/user_model_mapping_spec.rb
355
356
  - spec/lib/active_record/configuration_spec.rb
356
357
  - spec/lib/generators/commands_generator_spec.rb
357
358
  - spec/lib/generators/form_generator_spec.rb
@@ -359,4 +360,5 @@ test_files:
359
360
  - spec/lib/generators/relation_generator_spec.rb
360
361
  - spec/spec_helper.rb
361
362
  - spec/unit/form_spec.rb
363
+ - spec/unit/validator/embedded_spec.rb
362
364
  - spec/unit/validator_spec.rb
@@ -1,11 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe 'ROM initializer' do
4
- it 'allows setting up a custom repository' do
5
- repository = ROM::TestAdapter::Repository.new(foo: :bar)
6
- relation = DummyRelation.new([])
7
-
8
- expect(rom.repositories[:test]).to eql(repository)
9
- expect(rom.relations.dummy).to eql(relation)
10
- end
11
- end