rom-support 0.1.0

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,5 @@
1
+ module ROM
2
+ module Support
3
+ VERSION = '0.1.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ desc "Run mutant against a specific subject"
2
+ task :mutant do
3
+ subject = ARGV.last
4
+ if subject == 'mutant'
5
+ abort "usage: rake mutant SUBJECT\nexample: rake mutant ROM::Header"
6
+ else
7
+ opts = {
8
+ 'include' => 'lib',
9
+ 'require' => 'rom',
10
+ 'use' => 'rspec',
11
+ 'ignore-subject' => "#{subject}#respond_to_missing?"
12
+ }.to_a.map { |k, v| "--#{k} #{v}" }.join(' ')
13
+
14
+ exec("bundle exec mutant #{opts} #{subject}")
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ begin
2
+ require "rubocop/rake_task"
3
+
4
+ Rake::Task[:default].enhance [:rubocop]
5
+
6
+ RuboCop::RakeTask.new do |task|
7
+ task.options << "--display-cop-names"
8
+ end
9
+
10
+ namespace :rubocop do
11
+ desc 'Generate a configuration file acting as a TODO list.'
12
+ task :auto_gen_config do
13
+ exec "bundle exec rubocop --auto-gen-config"
14
+ end
15
+ end
16
+
17
+ rescue LoadError
18
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path('../lib/rom/support/version', __FILE__)
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = 'rom-support'
7
+ gem.summary = 'Ruby Object Mapper - Support libraries'
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::Support::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 'wisper', '~> 1.6', '>= 1.6.0'
19
+
20
+ gem.add_development_dependency 'rake', '~> 10.3'
21
+ gem.add_development_dependency 'rspec', '~> 3.3'
22
+ end
@@ -0,0 +1,52 @@
1
+ shared_examples_for "an enumerable dataset" do
2
+ subject(:dataset) { klass.new(data) }
3
+
4
+ let(:data) do
5
+ [{ 'name' => 'Jane' }, { 'name' => 'Joe' }]
6
+ end
7
+
8
+ describe '#each' do
9
+ it 'yields tuples through row_proc' do
10
+ result = []
11
+
12
+ dataset.each do |tuple|
13
+ result << tuple
14
+ end
15
+
16
+ expect(result).to match_array([{ name: 'Jane' }, { name: 'Joe' }])
17
+ end
18
+ end
19
+
20
+ describe '#to_a' do
21
+ it 'casts dataset to an array' do
22
+ expect(dataset.to_a).to eql([{ name: 'Jane' }, { name: 'Joe' }])
23
+ end
24
+ end
25
+
26
+ describe '#find_all' do
27
+ it 'yields tuples through row_proc' do
28
+ result = dataset.find_all { |tuple| tuple[:name] == 'Jane' }
29
+
30
+ expect(result).to be_instance_of(klass)
31
+ expect(result).to match_array([{ name: 'Jane' }])
32
+ end
33
+ end
34
+
35
+ describe '#kind_of?' do
36
+ it 'does not forward to data object' do
37
+ expect(dataset).to be_kind_of(klass)
38
+ end
39
+ end
40
+
41
+ describe '#instance_of?' do
42
+ it 'does not forward to data object' do
43
+ expect(dataset).to be_instance_of(klass)
44
+ end
45
+ end
46
+
47
+ describe '#is_a?' do
48
+ it 'does not forward to data object' do
49
+ expect(dataset.is_a?(klass)).to be(true)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,46 @@
1
+ # this is needed for guard to work, not sure why :(
2
+ require "bundler"
3
+ Bundler.setup
4
+
5
+ if RUBY_ENGINE == "rbx"
6
+ require "codeclimate-test-reporter"
7
+ CodeClimate::TestReporter.start
8
+ end
9
+
10
+ require 'rom-support'
11
+ require 'rom-mapper'
12
+
13
+ begin
14
+ require 'byebug'
15
+ rescue LoadError
16
+ end
17
+
18
+ root = Pathname(__FILE__).dirname
19
+
20
+ Dir[root.join('support/*.rb').to_s].each do |f|
21
+ require f
22
+ end
23
+ Dir[root.join('shared/*.rb').to_s].each do |f|
24
+ require f
25
+ end
26
+
27
+ # Namespace holding all objects created during specs
28
+ module Test
29
+ def self.remove_constants
30
+ constants.each(&method(:remove_const))
31
+ end
32
+ end
33
+
34
+ RSpec.configure do |config|
35
+ config.after do
36
+ Test.remove_constants
37
+ end
38
+
39
+ config.around do |example|
40
+ ConstantLeakFinder.find(example)
41
+ end
42
+ end
43
+
44
+ def T(*args)
45
+ ROM::Processor::Transproc::Functions[*args]
46
+ end
@@ -0,0 +1,14 @@
1
+ # Finds leaking constants created during ROM specs
2
+ module ConstantLeakFinder
3
+ def self.find(example)
4
+ constants = Object.constants
5
+
6
+ example.run
7
+
8
+ added_constants = (Object.constants - constants)
9
+ added = added_constants.map(&Object.method(:const_get))
10
+ if added.any? { |mod| mod.ancestors.map(&:name).grep(/\AROM/).any? }
11
+ raise "Leaking constants: #{added_constants.inspect}"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ module Mutant
2
+ class Selector
3
+ # Expression based test selector
4
+ class Expression < self
5
+ def call(_subject)
6
+ integration.all_tests
7
+ end
8
+ end # Expression
9
+ end # Selector
10
+ end # Mutant
@@ -0,0 +1,59 @@
1
+ require 'rom/support/array_dataset'
2
+
3
+ describe ROM::ArrayDataset do
4
+ let(:klass) do
5
+ Class.new do
6
+ include ROM::ArrayDataset
7
+
8
+ def self.row_proc
9
+ T(:symbolize_keys)
10
+ end
11
+ end
12
+ end
13
+
14
+ it_behaves_like 'an enumerable dataset' do
15
+ describe '#flatten' do
16
+ let(:data) { [[{ 'name' => 'Jane' }], [{ 'name' => 'Joe' }]] }
17
+
18
+ it 'returns a new dataset with flattened data' do
19
+ result = dataset.flatten
20
+
21
+ expect(result).to match_array([{ name: 'Jane' }, { name: 'Joe' }])
22
+ end
23
+ end
24
+
25
+ describe '#map!' do
26
+ context 'with a block' do
27
+ it 'returns a new dataset with mapped data' do
28
+ dataset.map! do |row|
29
+ row.merge(age: 21)
30
+ end
31
+
32
+ expect(dataset).to match_array([
33
+ { name: 'Jane', age: 21 }, { name: 'Joe', age: 21 }
34
+ ])
35
+ end
36
+ end
37
+
38
+ context 'without a block' do
39
+ it 'returns an enumerator' do
40
+ result = dataset.map!
41
+
42
+ expect(result).to be_instance_of(Enumerator)
43
+
44
+ expect(result).to match_array([
45
+ { name: 'Jane' }, { name: 'Joe' }
46
+ ])
47
+ end
48
+ end
49
+ end
50
+
51
+ describe '#values_at' do
52
+ it 'returns a new dataset with rows at given indices' do
53
+ result = dataset.values_at(1)
54
+
55
+ expect(result).to match_array([{ name: 'Joe' }])
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,42 @@
1
+ require 'rom/support/class_builder'
2
+
3
+ describe ROM::ClassBuilder do
4
+ subject(:builder) { ROM::ClassBuilder.new(options) }
5
+
6
+ let(:klass) { builder.call }
7
+
8
+ describe '#call' do
9
+ let(:options) do
10
+ { name: 'Test', parent: parent }
11
+ end
12
+
13
+ let(:parent) { Class.new }
14
+
15
+ it 'returns a class constant' do
16
+ expect(klass).to be_instance_of(Class)
17
+ end
18
+
19
+ it 'sets class name based on provided :name option' do
20
+ expect(klass.name).to eql(options[:name])
21
+ end
22
+
23
+ it 'uses a parent class provided by :parent option' do
24
+ expect(klass).to be < parent
25
+ end
26
+
27
+ it 'defines to_s and inspect' do
28
+ expect(klass.to_s).to eql(options[:name])
29
+ expect(klass.inspect).to eql(options[:name])
30
+ end
31
+
32
+ it 'yields created class' do
33
+ klass = builder.call { |yielded_class|
34
+ yielded_class.class_eval do
35
+ def self.testing; end
36
+ end
37
+ }
38
+
39
+ expect(klass).to respond_to(:testing)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,15 @@
1
+ require 'rom/support/enumerable_dataset'
2
+
3
+ describe ROM::EnumerableDataset do
4
+ let(:klass) do
5
+ Class.new do
6
+ include ROM::EnumerableDataset
7
+
8
+ def self.row_proc
9
+ T(:symbolize_keys)
10
+ end
11
+ end
12
+ end
13
+
14
+ it_behaves_like 'an enumerable dataset'
15
+ end
@@ -0,0 +1,89 @@
1
+ require 'rom/support/inflector'
2
+
3
+ describe ROM::Inflector do
4
+ shared_examples 'an inflector' do
5
+ it 'singularises' do
6
+ expect(api.singularize('tasks')).to eq 'task'
7
+ end
8
+
9
+ it 'pluralizes' do
10
+ expect(api.pluralize('task')).to eq 'tasks'
11
+ end
12
+
13
+ it 'camelizes' do
14
+ expect(api.camelize('task_user')).to eq 'TaskUser'
15
+ end
16
+
17
+ it 'underscores' do
18
+ expect(api.underscore('TaskUser')).to eq 'task_user'
19
+ end
20
+
21
+ it 'demodulizes' do
22
+ expect(api.demodulize('Task::User')).to eq 'User'
23
+ end
24
+
25
+ it 'constantizes' do
26
+ expect(api.constantize('String')).to equal String
27
+ end
28
+
29
+ it 'classifies' do
30
+ expect(api.classify('task_user/name')).to eq 'TaskUser::Name'
31
+ end
32
+ end
33
+
34
+ subject(:api) { ROM::Inflector }
35
+
36
+ context 'with detected inflector' do
37
+ before do
38
+ if api.instance_variables.include?(:@inflector)
39
+ api.__send__(:remove_instance_variable, :@inflector)
40
+ end
41
+ end
42
+
43
+ it 'prefers ActiveSupport::Inflector' do
44
+ expect(api.inflector == ::ActiveSupport::Inflector).to be true
45
+ end
46
+ end
47
+
48
+ context 'with automatic detection' do
49
+ before do
50
+ if api.instance_variables.include?(:@inflector)
51
+ api.__send__(:remove_instance_variable, :@inflector)
52
+ end
53
+ end
54
+
55
+ it 'automatically selects an inflector backend' do
56
+ expect(api.inflector).not_to be nil
57
+ end
58
+ end
59
+
60
+ context 'with ActiveSupport::Inflector' do
61
+ before do
62
+ api.select_backend(:activesupport)
63
+ end
64
+
65
+ it 'is ActiveSupport::Inflector' do
66
+ expect(api.inflector).to be(::ActiveSupport::Inflector)
67
+ end
68
+
69
+ it_behaves_like 'an inflector'
70
+ end
71
+
72
+ context 'with Inflecto' do
73
+ before do
74
+ api.select_backend(:inflecto)
75
+ end
76
+
77
+ it 'is Inflecto' do
78
+ expect(api.inflector).to be(::Inflecto)
79
+ end
80
+
81
+ it_behaves_like 'an inflector'
82
+ end
83
+
84
+ context 'an unrecognized inflector library is selected' do
85
+ it 'raises a NameError' do
86
+ expect { api.select_backend(:foo) }.to raise_error(NameError)
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,119 @@
1
+ require 'rom/support/options'
2
+
3
+ describe ROM::Options do
4
+ let(:klass) do
5
+ Class.new do
6
+ include ROM::Options
7
+
8
+ option :name, type: String, reader: true, allow: %w(foo bar)
9
+ option :repo, reader: true
10
+ option :other
11
+ end
12
+ end
13
+
14
+ describe '.new' do
15
+ it 'works without passing a hash' do
16
+ expect { klass.new }.not_to raise_error
17
+ end
18
+
19
+ it 'sets options hash' do
20
+ object = klass.new(name: 'foo')
21
+ expect(object.options).to eql(name: 'foo')
22
+ end
23
+
24
+ it 'allows any value when :allow is not specified' do
25
+ repo = double('repo')
26
+ object = klass.new(repo: repo)
27
+ expect(object.options).to eql(repo: repo)
28
+ end
29
+
30
+ it 'sets readers for options when specified' do
31
+ object = klass.new(name: 'bar', repo: 'default')
32
+ expect(object.name).to eql('bar')
33
+ expect(object.repo).to eql('default')
34
+ expect(object).to_not respond_to(:other)
35
+ end
36
+
37
+ it 'checks option key' do
38
+ expect { klass.new(unexpected: 'foo') }
39
+ .to raise_error(ROM::Options::InvalidOptionKeyError, /:unexpected/)
40
+ end
41
+
42
+ it 'checks option type' do
43
+ expect { klass.new(name: :foo) }
44
+ .to raise_error(ROM::Options::InvalidOptionValueError, /:foo/)
45
+ end
46
+
47
+ it 'checks option value' do
48
+ expect { klass.new(name: 'invalid') }
49
+ .to raise_error(ROM::Options::InvalidOptionValueError, /invalid/)
50
+ end
51
+
52
+ it 'copies klass options to descendant' do
53
+ other = Class.new(klass).new(name: 'foo')
54
+ expect(other.options).to eql(name: 'foo')
55
+ end
56
+
57
+ it 'does not interfere with its parent`s option definitions' do
58
+ Class.new(klass) do
59
+ option :child, default: :nope
60
+ end
61
+ object = klass.new
62
+ expect(object.options).to eql({})
63
+ end
64
+
65
+ it 'sets option defaults statically' do
66
+ default_value = []
67
+ klass.option :args, default: default_value
68
+
69
+ object = klass.new
70
+
71
+ expect(object.options).to eql(args: default_value)
72
+ expect(object.options[:args]).to equal(default_value)
73
+ end
74
+
75
+ it 'sets option defaults dynamically via proc' do
76
+ klass.option :args, default: proc { |*a| a }
77
+
78
+ object = klass.new
79
+
80
+ expect(object.options).to eql(args: [object])
81
+ end
82
+
83
+ it 'allow nil as default value' do
84
+ klass.option :args, default: nil
85
+
86
+ object = klass.new
87
+
88
+ expect(object.options).to eql(args: nil)
89
+ end
90
+
91
+ it 'options are frozen' do
92
+ object = klass.new
93
+
94
+ expect { object.options[:foo] = :bar }
95
+ .to raise_error(RuntimeError, /frozen/)
96
+ end
97
+
98
+ it 'call parent`s `inherited` hook' do
99
+ m = Module.new do
100
+ def inherited(_base)
101
+ raise "hook called"
102
+ end
103
+ end
104
+ klass.extend m
105
+
106
+ expect { Class.new(klass).new }
107
+ .to raise_error(/hook called/)
108
+ end
109
+
110
+ it 'does not modify passed options' do
111
+ options = {}
112
+ klass.option :foo, default: :bar
113
+
114
+ klass.new(options)
115
+
116
+ expect(options).to eq({})
117
+ end
118
+ end
119
+ end