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
data/README.md CHANGED
@@ -1,6 +1,5 @@
1
1
  [gem]: https://rubygems.org/gems/rom-rails
2
- [travis]: https://travis-ci.org/rom-rb/rom-rails
3
- [gemnasium]: https://gemnasium.com/rom-rb/rom-rails
2
+ [actions]: https://github.com/rom-rb/rom-rails/actions
4
3
  [codeclimate]: https://codeclimate.com/github/rom-rb/rom-rails
5
4
  [coveralls]: https://coveralls.io/r/rom-rb/rom-rails
6
5
  [inchpages]: http://inch-ci.org/github/rom-rb/rom-rails
@@ -8,8 +7,7 @@
8
7
  # rom-rails
9
8
 
10
9
  [![Gem Version](https://badge.fury.io/rb/rom-rails.svg)][gem]
11
- [![Build Status](https://travis-ci.org/rom-rb/rom-rails.svg?branch=master)][travis]
12
- [![Dependency Status](https://gemnasium.com/rom-rb/rom-rails.svg)][gemnasium]
10
+ [![CI Status](https://github.com/rom-rb/rom-rails/workflows/ci/badge.svg)][actions]
13
11
  [![Code Climate](https://codeclimate.com/github/rom-rb/rom-rails/badges/gpa.svg)][codeclimate]
14
12
  [![Test Coverage](https://codeclimate.com/github/rom-rb/rom-rails/badges/coverage.svg)][codeclimate]
15
13
  [![Inline docs](http://inch-ci.org/github/rom-rb/rom-rails.svg?branch=master)][inchpages]
@@ -34,7 +32,7 @@ To run tests:
34
32
  You can read more about ROM and Rails on the official website:
35
33
 
36
34
  * [Introduction to ROM](http://rom-rb.org/learn/)
37
- * [Rails setup](http://rom-rb.org/learn/getting-started/rails-setup/)
35
+ * [Rails setup](http://rom-rb.org/learn/rails/)
38
36
 
39
37
 
40
38
  ## Community
data/Rakefile CHANGED
@@ -3,7 +3,7 @@ require 'rubocop/rake_task'
3
3
 
4
4
  task default: %w(app:spec rubocop)
5
5
 
6
- APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
6
+ APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
7
7
  load 'rails/tasks/engine.rake'
8
8
 
9
9
  RuboCop::RakeTask.new do |task|
@@ -0,0 +1,165 @@
1
+ ---
2
+ position: 3
3
+ chapter: Rails
4
+ title: Setup
5
+ ---
6
+
7
+ Rails integration is provided by [rom-rails](https://github.com/rom-rb/rom-rails) project. Simply add it to your Gemfile:
8
+
9
+ ``` ruby
10
+ gem 'rom-rails'
11
+ ```
12
+
13
+ ## Configuring Railtie
14
+
15
+ Create a rom initializer:
16
+
17
+ ``` ruby
18
+ # config/initializers/rom.rb
19
+ ROM::Rails::Railtie.configure do |config|
20
+ config.gateways[:default] = [:sql, ENV.fetch('DATABASE_URL')]
21
+ end
22
+ ```
23
+
24
+ You can provide additional adapter-specific options, for example you can enable specific sql plugins for postgres:
25
+
26
+ ``` ruby
27
+ # config/initializers/rom.rb
28
+ ROM::Rails::Railtie.configure do |config|
29
+ config.gateways[:default] = [:sql,
30
+ ENV.fetch('DATABASE_URL'), extensions: [:pg_hstore]
31
+ ]
32
+ end
33
+ ```
34
+
35
+ You can also provide a list of relations that should not be inferred from your schema automatically:
36
+
37
+ ``` ruby
38
+ # config/initializers/rom.rb
39
+ ROM::Rails::Railtie.configure do |config|
40
+ config.gateways[:default] = [:sql,
41
+ ENV.fetch('DATABASE_URL'), not_inferrable_relations: [:schema_migrations]
42
+ ]
43
+ end
44
+ ```
45
+
46
+ ## Migration Tasks
47
+
48
+ The railtie provides rake tasks for managing your database schema. You need to enable them in your `Rakefile`:
49
+
50
+ ``` ruby
51
+ require 'rom/sql/rake_task'
52
+ ```
53
+
54
+ After that, you have access to following tasks:
55
+
56
+ * `rake db:create_migration[migration_name]` - creates a new migration file
57
+ * `rake db:migrate` - runs pending migrations
58
+ * `rake db:clean` - cleans the database
59
+ * `rake db:reset` - drops tables and re-runs migrations
60
+
61
+ ## Accessing Container
62
+
63
+ In Rails environment ROM container is accessible via `ROM.env`:
64
+
65
+ ``` ruby
66
+ ROM.env # returns the container
67
+ ```
68
+
69
+ In your controllers you can access ROM container by `rom` variable:
70
+
71
+ ``` ruby
72
+ class UsersController < ApplicationController
73
+ def show
74
+ @user = rom.relation(:users).by_id(params[:id]).one
75
+ end
76
+ end
77
+ ```
78
+
79
+ ^WARNING
80
+ Accessing the global container directly is considered as a bad practice. The recommended way is to use a DI mechanism to inject specific ROM components as dependencies into your objects.
81
+
82
+ For example you can use [dry-container](https://github.com/dryrb/dry-container) and [dry-auto_inject](https://github.com/dryrb/dry-auto_inject) to define your own application container and specify dependencies there to have them automatically injected.
83
+
84
+ See [rom-rails-skeleton](https://github.com/solnic/rom-rails-skeleton) for an example of such setup.
85
+ ^
86
+
87
+ ## Defining Relations
88
+
89
+ Relation class definitions are automatically loaded from `app/relations`. The following code defines a `users` relation for the `:sql` adapter:
90
+
91
+ ``` ruby
92
+ class Users < ROM::Relation[:sql]
93
+ # some methods
94
+ end
95
+
96
+ # access registered relation via container
97
+ ROM.env.relations[:users]
98
+ ```
99
+
100
+ ## Defining Commands
101
+
102
+ Command class definitions are automatically loaded from `app/commands`. The following code defines a command which inserts data into `users` relation:
103
+
104
+ ``` ruby
105
+ # app/commands/create_user.rb
106
+ class CreateUser < ROM::Commands::Create[:sql]
107
+ relation :users
108
+ register_as :create
109
+ result :one
110
+ end
111
+
112
+ # access registered relation via container
113
+ ROM.env.commands[:users][:create]
114
+ ```
115
+
116
+ ## Defining Custom Mappers
117
+
118
+ If you want to use custom mappers you can place them under `app/mappers`:
119
+
120
+ ``` ruby
121
+ # app/mappers/user_mapper.rb
122
+ class UserMapper < ROM::Mapper
123
+ relation :users
124
+
125
+ # some mapping logic
126
+ end
127
+ ```
128
+
129
+ ## Running alongside ActiveRecord
130
+
131
+ There might be some cases where you will want to run ROM alongside ActiveRecord. Since ROM is designed to work independently, you will need to take few additional steps. ROM creates its own connections and Rails above version 5 won't allow you to drop the database since there are active connections on it.
132
+
133
+ ``` ruby
134
+ # lib/tasks/db.rake
135
+ task :remove_rom_connection => [:environment] do
136
+ ROM.env && ROM.env.disconnect
137
+ end
138
+
139
+ Rake::Task["db:drop"].clear_prerequisites()
140
+ Rake::Task["db:drop"].enhance [:remove_rom_connection, :load_config, :check_protected_environments]
141
+
142
+ Rake::Task["db:reset"].clear_prerequisites()
143
+ Rake::Task["db:reset"].enhance [:remove_rom_connection, "db:drop", "db:setup"]
144
+ ```
145
+
146
+ Since migrations (and other) tasks require environment, ROM will be loaded and will throw an exception, because relations will try to load tables before migrations have actually run. We know this is an ugly solution, but we are working hard to solve this case. This monkey patch will give you reasonable information to act upon if the necessity arises.
147
+
148
+ ``` ruby
149
+ # config/initializers/rom_monkey.rb
150
+ module ROM
151
+ module Rails
152
+ class Railtie < ::Rails::Railtie
153
+ alias_method :create_container!, :create_container
154
+ def create_container
155
+ begin
156
+ create_container!
157
+ rescue => e
158
+ puts "Container failed to initialize because of #{e.inspect}"
159
+ puts "This message comes from the monkey patch in #{__FILE__}, if you are using rake, then this is fine"
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+ ```
@@ -18,6 +18,14 @@ module ROM
18
18
  __FILE__
19
19
  )
20
20
  end
21
+
22
+ def self.default_gateway
23
+ ROM.env.gateways[:default]
24
+ end
25
+
26
+ def self.default_adapter
27
+ (default_gateway && default_gateway.adapter)
28
+ end
21
29
  end
22
30
  end
23
31
  end
@@ -3,9 +3,7 @@ class Create<%= model_name %> < ROM::Commands::Create<%= "[:#{adapter}]" %>
3
3
  register_as :create
4
4
  result :one
5
5
 
6
- <% if adapter.to_sym == :sql -%>
7
6
  # set Timestamp plugin
8
7
  # use :timestamps
9
8
  # timestamp :created_at, :updated_at
10
- <% end -%>
11
9
  end
@@ -2,5 +2,4 @@ class Delete<%= model_name %> < ROM::Commands::Delete<%= "[:#{adapter}]" %>
2
2
  relation :<%= relation %>
3
3
  register_as :delete
4
4
  result :one
5
-
6
5
  end
@@ -3,9 +3,7 @@ class Update<%= model_name %> < ROM::Commands::Update<%= "[:#{adapter}]" %>
3
3
  register_as :update
4
4
  result :one
5
5
 
6
- <% if adapter.to_sym == :sql -%>
7
6
  # set Timestamp plugin
8
7
  # use :timestamps
9
8
  # timestamp :updated_at
10
- <% end -%>
11
9
  end
@@ -6,7 +6,7 @@ module ROM
6
6
  class_option :adapter,
7
7
  banner: "--adapter=adapter",
8
8
  desc: "specify an adapter to use", required: true,
9
- default: ROM.adapters.keys.first
9
+ default: default_adapter
10
10
 
11
11
  def create_create_command
12
12
  template 'create.rb.erb', command_file(:create)
@@ -1,10 +1,10 @@
1
- require 'types'
1
+ require "types"
2
2
 
3
3
  class ApplicationModel < ROM::Struct
4
4
  def self.inherited(base)
5
5
  super
6
6
 
7
- base.constructor_type :schema
7
+ base.transform_types(&:omittable)
8
8
 
9
9
  base.extend ActiveModel::Naming
10
10
  base.include ActiveModel::Conversion
@@ -1,3 +1,3 @@
1
1
  ROM::Rails::Railtie.configure do |config|
2
- config.gateways[:default] = [:<%= adapter %>, ENV.fetch('DATABASE_URL')]
2
+ # config.gateways[:default] = [:<%= adapter %>, ENV.fetch('DATABASE_URL')]
3
3
  end
@@ -1,9 +1,9 @@
1
- require 'dry/types'
1
+ require "dry/types"
2
2
 
3
3
  module Types
4
- include Dry::Types.module
4
+ include Dry.Types
5
5
 
6
- ID = Coercible::Int.optional.meta(primary_key: true)
6
+ ID = Coercible::Integer.optional.meta(primary_key: true)
7
7
 
8
8
  # Include your own type definitions and coersions here.
9
9
  # See http://dry-rb.org/gems/dry-types
@@ -8,7 +8,7 @@ module ROM
8
8
  end
9
9
 
10
10
  def self.source_root
11
- File.expand_path("../install/templates", __FILE__)
11
+ File.expand_path('install/templates', __dir__)
12
12
  end
13
13
 
14
14
  class_option :adapter,
@@ -29,14 +29,11 @@ module ROM
29
29
  copy_file "application_model.rb", "app/models/application_model.rb"
30
30
  end
31
31
 
32
-
33
-
34
32
  private
35
33
 
36
34
  def adapter
37
35
  options[:adapter].to_sym
38
36
  end
39
-
40
37
  end
41
38
  end
42
39
  end
@@ -6,7 +6,7 @@ module ROM
6
6
  class_option :adapter,
7
7
  banner: "--adapter=adapter",
8
8
  desc: "specify an adapter to use", required: true,
9
- default: ROM.adapters.keys.first
9
+ default: default_adapter
10
10
 
11
11
  class_option :gateway,
12
12
  banner: "--gateway=repo",
@@ -4,4 +4,12 @@ class <%= model_name %>Repository < ROM::Repository::Root
4
4
  commands :create, update: :by_pk, delete: :by_pk
5
5
 
6
6
  struct_namespace <%= struct_namespace %>
7
+
8
+ def by_id(id)
9
+ root.by_pk(id).one
10
+ end
11
+
12
+ def all
13
+ root.to_a
14
+ end
7
15
  end
@@ -4,11 +4,10 @@ if defined? ROM::Repository
4
4
  module ROM
5
5
  module Generators
6
6
  class RepositoryGenerator < Base
7
-
8
7
  class_option :namespace,
9
8
  banner: '--namespace=namespace',
10
9
  desc: "specify a struct namespace for the relation", required: true,
11
- default: ::Rails.application.class.name.split("::").first
10
+ default: ::Rails.application.class.name.split("::").first
12
11
 
13
12
  def create_repository_file
14
13
  template(
@@ -1,4 +1,4 @@
1
- require 'addressable/uri'
1
+ require_relative 'uri_builder'
2
2
 
3
3
  module ROM
4
4
  module Rails
@@ -17,15 +17,43 @@ module ROM
17
17
  :host
18
18
  ].freeze
19
19
 
20
+ attr_reader :configurations
21
+ attr_reader :env
22
+ attr_reader :root
23
+ attr_reader :uri_builder
24
+
25
+ def initialize(env: ::Rails.env, root: ::Rails.root, configurations: ::ActiveRecord::Base.configurations)
26
+ @configurations = configurations
27
+ @env = env
28
+ @root = root
29
+
30
+ @uri_builder = ROM::Rails::ActiveRecord::UriBuilder.new
31
+ end
32
+
20
33
  # Returns gateway configuration for the current environment.
21
34
  #
22
35
  # @note This relies on ActiveRecord being initialized already.
23
36
  # @param [Rails::Application]
24
37
  #
25
38
  # @api private
26
- def self.call
27
- configuration = ::ActiveRecord::Base.configurations.fetch(::Rails.env)
28
- build(configuration.symbolize_keys.update(root: ::Rails.root))
39
+ def call
40
+ specs = { default: build(default_configuration.symbolize_keys) }
41
+
42
+ if rails6?
43
+ configurations.configs_for(env_name: env).each do |config|
44
+ specs[config.spec_name.to_sym] = build(config.config.symbolize_keys)
45
+ end
46
+ end
47
+
48
+ specs
49
+ end
50
+
51
+ def default_configuration
52
+ if rails6?
53
+ configurations.default_hash(env)
54
+ else
55
+ configurations.fetch(env)
56
+ end
29
57
  end
30
58
 
31
59
  # Builds a configuration hash from a flat database config hash.
@@ -38,64 +66,22 @@ module ROM
38
66
  # @return [Hash]
39
67
  #
40
68
  # @api private
41
- def self.build(config)
69
+ def build(config)
42
70
  adapter = config.fetch(:adapter)
43
- uri_options = config.except(:adapter).merge(scheme: adapter)
71
+ uri_options = config.except(:adapter).merge(
72
+ root: root,
73
+ scheme: adapter
74
+ )
44
75
  other_options = config.except(*BASE_OPTIONS)
45
76
 
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
- if RUBY_ENGINE == 'jruby' && adapter != 'postgresql'
55
- uri = "jdbc:#{uri}"
56
- end
57
-
77
+ uri = uri_builder.build(adapter, uri_options)
58
78
  { uri: uri, options: other_options }
59
79
  end
60
80
 
61
- def self.sqlite3_uri(config)
62
- path = Pathname.new(config.fetch(:root)).join(config.fetch(:database))
63
-
64
- build_uri(
65
- scheme: 'sqlite',
66
- host: '',
67
- path: path.to_s
68
- )
69
- end
70
-
71
- def self.postgresql_uri(config)
72
- generic_uri(config.merge(
73
- host: config.fetch(:host) { '' },
74
- scheme: 'postgres'
75
- ))
76
- end
77
-
78
- def self.mysql_uri(config)
79
- if config.key?(:username) && !config.key?(:password)
80
- config.update(password: '')
81
- end
82
-
83
- generic_uri(config)
84
- end
85
-
86
- def self.generic_uri(config)
87
- build_uri(
88
- scheme: config.fetch(:scheme),
89
- user: config[:username],
90
- password: config[:password],
91
- host: config[:host],
92
- port: config[:port],
93
- path: config[:database]
94
- )
95
- end
81
+ private
96
82
 
97
- def self.build_uri(attrs)
98
- Addressable::URI.new(attrs).to_s
83
+ def rails6?
84
+ ::ActiveRecord::VERSION::MAJOR >= 6
99
85
  end
100
86
  end
101
87
  end