rom-sql 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b33ebf8637ba61f769f9ef29bef9f8669600a5ec
4
+ data.tar.gz: e6aa49e9b572394bd11cabfb2af4901a2f7c2d49
5
+ SHA512:
6
+ metadata.gz: 7cbd522a297fad97d735728bdbb501a96a5660a41d4129a56d7cd9e829bd04f18dae86ff58db1f92d32b2d445a9dc30d1f71a9fb5faacbb70ba682921e827a50
7
+ data.tar.gz: c7a8943d94016f88a5782cfa0b1a83c802649831841be8fe04093e1b0a49e76d5a74502dedb5742fc76852fafe205824af56145164244eeb17aa812a68ffaf72
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --order random
@@ -0,0 +1,23 @@
1
+ language: ruby
2
+ bundler_args: --without yard guard benchmarks
3
+ env:
4
+ - CODECLIMATE_REPO_TOKEN=03d7f66589572702b12426d2bc71c4de6281a96139e33b335b894264b1f8f0b0
5
+ before_script:
6
+ - psql -c 'create database rom;' -U postgres
7
+ script: "bundle exec rake spec"
8
+ rvm:
9
+ - 2.0
10
+ - 2.1
11
+ - rbx-2
12
+ - jruby
13
+ - ruby-head
14
+ matrix:
15
+ allow_failures:
16
+ - rvm: ruby-head
17
+ notifications:
18
+ webhooks:
19
+ urls:
20
+ - https://webhooks.gitter.im/e/39e1225f489f38b0bd09
21
+ on_success: change
22
+ on_failure: always
23
+ on_start: false
@@ -0,0 +1,3 @@
1
+ # v0.3.0 2014-11-24
2
+
3
+ First release powered by Sequel
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'rom', git: 'https://github.com/rom-rb/rom.git', branch: 'master'
7
+ gem 'rspec', '~> 3.1'
8
+ gem 'codeclimate-test-reporter', require: false
9
+ gem 'pg', platforms: [:mri, :rbx]
10
+ gem 'pg_jruby', platforms: :jruby
11
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Piotr Solnica
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,153 @@
1
+ [gem]: https://rubygems.org/gems/rom-sql
2
+ [travis]: https://travis-ci.org/rom-rb/rom-sql
3
+ [gemnasium]: https://gemnasium.com/rom-rb/rom-sql
4
+ [codeclimate]: https://codeclimate.com/github/rom-rb/rom-sql
5
+ [inchpages]: http://inch-ci.org/github/rom-rb/rom-sql
6
+
7
+ # ROM::SQL
8
+
9
+ [![Gem Version](https://badge.fury.io/rb/rom-sql.svg)][gem]
10
+ [![Build Status](https://travis-ci.org/rom-rb/rom-sql.svg?branch=master)][travis]
11
+ [![Dependency Status](https://gemnasium.com/rom-rb/rom-sql.png)][gemnasium]
12
+ [![Code Climate](https://codeclimate.com/github/rom-rb/rom-sql/badges/gpa.svg)][codeclimate]
13
+ [![Test Coverage](https://codeclimate.com/github/rom-rb/rom-sql/badges/coverage.svg)][codeclimate]
14
+ [![Inline docs](http://inch-ci.org/github/rom-rb/rom-sql.svg?branch=master)][inchpages]
15
+
16
+
17
+ RDBMS suport for [Ruby Object Mapper](https://github.com/rom-rb/rom).
18
+
19
+ ## Installation
20
+
21
+ Add this line to your application's Gemfile:
22
+
23
+ ```ruby
24
+ gem 'rom-sql'
25
+ ```
26
+
27
+ And then execute:
28
+
29
+ $ bundle
30
+
31
+ Or install it yourself as:
32
+
33
+ $ gem install rom-sql
34
+
35
+ ## Setup
36
+
37
+ ROM uses [Sequel](http://sequel.jeremyevans.net) under the hood and exposes its
38
+ [Dataset API](http://sequel.jeremyevans.net/rdoc/files/doc/dataset_basics_rdoc.html)
39
+ in relation objects. For schema migrations you can use its
40
+ [Migration API](http://sequel.jeremyevans.net/rdoc/files/doc/migration_rdoc.html)
41
+ which is available via repositories.
42
+
43
+ ``` ruby
44
+ setup = ROM.setup(sqlite: "memory::sqlite")
45
+
46
+ setup.sqlite.connection.create_table(:users) do
47
+ primary_key :id
48
+ String :name
49
+ Boolean :admin
50
+ end
51
+
52
+ setup.sqlite.connection.create_table(:tasks) do
53
+ primary_key :id
54
+ Integer :user_id
55
+ String :title
56
+ end
57
+ ```
58
+
59
+ ## Relations
60
+
61
+ ROM doesn't have a relationship concept like in ActiveRecord or Sequel. Instead
62
+ it provides a convenient interface for building joined relations that can be
63
+ mapped to [aggregate objects](http://martinfowler.com/bliki/Aggregate.html).
64
+
65
+ There's no lazy-loading, eager-loading or any other magic happening behind the
66
+ scenes. You're in full control of how data are fetched from the database and it's
67
+ an explicit operation.
68
+
69
+ ``` ruby
70
+
71
+ setup.relation(:tasks)
72
+
73
+ setup.relation(:users) do
74
+ one_to_many :tasks, key: :user_id
75
+
76
+ def by_name(name)
77
+ where(name: name)
78
+ end
79
+
80
+ def with_tasks
81
+ association_join(:tasks)
82
+ end
83
+ end
84
+
85
+ rom = setup.finalize
86
+
87
+ users = rom.relations.users
88
+ tasks = rom.relations.tasks
89
+
90
+ users.insert(name: "Piotr")
91
+ tasks.insert(title: "Be happy")
92
+
93
+ puts users.by_name("Piotr").with_tasks.to_a.inspect
94
+ # => [{:id=>1, :name=>"Piotr", :user_id=>1, :title=>"Be happy"}]
95
+ ```
96
+
97
+ ## Mapping
98
+
99
+ Mapping joined relations can be simplified using `wrap` and `group` in-memory
100
+ operations:
101
+
102
+ ``` ruby
103
+ setup.relation(:tasks)
104
+
105
+ setup.relation(:users) do
106
+ one_to_many :tasks, key: :user_id
107
+
108
+ def by_name(name)
109
+ where(name: name)
110
+ end
111
+
112
+ def with_tasks
113
+ in_memory { group(association_join(:tasks), tasks: [:title]) }
114
+ end
115
+ end
116
+
117
+ setup.mappers do
118
+ define(:users) do
119
+ model name: 'User'
120
+
121
+ group :tasks do
122
+ attribute :title
123
+ end
124
+ end
125
+ end
126
+
127
+ rom = setup.finalize
128
+
129
+ users = rom.relations.users
130
+ tasks = rom.relations.tasks
131
+
132
+ users.insert(name: "Piotr")
133
+ tasks.insert(title: "Be happy")
134
+
135
+ rom.read(:users).with_tasks.by_name("Piotr").to_a
136
+ # => [#<User:0x007fb31542a098 @id=1, @name="Piotr", @tasks=[{:title=>"Be happy"}]>]
137
+ ```
138
+
139
+ ## ROADMAP
140
+
141
+ On a very high level:
142
+
143
+ * Make it dead simple to join relations and support all types of joins
144
+ * Make it dead simple to select and alias column names
145
+ * Discover conventions for typical query scenarios and make common things trivial,
146
+ less common things simple and super rare cases possible
147
+ * Make mapper interface smart enough to figure out when columns are aliased
148
+
149
+ For details please refer to [issues](https://github.com/rom-rb/rom-sql/issues).
150
+
151
+ ## License
152
+
153
+ See `LICENSE` file.
@@ -0,0 +1,3 @@
1
+ require "rspec/core/rake_task"
2
+
3
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1 @@
1
+ require 'rom/sql'
@@ -0,0 +1,10 @@
1
+ require "sequel"
2
+
3
+ require "rom"
4
+ require "rom/sql/version"
5
+ require "rom/sql/header"
6
+ require "rom/sql/relation_extension"
7
+ require "rom/sql/relation_inclusion"
8
+ require "rom/sql/adapter"
9
+
10
+ require "rom/sql/support/sequel_dataset_ext"
@@ -0,0 +1,62 @@
1
+ module ROM
2
+ module SQL
3
+
4
+ class Adapter < ROM::Adapter
5
+ attr_reader :connection
6
+
7
+ def self.schemes
8
+ [:ado, :amalgalite, :cubrid, :db2, :dbi, :do, :fdbsql, :firebird, :ibmdb,
9
+ :informix, :jdbc, :mysql, :mysql2, :odbc, :openbase, :oracle, :postgres,
10
+ :sqlanywhere, :sqlite, :swift, :tinytds]
11
+ end
12
+
13
+ def initialize(*args)
14
+ super
15
+ @connection = ::Sequel.connect(uri.to_s)
16
+ end
17
+
18
+ def [](name)
19
+ connection[name]
20
+ end
21
+
22
+ def schema
23
+ tables.map { |table| [table, dataset(table), dataset(table).columns] }
24
+ end
25
+
26
+ def extend_relation_class(klass)
27
+ klass.send(:include, RelationInclusion)
28
+ end
29
+
30
+ def extend_relation_instance(relation)
31
+ relation.extend(RelationExtension)
32
+ end
33
+
34
+ private
35
+
36
+ def tables
37
+ connection.tables
38
+ end
39
+
40
+ def dataset(table)
41
+ connection[table]
42
+ end
43
+
44
+ def attributes(table)
45
+ map_attribute_types connection.schema(table)
46
+ end
47
+
48
+ def map_attribute_types(attrs)
49
+ attrs.map do |column, opts|
50
+ [column, { type: map_schema_type(opts[:type]) }]
51
+ end.to_h
52
+ end
53
+
54
+ def map_schema_type(type)
55
+ connection.class::SCHEMA_TYPE_CLASSES.fetch(type)
56
+ end
57
+
58
+ ROM::Adapter.register(self)
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,54 @@
1
+ module ROM
2
+ module SQL
3
+
4
+ class Header
5
+ include Charlatan.new(:columns)
6
+ include Equalizer.new(:columns, :name)
7
+
8
+ attr_reader :table
9
+
10
+ def initialize(columns, table)
11
+ super
12
+ @table = table
13
+ end
14
+
15
+ def to_ary
16
+ columns
17
+ end
18
+ alias_method :to_a, :to_ary
19
+
20
+ def to_h
21
+ columns.each_with_object({}) { |col, h|
22
+ left, right = col.to_s.split('___')
23
+ h[left.to_sym] = (right || left).to_sym
24
+ }
25
+ end
26
+
27
+ def project(*names)
28
+ find_all { |col| names.include?(col) }
29
+ end
30
+
31
+ def qualified
32
+ map { |col| :"#{table}__#{col}" }
33
+ end
34
+
35
+ def rename(options)
36
+ map { |col|
37
+ new_name = options[col]
38
+
39
+ if new_name
40
+ :"#{col}___#{new_name}"
41
+ else
42
+ col
43
+ end
44
+ }
45
+ end
46
+
47
+ def prefix(col_prefix)
48
+ rename(Hash[map { |col| [col, :"#{col_prefix}_#{col}"] }])
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,15 @@
1
+ module ROM
2
+ module SQL
3
+
4
+ module RelationExtension
5
+ attr_reader :model
6
+
7
+ def self.extended(relation)
8
+ relation.model.set_dataset(relation.dataset)
9
+ relation.model.dataset.naked!
10
+ end
11
+
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,133 @@
1
+ module ROM
2
+ module SQL
3
+
4
+ # Sequel-specific relation extensions
5
+ #
6
+ module RelationInclusion
7
+
8
+ def self.included(klass)
9
+ klass.extend(AssociationDSL)
10
+
11
+ klass.send(:undef_method, :select)
12
+
13
+ klass.class_eval {
14
+ class << self
15
+ attr_accessor :model
16
+ end
17
+
18
+ self.model = Class.new(Sequel::Model)
19
+ }
20
+ end
21
+
22
+ def initialize(*args)
23
+ super
24
+ @model = self.class.model
25
+ @header = dataset.header
26
+ end
27
+
28
+ # Join configured association.
29
+ #
30
+ # Uses INNER JOIN type.
31
+ #
32
+ # @example
33
+ #
34
+ # setup.relation(:tasks)
35
+ #
36
+ # setup.relations(:users) do
37
+ # one_to_many :tasks, key: :user_id
38
+ #
39
+ # def with_tasks
40
+ # association_join(:tasks, select: [:title])
41
+ # end
42
+ # end
43
+ #
44
+ # @api public
45
+ def association_join(*args)
46
+ send(:append_association, __method__, *args)
47
+ end
48
+
49
+ # Join configured association
50
+ #
51
+ # Uses LEFT JOIN type.
52
+ #
53
+ # @example
54
+ #
55
+ # setup.relation(:tasks)
56
+ #
57
+ # setup.relations(:users) do
58
+ # one_to_many :tasks, key: :user_id
59
+ #
60
+ # def with_tasks
61
+ # association_left_join(:tasks, select: [:title])
62
+ # end
63
+ # end
64
+ #
65
+ # @api public
66
+ def association_left_join(*args)
67
+ send(:append_association, __method__, *args)
68
+ end
69
+
70
+ private
71
+
72
+ # @api private
73
+ def append_association(type, name, options = {})
74
+ self.class.new(
75
+ dataset.public_send(type, name).
76
+ select_append(*columns_for_association(name, options))
77
+ )
78
+ end
79
+
80
+ # @api private
81
+ def columns_for_association(name, options)
82
+ col_names = options[:select]
83
+
84
+ return send(Inflecto.pluralize(name)).qualified_columns unless col_names
85
+
86
+ relations = col_names.is_a?(Hash) ? col_names.keys : [name]
87
+
88
+ columns = relations.each_with_object([]) do |rel_name, a|
89
+ relation = send(Inflecto.pluralize(rel_name))
90
+ names = col_names.is_a?(Hash) ? col_names[rel_name] : col_names
91
+
92
+ a.concat(relation.select(*names).prefix.qualified_columns)
93
+ end
94
+
95
+ columns
96
+ end
97
+
98
+ module AssociationDSL
99
+
100
+ def one_to_many(name, options)
101
+ associations << [__method__, name, options.merge(relation: name)]
102
+ end
103
+
104
+ def many_to_many(name, options = {})
105
+ associations << [__method__, name, options.merge(relation: name)]
106
+ end
107
+
108
+ def many_to_one(name, options = {})
109
+ associations << [__method__, name, options.merge(relation: Inflecto.pluralize(name).to_sym)]
110
+ end
111
+
112
+ def finalize(relations, relation)
113
+ associations.each do |*args, options|
114
+ model = relation.model
115
+ other = relations[options.fetch(:relation)].model
116
+
117
+ model.public_send(*args, options.merge(class: other))
118
+ end
119
+
120
+ model.freeze
121
+
122
+ super
123
+ end
124
+
125
+ def associations
126
+ @associations ||= []
127
+ end
128
+
129
+ end
130
+ end
131
+
132
+ end
133
+ end
@@ -0,0 +1,24 @@
1
+ if defined? JRUBY_VERSION
2
+ USING_JRUBY = true
3
+ else
4
+ USING_JRUBY = false
5
+ end
6
+
7
+ if USING_JRUBY
8
+ SEQUEL_TEST_DB_URI = "jdbc:sqlite::memory:"
9
+ else
10
+ SEQUEL_TEST_DB_URI = "sqlite::memory"
11
+ end
12
+
13
+ DB = Sequel.connect(SEQUEL_TEST_DB_URI)
14
+
15
+ def seed(db = DB)
16
+ db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name STRING)")
17
+
18
+ db[:users].insert(id: 1, name: 'Jane')
19
+ db[:users].insert(id:2, name: 'Joe')
20
+ end
21
+
22
+ def deseed(db = DB)
23
+ db.drop_table? :users
24
+ end
@@ -0,0 +1,33 @@
1
+ class Sequel::Dataset
2
+
3
+ def header
4
+ ROM::SQL::Header.new(opts.fetch(:select) { columns }, opts[:from].first)
5
+ end
6
+
7
+ def project(*names)
8
+ select(*header.project(*names))
9
+ end
10
+
11
+ def rename(options)
12
+ select(*header.rename(options))
13
+ end
14
+
15
+ def prefix(col_prefix = default_prefix)
16
+ rename(header.prefix(col_prefix).to_h)
17
+ end
18
+
19
+ def qualified
20
+ select(*qualified_columns)
21
+ end
22
+
23
+ def qualified_columns
24
+ header.qualified.to_a
25
+ end
26
+
27
+ private
28
+
29
+ def default_prefix
30
+ Inflecto.singularize(opts[:from].first)
31
+ end
32
+
33
+ end
@@ -0,0 +1,5 @@
1
+ module ROM
2
+ module SQL
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rom/sql/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rom-sql"
8
+ spec.version = ROM::SQL::VERSION
9
+ spec.authors = ["Piotr Solnica"]
10
+ spec.email = ["piotr.solnica@gmail.com"]
11
+ spec.summary = %q{RDBMS support for ROM}
12
+ spec.description = spec.summary
13
+ spec.homepage = "http://rom-rb.org"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "sequel", "~> 4.16"
22
+ spec.add_runtime_dependency "equalizer", "~> 0.0", ">= 0.0.9"
23
+ spec.add_runtime_dependency "rom", "~> 0.3", "~> 0.3.0"
24
+
25
+ spec.add_development_dependency "bundler"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ end
@@ -0,0 +1,37 @@
1
+ shared_context 'users and tasks' do
2
+ subject(:rom) { setup.finalize }
3
+
4
+ let(:setup) { ROM.setup(postgres: 'postgres://localhost/rom') }
5
+ let(:conn) { setup.postgres.connection }
6
+
7
+ before do
8
+ [:users, :tasks, :tags, :task_tags].each { |name| conn.drop_table?(name) }
9
+
10
+ conn.create_table :users do
11
+ primary_key :id
12
+ String :name
13
+ end
14
+
15
+ conn.create_table :tasks do
16
+ primary_key :id
17
+ Integer :user_id
18
+ String :title
19
+ end
20
+
21
+ conn.create_table :tags do
22
+ primary_key :id
23
+ String :name
24
+ end
25
+
26
+ conn.create_table :task_tags do
27
+ primary_key :tag_id, :task_id
28
+ Integer :tag_id
29
+ Integer :task_id
30
+ end
31
+
32
+ conn[:users].insert id: 1, name: 'Piotr'
33
+ conn[:tasks].insert id: 1, user_id: 1, title: 'Finish ROM'
34
+ conn[:tags].insert id: 1, name: 'important'
35
+ conn[:task_tags].insert(tag_id: 1, task_id: 1)
36
+ end
37
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+
3
+ if RUBY_ENGINE == 'rbx'
4
+ require "codeclimate-test-reporter"
5
+ CodeClimate::TestReporter.start
6
+ end
7
+
8
+ require 'rom-sql'
9
+
10
+ root = Pathname(__FILE__).dirname
11
+
12
+ Dir[root.join('shared/*.rb').to_s].each { |f| require f }
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Defining multiple associations' do
4
+ include_context 'users and tasks'
5
+
6
+ before do
7
+ conn[:tasks].insert id: 2, user_id: 1, title: 'Go to sleep'
8
+ end
9
+
10
+ it 'extends relation with association methods' do
11
+ setup.relation(:users)
12
+ setup.relation(:tags)
13
+
14
+ setup.relation(:tasks) do
15
+
16
+ many_to_one :users, key: :user_id
17
+
18
+ many_to_many :tags,
19
+ join_table: :task_tags,
20
+ left_key: :task_id,
21
+ right_key: :tag_id
22
+
23
+ def with_user_and_tags
24
+ all.with_tags.with_user
25
+ end
26
+
27
+ def all
28
+ select(:id, :title).qualified
29
+ end
30
+
31
+ def by_tag(name)
32
+ where(tags__name: name)
33
+ end
34
+
35
+ def with_tags
36
+ association_left_join(:tags, select: :name)
37
+ end
38
+
39
+ def with_user
40
+ association_join(:users, select: :name)
41
+ end
42
+
43
+ def sorted_by_tag_name
44
+ order(Sequel.desc(:tasks__title))
45
+ end
46
+
47
+ end
48
+
49
+ expect(rom.relations.tasks.with_user_and_tags.to_a).to eql([
50
+ { id: 1, title: 'Finish ROM', user_name: 'Piotr', tag_name: 'important' },
51
+ { id: 2, title: 'Go to sleep', user_name: 'Piotr', tag_name: nil }
52
+ ])
53
+
54
+ expect(rom.relations.tasks.with_user_and_tags.sorted_by_tag_name.to_a).to eql([
55
+ { id: 2, title: 'Go to sleep', user_name: 'Piotr', tag_name: nil },
56
+ { id: 1, title: 'Finish ROM', user_name: 'Piotr', tag_name: 'important' }
57
+ ])
58
+
59
+ expect(rom.relations.tasks.with_user_and_tags.by_tag('important').to_a).to eql([
60
+ { id: 1, title: 'Finish ROM', user_name: 'Piotr', tag_name: 'important' }
61
+ ])
62
+
63
+ expect(rom.relations.tasks.all.with_user.to_a).to eql([
64
+ { id: 1, title: 'Finish ROM', user_name: 'Piotr' },
65
+ { id: 2, title: 'Go to sleep', user_name: 'Piotr' }
66
+ ])
67
+
68
+ expect(rom.relations.tasks.where(title: 'Go to sleep').to_a).to eql(
69
+ [{ id: 2, user_id: 1, title: 'Go to sleep'}]
70
+ )
71
+ end
72
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Defining many-to-one association' do
4
+ include_context 'users and tasks'
5
+
6
+ before do
7
+ conn[:tasks].insert id: 2, user_id: 1, title: 'Go to sleep'
8
+ end
9
+
10
+ it 'extends relation with association methods' do
11
+ setup.relation(:tasks) do
12
+
13
+ many_to_many :tags,
14
+ join_table: :task_tags,
15
+ left_key: :task_id,
16
+ right_key: :tag_id
17
+
18
+ def with_tags
19
+ association_left_join(:tags).select(:tasks__id, :tasks__title, :tags__name)
20
+ end
21
+
22
+ def by_tag(name)
23
+ with_tags.where(tags__name: name)
24
+ end
25
+ end
26
+
27
+ setup.relation(:tags)
28
+
29
+ tasks = rom.relations.tasks
30
+
31
+ expect(tasks.with_tags.to_a).to eql([
32
+ { id: 1, title: 'Finish ROM', name: 'important' },
33
+ { id: 2, title: 'Go to sleep', name: nil }
34
+ ])
35
+
36
+ expect(tasks.by_tag("important").to_a).to eql([
37
+ { id: 1, title: 'Finish ROM', name: 'important' }
38
+ ])
39
+
40
+ expect(tasks.by_tag("not-here").to_a).to be_empty
41
+ end
42
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Defining many-to-one association' do
4
+ include_context 'users and tasks'
5
+
6
+ it 'extends relation with association methods' do
7
+ setup.relation(:tasks) do
8
+ many_to_one :users, key: :user_id
9
+
10
+ def all
11
+ select(:id, :title).rename(title: :task_title).qualified
12
+ end
13
+
14
+ def with_user
15
+ association_join(:users, select: [:name])
16
+ end
17
+ end
18
+
19
+ setup.relation(:users)
20
+
21
+ tasks = rom.relations.tasks
22
+
23
+ expect(tasks.all.with_user.to_a).to eql(
24
+ [{ id: 1, user_name: 'Piotr', task_title: 'Finish ROM' }]
25
+ )
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Defining one-to-many association' do
4
+ include_context 'users and tasks'
5
+
6
+ it 'extends relation with association methods' do
7
+ setup.relation(:tasks)
8
+
9
+ setup.relation(:users) do
10
+ one_to_many :tasks, key: :user_id
11
+
12
+ def by_name(name)
13
+ where(name: name)
14
+ end
15
+
16
+ def with_tasks
17
+ association_join(:tasks)
18
+ end
19
+ end
20
+
21
+ users = rom.relations.users
22
+
23
+ expect(users.with_tasks.by_name("Piotr").to_a).to eql(
24
+ [{ id: 1, user_id: 1, name: 'Piotr', title: 'Finish ROM' }]
25
+ )
26
+ end
27
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe ROM::Relation do
4
+ include_context 'users and tasks'
5
+
6
+ subject(:users) { rom.relations.users }
7
+
8
+ before do
9
+ setup.relation(:users) do
10
+ def sorted
11
+ order(:id)
12
+ end
13
+ end
14
+ end
15
+
16
+ describe '#project' do
17
+ it 'projects the dataset using new column names' do
18
+ projected = users.sorted.project(:name)
19
+
20
+ expect(projected.header).to match_array([:name])
21
+ expect(projected.to_a).to eql([{ name: 'Piotr'}])
22
+ end
23
+ end
24
+
25
+ describe '#rename' do
26
+ it 'projects the dataset using new column names' do
27
+ renamed = users.sorted.rename(id: :user_id, name: :user_name)
28
+
29
+ expect(renamed.to_a).to eql([{ user_id: 1, user_name: 'Piotr'}])
30
+ end
31
+ end
32
+
33
+ describe '#prefix' do
34
+ it 'projects the dataset using new column names' do
35
+ prefixed = users.sorted.prefix(:user)
36
+
37
+ expect(prefixed.to_a).to eql([{ user_id: 1, user_name: 'Piotr'}])
38
+ end
39
+
40
+ it 'uses singularized table name as the default prefix' do
41
+ prefixed = users.sorted.prefix
42
+
43
+ expect(prefixed.to_a).to eql([{ user_id: 1, user_name: 'Piotr'}])
44
+ end
45
+ end
46
+
47
+ describe '#qualified_columns' do
48
+ it 'returns qualified column names' do
49
+ columns = users.sorted.prefix(:user).qualified_columns
50
+
51
+ expect(columns).to eql([:users__id___user_id, :users__name___user_name])
52
+ end
53
+
54
+ it 'returns projected qualified column names' do
55
+ columns = users.sorted.project(:id).prefix(:user).qualified_columns
56
+
57
+ expect(columns).to eql([:users__id___user_id])
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Inferring schema from database' do
4
+ let(:setup) { ROM.setup(postgres: "postgres://localhost/rom") }
5
+
6
+ context "when database schema exists" do
7
+ after { rom.postgres.connection.drop_table?(:people) }
8
+
9
+ let(:rom) { setup.finalize }
10
+
11
+ it "infers the schema from the database relations" do
12
+ setup.postgres.connection.create_table :people do
13
+ primary_key :id
14
+ String :name
15
+ end
16
+
17
+ schema = rom.schema
18
+
19
+ expect(schema.people.to_a).to eql(rom.postgres.people.to_a)
20
+ expect(schema.people.header).to eql([:id, :name])
21
+ end
22
+ end
23
+
24
+ context "for empty database schemas" do
25
+ it "returns an empty schema" do
26
+ rom = setup.finalize
27
+ schema = rom.schema
28
+
29
+ expect(schema.postgres).to be(nil)
30
+ end
31
+ end
32
+ end
metadata ADDED
@@ -0,0 +1,160 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rom-sql
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Piotr Solnica
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sequel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '4.16'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '4.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: equalizer
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '0.0'
34
+ - - '>='
35
+ - !ruby/object:Gem::Version
36
+ version: 0.0.9
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: '0.0'
44
+ - - '>='
45
+ - !ruby/object:Gem::Version
46
+ version: 0.0.9
47
+ - !ruby/object:Gem::Dependency
48
+ name: rom
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '0.3'
54
+ - - ~>
55
+ - !ruby/object:Gem::Version
56
+ version: 0.3.0
57
+ type: :runtime
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ~>
62
+ - !ruby/object:Gem::Version
63
+ version: '0.3'
64
+ - - ~>
65
+ - !ruby/object:Gem::Version
66
+ version: 0.3.0
67
+ - !ruby/object:Gem::Dependency
68
+ name: bundler
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ type: :development
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ - !ruby/object:Gem::Dependency
82
+ name: rake
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ~>
86
+ - !ruby/object:Gem::Version
87
+ version: '10.0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ~>
93
+ - !ruby/object:Gem::Version
94
+ version: '10.0'
95
+ description: RDBMS support for ROM
96
+ email:
97
+ - piotr.solnica@gmail.com
98
+ executables: []
99
+ extensions: []
100
+ extra_rdoc_files: []
101
+ files:
102
+ - .gitignore
103
+ - .rspec
104
+ - .travis.yml
105
+ - CHANGELOG.md
106
+ - Gemfile
107
+ - LICENSE.txt
108
+ - README.md
109
+ - Rakefile
110
+ - lib/rom-sql.rb
111
+ - lib/rom/sql.rb
112
+ - lib/rom/sql/adapter.rb
113
+ - lib/rom/sql/header.rb
114
+ - lib/rom/sql/relation_extension.rb
115
+ - lib/rom/sql/relation_inclusion.rb
116
+ - lib/rom/sql/spec/support.rb
117
+ - lib/rom/sql/support/sequel_dataset_ext.rb
118
+ - lib/rom/sql/version.rb
119
+ - rom-sql.gemspec
120
+ - spec/shared/users_and_tasks.rb
121
+ - spec/spec_helper.rb
122
+ - spec/unit/combined_associations_spec.rb
123
+ - spec/unit/many_to_many_spec.rb
124
+ - spec/unit/many_to_one_spec.rb
125
+ - spec/unit/one_to_many_spec.rb
126
+ - spec/unit/relation_spec.rb
127
+ - spec/unit/schema_spec.rb
128
+ homepage: http://rom-rb.org
129
+ licenses:
130
+ - MIT
131
+ metadata: {}
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - '>='
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubyforge_project:
148
+ rubygems_version: 2.0.14
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: RDBMS support for ROM
152
+ test_files:
153
+ - spec/shared/users_and_tasks.rb
154
+ - spec/spec_helper.rb
155
+ - spec/unit/combined_associations_spec.rb
156
+ - spec/unit/many_to_many_spec.rb
157
+ - spec/unit/many_to_one_spec.rb
158
+ - spec/unit/one_to_many_spec.rb
159
+ - spec/unit/relation_spec.rb
160
+ - spec/unit/schema_spec.rb