pod4 0.6.2
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.
- checksums.yaml +7 -0
- data/.hgignore +18 -0
- data/.hgtags +19 -0
- data/.rspec +4 -0
- data/.ruby-version +1 -0
- data/Gemfile +3 -0
- data/LICENSE.md +21 -0
- data/README.md +556 -0
- data/Rakefile +30 -0
- data/lib/pod4/alert.rb +87 -0
- data/lib/pod4/basic_model.rb +137 -0
- data/lib/pod4/errors.rb +80 -0
- data/lib/pod4/interface.rb +110 -0
- data/lib/pod4/metaxing.rb +66 -0
- data/lib/pod4/model.rb +347 -0
- data/lib/pod4/nebulous_interface.rb +408 -0
- data/lib/pod4/null_interface.rb +148 -0
- data/lib/pod4/param.rb +29 -0
- data/lib/pod4/pg_interface.rb +460 -0
- data/lib/pod4/sequel_interface.rb +303 -0
- data/lib/pod4/tds_interface.rb +394 -0
- data/lib/pod4/version.rb +3 -0
- data/lib/pod4.rb +54 -0
- data/md/fixme.md +32 -0
- data/md/roadmap.md +69 -0
- data/pod4.gemspec +49 -0
- data/spec/README.md +19 -0
- data/spec/alert_spec.rb +173 -0
- data/spec/basic_model_spec.rb +220 -0
- data/spec/doc_no_pending.rb +5 -0
- data/spec/fixtures/database.rb +13 -0
- data/spec/model_spec.rb +760 -0
- data/spec/nebulous_interface_spec.rb +286 -0
- data/spec/null_interface_spec.rb +153 -0
- data/spec/param_spec.rb +89 -0
- data/spec/pg_interface_spec.rb +452 -0
- data/spec/pod4_spec.rb +88 -0
- data/spec/sequel_interface_spec.rb +466 -0
- data/spec/shared_examples_for_interface.rb +160 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/tds_interface_spec.rb +494 -0
- data/tags +106 -0
- metadata +316 -0
data/lib/pod4.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'devnull'
|
3
|
+
|
4
|
+
require_relative 'pod4/param'
|
5
|
+
require_relative 'pod4/basic_model'
|
6
|
+
require_relative 'pod4/model'
|
7
|
+
require_relative 'pod4/alert'
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
##
|
12
|
+
# Pod4, which:
|
13
|
+
#
|
14
|
+
# * will gather data from absolutely anything. Nebulous, Sequel, Pg,
|
15
|
+
# TinyTds, whatever. Add your own on the fly.
|
16
|
+
#
|
17
|
+
# * will allow you to define models which are genuinely represent the data
|
18
|
+
# your way, not the way the data source sees it.
|
19
|
+
#
|
20
|
+
# * is hopefully simple and clean; just a very light helper layer with the
|
21
|
+
# absolute minimum of magic or surprises for the developer.
|
22
|
+
#
|
23
|
+
# For more information:
|
24
|
+
#
|
25
|
+
# * There is a short tutorial in the readme.
|
26
|
+
#
|
27
|
+
# * you should look at the contract Pod4 makes with its
|
28
|
+
# callers -- you should find all that you need in the classes Pod4::Interface
|
29
|
+
# and Pod4::Model.
|
30
|
+
#
|
31
|
+
# * Or, read the tests, of course.
|
32
|
+
#
|
33
|
+
module Pod4
|
34
|
+
|
35
|
+
|
36
|
+
##
|
37
|
+
# If you have a logger instance, set it here to have Pod4 models and
|
38
|
+
# interfaces write to it.
|
39
|
+
#
|
40
|
+
def self.set_logger(instance)
|
41
|
+
Param.set(:logger, instance)
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
##
|
46
|
+
# Return a logger instance if you set one using set_logger.
|
47
|
+
# Otherwise, return a logger instance that points to a DevNull IO object.
|
48
|
+
#
|
49
|
+
def self.logger
|
50
|
+
Param.get(:logger) || Logger.new( DevNull.new )
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
end
|
data/md/fixme.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
Things I Wish I Could Do
|
2
|
+
========================
|
3
|
+
|
4
|
+
* TinyTds gem fails to cast date fields as date. Nothing we can do about that,
|
5
|
+
AFAICS.
|
6
|
+
|
7
|
+
* PG gem raises lots of "please cast this type explicitly" warnings for money,
|
8
|
+
numeric types. There is no documentation for how to do this, and apparently
|
9
|
+
no-one knows how /O\
|
10
|
+
|
11
|
+
|
12
|
+
Things To Do
|
13
|
+
============
|
14
|
+
|
15
|
+
* I had a note here on how SequelInterface does not support the table and
|
16
|
+
quoted_table variables. Well, these definitely aren't part of the contract:
|
17
|
+
how would NebulousInterface support them? But there might be an issue with
|
18
|
+
passing selection parameters to SequelInterface.list if the schema is set. We
|
19
|
+
need to tie down a test for that and fix it if it exists.
|
20
|
+
|
21
|
+
* PgInterface works pretty well for the PG gem, but not the pg_jruby gem. We
|
22
|
+
need to take a rather more paranoid approach to the thing; how we go about
|
23
|
+
adding test coverage for this I have literally no idea...
|
24
|
+
|
25
|
+
* TinyTDS just updated to 1.0 and ... fell over. We need to work out what's
|
26
|
+
going on there.
|
27
|
+
|
28
|
+
* Ideally interfaces should support parameterised insertion. Ideally in a
|
29
|
+
manner consistent for all interfaces...
|
30
|
+
|
31
|
+
* We should have a test suite for jRuby.
|
32
|
+
|
data/md/roadmap.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
Connection Object
|
2
|
+
=================
|
3
|
+
|
4
|
+
PgInterface and TdsInterface both take a connection Hash, which is all very
|
5
|
+
well, but it means that we are running one database connection per model.
|
6
|
+
Presumably this is a bad idea. :-)
|
7
|
+
|
8
|
+
This actually hasn't come up in my own use of Pod4 -- for complex reasons I'm
|
9
|
+
either using SequelInterface or running transient jobs which start up a couple
|
10
|
+
of models, do some work, and then stop entirely -- but it _is_ rather silly.
|
11
|
+
|
12
|
+
Connection is baked into those interfaces, and interface dependant. So I'm
|
13
|
+
thinking in terms of a memoising object that stores the connection hash and
|
14
|
+
then gets passed to the interface. When the interface wants a connection, then
|
15
|
+
it asks the connection object. If the connection object doesn't have one, then
|
16
|
+
the interface connects, and gives the connection to the connection object.
|
17
|
+
|
18
|
+
|
19
|
+
Transactions
|
20
|
+
============
|
21
|
+
|
22
|
+
We really need this, because without it we can't even pretend to be doing
|
23
|
+
proper pessimistic locking.
|
24
|
+
|
25
|
+
I've got a pretty solid idea for a nice, simple way to make this happen. It
|
26
|
+
will be in place soon.
|
27
|
+
|
28
|
+
|
29
|
+
Migrations
|
30
|
+
==========
|
31
|
+
|
32
|
+
This will almost certainly be something crude -- since we don't really control
|
33
|
+
the database connection in the same way as, say, ActiveRecord -- but I honestly
|
34
|
+
think it's a worthwhile feature. Just having something that you can version
|
35
|
+
control and run to update a data model is enough, really.
|
36
|
+
|
37
|
+
I'm not yet sure of the least useless way to implement it. Again, I favour SQL
|
38
|
+
as the DSL.
|
39
|
+
|
40
|
+
|
41
|
+
JDBC-SQL interface
|
42
|
+
==================
|
43
|
+
|
44
|
+
For the jdbc-msssqlserver gem. Doable ... I *think*.
|
45
|
+
|
46
|
+
driver = Java::com.microsoft.sqlserver.jdbc.SQLServerDriver.new
|
47
|
+
props = java.util.Properties.new
|
48
|
+
props.setProperty("user", "username")
|
49
|
+
props.setProperty("password", "password")
|
50
|
+
url = 'jdbc:sqlserver://servername;instanceName=instance;databaseName=DbName;'
|
51
|
+
|
52
|
+
conn = driver.connect(url, props)
|
53
|
+
#or maybe conn = driver.get_connection(url, "username", "password")
|
54
|
+
|
55
|
+
stmt = conn.create_statement
|
56
|
+
sql = %Q|blah;|
|
57
|
+
|
58
|
+
rsS = stmt.execute_query(sql)
|
59
|
+
|
60
|
+
while (rsS.next) do
|
61
|
+
veg = Hash.new
|
62
|
+
veg["vegName"] = rsS.getObject("name")
|
63
|
+
# etc
|
64
|
+
end
|
65
|
+
|
66
|
+
stmt.close
|
67
|
+
conn.close
|
68
|
+
|
69
|
+
see https://github.com/jruby/jruby/wiki/JDBC
|
data/pod4.gemspec
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'pod4/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "pod4"
|
7
|
+
spec.version = Pod4::VERSION
|
8
|
+
spec.authors = ["Andy Jones"]
|
9
|
+
spec.email = ["andy.jones@twosticksconsulting.co.uk"]
|
10
|
+
spec.summary = %q|Totally not an ORM|
|
11
|
+
spec.description = <<~DESC
|
12
|
+
Provides a simple, common framework to talk to a bunch of data sources,
|
13
|
+
using model classes which consist of a bare minimum of DSL plus vanilla Ruby
|
14
|
+
inheritance.
|
15
|
+
DESC
|
16
|
+
|
17
|
+
spec.homepage = "https://bitbucket.org/andy-twosticks/pod4"
|
18
|
+
spec.license = "MIT"
|
19
|
+
|
20
|
+
spec.files = `hg status -macn0`.split("\x0")
|
21
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
22
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
|
25
|
+
spec.extra_rdoc_files = spec.files.grep(%r{^md/})
|
26
|
+
|
27
|
+
spec.add_runtime_dependency "devnull", '~>0.1'
|
28
|
+
spec.add_runtime_dependency "octothorpe", '~>0.1'
|
29
|
+
|
30
|
+
# for bundler, management, etc etc
|
31
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
32
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
33
|
+
spec.add_development_dependency "rspec"
|
34
|
+
spec.add_development_dependency "rdoc"
|
35
|
+
|
36
|
+
# For testing
|
37
|
+
spec.add_development_dependency "sequel"
|
38
|
+
spec.add_development_dependency "sqlite3"
|
39
|
+
spec.add_development_dependency "tiny_tds"
|
40
|
+
spec.add_development_dependency "pg"
|
41
|
+
spec.add_development_dependency "nebulous"
|
42
|
+
|
43
|
+
# Development tools
|
44
|
+
spec.add_development_dependency "pry"
|
45
|
+
spec.add_development_dependency "pry-doc"
|
46
|
+
spec.add_development_dependency "ripper-tags"
|
47
|
+
spec.add_development_dependency "geminabox"
|
48
|
+
|
49
|
+
end
|
data/spec/README.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Some Notes On Testing
|
2
|
+
=====================
|
3
|
+
|
4
|
+
Some of the interface tests require an actual database to run.
|
5
|
+
|
6
|
+
If you want to run the whole test suite you will have to configure
|
7
|
+
spec/fixtures/database.yaml so that you can connect to both an SQL Server
|
8
|
+
database and a Postgres database.
|
9
|
+
|
10
|
+
In each case the database name you need to use is hardcoded: pod4_test. (Pod4
|
11
|
+
assumes that the schema names can be hardcoded, and since we create and wipe
|
12
|
+
tables in these tests, you should really have a seperate database for them
|
13
|
+
anyway.)
|
14
|
+
|
15
|
+
Sequel
|
16
|
+
------
|
17
|
+
Note that the Sequel ORM adapter does **not** require a database -- we
|
18
|
+
currently use an in-memory sqlite database instead.
|
19
|
+
|
data/spec/alert_spec.rb
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'pod4/alert'
|
2
|
+
|
3
|
+
|
4
|
+
describe Alert do
|
5
|
+
|
6
|
+
let(:err) { StandardError.new('whoa') }
|
7
|
+
|
8
|
+
|
9
|
+
describe '#new' do
|
10
|
+
|
11
|
+
it 'requires a type of error, warning, info or success; and a message' do
|
12
|
+
expect{ Alert.new }.to raise_exception ArgumentError
|
13
|
+
expect{ Alert.new(nil) }.to raise_exception ArgumentError
|
14
|
+
expect{ Alert.new('foo') }.to raise_exception ArgumentError
|
15
|
+
|
16
|
+
[:baz, :werning, nil, :note, :debug].each do |badType|
|
17
|
+
expect{ Alert.new(badType, 'foo') }.
|
18
|
+
to raise_exception(ArgumentError), "Alert.new(#{badType.inspect}...)"
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
[:error, :warning, :info, 'success', 'error'].each do |type|
|
23
|
+
expect{ Alert.new(type, 'foo') }.
|
24
|
+
not_to raise_exception, "Alert.new(#{type.inspect}...)"
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'allows the message to be a string' do
|
31
|
+
expect{ Alert.new(:warning, 'foo') }.not_to raise_exception
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'allows the message to be an exception' do
|
35
|
+
expect{ Alert.new(:error, err) }.not_to raise_exception
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'allows entry of a field name' do
|
39
|
+
expect{ Alert.new(:success, 'foo', 'bar') }.not_to raise_exception
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
##
|
44
|
+
|
45
|
+
|
46
|
+
describe '#type' do
|
47
|
+
|
48
|
+
it 'reflects the type passed to #new' do
|
49
|
+
expect( Alert.new(:info, 'foo').type ).to eq :info
|
50
|
+
expect( Alert.new('info', 'foo').type ).to eq :info
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
##
|
55
|
+
|
56
|
+
|
57
|
+
describe '#message' do
|
58
|
+
|
59
|
+
let(:al1) { Alert.new(:info, 'foo') }
|
60
|
+
let(:al2) { Alert.new(:info, err) }
|
61
|
+
|
62
|
+
it 'reflects the message passed to #new' do
|
63
|
+
expect( al1.message ).to eq 'foo'
|
64
|
+
expect( al2.message ).to eq 'whoa'
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'allows you to change it' do
|
68
|
+
al1.message = "one"; al2.message = "two"
|
69
|
+
|
70
|
+
expect( al1.message ).to eq 'one'
|
71
|
+
expect( al2.message ).to eq 'two'
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
##
|
76
|
+
|
77
|
+
|
78
|
+
describe '#field' do
|
79
|
+
|
80
|
+
it 'defaults to nil' do
|
81
|
+
expect( Alert.new(:error, 'baz').field ).to be_nil
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'reflects the field passed to #new' do
|
85
|
+
expect( Alert.new(:info, 'fld1', 'foo').field ).to eq :fld1
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'allows you to change it' do
|
89
|
+
al = Alert.new(:success, :boris, 'foo')
|
90
|
+
al.field = :yuri
|
91
|
+
|
92
|
+
expect( al.field ).to eq :yuri
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
##
|
97
|
+
|
98
|
+
|
99
|
+
describe '#exception' do
|
100
|
+
|
101
|
+
it 'defaults to nil' do
|
102
|
+
expect( Alert.new('warning', 'one').exception ).to be_nil
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'reflects the exception passed to #new' do
|
106
|
+
expect( Alert.new(:info, err).exception ).to eq err
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
##
|
111
|
+
|
112
|
+
|
113
|
+
describe '#log' do
|
114
|
+
|
115
|
+
after do
|
116
|
+
Pod4::Param.reset
|
117
|
+
end
|
118
|
+
|
119
|
+
let(:alert_error) { Alert.new(:error, 'error') }
|
120
|
+
let(:alert_warning) { Alert.new(:warning, 'warning') }
|
121
|
+
let(:alert_info) { Alert.new(:info, 'info') }
|
122
|
+
let(:alert_success) { Alert.new(:success, 'success') }
|
123
|
+
|
124
|
+
it 'accepts a context field' do
|
125
|
+
expect{ alert_warning.log }.not_to raise_exception
|
126
|
+
expect{ alert_warning.log('foo') }.not_to raise_exception
|
127
|
+
expect{ alert_warning.log('foo', 2) }.to raise_exception ArgumentError
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'outputs itself to the log, at the right level' do
|
131
|
+
lugger = double(Logger)
|
132
|
+
Pod4.set_logger lugger
|
133
|
+
|
134
|
+
expect(lugger).to receive(:error)
|
135
|
+
alert_error.log
|
136
|
+
|
137
|
+
expect(lugger).to receive(:warn)
|
138
|
+
alert_warning.log
|
139
|
+
|
140
|
+
expect(lugger).to receive(:info)
|
141
|
+
alert_info.log
|
142
|
+
|
143
|
+
expect(lugger).to receive(:info)
|
144
|
+
alert_success.log
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'returns self' do
|
148
|
+
expect( alert_error.log ).to eq alert_error
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
##
|
153
|
+
|
154
|
+
|
155
|
+
context 'when collected in an array' do
|
156
|
+
|
157
|
+
it 'orders itself by severity of type' do
|
158
|
+
arr = []
|
159
|
+
arr << Alert.new(:warning, 'one')
|
160
|
+
arr << Alert.new(:error, 'two')
|
161
|
+
arr << Alert.new(:info, 'three')
|
162
|
+
arr << Alert.new(:success, 'four')
|
163
|
+
|
164
|
+
expect( arr.sort.map{|a| a.type } ).
|
165
|
+
to eq([:error,:warning,:info,:success])
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
##
|
171
|
+
|
172
|
+
end
|
173
|
+
|
@@ -0,0 +1,220 @@
|
|
1
|
+
require 'octothorpe'
|
2
|
+
|
3
|
+
require 'pod4/basic_model'
|
4
|
+
require 'pod4/null_interface'
|
5
|
+
|
6
|
+
|
7
|
+
##
|
8
|
+
# We define a model class to test, since in normal operation we would never use
|
9
|
+
# Model directly, and since it needs an inner Interface.
|
10
|
+
#
|
11
|
+
# We can't use a mock for the interface -- class definitions fall outside the
|
12
|
+
# RSpec DSL as far as I can tell, so I can neither create a mock here or inject
|
13
|
+
# it. Which means we can't mock the interface in the rest of the test either;
|
14
|
+
# any mock we created would not get called.
|
15
|
+
#
|
16
|
+
# But: we want to test that Model calls Interface correctly.
|
17
|
+
#
|
18
|
+
# We do have what appears to be a perfectly sane way of testing. We can define
|
19
|
+
# an inner class based on the genuinely existing, non-mock NullInterface class;
|
20
|
+
# and then define expectations on it. When we do this, Rspec fails to pass the
|
21
|
+
# call on to the object, unless we specifically say `.and_call_original`
|
22
|
+
# instead of `.and_return`.
|
23
|
+
#
|
24
|
+
# This is actually quite nice, but more than a little confusing when you see it
|
25
|
+
# for the first time. Its use isn't spelled out in the RSpec docs AFAICS.
|
26
|
+
#
|
27
|
+
class WeirdModel < Pod4::BasicModel
|
28
|
+
set_interface NullInterface.new(:id, :name, :price, :groups, [])
|
29
|
+
|
30
|
+
def fake_an_alert(*args)
|
31
|
+
add_alert(*args) #protected method
|
32
|
+
end
|
33
|
+
|
34
|
+
def reset_alerts; @alerts = []; end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
describe 'WeirdModel' do
|
40
|
+
let(:model) { WeirdModel.new(20) }
|
41
|
+
|
42
|
+
|
43
|
+
describe 'Model.set_interface' do
|
44
|
+
it 'requires an Interface object' do
|
45
|
+
expect( WeirdModel ).to respond_to(:set_interface).with(1).argument
|
46
|
+
end
|
47
|
+
|
48
|
+
# it 'sets interface' - covered by the interface test
|
49
|
+
end
|
50
|
+
##
|
51
|
+
|
52
|
+
|
53
|
+
describe 'Model.interface' do
|
54
|
+
it 'is the interface object' do
|
55
|
+
expect( WeirdModel.interface ).to be_a_kind_of NullInterface
|
56
|
+
expect( WeirdModel.interface.id_fld ).to eq :id
|
57
|
+
end
|
58
|
+
end
|
59
|
+
##
|
60
|
+
|
61
|
+
|
62
|
+
describe '#new' do
|
63
|
+
|
64
|
+
it 'takes an optional ID' do
|
65
|
+
expect{ WeirdModel.new }.not_to raise_exception
|
66
|
+
expect{ WeirdModel.new(1) }.not_to raise_exception
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'sets the ID attribute' do
|
70
|
+
expect( WeirdModel.new(23).model_id ).to eq 23
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'sets the status to empty' do
|
74
|
+
expect( WeirdModel.new.model_status ).to eq :empty
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'initializes the alerts attribute' do
|
78
|
+
expect( WeirdModel.new.alerts ).to eq([])
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
##
|
83
|
+
|
84
|
+
|
85
|
+
describe '#interface' do
|
86
|
+
it 'returns the interface set in the class definition, again' do
|
87
|
+
expect( WeirdModel.new.interface ).to be_a_kind_of NullInterface
|
88
|
+
expect( WeirdModel.new.interface.id_fld ).to eq :id
|
89
|
+
end
|
90
|
+
end
|
91
|
+
##
|
92
|
+
|
93
|
+
|
94
|
+
describe '#alerts' do
|
95
|
+
it 'returns the list of alerts against the model' do
|
96
|
+
cm = WeirdModel.new
|
97
|
+
cm.fake_an_alert(:warning, :foo, 'one')
|
98
|
+
cm.fake_an_alert(:error, :bar, 'two')
|
99
|
+
|
100
|
+
expect( cm.alerts.size ).to eq 2
|
101
|
+
expect( cm.alerts.map{|a| a.message} ).to match_array(%w|one two|)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
##
|
105
|
+
|
106
|
+
|
107
|
+
describe '#add_alert' do
|
108
|
+
# add_alert is a protected method, which is only supposed to be called
|
109
|
+
# within the validate method of a subclass of Method. So we test it by
|
110
|
+
# calling our alert faking method
|
111
|
+
|
112
|
+
it 'requires type, message or type, field, message' do
|
113
|
+
expect{ model.fake_an_alert }.to raise_exception ArgumentError
|
114
|
+
expect{ model.fake_an_alert(nil) }.to raise_exception ArgumentError
|
115
|
+
expect{ model.fake_an_alert('foo') }.to raise_exception ArgumentError
|
116
|
+
|
117
|
+
expect{ model.fake_an_alert(:error, 'foo') }.not_to raise_exception
|
118
|
+
expect{ model.fake_an_alert(:warning, :name, 'bar') }.
|
119
|
+
not_to raise_exception
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'only allows valid types' do
|
124
|
+
[:brian, :werning, nil, :alert, :danger].each do |l|
|
125
|
+
expect{ model.fake_an_alert(l, 'foo') }.to raise_exception ArgumentError
|
126
|
+
end
|
127
|
+
|
128
|
+
[:warning, :error, :success, :info].each do |l|
|
129
|
+
expect{ model.fake_an_alert(l, 'foo') }.not_to raise_exception
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'creates an Alert and adds it to @alerts' do
|
135
|
+
lurch = 'Dnhhhhhh'
|
136
|
+
model.fake_an_alert(:error, :price, lurch)
|
137
|
+
|
138
|
+
expect( model.alerts.size ).to eq 1
|
139
|
+
expect( model.alerts.first ).to be_a_kind_of Pod4::Alert
|
140
|
+
expect( model.alerts.first.message ).to eq lurch
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'sets @model_status if the type is worse than @model_status' do
|
144
|
+
model.fake_an_alert(:warning, :price, 'xoo')
|
145
|
+
expect( model.model_status ).to eq :warning
|
146
|
+
|
147
|
+
model.fake_an_alert(:success, :price, 'flom')
|
148
|
+
expect( model.model_status ).to eq :warning
|
149
|
+
|
150
|
+
model.fake_an_alert(:info, :price, 'flom')
|
151
|
+
expect( model.model_status ).to eq :warning
|
152
|
+
|
153
|
+
model.fake_an_alert(:error, :price, 'qar')
|
154
|
+
expect( model.model_status ).to eq :error
|
155
|
+
|
156
|
+
model.fake_an_alert(:warning, :price, 'drazq')
|
157
|
+
expect( model.model_status ).to eq :error
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'ignores a new alert if identical to an existing one' do
|
161
|
+
lurch = 'Dnhhhhhh'
|
162
|
+
2.times { model.fake_an_alert(:error, :price, lurch) }
|
163
|
+
|
164
|
+
expect( model.alerts.size ).to eq 1
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
##
|
169
|
+
|
170
|
+
|
171
|
+
describe '#clear_alerts' do
|
172
|
+
before do
|
173
|
+
model.fake_an_alert(:error, "bad stuff")
|
174
|
+
model.clear_alerts
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'resets the @alerts array' do
|
178
|
+
expect( model.alerts ).to eq([])
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'sets model_status to :okay' do
|
182
|
+
expect( model.model_status ).to eq :okay
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
end
|
187
|
+
##
|
188
|
+
|
189
|
+
|
190
|
+
describe '#raise_exceptions' do
|
191
|
+
|
192
|
+
it 'is also known as .or_die' do
|
193
|
+
cm = WeirdModel.new
|
194
|
+
expect( cm.method(:raise_exceptions) ).to eq( cm.method(:or_die) )
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'raises ValidationError if model status is :error' do
|
198
|
+
model.fake_an_alert(:error, :price, 'qar')
|
199
|
+
expect{ model.raise_exceptions }.to raise_exception Pod4::ValidationError
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'does nothing if model status is not :error' do
|
203
|
+
expect{ model.raise_exceptions }.not_to raise_exception
|
204
|
+
|
205
|
+
model.fake_an_alert(:info, :price, 'qar')
|
206
|
+
expect{ model.raise_exceptions }.not_to raise_exception
|
207
|
+
|
208
|
+
model.fake_an_alert(:success, :price, 'qar')
|
209
|
+
expect{ model.raise_exceptions }.not_to raise_exception
|
210
|
+
|
211
|
+
model.fake_an_alert(:warning, :price, 'qar')
|
212
|
+
expect{ model.raise_exceptions }.not_to raise_exception
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
##
|
217
|
+
|
218
|
+
|
219
|
+
end
|
220
|
+
|