rom-rails 1.1.0 → 2.3.0

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +10 -0
  3. data/.github/ISSUE_TEMPLATE/---bug-report.md +30 -0
  4. data/.github/ISSUE_TEMPLATE/---feature-request.md +18 -0
  5. data/.github/workflows/ci.yml +67 -0
  6. data/.github/workflows/docsite.yml +34 -0
  7. data/.github/workflows/sync_configs.yml +30 -0
  8. data/.rubocop.yml +46 -9
  9. data/CHANGELOG.md +68 -0
  10. data/CONTRIBUTING.md +29 -0
  11. data/Gemfile +21 -12
  12. data/README.md +3 -5
  13. data/Rakefile +1 -1
  14. data/docsite/source/index.html.md +165 -0
  15. data/lib/generators/rom.rb +8 -0
  16. data/lib/generators/rom/commands/templates/create.rb.erb +0 -2
  17. data/lib/generators/rom/commands/templates/delete.rb.erb +0 -1
  18. data/lib/generators/rom/commands/templates/update.rb.erb +0 -2
  19. data/lib/generators/rom/commands_generator.rb +1 -1
  20. data/lib/generators/rom/install/templates/application_model.rb +2 -2
  21. data/lib/generators/rom/install/templates/initializer.rb.erb +1 -1
  22. data/lib/generators/rom/install/templates/types.rb +3 -3
  23. data/lib/generators/rom/install_generator.rb +1 -4
  24. data/lib/generators/rom/relation_generator.rb +1 -1
  25. data/lib/generators/rom/repository/templates/repository.rb.erb +8 -0
  26. data/lib/generators/rom/repository_generator.rb +1 -2
  27. data/lib/rom/rails/active_record/configuration.rb +41 -55
  28. data/lib/rom/rails/active_record/uri_builder.rb +70 -0
  29. data/lib/rom/rails/configuration.rb +1 -1
  30. data/lib/rom/rails/railtie.rb +30 -12
  31. data/lib/rom/rails/tasks/db.rake +6 -0
  32. data/lib/rom/rails/version.rb +1 -1
  33. data/rom-rails.gemspec +8 -9
  34. data/spec/dummy/app/commands/create_user.rb +0 -1
  35. data/spec/dummy/app/forms/user_form.rb +1 -2
  36. data/spec/dummy/app/relations/dummy_relation.rb +1 -1
  37. data/spec/dummy/app/relations/tasks.rb +1 -0
  38. data/spec/dummy/app/relations/users.rb +1 -0
  39. data/spec/dummy/bin/bundle +1 -1
  40. data/spec/dummy/bin/rails +1 -1
  41. data/spec/dummy/config/initializers/rom.rb +4 -3
  42. data/spec/dummy/lib/additional_app/{app → persistence}/commands/create_additional_task.rb +0 -0
  43. data/spec/dummy/lib/rom/test_adapter.rb +1 -0
  44. data/spec/integration/controller_extensions_spec.rb +23 -0
  45. data/spec/integration/initializer_spec.rb +1 -1
  46. data/spec/integration/user_commands_spec.rb +1 -1
  47. data/spec/lib/active_record/configuration_spec.rb +87 -1
  48. data/spec/lib/generators/commands_generator_spec.rb +2 -3
  49. data/spec/lib/generators/install_generator_spec.rb +7 -10
  50. data/spec/lib/generators/mapper_generator_spec.rb +1 -1
  51. data/spec/lib/generators/relation_generator_spec.rb +5 -2
  52. data/spec/lib/generators/repository_generator_spec.rb +17 -1
  53. metadata +45 -34
  54. data/.travis.yml +0 -20
@@ -0,0 +1,70 @@
1
+ require 'addressable/uri'
2
+
3
+ module ROM
4
+ module Rails
5
+ module ActiveRecord
6
+ class UriBuilder
7
+ def build(adapter, uri_options)
8
+ builder_method = :"#{adapter}_uri"
9
+
10
+ uri = if respond_to?(builder_method)
11
+ send(builder_method, uri_options)
12
+ else
13
+ generic_uri(uri_options)
14
+ end
15
+
16
+ # JRuby connection strings require special care.
17
+ if RUBY_ENGINE == 'jruby' && adapter != 'postgresql'
18
+ uri = "jdbc:#{uri}"
19
+ end
20
+
21
+ uri
22
+ end
23
+
24
+ def sqlite3_uri(config)
25
+ path = Pathname.new(config.fetch(:root)).join(config.fetch(:database))
26
+
27
+ build_uri(
28
+ scheme: 'sqlite',
29
+ host: '',
30
+ path: path.to_s
31
+ )
32
+ end
33
+
34
+ def postgresql_uri(config)
35
+ generic_uri(config.merge(
36
+ host: config.fetch(:host) { '' },
37
+ scheme: 'postgres'
38
+ ))
39
+ end
40
+
41
+ def mysql_uri(config)
42
+ if config.key?(:username) && !config.key?(:password)
43
+ config.update(password: '')
44
+ end
45
+
46
+ generic_uri(config)
47
+ end
48
+
49
+ def generic_uri(config)
50
+ build_uri(
51
+ scheme: config.fetch(:scheme),
52
+ user: escape_option(config[:username]),
53
+ password: escape_option(config[:password]),
54
+ host: config[:host],
55
+ port: config[:port],
56
+ path: config[:database]
57
+ )
58
+ end
59
+
60
+ def build_uri(attrs)
61
+ Addressable::URI.new(attrs).to_s
62
+ end
63
+
64
+ def escape_option(option)
65
+ option.nil? ? option : CGI.escape(option)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -11,7 +11,7 @@ module ROM
11
11
  end
12
12
 
13
13
  config_accessor :auto_registration_paths do
14
- []
14
+ ['app']
15
15
  end
16
16
  end
17
17
  end
@@ -16,20 +16,21 @@ module ROM
16
16
 
17
17
  # Make `ROM::Rails::Configuration` instance available to the user via
18
18
  # `Rails.application.config` before other initializers run.
19
- config.before_initialize do |_app|
19
+ config.before_configuration do |_app|
20
20
  config.rom = Configuration.new
21
21
  end
22
22
 
23
23
  initializer 'rom.configure_action_controller' do
24
24
  ActiveSupport.on_load(:action_controller) do
25
25
  ActionController::Base.send(:include, ControllerExtension)
26
+ ActionController::API.send(:include, ControllerExtension) if defined?(ActionController::API)
26
27
  end
27
28
  end
28
29
 
29
30
  initializer 'rom.adjust_eager_load_paths' do |app|
30
31
  paths =
31
32
  auto_registration_paths.inject([]) do |result, root_path|
32
- result.concat(COMPONENT_DIRS.map { |dir| root_path.join('app', dir).to_s })
33
+ result.concat(COMPONENT_DIRS.map { |dir| ::Rails.root.join(root_path, dir).to_s })
33
34
  end
34
35
 
35
36
  app.config.eager_load_paths -= paths
@@ -44,6 +45,10 @@ module ROM
44
45
  ROM.env = Railtie.create_container
45
46
  end
46
47
 
48
+ console do |_app|
49
+ Railtie.configure_console_logger
50
+ end
51
+
47
52
  # Behaves like `Railtie#configure` if the given block does not take any
48
53
  # arguments. Otherwise yields the ROM configuration to the block.
49
54
  #
@@ -73,7 +78,7 @@ module ROM
73
78
  configuration = create_configuration
74
79
 
75
80
  auto_registration_paths.each do |root_path|
76
- configuration.auto_registration(root_path.join('app'), namespace: false)
81
+ configuration.auto_registration(::Rails.root.join(root_path), namespace: false)
77
82
  end
78
83
 
79
84
  ROM.container(configuration)
@@ -81,10 +86,14 @@ module ROM
81
86
 
82
87
  # @api private
83
88
  def gateways
84
- config.rom.gateways[:default] ||= infer_default_gateway if active_record?
89
+ if active_record?
90
+ load_active_record_config.each do |name, spec|
91
+ config.rom.gateways[name] ||= [:sql, spec[:uri], spec[:options]]
92
+ end
93
+ end
85
94
 
86
95
  if config.rom.gateways.empty?
87
- Rails.logger.warn "It seems that you have not configured any gateways"
96
+ ::Rails.logger.warn "It seems that you have not configured any gateways"
88
97
 
89
98
  config.rom.gateways[:default] = [ :memory, "memory://test" ]
90
99
  end
@@ -92,13 +101,9 @@ module ROM
92
101
  config.rom.gateways
93
102
  end
94
103
 
95
- # If there's no default gateway configured, try to infer it from
96
- # other sources, e.g. ActiveRecord.
97
- #
98
- # @api private
99
- def infer_default_gateway
100
- spec = ROM::Rails::ActiveRecord::Configuration.call
101
- [:sql, spec[:uri], spec[:options]]
104
+ # Attempt to infer all configured gateways from activerecord
105
+ def load_active_record_config
106
+ ROM::Rails::ActiveRecord::Configuration.new.call
102
107
  end
103
108
 
104
109
  def load_initializer
@@ -129,6 +134,19 @@ module ROM
129
134
  def active_record?
130
135
  defined?(::ActiveRecord)
131
136
  end
137
+
138
+ # @api private
139
+ def std_err_out_logger?
140
+ ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, STDERR, STDOUT)
141
+ end
142
+
143
+ # @api private
144
+ def configure_console_logger
145
+ return if active_record? || std_err_out_logger?
146
+
147
+ console = ActiveSupport::Logger.new(STDERR)
148
+ ::Rails.logger.extend ActiveSupport::Logger.broadcast console
149
+ end
132
150
  end
133
151
  end
134
152
  end
@@ -1,3 +1,9 @@
1
+ begin
2
+ require "rom/sql/rake_task"
3
+ rescue LoadError
4
+ # rom-sql is optional
5
+ end
6
+
1
7
  namespace :db do
2
8
  desc 'Set up ROM gateways'
3
9
  task :setup do
@@ -1,5 +1,5 @@
1
1
  module ROM
2
2
  module Rails
3
- VERSION = '1.1.0'.freeze
3
+ VERSION = '2.3.0'.freeze
4
4
  end
5
5
  end
data/rom-rails.gemspec CHANGED
@@ -1,13 +1,12 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path('lib', __dir__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
  require 'rom/rails/version'
5
4
 
6
5
  Gem::Specification.new do |spec|
7
6
  spec.name = "rom-rails"
8
7
  spec.version = ROM::Rails::VERSION.dup
9
- spec.authors = ["Piotr Solnica"]
10
- spec.email = ["piotr.solnica@gmail.com"]
8
+ spec.authors = ["Chris Flipse", "Piotr Solnica"]
9
+ spec.email = ["cflipse@gmail.com", "piotr.solnica@gmail.com"]
11
10
  spec.summary = 'Integrate Ruby Object Mapper with Rails'
12
11
  spec.homepage = "http://rom-rb.org"
13
12
  spec.license = "MIT"
@@ -17,14 +16,14 @@ Gem::Specification.new do |spec|
17
16
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
17
  spec.require_paths = ["lib"]
19
18
 
20
- spec.add_runtime_dependency 'rom', '~> 4.0'
21
- spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
22
- spec.add_runtime_dependency 'dry-core', '~> 0.3'
23
19
  spec.add_runtime_dependency 'addressable', '~> 2.3'
24
- spec.add_runtime_dependency 'railties', '>= 3.0', '< 6.0'
20
+ spec.add_runtime_dependency 'dry-core', '~> 0.4'
21
+ spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
22
+ spec.add_runtime_dependency 'railties', '>= 3.0', '< 6.2'
23
+ spec.add_runtime_dependency 'rom', '~> 5.2'
25
24
 
26
- spec.add_development_dependency "rom-repository"
27
25
  spec.add_development_dependency "bundler"
28
26
  spec.add_development_dependency "rake"
27
+ spec.add_development_dependency "rom-repository"
29
28
  spec.add_development_dependency "rubocop", "~> 0.50"
30
29
  end
@@ -6,5 +6,4 @@ class CreateUser < ROM::Commands::Create[:sql]
6
6
  use :timestamps
7
7
 
8
8
  timestamp :created_at
9
-
10
9
  end
@@ -25,11 +25,10 @@ class UserForm
25
25
  end
26
26
 
27
27
  def persisted?
28
- !!id
28
+ id.present?
29
29
  end
30
30
 
31
31
  def self.model_name
32
32
  ActiveModel::Name.new(User)
33
33
  end
34
-
35
34
  end
@@ -1,5 +1,5 @@
1
1
  class DummyRelation < ROM::Relation[:test_adapter]
2
- gateway :test
2
+ gateway :default
3
3
 
4
4
  schema(:dummy, infer: true)
5
5
  end
@@ -1,4 +1,5 @@
1
1
  class Tasks < ROM::Relation[:sql]
2
+ gateway :sql
2
3
  schema(:tasks, infer: true)
3
4
 
4
5
  def by_id(id)
@@ -1,4 +1,5 @@
1
1
  class Users < ROM::Relation[:sql]
2
+ gateway :sql
2
3
  schema(:users, infer: true)
3
4
 
4
5
  def by_id(id)
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env ruby
2
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
3
3
  load Gem.bin_path('bundler', 'bundle')
data/spec/dummy/bin/rails CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
- APP_PATH = File.expand_path('../../config/application', __FILE__)
2
+ APP_PATH = File.expand_path('../config/application', __dir__)
3
3
  require_relative '../config/boot'
4
4
  require 'rails/commands'
@@ -1,6 +1,7 @@
1
1
  ROM::Rails::Railtie.configure do |config|
2
2
  scheme = RUBY_ENGINE == 'jruby' ? 'jdbc:sqlite' : 'sqlite'
3
- config.gateways[:default] = [:sql, "#{scheme}://#{Rails.root}/db/#{Rails.env}.sqlite3"]
4
- config.gateways[:test] = [:test_adapter, foo: :bar]
5
- config.auto_registration_paths += [Rails.root.join('lib', 'additional_app')]
3
+ config.gateways[:arro] = [:test_adapter, uri: 'http://example.org' ]
4
+ config.gateways[:sql] = [:sql, "#{scheme}://#{Rails.root}/db/#{Rails.env}.sqlite3"]
5
+ config.gateways[:default] = [:test_adapter, foo: :bar]
6
+ config.auto_registration_paths += [Rails.root.join('lib', 'additional_app', 'persistence')]
6
7
  end
@@ -6,6 +6,7 @@ module ROM
6
6
 
7
7
  class Gateway < ROM::Gateway
8
8
  include Dry::Equalizer(:args)
9
+ adapter :test_adapter
9
10
 
10
11
  attr_reader :args, :datasets
11
12
 
@@ -0,0 +1,23 @@
1
+ RSpec.describe "including controller extensions" do
2
+ describe "An application controller" do
3
+ let(:controller) do
4
+ Class.new(ActionController::Base)
5
+ end
6
+
7
+ it "includes the controller extensions" do
8
+ expect(controller.ancestors).to include(ROM::Rails::ControllerExtension)
9
+ end
10
+ end
11
+
12
+ if defined?(ActionController::API)
13
+ describe "An API controller" do
14
+ let(:controller) do
15
+ Class.new(ActionController::API)
16
+ end
17
+
18
+ it "includes the controller extensions" do
19
+ expect(controller.ancestors).to include(ROM::Rails::ControllerExtension)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -3,7 +3,7 @@ RSpec.describe 'ROM initializer' do
3
3
  gateway = ROM::TestAdapter::Gateway.new(foo: :bar)
4
4
  relation = DummyRelation.new([])
5
5
 
6
- expect(rom.gateways[:test]).to eql(gateway)
6
+ expect(rom.gateways[:default]).to eql(gateway)
7
7
  expect(rom.relations.dummy).to eql(relation)
8
8
  end
9
9
 
@@ -6,7 +6,7 @@ RSpec.describe 'User commands' do
6
6
  relation = rom.relations[:users]
7
7
  relation.insert(name: 'Piotr', email: 'piotr@test.com')
8
8
 
9
- expect{
9
+ expect {
10
10
  users.delete.by_name('Piotr').call
11
11
  }.to change(relation, :count).by(-1)
12
12
  end
@@ -1,15 +1,19 @@
1
1
  require 'rom/rails/active_record/configuration'
2
+ require 'active_record'
3
+ require 'active_record/database_configurations' if Rails::VERSION::MAJOR >= 6
2
4
 
3
5
  RSpec.describe ROM::Rails::ActiveRecord::Configuration do
4
6
  let(:root) { Pathname.new('/path/to/app') }
5
7
 
8
+ subject(:configuration) { described_class.new(root: root) }
9
+
6
10
  def uri_for(config)
7
11
  result = read(config)
8
12
  result.is_a?(Hash) ? result[:uri] : result
9
13
  end
10
14
 
11
15
  def read(config)
12
- described_class.build(config.merge(root: root))
16
+ configuration.build(config)
13
17
  end
14
18
 
15
19
  def parse(uri)
@@ -112,6 +116,88 @@ RSpec.describe ROM::Rails::ActiveRecord::Configuration do
112
116
 
113
117
  expect(read(config)).to eq uri: expected_uri, options: { pool: 5 }
114
118
  end
119
+
120
+ it 'handles special characters in username and password' do
121
+ config = {
122
+ pool: 5,
123
+ adapter: 'mysql2',
124
+ username: 'r@o%ot',
125
+ password: 'p@ssw0rd#',
126
+ database: 'database',
127
+ host: 'example.com'
128
+ }
129
+
130
+ expected_uri = 'mysql2://r%40o%25ot:p%40ssw0rd%23@example.com/database'
131
+ expected_uri = "jdbc:#{expected_uri}" if RUBY_ENGINE == 'jruby'
132
+
133
+ expect(read(config)).to eq uri: expected_uri, options: { pool: 5 }
134
+ end
135
+ end
136
+ end
137
+
138
+ if Rails::VERSION::MAJOR >= 6
139
+ context "with an activerecord 6 configuration" do
140
+ subject(:configuration) { described_class.new(root: root, configurations: railsconfig, env: "test") }
141
+ let(:railsconfig) { ActiveRecord::DatabaseConfigurations.new(config_file) }
142
+
143
+ context "with only a single database" do
144
+ let(:config_file) {
145
+ {
146
+ test: {
147
+ adapter: 'mysql',
148
+ host: 'example.com',
149
+ database: 'test',
150
+ username: 'user',
151
+ encoding: 'utf8'
152
+ }
153
+ }
154
+ }
155
+
156
+ it "returns the default hash" do
157
+ expected_uri = uri_for(config_file[:test])
158
+ expect(configuration.call[:default]).to match(uri: expected_uri, options: { encoding: 'utf8' })
159
+ end
160
+ end
161
+
162
+ context "with multiple configured databases" do
163
+ let(:config_file) {
164
+ {
165
+ test: {
166
+ reader: {
167
+ adapter: 'mysql',
168
+ host: 'example.com',
169
+ database: 'test_reader',
170
+ username: 'user',
171
+ encoding: 'utf8'
172
+ },
173
+ writer: {
174
+ adapter: 'mysql',
175
+ host: 'write.example.com',
176
+ database: 'test_writer',
177
+ username: 'user',
178
+ encoding: 'utf8'
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ it "configures the first database as the default" do
185
+ expected_uri = uri_for(config_file[:test][:reader])
186
+ expect(configuration.call[:default]).to match(uri: expected_uri, options: {encoding: 'utf8'})
187
+ end
188
+
189
+ it "returns each configured database" do
190
+ options = { encoding: 'utf8' }
191
+ expected_reader_uri = uri_for(config_file[:test][:reader])
192
+ expected_writer_uri = uri_for(config_file[:test][:writer])
193
+
194
+ expect(configuration.call).to include(
195
+ reader: { uri: expected_reader_uri, options: options },
196
+ writer: { uri: expected_writer_uri, options: options }
197
+ )
198
+ end
199
+ end
115
200
  end
201
+
116
202
  end
117
203
  end