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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +26 -0
  3. data/CHANGELOG.md +17 -0
  4. data/README.md +4 -0
  5. data/lib/generators/rom/commands/templates/create.rb.erb +10 -0
  6. data/lib/generators/rom/commands/templates/delete.rb.erb +7 -0
  7. data/lib/generators/rom/commands/templates/update.rb.erb +10 -0
  8. data/lib/generators/rom/commands_generator.rb +33 -5
  9. data/lib/generators/rom/form/templates/edit_form.rb.erb +24 -0
  10. data/lib/generators/rom/form/templates/new_form.rb.erb +24 -0
  11. data/lib/generators/rom/form_generator.rb +39 -0
  12. data/lib/generators/rom/mapper/templates/mapper.rb.erb +9 -11
  13. data/lib/generators/rom/mapper_generator.rb +11 -1
  14. data/lib/generators/rom/relation/templates/relation.rb.erb +3 -2
  15. data/lib/generators/rom/relation_generator.rb +16 -1
  16. data/lib/rom-rails.rb +0 -5
  17. data/lib/rom/model.rb +7 -90
  18. data/lib/rom/rails/active_record/configuration.rb +105 -0
  19. data/lib/rom/rails/configuration.rb +4 -27
  20. data/lib/rom/rails/model/form.rb +59 -0
  21. data/lib/rom/rails/model/form/dsl.rb +173 -0
  22. data/lib/rom/rails/model/params.rb +72 -0
  23. data/lib/rom/rails/model/validator.rb +74 -0
  24. data/lib/rom/rails/model/validator/uniqueness_validator.rb +39 -0
  25. data/lib/rom/rails/railtie.rb +65 -29
  26. data/lib/rom/rails/version.rb +1 -1
  27. data/rom-rails.gemspec +3 -1
  28. data/spec/dummy/app/commands/tasks.rb +5 -0
  29. data/spec/dummy/app/commands/users.rb +4 -10
  30. data/spec/dummy/app/controllers/users_controller.rb +30 -0
  31. data/spec/dummy/app/forms/new_user_form.rb +9 -0
  32. data/spec/dummy/app/forms/update_user_form.rb +9 -0
  33. data/spec/dummy/app/forms/user_form.rb +15 -0
  34. data/spec/dummy/app/mappers/users.rb +6 -6
  35. data/spec/dummy/app/relations/tasks.rb +9 -0
  36. data/spec/dummy/app/relations/users.rb +5 -1
  37. data/spec/dummy/app/views/users/edit.html.erb +6 -0
  38. data/spec/dummy/app/views/users/new.html.erb +6 -0
  39. data/spec/dummy/config/routes.rb +5 -1
  40. data/spec/dummy/db/migrate/20141110205016_add_users.rb +2 -0
  41. data/spec/dummy/db/migrate/20150202194440_create_tasks.rb +7 -0
  42. data/spec/dummy/db/schema.rb +8 -8
  43. data/spec/dummy/spec/features/users_spec.rb +30 -0
  44. data/spec/dummy/spec/integration/logger_spec.rb +1 -1
  45. data/spec/dummy/spec/integration/user_commands_spec.rb +2 -18
  46. data/spec/dummy/spec/integration/user_model_mapping_spec.rb +2 -2
  47. data/spec/dummy/spec/integration/user_params_spec.rb +30 -15
  48. data/spec/lib/active_record/configuration_spec.rb +98 -0
  49. data/spec/lib/generators/commands_generator_spec.rb +54 -14
  50. data/spec/lib/generators/form_generator_spec.rb +89 -0
  51. data/spec/lib/generators/mapper_generator_spec.rb +10 -12
  52. data/spec/lib/generators/relation_generator_spec.rb +16 -6
  53. data/spec/spec_helper.rb +1 -1
  54. data/spec/unit/form_spec.rb +297 -0
  55. data/spec/unit/{model_spec.rb → params_spec.rb} +0 -0
  56. data/spec/unit/validator_spec.rb +75 -0
  57. metadata +72 -20
  58. data/lib/generators/rom/commands/templates/commands.rb.erb +0 -15
  59. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  60. data/spec/dummy/app/params/user_params.rb +0 -7
  61. 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
- attr_reader :config, :setup, :env
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
- def finalize!
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