sp-duh 2.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +661 -0
  3. data/README.md +2 -0
  4. data/Rakefile +32 -0
  5. data/config/i18n/i18n.xlsx +0 -0
  6. data/config/initializers/active_record/connection_adapters_postgre_sql_adapter.rb +165 -0
  7. data/config/initializers/active_record/migration_without_transaction.rb +4 -0
  8. data/config/initializers/active_record/migrator.rb +34 -0
  9. data/config/initializers/rails/generators.rb +13 -0
  10. data/config/jsonapi/settings.yml +14 -0
  11. data/config/locales/pt.yml +15 -0
  12. data/lib/generators/accounting_migration/accounting_migration_generator.rb +10 -0
  13. data/lib/generators/accounting_migration/templates/migration.rb +42 -0
  14. data/lib/generators/accounting_payroll_migration/accounting_payroll_migration_generator.rb +10 -0
  15. data/lib/generators/accounting_payroll_migration/templates/migration.rb +73 -0
  16. data/lib/generators/sharded_migration/sharded_migration_generator.rb +10 -0
  17. data/lib/generators/sharded_migration/templates/migration.rb +45 -0
  18. data/lib/sp-duh.rb +32 -0
  19. data/lib/sp/duh.rb +180 -0
  20. data/lib/sp/duh/adapters/pg/text_decoder/json.rb +15 -0
  21. data/lib/sp/duh/adapters/pg/text_encoder/json.rb +15 -0
  22. data/lib/sp/duh/db/transfer/backup.rb +71 -0
  23. data/lib/sp/duh/db/transfer/restore.rb +89 -0
  24. data/lib/sp/duh/engine.rb +35 -0
  25. data/lib/sp/duh/exceptions.rb +70 -0
  26. data/lib/sp/duh/i18n/excel_loader.rb +26 -0
  27. data/lib/sp/duh/jsonapi/adapters/base.rb +168 -0
  28. data/lib/sp/duh/jsonapi/adapters/db.rb +36 -0
  29. data/lib/sp/duh/jsonapi/adapters/raw_db.rb +77 -0
  30. data/lib/sp/duh/jsonapi/configuration.rb +167 -0
  31. data/lib/sp/duh/jsonapi/doc/apidoc_documentation_format_generator.rb +286 -0
  32. data/lib/sp/duh/jsonapi/doc/generator.rb +32 -0
  33. data/lib/sp/duh/jsonapi/doc/schema_catalog_helper.rb +97 -0
  34. data/lib/sp/duh/jsonapi/doc/victor_pinus_metadata_format_parser.rb +374 -0
  35. data/lib/sp/duh/jsonapi/exceptions.rb +56 -0
  36. data/lib/sp/duh/jsonapi/model/base.rb +25 -0
  37. data/lib/sp/duh/jsonapi/model/concerns/attributes.rb +94 -0
  38. data/lib/sp/duh/jsonapi/model/concerns/model.rb +42 -0
  39. data/lib/sp/duh/jsonapi/model/concerns/persistence.rb +221 -0
  40. data/lib/sp/duh/jsonapi/model/concerns/serialization.rb +59 -0
  41. data/lib/sp/duh/jsonapi/parameters.rb +44 -0
  42. data/lib/sp/duh/jsonapi/resource_publisher.rb +28 -0
  43. data/lib/sp/duh/jsonapi/service.rb +110 -0
  44. data/lib/sp/duh/migrations.rb +47 -0
  45. data/lib/sp/duh/migrations/migrator.rb +41 -0
  46. data/lib/sp/duh/repl.rb +193 -0
  47. data/lib/sp/duh/version.rb +25 -0
  48. data/lib/tasks/db_utils.rake +98 -0
  49. data/lib/tasks/doc.rake +27 -0
  50. data/lib/tasks/i18n.rake +23 -0
  51. data/lib/tasks/oauth.rake +29 -0
  52. data/lib/tasks/transfer.rake +48 -0
  53. data/lib/tasks/xls2jrxml.rake +15 -0
  54. data/test/jsonapi/server.rb +67 -0
  55. data/test/tasks/test.rake +10 -0
  56. metadata +170 -0
@@ -0,0 +1,32 @@
1
+ #
2
+ # Copyright (c) 2011-2017 Cloudware S.A. All rights reserved.
3
+ #
4
+ # This file is part of sp-duh.
5
+ #
6
+ # sp-duh is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # sp-duh is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with sp-duh. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+ # encoding: utf-8
20
+ #
21
+ def require_silently(require_name)
22
+ begin
23
+ require require_name
24
+ rescue LoadError
25
+ end
26
+ end
27
+
28
+ require 'highline/import'
29
+ require_silently 'byebug'
30
+ require_silently 'awesome_print'
31
+
32
+ require File.expand_path(File.join(File.dirname(__FILE__), 'sp', 'duh'))
@@ -0,0 +1,180 @@
1
+ #
2
+ # Copyright (c) 2011-2017 Cloudware S.A. All rights reserved.
3
+ #
4
+ # This file is part of sp-duh.
5
+ #
6
+ # sp-duh is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # sp-duh is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with sp-duh. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+ # encoding: utf-8
20
+ #
21
+ require File.expand_path(File.join(File.dirname(__FILE__), 'duh', 'version'))
22
+ require File.expand_path(File.join(File.dirname(__FILE__), 'duh', 'engine'))
23
+ require File.expand_path(File.join(File.dirname(__FILE__), 'duh', 'exceptions'))
24
+ require File.expand_path(File.join(File.dirname(__FILE__), 'duh', 'repl'))
25
+
26
+ # JSONAPI library classes
27
+
28
+ require File.expand_path(File.join(File.dirname(__FILE__), 'duh', 'jsonapi', 'exceptions'))
29
+ require File.expand_path(File.join(File.dirname(__FILE__), 'duh', 'jsonapi', 'resource_publisher'))
30
+ # Parameters class
31
+ require File.expand_path(File.join(File.dirname(__FILE__), 'duh', 'jsonapi', 'parameters'))
32
+ # Service classes
33
+ require File.expand_path(File.join(File.dirname(__FILE__), 'duh', 'jsonapi', 'service'))
34
+ require File.expand_path(File.join(File.dirname(__FILE__), 'duh', 'jsonapi', 'configuration'))
35
+ # Adpater classes
36
+ require File.expand_path(File.join(File.dirname(__FILE__), 'duh', 'jsonapi', 'adapters', 'base'))
37
+ require File.expand_path(File.join(File.dirname(__FILE__), 'duh', 'jsonapi', 'adapters', 'raw_db'))
38
+ require File.expand_path(File.join(File.dirname(__FILE__), 'duh', 'jsonapi', 'adapters', 'db'))
39
+ # PG Adapters
40
+ require 'pg'
41
+ require File.expand_path(File.join(File.dirname(__FILE__), 'duh', 'adapters', 'pg', 'text_decoder', 'json'))
42
+ require File.expand_path(File.join(File.dirname(__FILE__), 'duh', 'adapters', 'pg', 'text_encoder', 'json'))
43
+
44
+ require File.expand_path(File.join(File.dirname(__FILE__), 'duh', 'jsonapi', 'model', 'base'))
45
+
46
+ # Migrations library classes
47
+ require File.expand_path(File.join(File.dirname(__FILE__), 'duh', 'migrations'))
48
+ require File.expand_path(File.join(File.dirname(__FILE__), 'duh', 'migrations', 'migrator'))
49
+
50
+ # Backup and restore (transfer) library classes
51
+ require File.expand_path(File.join(File.dirname(__FILE__), 'duh', 'db', 'transfer', 'backup'))
52
+ require File.expand_path(File.join(File.dirname(__FILE__), 'duh', 'db', 'transfer', 'restore'))
53
+
54
+ # API documentationlibrary classes
55
+ require File.expand_path(File.join(File.dirname(__FILE__), 'duh', 'jsonapi', 'doc', 'generator'))
56
+
57
+ # i18n classes
58
+ require File.expand_path(File.join(File.dirname(__FILE__), 'duh', 'i18n', 'excel_loader'))
59
+
60
+ # SP helper lib
61
+ require 'sp-excel-loader'
62
+
63
+ module SP
64
+ module Duh
65
+ def self.root
66
+ File.expand_path '../../..', __FILE__
67
+ end
68
+
69
+ def self.initsee (a_pg_conn, a_recreate = false)
70
+ if a_recreate
71
+ a_pg_conn.exec(%Q[
72
+
73
+ DROP FUNCTION IF EXISTS see (
74
+ a_module text,
75
+ a_version text,
76
+ a_query_map text,
77
+ a_calc_parameters text
78
+ );
79
+
80
+ DROP FUNCTION IF EXISTS see (
81
+ a_module text,
82
+ a_version text,
83
+ a_query_map text,
84
+ a_calc_parameters text,
85
+ a_log text,
86
+ a_debug boolean
87
+ );
88
+
89
+ DROP FUNCTION IF EXISTS see_payroll (
90
+ a_module text,
91
+ a_version text,
92
+ a_query_map text,
93
+ a_calc_parameters text,
94
+ a_clones text,
95
+ a_log text,
96
+ a_debug boolean
97
+ );
98
+
99
+ DROP FUNCTION IF EXISTS see_evaluate_expression (
100
+ a_expression text
101
+ );
102
+ DROP TYPE IF EXISTS see_record;
103
+ DROP TABLE IF EXISTS pg_see_json_table;
104
+ ]);
105
+ end
106
+ begin
107
+ a_pg_conn.exec(%Q[
108
+ CREATE TYPE see_record AS (json text, status text);
109
+ ])
110
+ rescue Exception => e
111
+ if a_recreate
112
+ raise e
113
+ end
114
+ end
115
+ begin
116
+ a_pg_conn.exec(%Q[
117
+ CREATE OR REPLACE FUNCTION see (
118
+ a_module text,
119
+ a_version text,
120
+ a_query_map text,
121
+ a_calc_parameters text,
122
+ a_log text default null,
123
+ a_debug boolean default false
124
+ ) RETURNS see_record AS '$libdir/pg-see.so', 'see' LANGUAGE C STRICT;
125
+
126
+ CREATE OR REPLACE FUNCTION see_payroll (
127
+ a_module text,
128
+ a_version text,
129
+ a_query_map text,
130
+ a_calc_parameters text,
131
+ a_clones text,
132
+ a_log text default null,
133
+ a_debug boolean default false
134
+ ) RETURNS see_record AS '$libdir/pg-see.so', 'see_payroll' LANGUAGE C STRICT;
135
+
136
+ CREATE OR REPLACE FUNCTION see_evaluate_expression (
137
+ a_expression text
138
+ ) RETURNS see_record AS '$libdir/pg-see.so', 'see_evaluate_expression' LANGUAGE C STRICT;
139
+
140
+ ])
141
+ rescue Exception => e
142
+ if a_recreate
143
+ raise e
144
+ end
145
+ end
146
+ begin
147
+ a_pg_conn.exec(%Q[
148
+ CREATE TABLE pg_see_json_table (
149
+ namespace character varying(255),
150
+ table_name character varying(255),
151
+ version character varying(20),
152
+ is_model boolean NOT NULL DEFAULT FALSE,
153
+ json text,
154
+ commit_hash character varying(255),
155
+ CONSTRAINT pg_see_json_table_pkey PRIMARY KEY(namespace, table_name, version)
156
+ );
157
+ ])
158
+ rescue => e
159
+ if a_recreate
160
+ raise e
161
+ end
162
+ end
163
+
164
+ end
165
+ end
166
+ end
167
+
168
+ def _log(message, prefix = nil)
169
+ message = message.is_a?(String) ? message : message.inspect
170
+ prefix = "SP::Duh#{prefix.blank? ? '' : ' [' + prefix + ']'}: "
171
+ if Rails.logger && !defined?(Rails::Console)
172
+ Rails.logger.debug "#{prefix}#{message}"
173
+ else
174
+ puts "#{prefix}#{message}"
175
+ end
176
+ end
177
+
178
+ # Configure the I18n module for correct usage when outside a Rails app (tests)
179
+ I18n.load_path += Dir[File.join(SP::Duh.root, 'config', 'locales', '*.{rb,yml}')]
180
+ I18n.default_locale = :pt
@@ -0,0 +1,15 @@
1
+ module SP
2
+ module Duh
3
+ module Adapters
4
+ module PG
5
+ module TextDecoder
6
+ class Json < ::PG::SimpleDecoder
7
+ def decode(string, tuple=nil, field=nil)
8
+ JSON.parse string
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module SP
2
+ module Duh
3
+ module Adapters
4
+ module PG
5
+ module TextEncoder
6
+ class Json < ::PG::SimpleEncoder
7
+ def decode(string, tuple=nil, field=nil)
8
+ JSON.parse string
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,71 @@
1
+ module SP
2
+ module Duh
3
+ module Db
4
+ module Transfer
5
+
6
+ def self.log_with_time(message)
7
+ puts "[#{Time.now.strftime('%d-%m-%Y %H:%M')}] #{message}"
8
+ end
9
+
10
+ class Backup
11
+
12
+ attr_reader :status
13
+
14
+ def initialize(source_pg_connection)
15
+ @connection = source_pg_connection
16
+ end
17
+
18
+ def execute(company_id, dump_file = nil)
19
+ self.status = :ok
20
+ @company_id = company_id
21
+ yield(:before_execute) if block_given?
22
+ before_execute
23
+ yield(:do_execute) if block_given?
24
+ do_execute(dump_file) if self.status == :ok
25
+ yield(:after_execute) if block_given?
26
+ after_execute if self.status == :ok
27
+ end
28
+
29
+ protected
30
+
31
+ def before_execute
32
+ SP::Duh::Db::Transfer.log_with_time "STARTED backing up company #{@company_id}"
33
+ SP::Duh::Db::Transfer.log_with_time "Preparing backup..."
34
+ @started_at = Time.now
35
+ @schemas = @connection.exec %Q[
36
+ SELECT * FROM transfer.backup_before_execute(#{@company_id});
37
+ ]
38
+ @schemas = @schemas.map { |result| result['schema_name'] }
39
+ end
40
+
41
+ def do_execute(dump_file = nil)
42
+ SP::Duh::Db::Transfer.log_with_time "Executing backup..."
43
+ dump_file = "#{Time.now.strftime('%Y%m%d%H%M')}_c#{@company_id}.dump" if dump_file.nil?
44
+ command = "pg_dump -Fc -O --quote-all-identifiers --data-only -n #{@schemas.join(' -n ')} -h #{@connection.host} -p #{@connection.port} -U #{@connection.user} #{@connection.db} > #{dump_file}"
45
+ SP::Duh::Db::Transfer.log_with_time command
46
+ %x[ #{command} ]
47
+ if $?.exitstatus != 0
48
+ File.delete dump_file
49
+ self.status = :error_dumping_company
50
+ end
51
+ end
52
+
53
+ def after_execute
54
+ @ended_at = Time.now
55
+ SP::Duh::Db::Transfer.log_with_time "FINISHED backing up company #{@company_id} in #{(@ended_at - @started_at).round(2)}s"
56
+ end
57
+
58
+ def status=(value)
59
+ @status = value
60
+ if value != :ok
61
+ @ended_at = Time.now
62
+ SP::Duh::Db::Transfer.log_with_time "CANCELED backing up company #{@company_id} in #{(@ended_at - @started_at).round(2)}s"
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,89 @@
1
+ module SP
2
+ module Duh
3
+ module Db
4
+ module Transfer
5
+
6
+ class Restore
7
+
8
+ def initialize(destination_pg_connection)
9
+ @connection = destination_pg_connection
10
+ end
11
+
12
+ def execute(company_id, dump_file, skip = false)
13
+
14
+ @company_id = company_id
15
+ @dump_file = dump_file
16
+
17
+ yield(:before, :before_execute) if block_given?
18
+ results = before_execute(skip)
19
+ yield(:after, :before_execute, results) if block_given?
20
+
21
+ return results if skip
22
+
23
+ yield(:before, :do_execute) if block_given?
24
+ results = do_execute
25
+ yield(:after, :do_execute, results) if block_given?
26
+
27
+ yield(:before, :after_execute) if block_given?
28
+ results = after_execute
29
+ yield(:after, :after_execute, results) if block_given?
30
+
31
+ return results
32
+
33
+ end
34
+
35
+ protected
36
+
37
+ def before_execute(skip = false)
38
+ services_configuration_file = File.join(Dir.pwd, "config", "cloudware_services.yml")
39
+ raise "Configuration file #{services_configuration_file} not found." if !File.exist?(services_configuration_file)
40
+ services_configuration = YAML.load_file(services_configuration_file)
41
+ raise "Invalid configuration in file #{services_configuration_file}." if services_configuration.nil?
42
+ @template_company_id = services_configuration[:company_id] || services_configuration['company_id']
43
+ raise "No template company id found in file #{services_configuration_file}." if @template_company_id.nil?
44
+ @template_company_id = @template_company_id.to_i
45
+ SP::Duh::Db::Transfer.log_with_time "STARTED restoring company #{@company_id} from dump #{@dump_file}"
46
+ SP::Duh::Db::Transfer.log_with_time "Preparing restore..."
47
+ @started_at = Time.now
48
+ meta_schema = @connection.exec %Q[
49
+ SELECT * FROM transfer.restore_before_before_execute(#{@company_id});
50
+ ]
51
+ meta_schema = meta_schema.first.values.first
52
+ raise "Backup file #{@dump_file} not found." if !File.exist?(@dump_file)
53
+ command = "pg_restore -Fc -n #{meta_schema} --data-only -h #{@connection.host} -p #{@connection.port} -U #{@connection.user} -d #{@connection.db} < #{@dump_file}"
54
+ SP::Duh::Db::Transfer.log_with_time "Restoring the backup metadata..."
55
+ SP::Duh::Db::Transfer.log_with_time command
56
+ result = %x[ #{command} ]
57
+ if skip
58
+ SP::Duh::Db::Transfer.log_with_time "Processing metadata..."
59
+ else
60
+ SP::Duh::Db::Transfer.log_with_time "Processing metadata and foreign records..."
61
+ end
62
+ @schemas = @connection.exec %Q[
63
+ SELECT * FROM transfer.restore_after_before_execute(#{@company_id}, #{@template_company_id}, #{skip});
64
+ ]
65
+ @schemas = @schemas.map { |result| result['schema_name'] }
66
+ end
67
+
68
+ def do_execute
69
+ SP::Duh::Db::Transfer.log_with_time "Executing restore..."
70
+ command = "pg_restore -Fc -n #{@schemas.join(' -n ')} -h #{@connection.host} -p #{@connection.port} -U #{@connection.user} -d #{@connection.db} < #{@dump_file}"
71
+ SP::Duh::Db::Transfer.log_with_time command
72
+ result = %x[ #{command} ]
73
+ end
74
+
75
+ def after_execute
76
+ SP::Duh::Db::Transfer.log_with_time "Finishing restore..."
77
+ @connection.exec %Q[
78
+ SELECT * FROM transfer.restore_after_execute(#{@company_id}, #{@template_company_id});
79
+ ]
80
+ @ended_at = Time.now
81
+ SP::Duh::Db::Transfer.log_with_time "FINISHED restoring company #{@company_id} in #{(@ended_at - @started_at).round(2)}s"
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,35 @@
1
+ #
2
+ # Copyright (c) 2011-2017 Cloudware S.A. All rights reserved.
3
+ #
4
+ # This file is part of sp-duh.
5
+ #
6
+ # sp-duh is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # sp-duh is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with sp-duh. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+ # encoding: utf-8
20
+ #
21
+ require 'rails'
22
+
23
+ module SP
24
+ module Duh
25
+ class Engine < ::Rails::Engine
26
+ isolate_namespace SP::Duh
27
+
28
+ initializer :append_migrations do |app|
29
+ unless app.root.to_s.match root.to_s
30
+ app.config.paths["db/migrate"] += config.paths["db/migrate"].expanded
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,70 @@
1
+ #
2
+ # Copyright (c) 2011-2017 Cloudware S.A. All rights reserved.
3
+ #
4
+ # This file is part of sp-duh.
5
+ #
6
+ # sp-duh is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # sp-duh is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with sp-duh. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+ # encoding: utf-8
20
+ #
21
+ module SP
22
+ module Duh
23
+ module Exceptions
24
+
25
+ # Generic errors
26
+
27
+ class GenericError < StandardError
28
+
29
+ attr_reader :nested
30
+ attr_reader :raw_backtrace
31
+
32
+ def initialize(message = nil, nested = $!)
33
+ if message.nil?
34
+ message = I18n.t("sp-duh.exceptions.#{type.underscore.gsub('/','.')}") if I18n.exists?("sp-duh.exceptions.#{type.underscore.gsub('/','.')}")
35
+ end
36
+ super(message)
37
+ @nested = nested
38
+ end
39
+
40
+ def set_backtrace(backtrace)
41
+ @raw_backtrace = backtrace
42
+ if nested
43
+ backtrace = backtrace - nested_raw_backtrace
44
+ backtrace += ["#{nested.backtrace.first}: #{nested.message} (#{nested.class.name})"]
45
+ backtrace += nested.backtrace[1..-1] || []
46
+ end
47
+ super(backtrace)
48
+ end
49
+
50
+ protected
51
+
52
+ def type ; self.class.name.sub("SP::Duh::", "").sub("Exceptions::", "") ; end
53
+
54
+ private
55
+
56
+ def nested_raw_backtrace
57
+ nested.respond_to?(:raw_backtrace) ? nested.raw_backtrace : nested.backtrace
58
+ end
59
+ end
60
+
61
+ class GenericDetailedError < GenericError
62
+ def initialize(details = {})
63
+ message = I18n.t("sp-duh.exceptions.#{type.underscore.gsub('/','.')}", details)
64
+ super(message)
65
+ end
66
+ end
67
+
68
+ end
69
+ end
70
+ end