rom-rails 1.1.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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