arc 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/.rspec +1 -0
- data/Gemfile +21 -0
- data/Rakefile +35 -0
- data/arc.gemspec +20 -0
- data/lib/arc.rb +13 -0
- data/lib/arc/casting.rb +66 -0
- data/lib/arc/data_stores.rb +9 -0
- data/lib/arc/data_stores/abstract/arel_compatibility.rb +50 -0
- data/lib/arc/data_stores/abstract/object_definitions.rb +55 -0
- data/lib/arc/data_stores/abstract/store.rb +55 -0
- data/lib/arc/data_stores/mysql/object_definitions.rb +54 -0
- data/lib/arc/data_stores/mysql/store.rb +51 -0
- data/lib/arc/data_stores/postgres/object_definitions.rb +70 -0
- data/lib/arc/data_stores/postgres/store.rb +38 -0
- data/lib/arc/data_stores/sqlite/object_definitions.rb +51 -0
- data/lib/arc/data_stores/sqlite/store.rb +43 -0
- data/lib/arc/quoting.rb +91 -0
- data/lib/arc/reactor.rb +29 -0
- data/lib/arc/version.rb +3 -0
- data/spec/casting_spec.rb +74 -0
- data/spec/data_stores/abstract/arel_compatibility_spec.rb +63 -0
- data/spec/data_stores/abstract/object_definitions_spec.rb +75 -0
- data/spec/data_stores/abstract/store_spec.rb +59 -0
- data/spec/data_stores/integration/crud_spec.rb +79 -0
- data/spec/data_stores/integration/schema_spec.rb +47 -0
- data/spec/data_stores/mysql/store_spec.rb +11 -0
- data/spec/data_stores/postgres/store_spec.rb +11 -0
- data/spec/data_stores/sqlite/store_spec.rb +11 -0
- data/spec/data_stores_spec.rb +15 -0
- data/spec/quoting_spec.rb +97 -0
- data/spec/spec_helper.rb +81 -0
- data/spec/support/config.yml +16 -0
- data/spec/support/resources/rails.png +0 -0
- data/spec/support/schemas/drop_mysql.sql +1 -0
- data/spec/support/schemas/drop_postgres.sql +1 -0
- data/spec/support/schemas/drop_sqlite.sql +1 -0
- data/spec/support/schemas/mysql.sql +10 -0
- data/spec/support/schemas/postgres.sql +12 -0
- data/spec/support/schemas/sqlite.sql +12 -0
- metadata +105 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'arc/data_stores/abstract/object_definitions'
|
3
|
+
|
4
|
+
module Arc
|
5
|
+
module DataStores
|
6
|
+
module ObjectDefinitions
|
7
|
+
describe ObjectDefinitions do
|
8
|
+
before :each do
|
9
|
+
@store = AbstractDataStore.new ArcTest.config[:empty]
|
10
|
+
end
|
11
|
+
|
12
|
+
describe Schema do
|
13
|
+
it 'includes Collector' do
|
14
|
+
Schema.included_modules.should include(Collector)
|
15
|
+
end
|
16
|
+
it 'aliases item_names to table_names' do
|
17
|
+
schema = Schema.new nil
|
18
|
+
schema.should respond_to(:table_names)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe Table do
|
23
|
+
it 'includes Collector' do
|
24
|
+
Table.included_modules.should include(Collector)
|
25
|
+
end
|
26
|
+
it 'aliases item_names to column_names' do
|
27
|
+
table = Table.new nil, :superheros
|
28
|
+
table.should respond_to(:column_names)
|
29
|
+
end
|
30
|
+
describe '#new' do
|
31
|
+
it 'has a name' do
|
32
|
+
Table.new('superheros').name.should == 'superheros'
|
33
|
+
end
|
34
|
+
it 'sets the data store' do
|
35
|
+
Table.new('superheros', @store).instance_variable_get(:@data_store).should be(@store)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "Column#new" do
|
41
|
+
it 'sets the datastore' do
|
42
|
+
c = Column.new @store
|
43
|
+
c.instance_variable_get(:@data_store).should be(@store)
|
44
|
+
end
|
45
|
+
it "sets the column's name" do
|
46
|
+
c = Column.new nil, :name => :superheros
|
47
|
+
c.name.should == :superheros
|
48
|
+
end
|
49
|
+
it "sets the column's allows_null" do
|
50
|
+
c = Column.new nil, :allows_null => false
|
51
|
+
c.allows_null?.should == false
|
52
|
+
end
|
53
|
+
it "sets the column's default value" do
|
54
|
+
c = Column.new nil, :default => "superman"
|
55
|
+
c.default.should == "superman"
|
56
|
+
end
|
57
|
+
it "sets the column's primary_key?" do
|
58
|
+
c = Column.new nil, :primary_key => true
|
59
|
+
c.pk?.should be_true
|
60
|
+
c.primary_key?.should be_true
|
61
|
+
end
|
62
|
+
it "sets the string representation of the column's type" do
|
63
|
+
c = Column.new nil, :type => "INTEGER"
|
64
|
+
c.instance_variable_get(:@stype).should == "INTEGER"
|
65
|
+
end
|
66
|
+
it 'throws NotImplementedError when querying type' do
|
67
|
+
c = Column.new nil
|
68
|
+
->{ c.type }.should raise_error(NotImplementedError)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Arc
|
4
|
+
module DataStores
|
5
|
+
describe AbstractDataStore do
|
6
|
+
before :each do
|
7
|
+
@store = AbstractDataStore.new ArcTest.config[:empty]
|
8
|
+
@query = "omg"
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'proxies methods to the schema' do
|
12
|
+
store = AbstractDataStore.new({})
|
13
|
+
schema = ObjectDefinitions::Schema.new(store)
|
14
|
+
schema.stub(:[]).and_return(ObjectDefinitions::Table.new :fake_table)
|
15
|
+
store.stub!(:schema).and_return(schema)
|
16
|
+
store[:fake_table].should be_a(ObjectDefinitions::Table)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'includes arel compatibility' do
|
20
|
+
@store.should be_a(ArelCompatibility)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'includes quoting module' do
|
24
|
+
@store.should be_a(Arc::Quoting)
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#new' do
|
28
|
+
it 'does not define connection creation' do
|
29
|
+
store = AbstractDataStore.new ArcTest.config[:sqlite]
|
30
|
+
->{ store.send :with_store }.should raise_error(NotImplementedError)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'abstract methods throw NotImplementedError' do
|
35
|
+
it '#schema raises not implemented error' do
|
36
|
+
->{ @store.schema }.should raise_error(NotImplementedError)
|
37
|
+
end
|
38
|
+
it '#create raises not implemented error' do
|
39
|
+
->{ @store.create @query }.should raise_error(NotImplementedError)
|
40
|
+
end
|
41
|
+
it '#read raises not implemented error' do
|
42
|
+
->{ @store.read @query }.should raise_error(NotImplementedError)
|
43
|
+
end
|
44
|
+
it '#update raises not implemented error' do
|
45
|
+
->{ @store.update @query }.should raise_error(NotImplementedError)
|
46
|
+
end
|
47
|
+
it '#destroy raises not implemented error' do
|
48
|
+
->{ @store.destroy @query }.should raise_error(NotImplementedError)
|
49
|
+
end
|
50
|
+
it '#execute raises not implemented error' do
|
51
|
+
->{ @store.execute @rquery }.should raise_error(NotImplementedError)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Arc
|
4
|
+
module DataStores
|
5
|
+
describe 'The data store crud operations' do
|
6
|
+
|
7
|
+
describe '#create' do
|
8
|
+
it 'creates a new record' do
|
9
|
+
ArcTest.with_store do |store|
|
10
|
+
query = "SELECT * FROM superheros WHERE name = 'green hornet'"
|
11
|
+
result = store.read query
|
12
|
+
result.size.should == 0
|
13
|
+
|
14
|
+
store.create "INSERT INTO superheros (name) VALUES('green hornet');"
|
15
|
+
store.read(query).size.should == 1
|
16
|
+
|
17
|
+
#cleanup
|
18
|
+
store.destroy "DELETE FROM superheros where name = 'green hornet'"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'returns the record with a populated primary key' do
|
23
|
+
ArcTest.with_store do |store|
|
24
|
+
result = store.create "INSERT INTO superheros (name) VALUES('green lantern')"
|
25
|
+
result[:id].should_not be_nil
|
26
|
+
result[:name].should == 'green lantern'
|
27
|
+
#cleanup
|
28
|
+
store.destroy "DELETE FROM superheros where name = 'green lantern'"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#read' do
|
34
|
+
it 'reads existing data' do
|
35
|
+
heros = ['superman', 'batman', 'spiderman']
|
36
|
+
query = "SELECT * FROM superheros"
|
37
|
+
|
38
|
+
ArcTest.with_store do |store|
|
39
|
+
result = store.read query
|
40
|
+
result.size.should == 3
|
41
|
+
result.each do |h|
|
42
|
+
h.should be_a(Hash)
|
43
|
+
heros.should include(h[:name])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#update' do
|
50
|
+
it 'updates a record and returns the updated record' do
|
51
|
+
ArcTest.with_store do |store|
|
52
|
+
query = "UPDATE superheros SET name = 'wussy' WHERE name = 'batman'"
|
53
|
+
result = store.update query
|
54
|
+
batman = store.read "SELECT * from superheros WHERE name = 'batman'"
|
55
|
+
batman.size.should == 0
|
56
|
+
batman.should be_a(Enumerable)
|
57
|
+
batman = store.read "SELECT * from superheros WHERE name = 'wussy'"
|
58
|
+
batman.first[:name].should == 'wussy'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe '#destroy' do
|
64
|
+
it 'deletes a record' do
|
65
|
+
ArcTest.with_store do |store|
|
66
|
+
query = "SELECT * FROM superheros WHERE name = 'batman'"
|
67
|
+
batman = store.read query
|
68
|
+
batman.first[:name].should == 'batman'
|
69
|
+
store.destroy "DELETE FROM superheros WHERE name = 'batman'"
|
70
|
+
batman = store.read query
|
71
|
+
batman.size.should == 0
|
72
|
+
batman.should be_a(Enumerable)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Arc
|
4
|
+
module DataStores
|
5
|
+
module ObjectDefinitions
|
6
|
+
describe "All the Schemas!" do
|
7
|
+
it 'lists the table names' do
|
8
|
+
ArcTest.with_store do |store|
|
9
|
+
store.schema.table_names.should == [:superheros]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'provides a Table object for each table' do
|
14
|
+
ArcTest.with_store do |store|
|
15
|
+
heros = store[:superheros]
|
16
|
+
heros.should be_a(Table)
|
17
|
+
heros.column_names.should include(:id)
|
18
|
+
heros.column_names.should include(:name)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'provides a Column object for each column' do
|
23
|
+
ArcTest.with_store do |store|
|
24
|
+
heros = store[:superheros]
|
25
|
+
id = heros[:id]
|
26
|
+
id.should be_a(Column)
|
27
|
+
id.pk?.should be_true
|
28
|
+
id.allows_null?.should be_false
|
29
|
+
id.default.should be_nil
|
30
|
+
id.name.should == "id"
|
31
|
+
id.type.should == :integer
|
32
|
+
|
33
|
+
#name column
|
34
|
+
name = heros[:name]
|
35
|
+
name.should be_a(Column)
|
36
|
+
name.pk?.should be_false
|
37
|
+
name.allows_null?.should be_false
|
38
|
+
name.default.should be_nil
|
39
|
+
name.name.should == "name"
|
40
|
+
name.type.should == :varchar
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'arc/data_stores/mysql/store'
|
3
|
+
|
4
|
+
describe Arc::DataStores::MysqlDataStore do
|
5
|
+
describe "#schema" do
|
6
|
+
it 'is an instance of Arc::DataStores::MysqlSchema' do
|
7
|
+
store = Arc::DataStores::MysqlDataStore.new ArcTest.config[:mysql]
|
8
|
+
store.schema.should be_a(Arc::DataStores::ObjectDefinitions::MysqlSchema)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'arc/data_stores/postgres/store'
|
3
|
+
|
4
|
+
describe Arc::DataStores::PostgresDataStore do
|
5
|
+
describe "#schema" do
|
6
|
+
it 'is an instance of Arc::DataStores::PostgresSchema' do
|
7
|
+
store = Arc::DataStores::PostgresDataStore.new ArcTest.config[:Postgres]
|
8
|
+
store.schema.should be_a(Arc::DataStores::ObjectDefinitions::PostgresSchema)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'arc/data_stores/sqlite/store'
|
3
|
+
|
4
|
+
describe Arc::DataStores::SqliteDataStore do
|
5
|
+
describe "#schema" do
|
6
|
+
it 'is an instance of Arc::DataStores::SqliteSchema' do
|
7
|
+
store = Arc::DataStores::SqliteDataStore.new(:database => ":memory:")
|
8
|
+
store.schema.should be_a(Arc::DataStores::ObjectDefinitions::SqliteSchema)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Arc
|
4
|
+
describe DataStores do
|
5
|
+
it 'extends Q::Dispatcher' do
|
6
|
+
DataStores.should be_a(Q::Dispatcher)
|
7
|
+
end
|
8
|
+
it "specifies 'DataStore' as the constant_suffix" do
|
9
|
+
DataStores.instance_variable_get(:@constant_suffix).should == "DataStore"
|
10
|
+
end
|
11
|
+
it "specifies 'arc/data_stores/%s_data_store' as the require_pattern" do
|
12
|
+
DataStores.instance_variable_get(:@require_pattern).should == "arc/data_stores/%s/store.rb"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Arc
|
4
|
+
describe Quoting do
|
5
|
+
describe '#quote' do
|
6
|
+
def quoter
|
7
|
+
@quoter ||= Class.new { include Quoting }.new
|
8
|
+
end
|
9
|
+
it 'quotes a column name' do
|
10
|
+
quoter.quote_column('my_column').should == '"my_column"'
|
11
|
+
end
|
12
|
+
it 'quotes a table name' do
|
13
|
+
t = 'my_table'
|
14
|
+
quoter.should_receive(:quote_column_name).with(t)
|
15
|
+
quoter.quote_table(t)
|
16
|
+
end
|
17
|
+
it 'should throw an exception if it cannot find a convertible class' do
|
18
|
+
->{ quoter.quote(Class.new.new) }.should raise_error(Quoting::CannotCastValueError)
|
19
|
+
end
|
20
|
+
it 'should wrap the symbol using single quotes' do
|
21
|
+
quoter.quote(:superman).should == "'superman'"
|
22
|
+
quoter.quote('superman', Symbol).should == "'superman'"
|
23
|
+
end
|
24
|
+
it 'quotes a string' do
|
25
|
+
quoter.quote("\\").should == "'\\\\'"
|
26
|
+
quoter.quote("'").should == "''''"
|
27
|
+
quoter.quote("my%String", String).should == "'my%String'"
|
28
|
+
end
|
29
|
+
it 'quotes a date object' do
|
30
|
+
date = Date.today
|
31
|
+
fmt = '%Y-%m-%d'
|
32
|
+
quoter.quote(date).should == "'#{date.strftime fmt}'"
|
33
|
+
quoter.quote(date.strftime(fmt), Date).should == "'#{date.strftime fmt}'"
|
34
|
+
end
|
35
|
+
it 'quotes a time object' do
|
36
|
+
time = Time.now
|
37
|
+
fmt = '%Y-%m-%d %H:%M:%S'
|
38
|
+
quoter.quote(time).should == "'#{time.strftime fmt}'"
|
39
|
+
quoter.quote(time.strftime(fmt), Time).should == "'#{time.strftime fmt}'"
|
40
|
+
end
|
41
|
+
it 'quotes a fixnum' do
|
42
|
+
fixnum = 10
|
43
|
+
quoter.quote(fixnum).should == fixnum.to_s
|
44
|
+
quoter.quote(fixnum.to_s, Fixnum).should == fixnum.to_s
|
45
|
+
quoter.quote(fixnum.to_s, nil)
|
46
|
+
end
|
47
|
+
it 'quotes a bignum' do
|
48
|
+
bignum = 1<<100
|
49
|
+
quoter.quote(bignum).should == bignum.to_s
|
50
|
+
quoter.quote(bignum.to_s, Bignum).should == bignum.to_s
|
51
|
+
end
|
52
|
+
it 'quotes a float' do
|
53
|
+
float = 1.2
|
54
|
+
quoter.quote(float).should == float.to_s
|
55
|
+
quoter.quote(float, Float).should == float.to_s
|
56
|
+
end
|
57
|
+
it 'quotes a class name' do
|
58
|
+
klass = String
|
59
|
+
quoter.quote(klass).should == "'String'"
|
60
|
+
end
|
61
|
+
it 'quotes boolean values' do
|
62
|
+
quoter.quote(true, :boolean).should == "'t'"
|
63
|
+
quoter.quote(false, :boolean).should == "'f'"
|
64
|
+
end
|
65
|
+
it 'quotes an object based on column type' do
|
66
|
+
superman = "superman"
|
67
|
+
ArcTest.with_store do |store|
|
68
|
+
c = store[:superheros][:name]
|
69
|
+
quoter.quote(superman, c).should == "'superman'"
|
70
|
+
c = store[:superheros][:id]
|
71
|
+
quoter.quote(10, c).should == '10'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
it 'quotes a bigdecimal' do
|
75
|
+
big_decimal = BigDecimal.new((1 << 100).to_s)
|
76
|
+
quoter.quote(big_decimal).should == big_decimal.to_s('F')
|
77
|
+
quoter.quote(big_decimal.to_s('F'), BigDecimal).should == big_decimal.to_s('F')
|
78
|
+
end
|
79
|
+
it 'quotes nil' do
|
80
|
+
quoter.quote(nil).should == 'NULL'
|
81
|
+
quoter.quote('NULL', NilClass).should == 'NULL'
|
82
|
+
quoter.quote(nil, NilClass).should == 'NULL'
|
83
|
+
end
|
84
|
+
it 'quotes true' do
|
85
|
+
quoter.quote(true).should == "'t'"
|
86
|
+
quoter.quote('true', TrueClass).should == "'t'"
|
87
|
+
end
|
88
|
+
it 'quotes false' do
|
89
|
+
quoter.quote(false).should == "'f'"
|
90
|
+
quoter.quote('false', FalseClass).should == "'f'"
|
91
|
+
end
|
92
|
+
it 'escapes and quotes a binary string' do
|
93
|
+
quoter.quote("\0%", :binary).should == "'%00%25'"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
#generate a coverage report
|
2
|
+
require 'simplecov'
|
3
|
+
SimpleCov.start
|
4
|
+
|
5
|
+
require 'bundler/setup'
|
6
|
+
require 'rspec'
|
7
|
+
require 'arc'
|
8
|
+
require 'arel'
|
9
|
+
require 'q/resource_pool'
|
10
|
+
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.after(:all) do
|
14
|
+
ArcTest.drop
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ArcTest
|
19
|
+
DEFAULT_ADAPTER = 'sqlite'
|
20
|
+
|
21
|
+
class StoreProvider < ResourcePool
|
22
|
+
def create_resource
|
23
|
+
Arc::DataStores[ArcTest.adapter].new ArcTest.current_config
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class << self
|
28
|
+
|
29
|
+
def config_key
|
30
|
+
@config_key ||= (ENV['ARC_ENV'] ||= ArcTest::DEFAULT_ADAPTER).to_sym
|
31
|
+
end
|
32
|
+
|
33
|
+
def file_root
|
34
|
+
@file_root ||= "#{File.dirname __FILE__}/support/schemas"
|
35
|
+
end
|
36
|
+
|
37
|
+
def config
|
38
|
+
@config ||= YAML::load(File.read "#{File.dirname __FILE__}/support/config.yml").symbolize_keys!
|
39
|
+
end
|
40
|
+
|
41
|
+
def drop
|
42
|
+
return unless @schema_loaded
|
43
|
+
@drop_ddl ||= File.read "#{file_root}/drop_#{config_key}.sql"
|
44
|
+
provider.with_resource { |store| store.schema.execute_ddl @drop_ddl }
|
45
|
+
@schema_loaded = false
|
46
|
+
end
|
47
|
+
|
48
|
+
def load_schema
|
49
|
+
return if @schema_loaded
|
50
|
+
@ddl ||= File.read "#{file_root}/#{config_key}.sql"
|
51
|
+
provider.with_resource { |store| store.schema.execute_ddl @ddl }
|
52
|
+
@schema_loaded = true
|
53
|
+
end
|
54
|
+
|
55
|
+
def reset_schema
|
56
|
+
drop
|
57
|
+
load_schema
|
58
|
+
end
|
59
|
+
|
60
|
+
def with_store
|
61
|
+
reset_schema
|
62
|
+
provider.with_resource do |store|
|
63
|
+
yield store
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def current_config
|
68
|
+
config[config_key]
|
69
|
+
end
|
70
|
+
|
71
|
+
def adapter
|
72
|
+
current_config[:adapter].to_sym
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
def provider
|
77
|
+
@provider ||= StoreProvider.new nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|