rom-repository 0.0.1

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.
@@ -0,0 +1,30 @@
1
+ require 'rom/repository/header_builder'
2
+
3
+ module ROM
4
+ class Repository < Gateway
5
+ # @api private
6
+ class MapperBuilder
7
+ attr_reader :header_builder
8
+
9
+ attr_reader :registry
10
+
11
+ def self.registry
12
+ @__registry__ ||= {}
13
+ end
14
+
15
+ def self.new(header_builder = HeaderBuilder.new)
16
+ super
17
+ end
18
+
19
+ def initialize(header_builder)
20
+ @header_builder = header_builder
21
+ @registry = self.class.registry
22
+ end
23
+
24
+ def call(ast)
25
+ registry[ast.hash] ||= Mapper.build(header_builder[ast])
26
+ end
27
+ alias_method :[], :call
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,38 @@
1
+ require 'anima'
2
+
3
+ require 'rom/struct'
4
+
5
+ module ROM
6
+ class Repository < Gateway
7
+ # @api private
8
+ class StructBuilder
9
+ attr_reader :registry
10
+
11
+ def self.registry
12
+ @__registry__ ||= {}
13
+ end
14
+
15
+ def initialize
16
+ @registry = self.class.registry
17
+ end
18
+
19
+ def call(*args)
20
+ name, columns = args
21
+ registry[args.hash] ||= build_class(name) { |klass|
22
+ klass.send(:include, Anima.new(*columns))
23
+ }
24
+ end
25
+ alias_method :[], :call
26
+
27
+ private
28
+
29
+ def build_class(name, &block)
30
+ ROM::ClassBuilder.new(name: class_name(name), parent: Struct).call(&block)
31
+ end
32
+
33
+ def class_name(name)
34
+ "ROM::Struct[#{Inflector.classify(Inflector.singularize(name))}]"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,9 @@
1
+ module ROM
2
+ # TODO: remove this once deprecated Repository is gone in rom core
3
+ class Gateway
4
+ end
5
+
6
+ class Repository < Gateway
7
+ VERSION = '0.0.1'.freeze
8
+ end
9
+ end
data/lib/rom/struct.rb ADDED
@@ -0,0 +1,32 @@
1
+ require 'anima'
2
+
3
+ require 'rom/support/class_builder'
4
+
5
+ module ROM
6
+ # Simple data-struct
7
+ #
8
+ # By default mappers use this as the model
9
+ #
10
+ # @api public
11
+ class Struct
12
+ # Coerce to hash
13
+ #
14
+ # @return [Hash]
15
+ #
16
+ # @api private
17
+ def to_hash
18
+ to_h
19
+ end
20
+
21
+ # Access attribute value
22
+ #
23
+ # @param [Symbol] name The name of the attribute
24
+ #
25
+ # @return [Object]
26
+ #
27
+ # @api public
28
+ def [](name)
29
+ instance_variable_get("@#{name}")
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path('../lib/rom/repository/version', __FILE__)
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = 'rom-repository'
7
+ gem.summary = 'Repository for ROM with auto-mapping and relation extensions'
8
+ gem.description = gem.summary
9
+ gem.author = 'Piotr Solnica'
10
+ gem.email = 'piotr.solnica@gmail.com'
11
+ gem.homepage = 'http://rom-rb.org'
12
+ gem.require_paths = ['lib']
13
+ gem.version = ROM::Repository::VERSION.dup
14
+ gem.files = `git ls-files`.split("\n").reject { |name| name.include?('benchmarks') }
15
+ gem.test_files = `git ls-files -- {spec}/*`.split("\n")
16
+ gem.license = 'MIT'
17
+
18
+ gem.add_runtime_dependency 'anima', '~> 0.2', '>= 0.2'
19
+ gem.add_runtime_dependency 'rom', '~> 0.8', '>= 0.8.1'
20
+ gem.add_runtime_dependency 'rom-sql', '~> 0.5', '>= 0.5.3'
21
+
22
+ gem.add_development_dependency 'rake', '~> 10.3'
23
+ gem.add_development_dependency 'rspec', '~> 3.3'
24
+ end
@@ -0,0 +1,40 @@
1
+ RSpec.describe 'ROM repository' do
2
+ include_context 'database'
3
+ include_context 'relations'
4
+ include_context 'seeds'
5
+ include_context 'structs'
6
+
7
+ it 'loads a single relation' do
8
+ expect(repo.all_users.to_a).to eql([jane, joe])
9
+ end
10
+
11
+ it 'loads a combine relation with one parent' do
12
+ expect(repo.task_with_user.first).to eql(task_with_user)
13
+ end
14
+
15
+ it 'loads a combine relation with one parent with custom tuple key' do
16
+ expect(repo.task_with_owner.first).to eql(task_with_owner)
17
+ end
18
+
19
+ it 'loads a combined relation with many children' do
20
+ expect(repo.users_with_tasks.to_a).to eql([jane_with_tasks, joe_with_tasks])
21
+ end
22
+
23
+ it 'loads a combined relation with one child' do
24
+ expect(repo.users_with_task.to_a).to eql([jane_with_task, joe_with_task])
25
+ end
26
+
27
+ it 'loads a combined relation with one child restricted by given criteria' do
28
+ expect(repo.users_with_task_by_title('Joe Task').to_a).to eql([
29
+ jane_without_task, joe_with_task
30
+ ])
31
+ end
32
+
33
+ it 'loads nested combined relations' do
34
+ expect(repo.users_with_tasks_and_tags.first).to eql(user_with_task_and_tags)
35
+ end
36
+
37
+ it 'loads a wrapped relation' do
38
+ expect(repo.tag_with_wrapped_task.first).to eql(tag_with_task)
39
+ end
40
+ end
@@ -0,0 +1,26 @@
1
+ RSpec.shared_context 'database' do
2
+ let(:setup) { ROM.setup(:sql, 'postgres://localhost/rom') }
3
+ let(:conn) { setup.gateways[:default].connection }
4
+ let(:rom) { setup.finalize }
5
+
6
+ before do
7
+ [:tags, :tasks, :users].each { |table| conn.drop_table?(table) }
8
+
9
+ conn.create_table :users do
10
+ primary_key :id
11
+ column :name, String
12
+ end
13
+
14
+ conn.create_table :tasks do
15
+ primary_key :id
16
+ foreign_key :user_id, :users, null: false, on_delete: :cascade
17
+ column :title, String
18
+ end
19
+
20
+ conn.create_table :tags do
21
+ primary_key :id
22
+ foreign_key :task_id, :tasks, null: false, on_delete: :cascade
23
+ column :name, String
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ RSpec.shared_context 'relations' do
2
+ let(:users) { rom.relation(:users) }
3
+ let(:tasks) { rom.relation(:tasks) }
4
+ let(:tags) { rom.relation(:tags) }
5
+
6
+ before do
7
+ setup.relation(:users) do
8
+ def all
9
+ select(:id, :name).order(:name, :id)
10
+ end
11
+
12
+ def find(criteria)
13
+ where(criteria)
14
+ end
15
+ end
16
+
17
+ setup.relation(:tasks) do
18
+ def find(criteria)
19
+ where(criteria)
20
+ end
21
+
22
+ def for_users(users)
23
+ where(user_id: users.map { |u| u[:id] })
24
+ end
25
+ end
26
+
27
+ setup.relation(:tags)
28
+ end
29
+ end
@@ -0,0 +1,49 @@
1
+ RSpec.shared_context('repo') do
2
+ let(:repo) { repo_class.new(rom) }
3
+
4
+ let(:repo_class) do
5
+ Class.new(ROM::Repository::Base) do
6
+ relations :users, :tasks, :tags
7
+
8
+ def find_users(criteria)
9
+ users.find(criteria)
10
+ end
11
+
12
+ def all_users
13
+ users.all
14
+ end
15
+
16
+ def task_with_user
17
+ tasks.find(id: 2).combine_parents(one: users)
18
+ end
19
+
20
+ def task_with_owner
21
+ tasks.find(id: 2).combine_parents(one: { owner: users })
22
+ end
23
+
24
+ def users_with_tasks
25
+ users.combine_children(many: { all_tasks: tasks.for_users })
26
+ end
27
+
28
+ def users_with_tasks_and_tags
29
+ users.combine_children(many: { all_tasks: tasks_with_tags(tasks.for_users) })
30
+ end
31
+
32
+ def tasks_with_tags(tasks = self.tasks)
33
+ tasks.combine_children(many: tags)
34
+ end
35
+
36
+ def users_with_task
37
+ users.combine_children(one: tasks)
38
+ end
39
+
40
+ def users_with_task_by_title(title)
41
+ users.combine_children(one: tasks.find(title: title))
42
+ end
43
+
44
+ def tag_with_wrapped_task
45
+ tags.wrap_parent(task: tasks)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,11 @@
1
+ RSpec.shared_context 'seeds' do
2
+ before do
3
+ jane_id = conn[:users].insert name: 'Jane'
4
+ joe_id = conn[:users].insert name: 'Joe'
5
+
6
+ conn[:tasks].insert user_id: joe_id, title: 'Joe Task'
7
+ task_id = conn[:tasks].insert user_id: jane_id, title: 'Jane Task'
8
+
9
+ conn[:tags].insert task_id: task_id, name: 'red'
10
+ end
11
+ end
@@ -0,0 +1,103 @@
1
+ RSpec.shared_context 'structs' do
2
+ include_context 'repo'
3
+
4
+ let(:user_struct) do
5
+ repo.users.mapper.model
6
+ end
7
+
8
+ let(:task_struct) do
9
+ repo.tasks.mapper.model
10
+ end
11
+
12
+ let(:tag_struct) do
13
+ repo.tags.mapper.model
14
+ end
15
+
16
+ let(:tag_with_task_struct) do
17
+ mapper_for(repo.tag_with_wrapped_task).model
18
+ end
19
+
20
+ let(:user_with_tasks_struct) do
21
+ mapper_for(repo.users_with_tasks).model
22
+ end
23
+
24
+ let(:user_with_task_struct) do
25
+ mapper_for(repo.users_with_task).model
26
+ end
27
+
28
+ let(:task_with_tags_struct) do
29
+ mapper_for(repo.tasks_with_tags).model
30
+ end
31
+
32
+ let(:task_with_user_struct) do
33
+ mapper_for(repo.task_with_user).model
34
+ end
35
+
36
+ let(:task_with_owner_struct) do
37
+ mapper_for(repo.task_with_owner).model
38
+ end
39
+
40
+ let(:jane) do
41
+ user_struct.new(id: 1, name: 'Jane')
42
+ end
43
+
44
+ let(:jane_with_tasks) do
45
+ user_with_tasks_struct.new(id: 1, name: 'Jane', all_tasks: [jane_task])
46
+ end
47
+
48
+ let(:jane_with_task) do
49
+ user_with_task_struct.new(id: 1, name: 'Jane', task: jane_task)
50
+ end
51
+
52
+ let(:jane_without_task) do
53
+ user_with_task_struct.new(id: 1, name: 'Jane', task: nil)
54
+ end
55
+
56
+ let(:jane_task) do
57
+ task_struct.new(id: 2, user_id: 1, title: 'Jane Task')
58
+ end
59
+
60
+ let(:task_with_user) do
61
+ task_with_user_struct.new(id: 2, user_id: 1, title: 'Jane Task', user: jane)
62
+ end
63
+
64
+ let(:task_with_owner) do
65
+ task_with_owner_struct.new(id: 2, user_id: 1, title: 'Jane Task', owner: jane)
66
+ end
67
+
68
+ let(:tag) do
69
+ tag_struct.new(id: 1, task_id: 2, name: 'red')
70
+ end
71
+
72
+ let(:task) do
73
+ task_struct.new(id: 2, user_id: 1, title: 'Jane Task')
74
+ end
75
+
76
+ let(:tag_with_task) do
77
+ tag_with_task_struct.new(id: 1, task_id: 2, name: 'red', task: task)
78
+ end
79
+
80
+ let(:task_with_tag) do
81
+ task_with_tags_struct.new(id: 2, user_id: 1, title: 'Jane Task', tags: [tag])
82
+ end
83
+
84
+ let(:user_with_task_and_tags) do
85
+ user_with_tasks_struct.new(id: 1, name: 'Jane', all_tasks: [task_with_tag])
86
+ end
87
+
88
+ let(:joe) do
89
+ user_struct.new(id: 2, name: 'Joe')
90
+ end
91
+
92
+ let(:joe_with_tasks) do
93
+ user_with_tasks_struct.new(id: 2, name: 'Joe', all_tasks: [joe_task])
94
+ end
95
+
96
+ let(:joe_with_task) do
97
+ user_with_task_struct.new(id: 2, name: 'Joe', task: joe_task)
98
+ end
99
+
100
+ let(:joe_task) do
101
+ task_struct.new(id: 1, user_id: 2, title: 'Joe Task')
102
+ end
103
+ end
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+
3
+ # this is needed for guard to work, not sure why :(
4
+ require "bundler"
5
+ Bundler.setup
6
+
7
+ if RUBY_ENGINE == "rbx"
8
+ require "codeclimate-test-reporter"
9
+ CodeClimate::TestReporter.start
10
+ end
11
+
12
+ require 'rom-repository'
13
+ require 'rom-sql'
14
+
15
+ begin
16
+ require 'byebug'
17
+ rescue LoadError
18
+ end
19
+
20
+ root = Pathname(__FILE__).dirname
21
+
22
+ Dir[root.join('support/*.rb').to_s].each do |f|
23
+ require f
24
+ end
25
+
26
+ Dir[root.join('shared/*.rb').to_s].each do |f|
27
+ require f
28
+ end
29
+
30
+ # Namespace holding all objects created during specs
31
+ module Test
32
+ def self.remove_constants
33
+ constants.each(&method(:remove_const))
34
+ end
35
+ end
36
+
37
+ RSpec.configure do |config|
38
+ config.after do
39
+ Test.remove_constants
40
+ end
41
+
42
+ config.include(MapperRegistry)
43
+ end