rom-repository 1.4.0 → 2.0.0.beta1

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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -6
  3. data/{LICENSE.txt → LICENSE} +1 -1
  4. data/README.md +18 -1
  5. data/lib/rom-repository.rb +1 -2
  6. data/lib/rom/repository.rb +9 -216
  7. data/lib/rom/repository/class_interface.rb +16 -33
  8. data/lib/rom/repository/relation_reader.rb +46 -0
  9. data/lib/rom/repository/root.rb +3 -59
  10. data/lib/rom/repository/version.rb +1 -1
  11. metadata +9 -98
  12. data/.gitignore +0 -3
  13. data/.rspec +0 -3
  14. data/.travis.yml +0 -27
  15. data/.yardopts +0 -2
  16. data/Gemfile +0 -38
  17. data/Rakefile +0 -19
  18. data/lib/rom/open_struct.rb +0 -35
  19. data/lib/rom/repository/changeset.rb +0 -155
  20. data/lib/rom/repository/changeset/associated.rb +0 -100
  21. data/lib/rom/repository/changeset/create.rb +0 -16
  22. data/lib/rom/repository/changeset/delete.rb +0 -17
  23. data/lib/rom/repository/changeset/pipe.rb +0 -97
  24. data/lib/rom/repository/changeset/restricted.rb +0 -28
  25. data/lib/rom/repository/changeset/stateful.rb +0 -282
  26. data/lib/rom/repository/changeset/update.rb +0 -82
  27. data/lib/rom/repository/command_compiler.rb +0 -257
  28. data/lib/rom/repository/command_proxy.rb +0 -26
  29. data/lib/rom/repository/header_builder.rb +0 -65
  30. data/lib/rom/repository/mapper_builder.rb +0 -23
  31. data/lib/rom/repository/relation_proxy.rb +0 -337
  32. data/lib/rom/repository/relation_proxy/combine.rb +0 -320
  33. data/lib/rom/repository/relation_proxy/wrap.rb +0 -78
  34. data/lib/rom/repository/struct_builder.rb +0 -83
  35. data/lib/rom/struct.rb +0 -113
  36. data/log/.gitkeep +0 -0
  37. data/rom-repository.gemspec +0 -23
  38. data/spec/integration/changeset_spec.rb +0 -193
  39. data/spec/integration/command_macros_spec.rb +0 -191
  40. data/spec/integration/command_spec.rb +0 -228
  41. data/spec/integration/multi_adapter_spec.rb +0 -73
  42. data/spec/integration/repository/aggregate_spec.rb +0 -58
  43. data/spec/integration/repository_spec.rb +0 -406
  44. data/spec/integration/root_repository_spec.rb +0 -106
  45. data/spec/integration/typed_structs_spec.rb +0 -64
  46. data/spec/shared/database.rb +0 -79
  47. data/spec/shared/mappers.rb +0 -35
  48. data/spec/shared/models.rb +0 -41
  49. data/spec/shared/plugins.rb +0 -66
  50. data/spec/shared/relations.rb +0 -115
  51. data/spec/shared/repo.rb +0 -86
  52. data/spec/shared/seeds.rb +0 -30
  53. data/spec/shared/structs.rb +0 -140
  54. data/spec/spec_helper.rb +0 -83
  55. data/spec/support/mapper_registry.rb +0 -9
  56. data/spec/support/mutant.rb +0 -10
  57. data/spec/unit/changeset/associate_spec.rb +0 -120
  58. data/spec/unit/changeset/map_spec.rb +0 -111
  59. data/spec/unit/changeset_spec.rb +0 -186
  60. data/spec/unit/relation_proxy_spec.rb +0 -202
  61. data/spec/unit/repository/changeset_spec.rb +0 -197
  62. data/spec/unit/repository/inspect_spec.rb +0 -18
  63. data/spec/unit/repository/session_spec.rb +0 -251
  64. data/spec/unit/repository/transaction_spec.rb +0 -42
  65. data/spec/unit/session_spec.rb +0 -46
  66. data/spec/unit/struct_builder_spec.rb +0 -128
@@ -1,78 +0,0 @@
1
- module ROM
2
- class Repository
3
- class RelationProxy
4
- # Provides convenient methods for producing wrapped relations
5
- #
6
- # @api public
7
- module Wrap
8
- # Wrap other relations
9
- #
10
- # @example
11
- # tasks.wrap(owner: [users, user_id: :id])
12
- #
13
- # @param [Hash] options
14
- #
15
- # @return [RelationProxy]
16
- #
17
- # @api public
18
- def wrap(*names, **options)
19
- new_wraps = wraps_from_names(names) + wraps_from_options(options)
20
-
21
- relation = new_wraps.reduce(self) { |a, e|
22
- name = e.meta[:wrap_from_assoc] ? e.meta[:combine_name] : e.base_name.relation
23
- a.relation.for_wrap(e.meta.fetch(:keys), name)
24
- }
25
-
26
- __new__(relation, meta: { wraps: wraps + new_wraps })
27
- end
28
-
29
- # Shortcut to wrap parents
30
- #
31
- # @example
32
- # tasks.wrap_parent(owner: users)
33
- #
34
- # @return [RelationProxy]
35
- #
36
- # @api public
37
- def wrap_parent(options)
38
- wrap(
39
- options.each_with_object({}) { |(name, parent), h|
40
- keys = combine_keys(parent, relation, :children)
41
- h[name] = [parent, keys]
42
- }
43
- )
44
- end
45
-
46
- # Return a wrapped representation of a loading-proxy relation
47
- #
48
- # This will carry meta info used to produce a correct AST from a relation
49
- # so that correct mapper can be generated
50
- #
51
- # @return [RelationProxy]
52
- #
53
- # @api private
54
- def wrapped(name, keys, wrap_from_assoc = false)
55
- with(
56
- name: name,
57
- meta: {
58
- keys: keys, wrap_from_assoc: wrap_from_assoc, wrap: true, combine_name: name
59
- }
60
- )
61
- end
62
-
63
- # @api private
64
- def wraps_from_options(options)
65
- options.map { |(name, (relation, keys))| relation.wrapped(name, keys) }
66
- end
67
-
68
- # @api private
69
- def wraps_from_names(names)
70
- names.map { |name|
71
- assoc = associations[name]
72
- registry[assoc.target.relation].wrapped(name, assoc.combine_keys(__registry__), true)
73
- }
74
- end
75
- end
76
- end
77
- end
78
- end
@@ -1,83 +0,0 @@
1
- require 'dry/core/inflector'
2
- require 'dry/core/cache'
3
- require 'dry/core/class_builder'
4
-
5
- require 'rom/struct'
6
- require 'rom/open_struct'
7
- require 'rom/schema/attribute'
8
-
9
- module ROM
10
- class Repository
11
- # @api private
12
- class StructBuilder
13
- extend Dry::Core::Cache
14
-
15
- def call(*args)
16
- fetch_or_store(*args) do
17
- name, header = args
18
- attributes = visit(header).compact
19
-
20
- if attributes.empty?
21
- ROM::OpenStruct
22
- else
23
- build_class(name, ROM::Struct) do |klass|
24
- attributes.each do |(name, type)|
25
- klass.attribute(name, type)
26
- end
27
- end
28
- end
29
- end
30
- end
31
- alias_method :[], :call
32
-
33
- attr_reader :namespace
34
-
35
- def initialize(namespace = nil)
36
- @namespace = namespace || ROM::Struct
37
- end
38
-
39
- private
40
-
41
- def visit(ast)
42
- name, node = ast
43
- __send__("visit_#{name}", node)
44
- end
45
-
46
- def visit_header(node)
47
- node.map(&method(:visit))
48
- end
49
-
50
- def visit_relation(node)
51
- relation_name, meta, header = node
52
- name = meta[:combine_name] || relation_name.relation
53
-
54
- model = meta.fetch(:model) { call(name, header) }
55
-
56
- member =
57
- if model < Dry::Struct
58
- model
59
- else
60
- Dry::Types::Definition.new(model).constructor(&model.method(:new))
61
- end
62
-
63
- if meta[:combine_type] == :many
64
- [name, Types::Array.member(member)]
65
- else
66
- [name, member.optional]
67
- end
68
- end
69
-
70
- def visit_attribute(attr)
71
- [attr.aliased? && !attr.wrapped? ? attr.alias : attr.name, attr.to_read_type]
72
- end
73
-
74
- def build_class(name, parent, &block)
75
- Dry::Core::ClassBuilder.new(name: class_name(name), parent: parent, namespace: namespace).call(&block)
76
- end
77
-
78
- def class_name(name)
79
- Dry::Core::Inflector.classify(Dry::Core::Inflector.singularize(name))
80
- end
81
- end
82
- end
83
- end
data/lib/rom/struct.rb DELETED
@@ -1,113 +0,0 @@
1
- require 'dry/struct'
2
-
3
- module ROM
4
- # Simple data-struct class
5
- #
6
- # ROM structs are plain data structures loaded by repositories.
7
- # They implement Hash protocol which means that they can be used
8
- # in places where Hash-like objects are supported.
9
- #
10
- # Repositories define subclasses of ROM::Struct automatically, they are
11
- # defined in the ROM::Struct namespace by default, but you set it up
12
- # to use your namespace/module as well.
13
- #
14
- # Structs are based on dry-struct gem, they include `schema` with detailed information
15
- # about attribute types returned from relations, thus can be introspected to build
16
- # additional functionality when desired.
17
- #
18
- # There is a caveat you should know about when working with structs. Struct classes
19
- # have names but at the same time they're anonymous, i.e. you can't get the User struct class
20
- # with ROM::Struct::User. ROM will create as many struct classes for User as needed,
21
- # they all will have the same name and ROM::Struct::User will be the common parent class for
22
- # them. Combined with the ability to provide your own namespace for structs this enables to
23
- # pre-define the parent class.
24
- #
25
- # @example accessing relation struct model
26
- # rom = ROM.container(:sql, 'sqlite::memory') do |conf|
27
- # conf.default.create_table(:users) do
28
- # primary_key :id
29
- # column :name, String
30
- # end
31
- # end
32
- #
33
- # class UserRepo < ROM::Repository[:users]
34
- # end
35
- #
36
- # user_repo = UserRepo.new(rom)
37
- #
38
- # # get auto-generated User struct
39
- # model = user_repo.users.mapper.model
40
- # # => ROM::Struct::User
41
- #
42
- # # see struct's schema attributes
43
- #
44
- # # model.schema[:id]
45
- # # => #<Dry::Types::Constrained type=#<Dry::Types::Definition primitive=Integer options={}> options={:rule=>#<Dry::Logic::Rule::Predicate predicate=#<Method: Module(Dry::Logic::Predicates::Methods)#gt?> options={:args=>[0]}>, :meta=>{:primary_key=>true, :name=>:id, :source=>ROM::Relation::Name(users)}} rule=#<Dry::Logic::Rule::Predicate predicate=#<Method: Module(Dry::Logic::Predicates::Methods)#gt?> options={:args=>[0]}>>
46
- #
47
- # model.schema[:name]
48
- # # => #<Dry::Types::Sum left=#<Dry::Types::Constrained type=#<Dry::Types::Definition primitive=NilClass options={}> options={:rule=>#<Dry::Logic::Rule::Predicate predicate=#<Method: Module(Dry::Logic::Predicates::Methods)#type?> options={:args=>[NilClass]}>} rule=#<Dry::Logic::Rule::Predicate predicate=#<Method: Module(Dry::Logic::Predicates::Methods)#type?> options={:args=>[NilClass]}>> right=#<Dry::Types::Definition primitive=String options={}> options={:meta=>{:name=>:name, :source=>ROM::Relation::Name(users)}}>
49
- #
50
- # @example passing a namespace with an existing parent class
51
- # module Entities
52
- # class User < ROM::Struct
53
- # def upcased_name
54
- # name.upcase
55
- # end
56
- # end
57
- # end
58
- #
59
- # class UserRepo < ROM::Repository[:users]
60
- # struct_namespace Entities
61
- # end
62
- #
63
- # user_repo = UserRepo.new(rom)
64
- # user = user_repo.users.by_pk(1).one!
65
- # user.name # => "Jane"
66
- # user.upcased_name # => "JANE"
67
- #
68
- # @see http://dry-rb.org/gems/dry-struct dry-struct
69
- # @see http://dry-rb.org/gems/dry-types dry-types
70
- #
71
- # @api public
72
- class Struct < Dry::Struct
73
- MissingAttribute = Class.new(NameError)
74
-
75
- # Returns a short string representation
76
- #
77
- # @return [String]
78
- #
79
- # @api public
80
- def to_s
81
- "#<#{self.class}:0x#{(object_id << 1).to_s(16)}>"
82
- end
83
-
84
- # Return attribute value
85
- #
86
- # @param [Symbol] name The attribute name
87
- #
88
- # @api public
89
- def fetch(name)
90
- __send__(name)
91
- end
92
-
93
- if RUBY_VERSION < '2.3'
94
- # @api private
95
- def respond_to?(*)
96
- super
97
- end
98
- else
99
- # @api private
100
- def respond_to_missing?(*)
101
- super
102
- end
103
- end
104
-
105
- private
106
-
107
- def method_missing(method, *)
108
- super
109
- rescue NameError => error
110
- raise MissingAttribute.new("#{ error.message } (not loaded attribute?)")
111
- end
112
- end
113
- end
data/log/.gitkeep DELETED
File without changes
@@ -1,23 +0,0 @@
1
- require File.expand_path('../lib/rom/repository/version', __FILE__)
2
-
3
- Gem::Specification.new do |gem|
4
- gem.name = 'rom-repository'
5
- gem.summary = 'Repository abstraction for rom-rb'
6
- gem.description = 'rom-repository adds support for auto-mapping and commands on top of rom-rb relations'
7
- gem.author = 'Piotr Solnica'
8
- gem.email = 'piotr.solnica+oss@gmail.com'
9
- gem.homepage = 'http://rom-rb.org'
10
- gem.require_paths = ['lib']
11
- gem.version = ROM::Repository::VERSION.dup
12
- gem.files = `git ls-files`.split("\n").reject { |name| name.include?('benchmarks') || name.include?('examples') || name.include?('bin/console') }
13
- gem.test_files = `git ls-files -- {spec}/*`.split("\n")
14
- gem.license = 'MIT'
15
-
16
- gem.add_runtime_dependency 'rom', '~> 3.3'
17
- gem.add_runtime_dependency 'rom-mapper', '~> 0.5'
18
- gem.add_runtime_dependency 'dry-core', '~> 0.3', '>= 0.3.1'
19
- gem.add_runtime_dependency 'dry-struct', '~> 0.3'
20
-
21
- gem.add_development_dependency 'rake', '~> 11.2'
22
- gem.add_development_dependency 'rspec', '~> 3.5'
23
- end
@@ -1,193 +0,0 @@
1
- RSpec.describe 'Using changesets' do
2
- include_context 'database'
3
- include_context 'relations'
4
-
5
- before do
6
- module Test
7
- class User < Dry::Struct
8
- attribute :id, Dry::Types['strict.int']
9
- attribute :name, Dry::Types['strict.string']
10
- end
11
- end
12
-
13
- configuration.mappers do
14
- define(:users) do
15
- model Test::User
16
- register_as :user
17
- end
18
- end
19
- end
20
-
21
- describe 'Create' do
22
- subject(:repo) do
23
- Class.new(ROM::Repository[:users]) {
24
- relations :books, :posts
25
- commands :create, update: :by_pk
26
- }.new(rom)
27
- end
28
-
29
- let(:create_changeset) do
30
- Class.new(ROM::Changeset::Create)
31
- end
32
-
33
- let(:add_book_changeset) do
34
- Class.new(ROM::Changeset::Create[:books])
35
- end
36
-
37
- let(:update_changeset) do
38
- Class.new(ROM::Changeset::Update)
39
- end
40
-
41
- it 'can be passed to a command' do
42
- changeset = repo.changeset(name: "Jane Doe")
43
- command = repo.command(:create, repo.users)
44
- result = command.(changeset)
45
-
46
- expect(result.id).to_not be(nil)
47
- expect(result.name).to eql("Jane Doe")
48
- end
49
-
50
- it 'can be passed to a command graph' do
51
- changeset = repo.changeset(
52
- name: "Jane Doe", posts: [{ title: "Just Do It", alien: "or sutin" }]
53
- )
54
-
55
- command = repo.command(:create, repo.aggregate(:posts))
56
- result = command.(changeset)
57
-
58
- expect(result.id).to_not be(nil)
59
- expect(result.name).to eql("Jane Doe")
60
- expect(result.posts.size).to be(1)
61
- expect(result.posts[0].title).to eql("Just Do It")
62
- end
63
-
64
- it 'preprocesses data using changeset pipes' do
65
- changeset = repo.changeset(:books, title: "rom-rb is awesome").map(:add_timestamps)
66
- command = repo.command(:create, repo.books)
67
- result = command.(changeset)
68
-
69
- expect(result.id).to_not be(nil)
70
- expect(result.title).to eql("rom-rb is awesome")
71
- expect(result.created_at).to be_instance_of(Time)
72
- expect(result.updated_at).to be_instance_of(Time)
73
- end
74
-
75
- it 'preprocesses data using custom block' do
76
- changeset = repo.
77
- changeset(:books, title: "rom-rb is awesome").
78
- map { |tuple| tuple.merge(created_at: Time.now) }
79
-
80
- command = repo.command(:create, repo.books)
81
- result = command.(changeset)
82
-
83
- expect(result.id).to_not be(nil)
84
- expect(result.title).to eql("rom-rb is awesome")
85
- expect(result.created_at).to be_instance_of(Time)
86
- end
87
-
88
- it 'preprocesses data using built-in steps and custom block' do
89
- changeset = repo.
90
- changeset(:books, title: "rom-rb is awesome").
91
- extend(:touch) { |tuple| tuple.merge(created_at: Time.now) }
92
-
93
- command = repo.command(:create, repo.books)
94
- result = command.(changeset)
95
-
96
- expect(result.id).to_not be(nil)
97
- expect(result.title).to eql("rom-rb is awesome")
98
- expect(result.created_at).to be_instance_of(Time)
99
- expect(result.updated_at).to be_instance_of(Time)
100
- end
101
-
102
- it 'preserves relation mappers with create' do
103
- changeset = repo.
104
- changeset(create_changeset).
105
- new(repo.users.relation.as(:user)).
106
- data(name: 'Joe Dane')
107
-
108
- expect(changeset.commit).to eql(Test::User.new(id: 1, name: 'Joe Dane'))
109
- end
110
-
111
- it 'creates changesets for non-root relations' do
112
- repo.create(name: 'John Doe')
113
- changeset = repo.changeset(add_book_changeset).data(title: 'The War of the Worlds')
114
-
115
- expect(changeset.commit).
116
- to include(
117
- id: 1,
118
- title: 'The War of the Worlds',
119
- created_at: nil,
120
- updated_at: nil
121
- )
122
- end
123
- end
124
-
125
- describe 'Update' do
126
- subject(:repo) do
127
- Class.new(ROM::Repository[:books]) {
128
- commands :create, update: :by_pk
129
- }.new(rom)
130
- end
131
-
132
- it 'can be passed to a command' do
133
- book = repo.create(title: 'rom-rb is awesome')
134
-
135
- changeset = repo
136
- .changeset(book.id, title: 'rom-rb is awesome for real')
137
- .extend(:touch)
138
-
139
- expect(changeset.diff).to eql(title: 'rom-rb is awesome for real')
140
-
141
- result = repo.update(book.id, changeset)
142
-
143
- expect(result.id).to be(book.id)
144
- expect(result.title).to eql('rom-rb is awesome for real')
145
- expect(result.updated_at).to be_instance_of(Time)
146
- end
147
-
148
- it 'skips update execution with no diff' do
149
- book = repo.create(title: 'rom-rb is awesome')
150
-
151
- changeset = repo
152
- .changeset(book.id, title: 'rom-rb is awesome')
153
- .extend(:touch)
154
-
155
- expect(changeset).to_not be_diff
156
-
157
- result = repo.update(book.id, changeset)
158
-
159
- expect(result.id).to be(book.id)
160
- expect(result.title).to eql('rom-rb is awesome')
161
- expect(result.updated_at).to be(nil)
162
- end
163
-
164
- it 'works with mixed several class-level pipes' do
165
- book = repo.create(title: 'rom-rb is awesome')
166
-
167
- changeset_class = Class.new(ROM::Changeset::Update[:books]) do
168
- map { |title: | { title: title.upcase } }
169
- extend { |title: | { title: title.reverse } }
170
- end
171
-
172
- changeset = repo
173
- .changeset(changeset_class)
174
- .by_pk(book.id)
175
- .data(title: 'rom-rb is really awesome')
176
-
177
- expect(changeset.diff).to eql(title: 'ROM-RB IS REALLY AWESOME')
178
- expect(changeset.to_h).to eql(title: 'EMOSEWA YLLAER SI BR-MOR')
179
- end
180
-
181
- it 'works with mixed several instance-level pipes' do
182
- book = repo.create(title: 'rom-rb is awesome')
183
-
184
- changeset = repo.
185
- changeset(book.id, title: 'rom-rb is really awesome').
186
- map { |title: | { title: title.upcase } }.
187
- extend { |title: | { title: title.reverse } }
188
-
189
- expect(changeset.diff).to eql(title: 'ROM-RB IS REALLY AWESOME')
190
- expect(changeset.to_h).to eql(title: 'EMOSEWA YLLAER SI BR-MOR')
191
- end
192
- end
193
- end