service_template 0.5.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.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.rubocop.yml +23 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +64 -0
- data/Gemfile +4 -0
- data/LICENSE +24 -0
- data/README.md +217 -0
- data/Rakefile +9 -0
- data/bin/service_template +5 -0
- data/lib/service_template.rb +55 -0
- data/lib/service_template/active_record_extensions/notifications_subscriber.rb +17 -0
- data/lib/service_template/active_record_extensions/seeder.rb +14 -0
- data/lib/service_template/active_record_extensions/stats.rb +37 -0
- data/lib/service_template/authentication.rb +8 -0
- data/lib/service_template/cli.rb +111 -0
- data/lib/service_template/deploy.rb +98 -0
- data/lib/service_template/gem_dependency.rb +37 -0
- data/lib/service_template/generators.rb +3 -0
- data/lib/service_template/generators/api_generator.rb +30 -0
- data/lib/service_template/generators/readme_generator.rb +47 -0
- data/lib/service_template/generators/scaffold_generator.rb +29 -0
- data/lib/service_template/generators/templates/api/app/apis/%name_tableize%_api.rb.tt +40 -0
- data/lib/service_template/generators/templates/api/app/models/%name_underscore%.rb.tt +2 -0
- data/lib/service_template/generators/templates/api/app/representers/%name_underscore%_representer.rb.tt +4 -0
- data/lib/service_template/generators/templates/api/spec/apis/%name_tableize%_api_spec.rb.tt +16 -0
- data/lib/service_template/generators/templates/api/spec/models/%name_underscore%_spec.rb.tt +9 -0
- data/lib/service_template/generators/templates/readme/README.md.tt +55 -0
- data/lib/service_template/generators/templates/readme/spec/docs/readme_spec.rb +7 -0
- data/lib/service_template/generators/templates/scaffold/.env.development.tt +9 -0
- data/lib/service_template/generators/templates/scaffold/.env.test.tt +10 -0
- data/lib/service_template/generators/templates/scaffold/.gitignore.tt +13 -0
- data/lib/service_template/generators/templates/scaffold/.rubocop.yml +24 -0
- data/lib/service_template/generators/templates/scaffold/.ruby-version.tt +1 -0
- data/lib/service_template/generators/templates/scaffold/Gemfile.tt +29 -0
- data/lib/service_template/generators/templates/scaffold/README.md +3 -0
- data/lib/service_template/generators/templates/scaffold/Rakefile +21 -0
- data/lib/service_template/generators/templates/scaffold/app.rb +19 -0
- data/lib/service_template/generators/templates/scaffold/app/apis/application_api.rb +9 -0
- data/lib/service_template/generators/templates/scaffold/app/apis/hello_api.rb.tt +10 -0
- data/lib/service_template/generators/templates/scaffold/config.ru.tt +21 -0
- data/lib/service_template/generators/templates/scaffold/config/database.yml.tt +19 -0
- data/lib/service_template/generators/templates/scaffold/config/initializers/active_record.rb +5 -0
- data/lib/service_template/generators/templates/scaffold/db/schema.rb +11 -0
- data/lib/service_template/generators/templates/scaffold/lib/.keep +0 -0
- data/lib/service_template/generators/templates/scaffold/log/.keep +0 -0
- data/lib/service_template/generators/templates/scaffold/spec/apis/hello_api_spec.rb.tt +17 -0
- data/lib/service_template/generators/templates/scaffold/spec/factories/.gitkeep +0 -0
- data/lib/service_template/generators/templates/scaffold/spec/spec_helper.rb +47 -0
- data/lib/service_template/grape_extenders.rb +30 -0
- data/lib/service_template/grape_extensions/error_formatter.rb +18 -0
- data/lib/service_template/grape_extensions/grape_helpers.rb +27 -0
- data/lib/service_template/identity.rb +45 -0
- data/lib/service_template/json_error.rb +24 -0
- data/lib/service_template/logger/log_transaction.rb +17 -0
- data/lib/service_template/logger/logger.rb +42 -0
- data/lib/service_template/logger/parseable.rb +37 -0
- data/lib/service_template/middleware/app_monitor.rb +17 -0
- data/lib/service_template/middleware/authentication.rb +32 -0
- data/lib/service_template/middleware/database_stats.rb +15 -0
- data/lib/service_template/middleware/logger.rb +67 -0
- data/lib/service_template/middleware/request_stats.rb +42 -0
- data/lib/service_template/output_formatters/entity.rb +15 -0
- data/lib/service_template/output_formatters/include_nil.rb +16 -0
- data/lib/service_template/output_formatters/json_api_representer.rb +9 -0
- data/lib/service_template/param_sanitizer.rb +30 -0
- data/lib/service_template/rspec_extensions/response_helpers.rb +46 -0
- data/lib/service_template/setup.rb +36 -0
- data/lib/service_template/sortable_api.rb +17 -0
- data/lib/service_template/stats.rb +43 -0
- data/lib/service_template/stats_d_timer.rb +26 -0
- data/lib/service_template/version.rb +45 -0
- data/lib/tasks/deploy.rake +11 -0
- data/lib/tasks/routes.rake +11 -0
- data/service_template.gemspec +42 -0
- data/spec/active_record_extensions/filter_by_hash_spec.rb +23 -0
- data/spec/active_record_extensions/seeder_spec.rb +13 -0
- data/spec/authentication_spec.rb +17 -0
- data/spec/deprecations/application_api_spec.rb +19 -0
- data/spec/deprecations/entity_spec.rb +9 -0
- data/spec/deprecations/filter_by_hash_spec.rb +9 -0
- data/spec/deprecations/napa_setup_spec.rb +52 -0
- data/spec/generators/api_generator_spec.rb +63 -0
- data/spec/generators/migration_generator_spec.rb +105 -0
- data/spec/generators/readme_generator_spec.rb +35 -0
- data/spec/generators/scaffold_generator_spec.rb +90 -0
- data/spec/grape_extenders_spec.rb +50 -0
- data/spec/grape_extensions/error_formatter_spec.rb +29 -0
- data/spec/grape_extensions/include_nil_spec.rb +23 -0
- data/spec/identity_spec.rb +50 -0
- data/spec/json_error_spec.rb +33 -0
- data/spec/logger/log_transaction_spec.rb +34 -0
- data/spec/logger/logger_spec.rb +14 -0
- data/spec/logger/parseable_spec.rb +16 -0
- data/spec/middleware/authentication_spec.rb +54 -0
- data/spec/middleware/database_stats_spec.rb +64 -0
- data/spec/middleware/request_stats_spec.rb +21 -0
- data/spec/sortable_api_spec.rb +56 -0
- data/spec/spec_helper.rb +45 -0
- data/spec/stats_d_timer_spec.rb +23 -0
- data/spec/stats_spec.rb +66 -0
- data/spec/version_spec.rb +40 -0
- data/tasks/spec.rake +9 -0
- data/tasks/version.rake +51 -0
- metadata +456 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module ServiceTemplate
|
|
2
|
+
module GrapeHelpers
|
|
3
|
+
def represent(data, with: nil, **args)
|
|
4
|
+
raise ArgumentError.new(":with option is required") if with.nil?
|
|
5
|
+
|
|
6
|
+
if data.respond_to?(:to_a)
|
|
7
|
+
return { data: data.map{ |item| with.new(item).to_hash(args) } }
|
|
8
|
+
else
|
|
9
|
+
return { data: with.new(data).to_hash(args)}
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def present_error(code, message = '', reasons={})
|
|
14
|
+
ServiceTemplate::JsonError.new(code, message, reasons)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def permitted_params(options = {})
|
|
18
|
+
options = { include_missing: false }.merge(options)
|
|
19
|
+
declared(params, options)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# extend all endpoints to include this
|
|
23
|
+
Grape::Endpoint.send :include, self if defined?(Grape)
|
|
24
|
+
# rails 4 controller concern
|
|
25
|
+
extend ActiveSupport::Concern if defined?(Rails)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module ServiceTemplate
|
|
2
|
+
class Identity
|
|
3
|
+
def self.health
|
|
4
|
+
{
|
|
5
|
+
name: name,
|
|
6
|
+
hostname: hostname,
|
|
7
|
+
revision: revision,
|
|
8
|
+
pid: pid,
|
|
9
|
+
parent_pid: parent_pid,
|
|
10
|
+
platform: platform
|
|
11
|
+
}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.name
|
|
15
|
+
ENV['SERVICE_NAME'] || 'api-service'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.hostname
|
|
19
|
+
@hostname ||= `hostname`.strip
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.revision
|
|
23
|
+
@revision ||= `git rev-parse HEAD`.strip
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.pid
|
|
27
|
+
@pid ||= Process.pid
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.parent_pid
|
|
31
|
+
@ppid ||= Process.ppid
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.platform
|
|
35
|
+
{
|
|
36
|
+
version: platform_revision,
|
|
37
|
+
name: "ServiceTemplate"
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.platform_revision
|
|
42
|
+
ServiceTemplate::VERSION
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module ServiceTemplate
|
|
2
|
+
class JsonError
|
|
3
|
+
def initialize(code, message, reasons={})
|
|
4
|
+
@code = code
|
|
5
|
+
@message = message
|
|
6
|
+
@reasons = reasons
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def to_json(options = {})
|
|
10
|
+
to_h.to_json(options)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_h
|
|
14
|
+
e = {
|
|
15
|
+
error: {
|
|
16
|
+
code: @code,
|
|
17
|
+
message: @message
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
e[:error][:reasons] = @reasons if @reasons.present?
|
|
21
|
+
e
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module ServiceTemplate
|
|
2
|
+
class LogTransaction
|
|
3
|
+
class << self
|
|
4
|
+
def id
|
|
5
|
+
Thread.current[:service_template_tid].nil? ? Thread.current[:service_template_tid] = SecureRandom.hex(10) : Thread.current[:service_template_tid]
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def id=(id)
|
|
9
|
+
Thread.current[:service_template_tid] = id
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def clear
|
|
13
|
+
Thread.current[:service_template_tid] = nil
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module ServiceTemplate
|
|
2
|
+
class Logger
|
|
3
|
+
class << self
|
|
4
|
+
def name
|
|
5
|
+
[ServiceTemplate::Identity.name, ServiceTemplate::LogTransaction.id].join('-')
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def logger=(logger)
|
|
9
|
+
@logger = logger
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def logger
|
|
13
|
+
unless @logger
|
|
14
|
+
Logging.appenders.stdout(
|
|
15
|
+
'stdout',
|
|
16
|
+
layout: Logging.layouts.json
|
|
17
|
+
)
|
|
18
|
+
Logging.appenders.file(
|
|
19
|
+
"log/#{ServiceTemplate.env}.log",
|
|
20
|
+
layout: Logging.layouts.json
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
@logger = Logging.logger["[#{name}]"]
|
|
24
|
+
@logger.add_appenders 'stdout' unless ServiceTemplate.env.test?
|
|
25
|
+
@logger.add_appenders "log/#{ServiceTemplate.env}.log"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
@logger
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def response(status, headers, body)
|
|
32
|
+
{ response:
|
|
33
|
+
{
|
|
34
|
+
status: status,
|
|
35
|
+
headers: headers,
|
|
36
|
+
response: body
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# override what is in logging gem to ALWAYS use a structured object, rather than a string
|
|
2
|
+
# original version of this: https://github.com/TwP/logging/blob/master/lib/logging/layouts/parseable.rb
|
|
3
|
+
module Logging
|
|
4
|
+
module Layouts
|
|
5
|
+
class Parseable < ::Logging::Layout
|
|
6
|
+
|
|
7
|
+
# Public: Take a given object and convert it into a format suitable for
|
|
8
|
+
# inclusion as a log message. The conversion allows the object to be more
|
|
9
|
+
# easily expressed in YAML or JSON form.
|
|
10
|
+
#
|
|
11
|
+
# If the object is an Exception, then this method will return a Hash
|
|
12
|
+
# containing the exception class name, message, and backtrace (if any).
|
|
13
|
+
#
|
|
14
|
+
# If the object is a string, wrap it in a hash.
|
|
15
|
+
#
|
|
16
|
+
# obj - The Object to format
|
|
17
|
+
#
|
|
18
|
+
# Returns the formatted Object.
|
|
19
|
+
#
|
|
20
|
+
def format_obj(obj)
|
|
21
|
+
case obj
|
|
22
|
+
when Exception
|
|
23
|
+
h = { class: obj.class.name,
|
|
24
|
+
message: obj.message }
|
|
25
|
+
h[:backtrace] = obj.backtrace if @backtrace && !obj.backtrace.nil?
|
|
26
|
+
h
|
|
27
|
+
when Time
|
|
28
|
+
iso8601_format(obj)
|
|
29
|
+
when String
|
|
30
|
+
{ text: obj }
|
|
31
|
+
else
|
|
32
|
+
obj
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end # Parseable
|
|
36
|
+
end # Layouts
|
|
37
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module ServiceTemplate
|
|
2
|
+
class Middleware
|
|
3
|
+
class AppMonitor
|
|
4
|
+
def initialize(app)
|
|
5
|
+
@app = app
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call(env)
|
|
9
|
+
if ["/health", "/health.json"].include? env['PATH_INFO']
|
|
10
|
+
[200, { 'Content-type' => 'application/json' }, [ServiceTemplate::Identity.health.to_json]]
|
|
11
|
+
else
|
|
12
|
+
@app.call(env)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module ServiceTemplate
|
|
2
|
+
class Middleware
|
|
3
|
+
class Authentication
|
|
4
|
+
def initialize(app)
|
|
5
|
+
@app = app
|
|
6
|
+
|
|
7
|
+
if ENV['HEADER_PASSWORDS']
|
|
8
|
+
@allowed_passwords = ENV['HEADER_PASSWORDS'].split(',').map { |pw| pw.strip }.freeze
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(env)
|
|
13
|
+
if authenticated_request?(env)
|
|
14
|
+
@app.call(env)
|
|
15
|
+
else
|
|
16
|
+
if @allowed_passwords
|
|
17
|
+
error_response = ServiceTemplate::JsonError.new('bad_password', 'bad password').to_json
|
|
18
|
+
else
|
|
19
|
+
error_response = ServiceTemplate::JsonError.new('not_configured', 'password not configured').to_json
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
[401, { 'Content-type' => 'application/json' }, Array.wrap(error_response)]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def authenticated_request?(env)
|
|
28
|
+
@allowed_passwords.include? env['HTTP_PASSWORD'] unless @allowed_passwords.nil?
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module ServiceTemplate
|
|
2
|
+
class Middleware
|
|
3
|
+
class DatabaseStats
|
|
4
|
+
def initialize(app)
|
|
5
|
+
@app = app
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call(env)
|
|
9
|
+
require 'service_template/active_record_extensions/notifications_subscriber'
|
|
10
|
+
status, headers, body = @app.call(env)
|
|
11
|
+
[status, headers, body]
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
require 'service_template/param_sanitizer'
|
|
2
|
+
|
|
3
|
+
module ServiceTemplate
|
|
4
|
+
class Middleware
|
|
5
|
+
class Logger
|
|
6
|
+
include ServiceTemplate::ParamSanitizer
|
|
7
|
+
|
|
8
|
+
def initialize(app)
|
|
9
|
+
@app = app
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(env)
|
|
13
|
+
# log the request
|
|
14
|
+
ServiceTemplate::Logger.logger.info format_request(env)
|
|
15
|
+
|
|
16
|
+
# process the request
|
|
17
|
+
status, headers, body = @app.call(env)
|
|
18
|
+
|
|
19
|
+
# log the response
|
|
20
|
+
ServiceTemplate::Logger.logger.debug format_response(status, headers, body)
|
|
21
|
+
|
|
22
|
+
# return the results
|
|
23
|
+
[status, headers, body]
|
|
24
|
+
ensure
|
|
25
|
+
# Clear the transaction id after each request
|
|
26
|
+
ServiceTemplate::LogTransaction.clear
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def format_request(env)
|
|
32
|
+
request = Rack::Request.new(env)
|
|
33
|
+
params = request.params
|
|
34
|
+
|
|
35
|
+
begin
|
|
36
|
+
params = JSON.parse(request.body.read) if env['CONTENT_TYPE'] == 'application/json'
|
|
37
|
+
rescue
|
|
38
|
+
# do nothing, params is already set
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
request_data = {
|
|
42
|
+
method: request.request_method,
|
|
43
|
+
path: request.path_info,
|
|
44
|
+
query: filtered_query_string(request.query_string),
|
|
45
|
+
host: ServiceTemplate::Identity.hostname,
|
|
46
|
+
pid: ServiceTemplate::Identity.pid,
|
|
47
|
+
revision: ServiceTemplate::Identity.revision,
|
|
48
|
+
params: filtered_parameters(params),
|
|
49
|
+
remote_ip: request.ip
|
|
50
|
+
}
|
|
51
|
+
request_data[:user_id] = current_user.try(:id) if defined?(current_user)
|
|
52
|
+
{ request: request_data }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def format_response(status, headers, body)
|
|
56
|
+
response_body = nil
|
|
57
|
+
begin
|
|
58
|
+
response_body = body.respond_to?(:body) ? body.body.map { |r| r } : nil
|
|
59
|
+
rescue
|
|
60
|
+
response_body = body.inspect
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
ServiceTemplate::Logger.response(status, headers, response_body)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module ServiceTemplate
|
|
2
|
+
class Middleware
|
|
3
|
+
class RequestStats
|
|
4
|
+
def initialize(app)
|
|
5
|
+
@app = app
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def normalize_path(path)
|
|
9
|
+
case
|
|
10
|
+
when path == '/'
|
|
11
|
+
'root'
|
|
12
|
+
else
|
|
13
|
+
path.start_with?('/') ? path[1..-1] : path
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call(env)
|
|
18
|
+
# Mark the request time
|
|
19
|
+
start = Time.now
|
|
20
|
+
|
|
21
|
+
# Process the request
|
|
22
|
+
status, headers, body = @app.call(env)
|
|
23
|
+
|
|
24
|
+
# Mark the response time
|
|
25
|
+
stop = Time.now
|
|
26
|
+
|
|
27
|
+
# Calculate total response time
|
|
28
|
+
response_time = (stop - start) * 1000
|
|
29
|
+
|
|
30
|
+
request = Rack::Request.new(env)
|
|
31
|
+
path = normalize_path(request.path_info)
|
|
32
|
+
|
|
33
|
+
# Emit stats to StatsD
|
|
34
|
+
ServiceTemplate::Stats.emitter.timing('response_time', response_time)
|
|
35
|
+
ServiceTemplate::Stats.emitter.timing("path.#{ServiceTemplate::Stats.path_to_key(request.request_method, path)}.response_time", response_time)
|
|
36
|
+
|
|
37
|
+
# Return the results
|
|
38
|
+
[status, headers, body]
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module ServiceTemplate
|
|
2
|
+
class Entity < Grape::Entity
|
|
3
|
+
def self.inherited(subclass)
|
|
4
|
+
ActiveSupport::Deprecation.warn 'Use of ServiceTemplate::Entity is discouraged, please transition your code to Roar representers - https://github.com/bellycard/service_template/blob/master/docs/grape_entity_to_roar.md', caller
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
format_with :to_s do |val|
|
|
8
|
+
val.to_s
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def object_type
|
|
12
|
+
object.class.name.underscore
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# include this in your representer, and you will always return all defined keys (even if their value is nil)
|
|
2
|
+
module ServiceTemplate
|
|
3
|
+
module Representable
|
|
4
|
+
module IncludeNil
|
|
5
|
+
def self.included base
|
|
6
|
+
base.extend ClassMethods
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
def property(name, options={}, &block)
|
|
11
|
+
super(name, options.merge(render_nil: true), &block)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'action_dispatch/http/filter_parameters'
|
|
2
|
+
|
|
3
|
+
module ServiceTemplate
|
|
4
|
+
module ParamSanitizer
|
|
5
|
+
include ActionDispatch::Http::FilterParameters
|
|
6
|
+
|
|
7
|
+
mattr_accessor :filter_params
|
|
8
|
+
|
|
9
|
+
KV_REGEXP = '[^&;=]+'
|
|
10
|
+
PAIR_REGEXP = %r{(#{KV_REGEXP})=(#{KV_REGEXP})}
|
|
11
|
+
|
|
12
|
+
def filter_params
|
|
13
|
+
@@filter_params || []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def parameter_filter
|
|
17
|
+
parameter_filter_for(filter_params)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def filtered_parameters(params)
|
|
21
|
+
parameter_filter.filter(params)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def filtered_query_string(query_string)
|
|
25
|
+
query_string.gsub(PAIR_REGEXP) do |_|
|
|
26
|
+
parameter_filter.filter([[$1, $2]]).first.join("=")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|