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 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