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.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +10 -0
- data/.github/ISSUE_TEMPLATE/---bug-report.md +30 -0
- data/.github/ISSUE_TEMPLATE/---feature-request.md +18 -0
- data/.github/workflows/ci.yml +67 -0
- data/.github/workflows/docsite.yml +34 -0
- data/.github/workflows/sync_configs.yml +30 -0
- data/.rubocop.yml +46 -9
- data/CHANGELOG.md +68 -0
- data/CONTRIBUTING.md +29 -0
- data/Gemfile +21 -12
- data/README.md +3 -5
- data/Rakefile +1 -1
- data/docsite/source/index.html.md +165 -0
- data/lib/generators/rom.rb +8 -0
- data/lib/generators/rom/commands/templates/create.rb.erb +0 -2
- data/lib/generators/rom/commands/templates/delete.rb.erb +0 -1
- data/lib/generators/rom/commands/templates/update.rb.erb +0 -2
- data/lib/generators/rom/commands_generator.rb +1 -1
- data/lib/generators/rom/install/templates/application_model.rb +2 -2
- data/lib/generators/rom/install/templates/initializer.rb.erb +1 -1
- data/lib/generators/rom/install/templates/types.rb +3 -3
- data/lib/generators/rom/install_generator.rb +1 -4
- data/lib/generators/rom/relation_generator.rb +1 -1
- data/lib/generators/rom/repository/templates/repository.rb.erb +8 -0
- data/lib/generators/rom/repository_generator.rb +1 -2
- data/lib/rom/rails/active_record/configuration.rb +41 -55
- data/lib/rom/rails/active_record/uri_builder.rb +70 -0
- data/lib/rom/rails/configuration.rb +1 -1
- data/lib/rom/rails/railtie.rb +30 -12
- data/lib/rom/rails/tasks/db.rake +6 -0
- data/lib/rom/rails/version.rb +1 -1
- data/rom-rails.gemspec +8 -9
- data/spec/dummy/app/commands/create_user.rb +0 -1
- data/spec/dummy/app/forms/user_form.rb +1 -2
- data/spec/dummy/app/relations/dummy_relation.rb +1 -1
- data/spec/dummy/app/relations/tasks.rb +1 -0
- data/spec/dummy/app/relations/users.rb +1 -0
- data/spec/dummy/bin/bundle +1 -1
- data/spec/dummy/bin/rails +1 -1
- data/spec/dummy/config/initializers/rom.rb +4 -3
- data/spec/dummy/lib/additional_app/{app → persistence}/commands/create_additional_task.rb +0 -0
- data/spec/dummy/lib/rom/test_adapter.rb +1 -0
- data/spec/integration/controller_extensions_spec.rb +23 -0
- data/spec/integration/initializer_spec.rb +1 -1
- data/spec/integration/user_commands_spec.rb +1 -1
- data/spec/lib/active_record/configuration_spec.rb +87 -1
- data/spec/lib/generators/commands_generator_spec.rb +2 -3
- data/spec/lib/generators/install_generator_spec.rb +7 -10
- data/spec/lib/generators/mapper_generator_spec.rb +1 -1
- data/spec/lib/generators/relation_generator_spec.rb +5 -2
- data/spec/lib/generators/repository_generator_spec.rb +17 -1
- metadata +45 -34
- data/.travis.yml +0 -20
data/README.md
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
[gem]: https://rubygems.org/gems/rom-rails
|
2
|
-
[
|
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
|
-
[![
|
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/
|
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(
|
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
|
+
```
|
data/lib/generators/rom.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
require
|
1
|
+
require "types"
|
2
2
|
|
3
3
|
class ApplicationModel < ROM::Struct
|
4
4
|
def self.inherited(base)
|
5
5
|
super
|
6
6
|
|
7
|
-
base.
|
7
|
+
base.transform_types(&:omittable)
|
8
8
|
|
9
9
|
base.extend ActiveModel::Naming
|
10
10
|
base.include ActiveModel::Conversion
|
@@ -1,9 +1,9 @@
|
|
1
|
-
require
|
1
|
+
require "dry/types"
|
2
2
|
|
3
3
|
module Types
|
4
|
-
include Dry
|
4
|
+
include Dry.Types
|
5
5
|
|
6
|
-
ID = Coercible::
|
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(
|
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
|
@@ -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
|
-
|
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
|
27
|
-
|
28
|
-
|
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
|
69
|
+
def build(config)
|
42
70
|
adapter = config.fetch(:adapter)
|
43
|
-
uri_options = config.except(:adapter).merge(
|
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
|
-
|
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
|
-
|
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
|
98
|
-
|
83
|
+
def rails6?
|
84
|
+
::ActiveRecord::VERSION::MAJOR >= 6
|
99
85
|
end
|
100
86
|
end
|
101
87
|
end
|