rom 0.3.1 → 0.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/Gemfile +2 -0
- data/README.md +20 -5
- data/lib/rom.rb +12 -2
- data/lib/rom/adapter.rb +110 -4
- data/lib/rom/adapter/memory.rb +9 -52
- data/lib/rom/adapter/memory/commands.rb +41 -0
- data/lib/rom/adapter/memory/dataset.rb +52 -0
- data/lib/rom/adapter/memory/storage.rb +25 -0
- data/lib/rom/boot.rb +28 -1
- data/lib/rom/boot/command_dsl.rb +48 -0
- data/lib/rom/boot/dsl.rb +7 -0
- data/lib/rom/command_registry.rb +68 -0
- data/lib/rom/commands.rb +64 -0
- data/lib/rom/commands/create.rb +28 -0
- data/lib/rom/commands/delete.rb +44 -0
- data/lib/rom/commands/update.rb +42 -0
- data/lib/rom/commands/with_options.rb +21 -0
- data/lib/rom/env.rb +22 -6
- data/lib/rom/relation.rb +0 -18
- data/lib/rom/repository.rb +25 -3
- data/lib/rom/version.rb +1 -1
- data/spec/integration/adapters/setting_logger_spec.rb +35 -0
- data/spec/integration/commands/create_spec.rb +96 -0
- data/spec/integration/commands/delete_spec.rb +70 -0
- data/spec/integration/commands/error_handling_spec.rb +23 -0
- data/spec/integration/commands/try_spec.rb +27 -0
- data/spec/integration/commands/update_spec.rb +106 -0
- data/spec/integration/setup_spec.rb +35 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/mutant.rb +7 -0
- data/spec/unit/rom/env_spec.rb +19 -0
- data/spec/unit/rom/repository_spec.rb +21 -0
- metadata +22 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 18e380f111c5a327bab9a80b4dd82844f82cd485
|
4
|
+
data.tar.gz: 04b327cb735b695647382f47bff9ed8d72fb456b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8ec1a1f1d50794053ebddbf37f88c7ea3f885f55c58e45e76edae8d9c915bfbec3b3419de85bda6298e9a4d25db9cd52b3ff6b4cdd2657f2c09dfbcf7fc9448c
|
7
|
+
data.tar.gz: f31429fd032a6994c27757a98bd2811aa74c16e92ac64d64ded652082dfe92da2f0f6ecf131acfca686222a88c6f1d19b5b0142ecba1dc6a6beee202d1af755d
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,23 @@
|
|
1
|
+
## v0.4.0 2014-12-06
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* Command API (solnic)
|
6
|
+
* Setup DSL is now available within the `ROM.setup` block (solnic)
|
7
|
+
* Support for setting up a logger for an adapter (solnic)
|
8
|
+
* New `Adapter#dataset?(name)` which every adapter must implement (solnic)
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
|
12
|
+
* method-missing in `Repository` and `Env` kindly calls `super` (solnic)
|
13
|
+
|
14
|
+
### Changed
|
15
|
+
|
16
|
+
* Abstract `Adapter` defines `:connection` reader so it doesn't have to be
|
17
|
+
defined in adapter descendants (solnic)
|
18
|
+
|
19
|
+
[Compare v0.3.1...v0.4.0](https://github.com/rom-rb/rom/compare/v0.3.1...v0.4.0)
|
20
|
+
|
1
21
|
## v0.3.1 2014-11-25
|
2
22
|
|
3
23
|
### Added
|
data/Gemfile
CHANGED
@@ -8,6 +8,7 @@ group :console do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
group :test do
|
11
|
+
gem 'virtus'
|
11
12
|
gem 'mutant'
|
12
13
|
gem 'mutant-rspec'
|
13
14
|
gem 'rubysl-bigdecimal', platforms: :rbx
|
@@ -18,6 +19,7 @@ end
|
|
18
19
|
|
19
20
|
group :sql do
|
20
21
|
gem 'rom-sql', git: 'https://github.com/rom-rb/rom-sql.git', branch: 'master'
|
22
|
+
gem 'sequel'
|
21
23
|
gem 'jdbc-sqlite3', platforms: :jruby
|
22
24
|
gem 'sqlite3', platforms: [:mri, :rbx]
|
23
25
|
end
|
data/README.md
CHANGED
@@ -24,8 +24,8 @@ a couple of core concepts which makes it different from a typical ORM:
|
|
24
24
|
* It must be **simple** to use the full power of your database
|
25
25
|
|
26
26
|
With that in mind ROM ships with adapters that allow you to connect to any
|
27
|
-
database and exposes a DSL to define **relations** and **
|
28
|
-
accessing the data.
|
27
|
+
database and exposes a DSL to define **relations**, **mappers** and **commands**
|
28
|
+
to simplify accessing and changing the data.
|
29
29
|
|
30
30
|
Database support:
|
31
31
|
|
@@ -35,6 +35,9 @@ Database support:
|
|
35
35
|
See [issues](https://github.com/rom-rb/rom/issues?q=is%3Aopen+is%3Aissue+label%3Aadapter+label%3Afeature)
|
36
36
|
for a list of adapters that are planned to be added soon.
|
37
37
|
|
38
|
+
ROM can be used with Rails next to ActiveRecord via [rom-rails](https://github.com/rom-rb/rom-rails) railtie.
|
39
|
+
Integration with other frameworks is planned.
|
40
|
+
|
38
41
|
## Synopsis
|
39
42
|
|
40
43
|
``` ruby
|
@@ -48,6 +51,8 @@ setup.sqlite.connection.create_table :users do
|
|
48
51
|
Integer :age
|
49
52
|
end
|
50
53
|
|
54
|
+
# set up relations
|
55
|
+
|
51
56
|
setup.relation(:users) do
|
52
57
|
def by_name(name)
|
53
58
|
where(name: name)
|
@@ -58,6 +63,14 @@ setup.relation(:users) do
|
|
58
63
|
end
|
59
64
|
end
|
60
65
|
|
66
|
+
# set up commands
|
67
|
+
|
68
|
+
setup.commands(:users) do
|
69
|
+
define(:create)
|
70
|
+
end
|
71
|
+
|
72
|
+
# set up mappers
|
73
|
+
|
61
74
|
setup.mappers do
|
62
75
|
define(:users) do
|
63
76
|
model(name: 'User')
|
@@ -66,12 +79,14 @@ end
|
|
66
79
|
|
67
80
|
rom = setup.finalize
|
68
81
|
|
82
|
+
# accessing defined commands
|
83
|
+
|
84
|
+
rom.command(:users).try { create(name: "Joe", age: 17) }
|
85
|
+
rom.command(:users).try { create(name: "Jane", age: 18) }
|
86
|
+
|
69
87
|
# accessing registered relations
|
70
88
|
users = rom.relations.users
|
71
89
|
|
72
|
-
users.insert(name: "Joe", age: 17)
|
73
|
-
users.insert(name: "Jane", age: 18)
|
74
|
-
|
75
90
|
puts users.by_name("Jane").adults.to_a.inspect
|
76
91
|
# => [{:id=>2, :name=>"Jane", :age=>18}]
|
77
92
|
|
data/lib/rom.rb
CHANGED
@@ -10,6 +10,8 @@ require 'rom/relation'
|
|
10
10
|
require 'rom/mapper'
|
11
11
|
require 'rom/reader'
|
12
12
|
|
13
|
+
require 'rom/commands'
|
14
|
+
|
13
15
|
require 'rom/adapter'
|
14
16
|
require 'rom/repository'
|
15
17
|
require 'rom/env'
|
@@ -18,6 +20,14 @@ require 'rom/boot'
|
|
18
20
|
|
19
21
|
module ROM
|
20
22
|
EnvAlreadyFinalizedError = Class.new(StandardError)
|
23
|
+
CommandError = Class.new(StandardError)
|
24
|
+
TupleCountMismatchError = Class.new(CommandError)
|
25
|
+
|
26
|
+
InvalidOptionError = Class.new(StandardError) do
|
27
|
+
def initialize(option, valid_values)
|
28
|
+
super("#{option} should be one of #{valid_values.inspect}")
|
29
|
+
end
|
30
|
+
end
|
21
31
|
|
22
32
|
Schema = Class.new(Registry)
|
23
33
|
RelationRegistry = Class.new(Registry)
|
@@ -44,9 +54,9 @@ module ROM
|
|
44
54
|
if block
|
45
55
|
boot.instance_exec(&block)
|
46
56
|
boot.finalize
|
57
|
+
else
|
58
|
+
boot
|
47
59
|
end
|
48
|
-
|
49
|
-
boot
|
50
60
|
end
|
51
61
|
|
52
62
|
end
|
data/lib/rom/adapter.rb
CHANGED
@@ -2,13 +2,45 @@ require 'addressable/uri'
|
|
2
2
|
|
3
3
|
module ROM
|
4
4
|
|
5
|
+
# Abstract adapter class
|
6
|
+
#
|
7
|
+
# @api public
|
5
8
|
class Adapter
|
6
9
|
include Equalizer.new(:connection)
|
7
10
|
|
8
11
|
@adapters = []
|
9
12
|
|
13
|
+
# Return connection URI associated with the adapter
|
14
|
+
#
|
15
|
+
# @return [String]
|
16
|
+
#
|
17
|
+
# @api public
|
10
18
|
attr_reader :uri
|
11
19
|
|
20
|
+
# Return connection object
|
21
|
+
#
|
22
|
+
# @return [Object] type varies depending on the adapter
|
23
|
+
#
|
24
|
+
# @api public
|
25
|
+
attr_reader :connection
|
26
|
+
|
27
|
+
# Setup an adapter instance with the given connection URI
|
28
|
+
#
|
29
|
+
# @example
|
30
|
+
#
|
31
|
+
# Adapter = Class.new(ROM::Adapter)
|
32
|
+
#
|
33
|
+
# adapter = Adapter.new('mysql://localhost/test')
|
34
|
+
#
|
35
|
+
# adapter.uri.scheme # => 'mysql'
|
36
|
+
# adapter.uri.host # => 'localhost'
|
37
|
+
# adapter.uri.path # => '/test'
|
38
|
+
#
|
39
|
+
# @param [String] uri_string
|
40
|
+
#
|
41
|
+
# @return [Adapter]
|
42
|
+
#
|
43
|
+
# @api public
|
12
44
|
def self.setup(uri_string)
|
13
45
|
uri = Addressable::URI.parse(uri_string)
|
14
46
|
|
@@ -19,34 +51,108 @@ module ROM
|
|
19
51
|
adapter.new(uri)
|
20
52
|
end
|
21
53
|
|
54
|
+
# Register adapter class
|
55
|
+
#
|
56
|
+
# @example
|
57
|
+
#
|
58
|
+
# Adapter = Class.new(ROM::Adapter) {
|
59
|
+
# def self.schemes
|
60
|
+
# [:super_db]
|
61
|
+
# end
|
62
|
+
# }
|
63
|
+
#
|
64
|
+
# ROM::Adapter.register(Adapter)
|
65
|
+
# ROM::Adapter[:super_db] # => Adapter
|
66
|
+
#
|
67
|
+
# @return [Array] registered adapters
|
68
|
+
#
|
69
|
+
# @api public
|
22
70
|
def self.register(adapter)
|
23
71
|
@adapters.unshift adapter
|
24
72
|
end
|
25
73
|
|
74
|
+
# Return adapter class for the given scheme
|
75
|
+
#
|
76
|
+
# @see Adapter.register
|
77
|
+
#
|
78
|
+
# @return [Class] adapter class
|
79
|
+
#
|
80
|
+
# @api public
|
26
81
|
def self.[](scheme)
|
27
82
|
@adapters.detect { |adapter| adapter.schemes.include?(scheme.to_sym) }
|
28
83
|
end
|
29
84
|
|
85
|
+
# @api private
|
30
86
|
def initialize(uri)
|
31
87
|
@uri = uri
|
32
88
|
end
|
33
89
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
90
|
+
# Extension hook for adding adapter-specific behavior to a relation class
|
91
|
+
#
|
92
|
+
# @param [Class] klass Relation class generated by ROM
|
93
|
+
#
|
94
|
+
# @return [Class] extended relation class
|
95
|
+
#
|
96
|
+
# @api public
|
38
97
|
def extend_relation_class(klass)
|
39
98
|
klass
|
40
99
|
end
|
41
100
|
|
101
|
+
# Extension hook for adding adapter-specific behavior to a relation instance
|
102
|
+
#
|
103
|
+
# @param [Relation] relation
|
104
|
+
#
|
105
|
+
# @return [Relation] extended relation instance
|
106
|
+
#
|
107
|
+
# @api public
|
42
108
|
def extend_relation_instance(relation)
|
43
109
|
relation
|
44
110
|
end
|
45
111
|
|
112
|
+
# Builds a command
|
113
|
+
#
|
114
|
+
# @param [Symbol] name of the command
|
115
|
+
# @param [Relation] relation used by the command
|
116
|
+
# @param [CommandDSL::Definition] command definition object
|
117
|
+
#
|
118
|
+
# @return [Object] created command instance
|
119
|
+
#
|
120
|
+
# @api public
|
121
|
+
def command(name, relation, definition)
|
122
|
+
type = definition.type || name
|
123
|
+
|
124
|
+
klass =
|
125
|
+
case type
|
126
|
+
when :create then command_namespace.const_get(:Create)
|
127
|
+
when :update then command_namespace.const_get(:Update)
|
128
|
+
when :delete then command_namespace.const_get(:Delete)
|
129
|
+
else
|
130
|
+
raise ArgumentError, "#{type.inspect} is not a supported command type"
|
131
|
+
end
|
132
|
+
|
133
|
+
klass.new(relation, definition.to_h)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Schema inference hook
|
137
|
+
#
|
138
|
+
# Every adapter that supports schema inference should implement this method
|
139
|
+
#
|
140
|
+
# @return [Array] array with datasets and their names
|
141
|
+
#
|
142
|
+
# @api private
|
46
143
|
def schema
|
47
144
|
[]
|
48
145
|
end
|
49
146
|
|
147
|
+
# Return namespace with adapter-specific command classes
|
148
|
+
#
|
149
|
+
# @return [Module]
|
150
|
+
#
|
151
|
+
# @api private
|
152
|
+
def command_namespace
|
153
|
+
self.class.const_get(:Commands)
|
154
|
+
end
|
155
|
+
|
50
156
|
end
|
51
157
|
|
52
158
|
end
|
data/lib/rom/adapter/memory.rb
CHANGED
@@ -1,64 +1,17 @@
|
|
1
|
+
require 'rom/adapter/memory/storage'
|
2
|
+
require 'rom/adapter/memory/dataset'
|
3
|
+
require 'rom/adapter/memory/commands'
|
4
|
+
|
1
5
|
module ROM
|
2
6
|
class Adapter
|
3
7
|
|
4
8
|
class Memory < Adapter
|
5
|
-
|
9
|
+
attr_accessor :logger
|
6
10
|
|
7
11
|
def self.schemes
|
8
12
|
[:memory]
|
9
13
|
end
|
10
14
|
|
11
|
-
class Dataset
|
12
|
-
include Charlatan.new(:data)
|
13
|
-
|
14
|
-
def to_ary
|
15
|
-
data
|
16
|
-
end
|
17
|
-
alias_method :to_a, :to_ary
|
18
|
-
|
19
|
-
def each(&block)
|
20
|
-
return to_enum unless block
|
21
|
-
data.each(&block)
|
22
|
-
end
|
23
|
-
|
24
|
-
def restrict(criteria = nil, &block)
|
25
|
-
if criteria
|
26
|
-
find_all { |tuple| criteria.all? { |k, v| tuple[k] == v } }
|
27
|
-
else
|
28
|
-
find_all { |tuple| yield(tuple) }
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def project(*names)
|
33
|
-
map { |tuple| tuple.reject { |key,_| names.include?(key) } }
|
34
|
-
end
|
35
|
-
|
36
|
-
def order(*names)
|
37
|
-
sort_by { |tuple| tuple.values_at(*names) }
|
38
|
-
end
|
39
|
-
|
40
|
-
def insert(tuple)
|
41
|
-
data << tuple
|
42
|
-
end
|
43
|
-
|
44
|
-
def header
|
45
|
-
[]
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
class Storage
|
50
|
-
attr_reader :data
|
51
|
-
|
52
|
-
def initialize(*)
|
53
|
-
super
|
54
|
-
@data = {}
|
55
|
-
end
|
56
|
-
|
57
|
-
def [](name)
|
58
|
-
data[name] ||= Dataset.new([])
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
15
|
def initialize(*args)
|
63
16
|
super
|
64
17
|
@connection = Storage.new
|
@@ -68,6 +21,10 @@ module ROM
|
|
68
21
|
connection[name]
|
69
22
|
end
|
70
23
|
|
24
|
+
def dataset?(name)
|
25
|
+
connection.key?(name)
|
26
|
+
end
|
27
|
+
|
71
28
|
Adapter.register(self)
|
72
29
|
end
|
73
30
|
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ROM
|
2
|
+
class Adapter
|
3
|
+
class Memory < Adapter
|
4
|
+
|
5
|
+
module Commands
|
6
|
+
|
7
|
+
class Create < ROM::Commands::Create
|
8
|
+
|
9
|
+
def execute(tuple)
|
10
|
+
attributes = input[tuple]
|
11
|
+
validator.call(attributes)
|
12
|
+
[relation.insert(attributes.to_h).to_a.last]
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
class Update < ROM::Commands::Update
|
18
|
+
|
19
|
+
def execute(params)
|
20
|
+
attributes = input[params]
|
21
|
+
validator.call(attributes)
|
22
|
+
relation.map { |tuple| tuple.update(attributes.to_h) }
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
class Delete < ROM::Commands::Delete
|
28
|
+
|
29
|
+
def execute
|
30
|
+
tuples = target.to_a
|
31
|
+
tuples.each { |tuple| relation.delete(tuple) }
|
32
|
+
tuples
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module ROM
|
2
|
+
class Adapter
|
3
|
+
class Memory < Adapter
|
4
|
+
|
5
|
+
class Dataset
|
6
|
+
include Charlatan.new(:data)
|
7
|
+
|
8
|
+
def to_ary
|
9
|
+
data.dup
|
10
|
+
end
|
11
|
+
alias_method :to_a, :to_ary
|
12
|
+
|
13
|
+
def each(&block)
|
14
|
+
return to_enum unless block
|
15
|
+
data.each(&block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def restrict(criteria = nil, &block)
|
19
|
+
if criteria
|
20
|
+
find_all { |tuple| criteria.all? { |k, v| tuple[k] == v } }
|
21
|
+
else
|
22
|
+
find_all { |tuple| yield(tuple) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def project(*names)
|
27
|
+
map { |tuple| tuple.reject { |key,_| names.include?(key) } }
|
28
|
+
end
|
29
|
+
|
30
|
+
def order(*names)
|
31
|
+
sort_by { |tuple| tuple.values_at(*names) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def insert(tuple)
|
35
|
+
data << tuple
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete(tuple)
|
40
|
+
data.delete(tuple)
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def header
|
45
|
+
[]
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|