rom-rails 0.2.1 → 0.3.0.beta1
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 +4 -4
- data/.rubocop.yml +26 -0
- data/CHANGELOG.md +17 -0
- data/README.md +4 -0
- data/lib/generators/rom/commands/templates/create.rb.erb +10 -0
- data/lib/generators/rom/commands/templates/delete.rb.erb +7 -0
- data/lib/generators/rom/commands/templates/update.rb.erb +10 -0
- data/lib/generators/rom/commands_generator.rb +33 -5
- data/lib/generators/rom/form/templates/edit_form.rb.erb +24 -0
- data/lib/generators/rom/form/templates/new_form.rb.erb +24 -0
- data/lib/generators/rom/form_generator.rb +39 -0
- data/lib/generators/rom/mapper/templates/mapper.rb.erb +9 -11
- data/lib/generators/rom/mapper_generator.rb +11 -1
- data/lib/generators/rom/relation/templates/relation.rb.erb +3 -2
- data/lib/generators/rom/relation_generator.rb +16 -1
- data/lib/rom-rails.rb +0 -5
- data/lib/rom/model.rb +7 -90
- data/lib/rom/rails/active_record/configuration.rb +105 -0
- data/lib/rom/rails/configuration.rb +4 -27
- data/lib/rom/rails/model/form.rb +59 -0
- data/lib/rom/rails/model/form/dsl.rb +173 -0
- data/lib/rom/rails/model/params.rb +72 -0
- data/lib/rom/rails/model/validator.rb +74 -0
- data/lib/rom/rails/model/validator/uniqueness_validator.rb +39 -0
- data/lib/rom/rails/railtie.rb +65 -29
- data/lib/rom/rails/version.rb +1 -1
- data/rom-rails.gemspec +3 -1
- data/spec/dummy/app/commands/tasks.rb +5 -0
- data/spec/dummy/app/commands/users.rb +4 -10
- data/spec/dummy/app/controllers/users_controller.rb +30 -0
- data/spec/dummy/app/forms/new_user_form.rb +9 -0
- data/spec/dummy/app/forms/update_user_form.rb +9 -0
- data/spec/dummy/app/forms/user_form.rb +15 -0
- data/spec/dummy/app/mappers/users.rb +6 -6
- data/spec/dummy/app/relations/tasks.rb +9 -0
- data/spec/dummy/app/relations/users.rb +5 -1
- data/spec/dummy/app/views/users/edit.html.erb +6 -0
- data/spec/dummy/app/views/users/new.html.erb +6 -0
- data/spec/dummy/config/routes.rb +5 -1
- data/spec/dummy/db/migrate/20141110205016_add_users.rb +2 -0
- data/spec/dummy/db/migrate/20150202194440_create_tasks.rb +7 -0
- data/spec/dummy/db/schema.rb +8 -8
- data/spec/dummy/spec/features/users_spec.rb +30 -0
- data/spec/dummy/spec/integration/logger_spec.rb +1 -1
- data/spec/dummy/spec/integration/user_commands_spec.rb +2 -18
- data/spec/dummy/spec/integration/user_model_mapping_spec.rb +2 -2
- data/spec/dummy/spec/integration/user_params_spec.rb +30 -15
- data/spec/lib/active_record/configuration_spec.rb +98 -0
- data/spec/lib/generators/commands_generator_spec.rb +54 -14
- data/spec/lib/generators/form_generator_spec.rb +89 -0
- data/spec/lib/generators/mapper_generator_spec.rb +10 -12
- data/spec/lib/generators/relation_generator_spec.rb +16 -6
- data/spec/spec_helper.rb +1 -1
- data/spec/unit/form_spec.rb +297 -0
- data/spec/unit/{model_spec.rb → params_spec.rb} +0 -0
- data/spec/unit/validator_spec.rb +75 -0
- metadata +72 -20
- data/lib/generators/rom/commands/templates/commands.rb.erb +0 -15
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/params/user_params.rb +0 -7
- data/spec/dummy/app/validators/user_validator.rb +0 -3
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'addressable/uri'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
module Rails
|
5
|
+
module ActiveRecord
|
6
|
+
# A helper class to derive `rom-sql` configuration from ActiveRecord.
|
7
|
+
#
|
8
|
+
# @private
|
9
|
+
class Configuration
|
10
|
+
BASE_OPTIONS = [
|
11
|
+
:root,
|
12
|
+
:adapter,
|
13
|
+
:database,
|
14
|
+
:password,
|
15
|
+
:username,
|
16
|
+
:hostname,
|
17
|
+
:root
|
18
|
+
].freeze
|
19
|
+
|
20
|
+
# Returns repository configuration for the current environment.
|
21
|
+
#
|
22
|
+
# @note This relies on ActiveRecord being initialized already.
|
23
|
+
# @param [Rails::Application]
|
24
|
+
#
|
25
|
+
# @api private
|
26
|
+
def self.call
|
27
|
+
configuration = ::ActiveRecord::Base.configurations.fetch(::Rails.env)
|
28
|
+
build(configuration.symbolize_keys.update(root: ::Rails.root))
|
29
|
+
end
|
30
|
+
|
31
|
+
# Builds a configuration hash from a flat database config hash.
|
32
|
+
#
|
33
|
+
# This is used to support typical database.yml-complaint configs. It
|
34
|
+
# also uses adapter interface for things that are adapter-specific like
|
35
|
+
# handling schema naming.
|
36
|
+
#
|
37
|
+
# @param [Hash,String]
|
38
|
+
# @return [Hash]
|
39
|
+
#
|
40
|
+
# @api private
|
41
|
+
def self.build(config)
|
42
|
+
adapter = config.fetch(:adapter)
|
43
|
+
uri_options = config.except(:adapter).merge(scheme: adapter)
|
44
|
+
other_options = config.except(*BASE_OPTIONS)
|
45
|
+
|
46
|
+
builder_method = :"#{adapter}_uri"
|
47
|
+
uri = if respond_to?(builder_method)
|
48
|
+
send(builder_method, uri_options)
|
49
|
+
else
|
50
|
+
generic_uri(uri_options)
|
51
|
+
end
|
52
|
+
|
53
|
+
# JRuby connection strings require special care.
|
54
|
+
uri = if RUBY_ENGINE == 'jruby' && adapter != 'postgresql'
|
55
|
+
"jdbc:#{uri}"
|
56
|
+
else
|
57
|
+
uri
|
58
|
+
end
|
59
|
+
|
60
|
+
{ uri: uri, options: other_options }
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.sqlite3_uri(config)
|
64
|
+
path = Pathname.new(config.fetch(:root)).join(config.fetch(:database))
|
65
|
+
|
66
|
+
build_uri(
|
67
|
+
scheme: 'sqlite',
|
68
|
+
host: '',
|
69
|
+
path: path.to_s
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.postgresql_uri(config)
|
74
|
+
generic_uri(config.merge(
|
75
|
+
host: config.fetch(:host) { '' },
|
76
|
+
scheme: 'postgres'
|
77
|
+
))
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.mysql_uri(config)
|
81
|
+
if config.key?(:username) && !config.key?(:password)
|
82
|
+
config.update(password: '')
|
83
|
+
end
|
84
|
+
|
85
|
+
generic_uri(config)
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.generic_uri(config)
|
89
|
+
build_uri(
|
90
|
+
scheme: config.fetch(:scheme),
|
91
|
+
user: config[:username],
|
92
|
+
password: config[:password],
|
93
|
+
host: config[:host],
|
94
|
+
port: config[:port],
|
95
|
+
path: config[:database]
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.build_uri(attrs)
|
100
|
+
Addressable::URI.new(attrs).to_s
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -1,34 +1,11 @@
|
|
1
|
+
require 'virtus'
|
2
|
+
|
1
3
|
module ROM
|
2
4
|
module Rails
|
3
5
|
class Configuration
|
4
|
-
|
5
|
-
|
6
|
-
def self.build(app)
|
7
|
-
config = app.config.database_configuration[::Rails.env].
|
8
|
-
symbolize_keys.update(root: app.config.root)
|
9
|
-
|
10
|
-
new(ROM::Config.build(config))
|
11
|
-
end
|
12
|
-
|
13
|
-
def initialize(config)
|
14
|
-
@config = config
|
15
|
-
end
|
16
|
-
|
17
|
-
def setup!
|
18
|
-
@setup = ROM.setup(@config.symbolize_keys)
|
19
|
-
end
|
20
|
-
|
21
|
-
def load!
|
22
|
-
Railtie.load_all
|
23
|
-
end
|
6
|
+
include Virtus.model(strict: true)
|
24
7
|
|
25
|
-
|
26
|
-
# rescuing fixes the chicken-egg problem where we have a relation
|
27
|
-
# defined but the table doesn't exist yet
|
28
|
-
@env = ROM.finalize.env
|
29
|
-
rescue Registry::ElementNotFoundError => e
|
30
|
-
warn "Skipping ROM setup => #{e.message}"
|
31
|
-
end
|
8
|
+
attribute :repositories, Hash, default: {}
|
32
9
|
end
|
33
10
|
end
|
34
11
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rom/rails/model/form/dsl'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
module Model
|
5
|
+
class Form
|
6
|
+
include Equalizer.new(:params, :model, :result)
|
7
|
+
|
8
|
+
extend ROM::ClassMacros
|
9
|
+
extend Form::DSL
|
10
|
+
|
11
|
+
defines :relation
|
12
|
+
|
13
|
+
attr_reader :params, :model, :result
|
14
|
+
|
15
|
+
delegate :model_name, :persisted?, :to_key, to: :model
|
16
|
+
alias_method :to_model, :model
|
17
|
+
|
18
|
+
class << self
|
19
|
+
delegate :model_name, to: :params
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(params = {}, options = {})
|
23
|
+
@params = params
|
24
|
+
@model = self.class.model.new(params.merge(options.slice(*self.class.key)))
|
25
|
+
@result = nil
|
26
|
+
@errors = ActiveModel::Errors.new([])
|
27
|
+
options.each { |key, value| instance_variable_set("@#{key}", value) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def commit!
|
31
|
+
raise NotImplementedError, "#{self.class}#commit! must be implemented"
|
32
|
+
end
|
33
|
+
|
34
|
+
def save(*args)
|
35
|
+
@result = commit!(*args)
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def success?
|
40
|
+
errors.nil? || !errors.any?
|
41
|
+
end
|
42
|
+
|
43
|
+
def validate!
|
44
|
+
validator = self.class::Validator.new(attributes)
|
45
|
+
validator.validate
|
46
|
+
|
47
|
+
@errors = validator.errors
|
48
|
+
end
|
49
|
+
|
50
|
+
def attributes
|
51
|
+
self.class.params[params]
|
52
|
+
end
|
53
|
+
|
54
|
+
def errors
|
55
|
+
(result && result.error) || @errors
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
module ROM
|
2
|
+
module Model
|
3
|
+
class Form
|
4
|
+
module DSL
|
5
|
+
attr_reader :params, :validator, :self_commands, :injectible_commands,
|
6
|
+
:model, :input_block, :validations_block
|
7
|
+
|
8
|
+
def inherited(klass)
|
9
|
+
klass.inject_commands_for(*injectible_commands) if injectible_commands
|
10
|
+
klass.commands(*self_commands) if self_commands
|
11
|
+
klass.input(readers: false, &input_block) if input_block
|
12
|
+
klass.validations(&validations_block) if validations_block
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def commands(names)
|
17
|
+
names.each { |relation, _action| attr_reader(relation) }
|
18
|
+
@self_commands = names
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def key(*keys)
|
23
|
+
if keys.any? && !@key
|
24
|
+
@key = keys
|
25
|
+
attr_reader(*keys)
|
26
|
+
elsif !@key
|
27
|
+
@key = [:id]
|
28
|
+
attr_reader :id
|
29
|
+
elsif keys.any?
|
30
|
+
@key = keys
|
31
|
+
end
|
32
|
+
@key
|
33
|
+
end
|
34
|
+
|
35
|
+
def input(options = {}, &block)
|
36
|
+
readers = options.fetch(:readers) { true }
|
37
|
+
define_params!(block)
|
38
|
+
define_attribute_readers! if readers
|
39
|
+
define_model!
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def validations(&block)
|
44
|
+
define_validator!(block)
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def inject_commands_for(*names)
|
49
|
+
@injectible_commands = names
|
50
|
+
names.each { |name| attr_reader(name) }
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def build(input = {}, options = {})
|
55
|
+
new(clear_input(input), options.merge(command_registry))
|
56
|
+
end
|
57
|
+
|
58
|
+
def command_registry
|
59
|
+
@command_registry ||= setup_command_registry
|
60
|
+
end
|
61
|
+
|
62
|
+
def clear_input(input)
|
63
|
+
input.each_with_object({}) { |(key, value), object|
|
64
|
+
next if value.is_a?(String) && value.blank?
|
65
|
+
|
66
|
+
object[key] =
|
67
|
+
if value.is_a?(Hash)
|
68
|
+
clear_input(value)
|
69
|
+
elsif value.is_a?(Array)
|
70
|
+
value.map { |v| v.is_a?(Hash) ? clear_input(v) : v }
|
71
|
+
else
|
72
|
+
value
|
73
|
+
end
|
74
|
+
}.symbolize_keys
|
75
|
+
end
|
76
|
+
|
77
|
+
def define_params!(block)
|
78
|
+
@input_block = block
|
79
|
+
@params = ClassBuilder.new(name: "#{name}::Params", parent: Object).call { |klass|
|
80
|
+
klass.send(:include, ROM::Model::Params)
|
81
|
+
}
|
82
|
+
@params.class_eval(&block)
|
83
|
+
const_set(:Params, @params)
|
84
|
+
end
|
85
|
+
|
86
|
+
def define_attribute_readers!
|
87
|
+
@params.attribute_set.each do |attribute|
|
88
|
+
if public_instance_methods.include?(attribute.name)
|
89
|
+
raise(
|
90
|
+
ArgumentError,
|
91
|
+
"#{attribute.name} attribute is in conflict with #{self}##{attribute.name}"
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
96
|
+
def #{attribute.name}
|
97
|
+
params[:#{attribute.name}]
|
98
|
+
end
|
99
|
+
RUBY
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def define_model!
|
104
|
+
@model = ClassBuilder.new(name: "#{name}::Model", parent: @params).call { |klass|
|
105
|
+
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
106
|
+
def persisted?
|
107
|
+
to_key.any?
|
108
|
+
end
|
109
|
+
|
110
|
+
def to_key
|
111
|
+
to_h.values_at(#{key.map(&:inspect).join(', ')}).compact
|
112
|
+
end
|
113
|
+
RUBY
|
114
|
+
}
|
115
|
+
key.each { |name| @model.attribute(name) }
|
116
|
+
const_set(:Model, @model)
|
117
|
+
end
|
118
|
+
|
119
|
+
def define_validator!(block)
|
120
|
+
@validations_block = block
|
121
|
+
@validator = ClassBuilder.new(name: "#{name}::Validator", parent: Object).call { |klass|
|
122
|
+
klass.send(:include, ROM::Model::Validator)
|
123
|
+
}
|
124
|
+
@validator.class_eval(&block)
|
125
|
+
const_set(:Validator, @validator)
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def rom
|
131
|
+
ROM.env
|
132
|
+
end
|
133
|
+
|
134
|
+
def adapter
|
135
|
+
ROM.adapters.keys.first
|
136
|
+
end
|
137
|
+
|
138
|
+
def setup_command_registry
|
139
|
+
commands = {}
|
140
|
+
|
141
|
+
if self_commands
|
142
|
+
self_commands.each do |rel_name, name|
|
143
|
+
command = build_command(name, rel_name)
|
144
|
+
commands[rel_name] = CommandRegistry.new(name => command)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
if injectible_commands
|
149
|
+
injectible_commands.each do |relation|
|
150
|
+
commands[relation] = rom.command(relation)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
commands
|
155
|
+
end
|
156
|
+
|
157
|
+
def build_command(name, rel_name)
|
158
|
+
klass = Command.build_class(name, rel_name, adapter: adapter)
|
159
|
+
|
160
|
+
klass.result :one
|
161
|
+
klass.input @params
|
162
|
+
klass.validator @validator
|
163
|
+
|
164
|
+
relation = rom.relations[rel_name]
|
165
|
+
repository = rom.repositories[relation.repository]
|
166
|
+
repository.extend_command_class(klass, relation.dataset)
|
167
|
+
|
168
|
+
klass.build(relation)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'virtus'
|
2
|
+
require 'active_model/conversion'
|
3
|
+
|
4
|
+
module ROM
|
5
|
+
module Model
|
6
|
+
# Mixin for validatable and coercible parameters
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# class UserParams
|
11
|
+
# include ROM::Model::Params
|
12
|
+
#
|
13
|
+
# attribute :email, String
|
14
|
+
# attribute :age, Integer
|
15
|
+
#
|
16
|
+
# validates :email, :age, presence: true
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# user_params = UserParams.new(email: '', age: '18')
|
20
|
+
#
|
21
|
+
# user_params.email # => ''
|
22
|
+
# user_params.age # => 18
|
23
|
+
#
|
24
|
+
# user_params.valid? # => false
|
25
|
+
# user_params.errors # => #<ActiveModel::Errors:0x007fd2423fadb0 ...>
|
26
|
+
#
|
27
|
+
# @api public
|
28
|
+
module Params
|
29
|
+
VirtusModel = Virtus.model(strict: true, required: false)
|
30
|
+
|
31
|
+
def self.included(base)
|
32
|
+
base.class_eval do
|
33
|
+
include VirtusModel
|
34
|
+
include ActiveModel::Conversion
|
35
|
+
end
|
36
|
+
base.extend(ClassMethods)
|
37
|
+
end
|
38
|
+
|
39
|
+
def model_name
|
40
|
+
self.class.model_name
|
41
|
+
end
|
42
|
+
|
43
|
+
module ClassMethods
|
44
|
+
DEFAULT_TIMESTAMPS = [:created_at, :updated_at].freeze
|
45
|
+
|
46
|
+
def [](input)
|
47
|
+
input.is_a?(self) ? input : new(input)
|
48
|
+
end
|
49
|
+
|
50
|
+
def set_model_name(name)
|
51
|
+
class_eval <<-RUBY
|
52
|
+
def self.model_name
|
53
|
+
@model_name ||= ActiveModel::Name.new(self, nil, #{name.inspect})
|
54
|
+
end
|
55
|
+
RUBY
|
56
|
+
end
|
57
|
+
|
58
|
+
def timestamps(*attrs)
|
59
|
+
if attrs.empty?
|
60
|
+
DEFAULT_TIMESTAMPS.each do |t|
|
61
|
+
attribute t, DateTime, default: proc { DateTime.now }
|
62
|
+
end
|
63
|
+
else
|
64
|
+
attrs.each do |attr|
|
65
|
+
attribute attr, DateTime, default: proc { DateTime.now }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|