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