rom-repository 0.0.1

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