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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 728df93bc1b41195f801808c3849bc5012575181
4
- data.tar.gz: be8e7364087eb867e25f2c0347fde4f7eb0dd4db
3
+ metadata.gz: 18e380f111c5a327bab9a80b4dd82844f82cd485
4
+ data.tar.gz: 04b327cb735b695647382f47bff9ed8d72fb456b
5
5
  SHA512:
6
- metadata.gz: b72abeba04fcdfe33de4562a2d81aa1a0b6297dbbee46c9cbe45f13d4e06788c34af039cafb1b741e1a0e7ae6b865bc0f347823edf0735d57b54f57f3e1908ef
7
- data.tar.gz: a14e740c4769a9cc6929bee634eaffdfed36d08e58c7c79aa136849387c6e8ed8cfda7bcb9f3e4f4d1ce2ca7e658fb5fc6a6fbaea8b8d2e6fb8f2f535d68cd22
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 **mappers** to simplify
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
- def connection
35
- raise NotImplementedError, "#{self.class}#connection must be implemented"
36
- end
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
@@ -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
- attr_reader :connection
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