housing_misc 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +125 -0
- data/app/controllers/housing_misc/application_controller.rb +11 -0
- data/app/controllers/housing_misc/cache_controller.rb +11 -0
- data/app/controllers/housing_misc/logs_controller.rb +25 -0
- data/app/controllers/housing_misc/model_reflections_controller.rb +172 -0
- data/app/controllers/housing_misc/sidekiq_queue_check_controller.rb +62 -0
- data/app/middleware/request_tracer.rb +19 -0
- data/config/routes.rb +6 -0
- data/lib/helpers/configuration.rb +27 -0
- data/lib/housing_misc.rb +31 -0
- data/lib/housing_misc/aerospike_cache_extension.rb +100 -0
- data/lib/housing_misc/api_helper.rb +239 -0
- data/lib/housing_misc/boolean_evaluator.rb +7 -0
- data/lib/housing_misc/diff_checker.rb +30 -0
- data/lib/housing_misc/distance_unit.rb +140 -0
- data/lib/housing_misc/encrypt_decrypt.rb +21 -0
- data/lib/housing_misc/engine.rb +10 -0
- data/lib/housing_misc/json_slice_render.rb +44 -0
- data/lib/housing_misc/models_reflections_helper.rb +111 -0
- data/lib/housing_misc/net_http_generic_request.rb +14 -0
- data/lib/housing_misc/railtie.rb +24 -0
- data/lib/housing_misc/slice_helper.rb +43 -0
- data/lib/housing_misc/version.rb +3 -0
- metadata +200 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
module Configuration
|
2
|
+
|
3
|
+
def configuration
|
4
|
+
yield self
|
5
|
+
end
|
6
|
+
|
7
|
+
def define_setting(name, default = nil)
|
8
|
+
class_variable_set("@@#{name}", default)
|
9
|
+
|
10
|
+
define_class_method "#{name}=" do |value|
|
11
|
+
class_variable_set("@@#{name}", value)
|
12
|
+
end
|
13
|
+
|
14
|
+
define_class_method name do
|
15
|
+
class_variable_get("@@#{name}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def define_class_method(name, &block)
|
22
|
+
(class << self; self; end).instance_eval do
|
23
|
+
define_method name, &block
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/lib/housing_misc.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
require 'helpers/configuration'
|
3
|
+
module HousingMisc
|
4
|
+
extend Configuration
|
5
|
+
|
6
|
+
define_setting :aws_access_token
|
7
|
+
define_setting :aws_access_secret
|
8
|
+
define_setting :region
|
9
|
+
define_setting :bucket
|
10
|
+
define_setting :origin
|
11
|
+
define_setting :credentials_flag
|
12
|
+
define_setting :expose_headers
|
13
|
+
define_setting :max_age
|
14
|
+
define_setting :sidekiq_custom_threshold, {}
|
15
|
+
define_setting :sidekiq_message_url, "https://hooks.slack.com/services/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"
|
16
|
+
define_setting :app_name
|
17
|
+
|
18
|
+
|
19
|
+
require 'housing_misc/api_helper'
|
20
|
+
require 'housing_misc/engine'
|
21
|
+
require 'housing_misc/slice_helper'
|
22
|
+
require 'housing_misc/json_slice_render'
|
23
|
+
require 'housing_misc/boolean_evaluator'
|
24
|
+
require 'housing_misc/railtie'
|
25
|
+
require 'housing_misc/distance_unit'
|
26
|
+
require 'housing_misc/models_reflections_helper'
|
27
|
+
require 'housing_misc/aerospike_cache_extension'
|
28
|
+
require 'housing_misc/encrypt_decrypt'
|
29
|
+
require 'housing_misc/net_http_generic_request'
|
30
|
+
require 'housing_misc/diff_checker'
|
31
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'connection_pool'
|
2
|
+
require 'active_support/cache/aerospike_store'
|
3
|
+
module HousingMisc
|
4
|
+
class AerospikeCacheExtension < ActiveSupport::Cache::AerospikeStore
|
5
|
+
READ_WRITE_BATCH_SIZE = 10
|
6
|
+
def initialize(options = {})
|
7
|
+
super
|
8
|
+
aerospike_connection_policy = ::Aerospike::ClientPolicy.new(::JSON.parse(::Figaro.env.aerospike_connection_policy).with_indifferent_access)
|
9
|
+
@conn_pool = ConnectionPool.new(size: 20) { ::Aerospike::Client.new(::JSON.parse(Housing.aerospike_hosts).collect {|host_port| host_port.join(':') }.join(','), policy: aerospike_connection_policy)}
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
def read_multi(*names)
|
14
|
+
results = {}
|
15
|
+
aerospike_cache_config = ::JSON.parse(::Figaro.env.aerospike_cache_config)
|
16
|
+
key_list = names.map {|key| Aerospike::Key.new(aerospike_cache_config['namespace'], aerospike_cache_config['set'], key) }
|
17
|
+
@conn_pool.with do |conn|
|
18
|
+
unformatted_rslt = []
|
19
|
+
key_list.each_slice(READ_WRITE_BATCH_SIZE) do |batch_names|
|
20
|
+
unformatted_rslt += conn.batch_get(batch_names)
|
21
|
+
end
|
22
|
+
names.each_with_index do |name, i|
|
23
|
+
if unformatted_rslt[i] != nil
|
24
|
+
results[name] = decrypt_data(unformatted_rslt[i].bins['cache_entry']).value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
return results
|
29
|
+
end
|
30
|
+
|
31
|
+
def fetch(key, options=nil)
|
32
|
+
if block_given?
|
33
|
+
if !options.nil? and options[:force]
|
34
|
+
result = yield
|
35
|
+
write(key, result, options)
|
36
|
+
return result
|
37
|
+
end
|
38
|
+
|
39
|
+
cached_object = read(key, options)
|
40
|
+
return cached_object if cached_object != nil
|
41
|
+
|
42
|
+
result = yield
|
43
|
+
write(key, result, options)
|
44
|
+
return result
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def fetch_multi(*names)
|
50
|
+
options = names.extract_options!
|
51
|
+
options = merged_options(options)
|
52
|
+
results = read_multi(*names, options)
|
53
|
+
names.map do |name|
|
54
|
+
results.fetch(name) do
|
55
|
+
value = yield name
|
56
|
+
write(name, value, options)
|
57
|
+
results[name] = value
|
58
|
+
end
|
59
|
+
end
|
60
|
+
results
|
61
|
+
end
|
62
|
+
|
63
|
+
def write_multi(hash)
|
64
|
+
hash.each { |key, value| write(key, value.to_json) }
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
|
69
|
+
def read_entry(key, options)
|
70
|
+
if value = internal_read_entry(key, options)
|
71
|
+
# if it is not raw it is a marshalled ActiveSupport::Cache::Entry
|
72
|
+
value = options[:raw]? ActiveSupport::Cache::Entry.new(value) : decrypt_data(value)
|
73
|
+
else
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def write_entry(key, entry, options)
|
79
|
+
options[:expiration] ||= options[:expires_in] if options[:expires_in]
|
80
|
+
options[:record_exists_action] ||= options[:unless_exist]? Aerospike::RecordExistsAction::CREATE_ONLY : Aerospike::RecordExistsAction::REPLACE
|
81
|
+
value = options[:raw]? entry.value : Marshal.dump(entry)
|
82
|
+
begin
|
83
|
+
@client.put(as_key(key, options), {options[:bin] => Zlib::Deflate.deflate(value)}, options)
|
84
|
+
rescue Aerospike::Exceptions::Aerospike => e
|
85
|
+
raise unless (e.result_code == Aerospike::ResultCode::KEY_EXISTS_ERROR)
|
86
|
+
false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
def decrypt_data(data)
|
92
|
+
begin
|
93
|
+
return nil if data == nil
|
94
|
+
return Marshal.load(Zlib::Inflate.inflate(data))
|
95
|
+
rescue Zlib::DataError => e
|
96
|
+
return nil
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,239 @@
|
|
1
|
+
module HousingMisc::ApiHelper
|
2
|
+
require 'openssl'
|
3
|
+
MOBILE_REQUEST_TIME_THRESHOLD = 300 #in seconds
|
4
|
+
|
5
|
+
def get_request_attribute_value(headers, params, key)
|
6
|
+
headers["HTTP_#{key.upcase.tr('-', '_')}"] || params[key]
|
7
|
+
end
|
8
|
+
|
9
|
+
def filter_parameters(params)
|
10
|
+
filters = Rails.application.config.filter_parameters rescue []
|
11
|
+
f = ActionDispatch::Http::ParameterFilter.new filters
|
12
|
+
return f.filter(params)
|
13
|
+
end
|
14
|
+
|
15
|
+
def is_request_internal?(request)
|
16
|
+
Rails.env != "production" || request.host.include?("internal")
|
17
|
+
end
|
18
|
+
|
19
|
+
def is_request_from_mobile?(request, params, velocity_salt)
|
20
|
+
source = get_request_attribute_value(request.headers, params, "source") || ""
|
21
|
+
time_stamp = get_request_attribute_value(request.headers, params, "ts")
|
22
|
+
signed_param = get_request_attribute_value(request.headers, params, "sp")
|
23
|
+
|
24
|
+
if time_stamp.blank? || signed_param.blank?
|
25
|
+
if ["android", "ios"].include?(source.downcase) # To be removed once all the users moves to latest app version which includes ts-sp thing
|
26
|
+
log_file ||= Logger.new("#{Rails.root}/log/app_requests_with_missing_params.log")
|
27
|
+
log_invalid_csrf_request(log_file, request, cookies, params)
|
28
|
+
return true
|
29
|
+
elsif params["app_name"] == "locon.com.datacollection" # To be removed once dc app implements ts-sp thing
|
30
|
+
log_file ||= Logger.new("#{Rails.root}/log/dc_app_requests_with_missing_params.log")
|
31
|
+
log_invalid_csrf_request(log_file, request, cookies, params)
|
32
|
+
return true
|
33
|
+
end
|
34
|
+
else
|
35
|
+
return ((Time.now.to_i - time_stamp.to_i) <= MOBILE_REQUEST_TIME_THRESHOLD) && (Digest::MD5.hexdigest(velocity_salt + time_stamp) == signed_param)
|
36
|
+
end
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
|
40
|
+
def is_request_csrf_valid?(request, cookies, params, csrf_encryption_key, velocity_salt)
|
41
|
+
if request.get? || is_request_internal?(request) || is_request_from_mobile?(request, params, velocity_salt)
|
42
|
+
return true
|
43
|
+
else
|
44
|
+
csrf_id = cookies["cuid"] || ""
|
45
|
+
csrf_token = request.headers["HTTP_X_CSRF_TOKEN_V2"]
|
46
|
+
return OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), csrf_encryption_key, csrf_id) == csrf_token
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def internal_host_check
|
51
|
+
unless is_request_internal?(request)
|
52
|
+
log_file ||= Logger.new("#{Rails.root}/log/unauthorized_api_requests.log")
|
53
|
+
log_file.info "Internal host check violated | remote_ip:#{request.remote_ip}, host:#{request.host}, port:#{request.port}, params:#{filter_parameters(params)}"
|
54
|
+
render :json => {:message => "You are not authorized to make this call"}, status: 401
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def csrf_check(csrf_encryption_key, velocity_salt)
|
59
|
+
unless is_request_csrf_valid?(request, cookies, params, csrf_encryption_key, velocity_salt)
|
60
|
+
log_file ||= Logger.new("#{Rails.root}/log/unauthorized_api_requests.log")
|
61
|
+
log_invalid_csrf_request(log_file, request, cookies, params)
|
62
|
+
render :json => {:message => "You are not authorized to make this call", :error => "CSRF_CHECK_FAILED"}, status: 401
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def log_invalid_csrf_request(log_file, request, cookies, params)
|
67
|
+
log_file.info "CSRF check violated | new_csrf_token:#{request.headers["HTTP_X_CSRF_TOKEN_V2"]} | new_csrf_id:#{cookies["cuid"]} | remote_ip:#{request.remote_ip} | host:#{request.host} | port:#{request.port} | method:#{request.method} | referrer:#{request.referrer} | User-Agent:#{request.headers["User-Agent"]} | source:#{get_request_attribute_value(request.headers, params, "source")} | time_stamp: #{get_request_attribute_value(request.headers, params, "ts")} | signed_param:#{get_request_attribute_value(request.headers, params, "sp")} | params:#{filter_parameters(params)}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def send_generic_mail(template_slug, template_version, users, base_vars, merge_vars, high_priority=false, attachments=[], deferred=false, send_time=nil, google_analytics_campaign=nil)
|
71
|
+
base_vars = base_vars.with_indifferent_access
|
72
|
+
merge_vars = merge_vars.with_indifferent_access
|
73
|
+
email_template_hash = get_email_template_from_mail_service(template_slug, template_version).with_indifferent_access
|
74
|
+
email_template_hash = add_base_vars(email_template_hash, base_vars)
|
75
|
+
if google_analytics_campaign.present?
|
76
|
+
email_template_hash[:params_json].merge!(google_analytics_campaign: google_analytics_campaign)
|
77
|
+
end
|
78
|
+
email_template_hash = add_user_emails(email_template_hash, users)
|
79
|
+
email_template_hash = add_global_merge_vars(email_template_hash, merge_vars)
|
80
|
+
email_template_hash = add_merge_vars(email_template_hash, merge_vars)
|
81
|
+
email_template_hash = add_attachments(email_template_hash, attachments)
|
82
|
+
if deferred
|
83
|
+
response = Net::HTTP.post_form( URI( "#{Housing.mail_service_url}v1/email/send_deferred" ), "email_request"=> email_template_hash.to_json, "high_priority"=>high_priority, "send_time" => send_time)
|
84
|
+
else
|
85
|
+
response = Net::HTTP.post_form( URI( "#{Housing.mail_service_url}v1/email/send" ), "email_request"=> email_template_hash.to_json, "high_priority"=>high_priority)
|
86
|
+
end
|
87
|
+
return response.code.to_i, response.body
|
88
|
+
end
|
89
|
+
|
90
|
+
def add_base_vars(email_template_hash, base_vars)
|
91
|
+
email_template_hash[:params_json].merge!(base_vars)
|
92
|
+
email_template_hash[:test_email] = Housing.mail_test_id
|
93
|
+
return email_template_hash
|
94
|
+
end
|
95
|
+
|
96
|
+
def add_user_emails(email_template_hash, users)
|
97
|
+
email_template_hash[:params_json][:users] = []
|
98
|
+
users.each do |user_email|
|
99
|
+
user_email = user_email.with_indifferent_access
|
100
|
+
email_template_hash[:params_json][:users].push({name: user_email[:name], email: user_email[:email]})
|
101
|
+
end
|
102
|
+
return email_template_hash
|
103
|
+
end
|
104
|
+
|
105
|
+
def add_global_merge_vars(email_template_hash, vars)
|
106
|
+
email_template_hash[:params_json][:global_merge_vars][:modules].each do |mod_name|
|
107
|
+
mod_name[:merge_vars].first.each do |key, value|
|
108
|
+
mod_name[:merge_vars].first[key] = vars[key] if value.nil?
|
109
|
+
end
|
110
|
+
end
|
111
|
+
return email_template_hash
|
112
|
+
end
|
113
|
+
|
114
|
+
def add_merge_vars(email_template_hash, vars)
|
115
|
+
email_template_hash[:params_json][:merge_vars] = []
|
116
|
+
email_template_hash[:params_json][:users].each do |user_email|
|
117
|
+
email = user_email[:email]
|
118
|
+
email_template_hash[:params_json][:merge_vars].push(email_template_hash[:params_json][:global_merge_vars].merge({email: email}))
|
119
|
+
end
|
120
|
+
if email_template_hash[:params_json][:global_merge_vars][:template_merge_vars].present?
|
121
|
+
email_template_hash[:params_json][:global_merge_vars][:template_merge_vars].each do |key, value|
|
122
|
+
email_template_hash[:params_json][:global_merge_vars][:template_merge_vars][key] = vars[key]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
return email_template_hash
|
126
|
+
end
|
127
|
+
|
128
|
+
def add_attachments(email_template_hash, attachments)
|
129
|
+
email_template_hash[:params_json][:attachments] = attachments if attachments.present?
|
130
|
+
return email_template_hash
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
def get_email_template_from_mail_service(template_slug, template_version)
|
135
|
+
template_name = template_slug.gsub("-","_")
|
136
|
+
uri = URI.parse("#{Housing.mail_service_url}v1/email/request_format?template_name=#{template_name}&template_version=#{template_version}")
|
137
|
+
response = (Oj.load(Net::HTTP.get(uri))["data"]) rescue {}
|
138
|
+
end
|
139
|
+
|
140
|
+
def get_past_channel_details(profiles_hash, start_datetime, end_datetime, channel, template_details)
|
141
|
+
response = {}
|
142
|
+
query_params = {
|
143
|
+
template_name: template_details[:template_name],
|
144
|
+
end_datetime: end_datetime
|
145
|
+
}
|
146
|
+
query_params[:template_version] = template_details[:template_version] if channel == "email"
|
147
|
+
|
148
|
+
profiles_date_hash = {}
|
149
|
+
profiles_hash.each do |key, value|
|
150
|
+
date = value["last_activity_time"] || start_datetime
|
151
|
+
date = Date.strptime(date.to_s,'%s').to_time.to_i
|
152
|
+
(profiles_date_hash[date] ||= []) << key
|
153
|
+
end
|
154
|
+
|
155
|
+
profiles_date_hash.each do |date, email_or_numbers|
|
156
|
+
# increasing batch size would lead to exception 414 URI too large, Please don't increase batch size
|
157
|
+
email_or_numbers.each_slice(100) do |batch|
|
158
|
+
query_params[identifier[channel.to_sym]] = batch.join(',')
|
159
|
+
query_params[:start_datetime] = date
|
160
|
+
query = query_params.to_query
|
161
|
+
url = send("#{channel}_report_url".to_sym) + query
|
162
|
+
batch_response = (Oj.load(get_api_call(url))["data"])
|
163
|
+
response.merge!(batch_response)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
response
|
167
|
+
end
|
168
|
+
|
169
|
+
def get_api_call(url, request_timeout = 15.0)
|
170
|
+
parsed_url = URI.parse(url)
|
171
|
+
http = Net::HTTP.new(parsed_url.host, parsed_url.port)
|
172
|
+
http.read_timeout = request_timeout
|
173
|
+
request = Net::HTTP::Get.new(parsed_url.request_uri)
|
174
|
+
begin
|
175
|
+
response = http.request(request)
|
176
|
+
raise "request failed :: #{response}" if response.code != "200"
|
177
|
+
request_response = response.body
|
178
|
+
rescue => exception
|
179
|
+
logging_file.error("CampaignError :: #{exception}")
|
180
|
+
raise "CampaignError for url: #{url}"
|
181
|
+
end
|
182
|
+
return request_response
|
183
|
+
end
|
184
|
+
|
185
|
+
def logging_file
|
186
|
+
@log_file ||= Logger.new("#{Rails.root}/log/campaign_errors.log")
|
187
|
+
end
|
188
|
+
|
189
|
+
def send_generic_sms(phone_number, template_name, send_time, sms_params)
|
190
|
+
post_args ={
|
191
|
+
"template_name" => template_name,
|
192
|
+
"sms_request[number]" => phone_number,
|
193
|
+
"sms_request[test_number]" => Housing.sms_test_number,
|
194
|
+
"send_time" => send_time,
|
195
|
+
"sms_request[params_json]" => sms_params.to_json
|
196
|
+
}
|
197
|
+
response = Net::HTTP.post_form( URI("#{Housing.sms_service_url}/v0/sms/send_deferred"), post_args)
|
198
|
+
end
|
199
|
+
|
200
|
+
def get_shortened_url(url)
|
201
|
+
shortened_url = ""
|
202
|
+
uri = URI.parse("#{Housing.url_shortener}/shorten")
|
203
|
+
response = Net::HTTP.post_form(uri, {url: url})
|
204
|
+
if response.code.to_i == 200
|
205
|
+
shortened_url = Housing.short_url_domain + JSON.parse(response.body)["id"]
|
206
|
+
end
|
207
|
+
shortened_url
|
208
|
+
end
|
209
|
+
|
210
|
+
def upload_log_to_s3(file_path)
|
211
|
+
s3_key = Rails.root.to_s.split('/')[-1] + '/' + file_path.split('/')[-1]
|
212
|
+
s3_client.bucket(HousingMisc.bucket).object(s3_key).upload_file(file_path)
|
213
|
+
end
|
214
|
+
|
215
|
+
private
|
216
|
+
|
217
|
+
def email_report_url
|
218
|
+
"#{Housing.mail_service_url}/api/v1/email-report?"
|
219
|
+
end
|
220
|
+
|
221
|
+
def sms_report_url
|
222
|
+
"#{Housing.sms_service_url}/v0/sms-report?"
|
223
|
+
end
|
224
|
+
|
225
|
+
def identifier
|
226
|
+
{
|
227
|
+
email: :emails,
|
228
|
+
sms: :phone_numbers
|
229
|
+
}
|
230
|
+
end
|
231
|
+
|
232
|
+
def s3_client
|
233
|
+
@s3_client ||= Aws::S3::Resource.new(
|
234
|
+
credentials: Aws::Credentials.new(HousingMisc.aws_access_token, HousingMisc.aws_access_secret),
|
235
|
+
region: HousingMisc.region
|
236
|
+
)
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
module BooleanEvaluator
|
2
|
+
def self.eval(val)
|
3
|
+
return true if val == true || val.to_s =~ (/^(true|t|yes|y|1|on)$/i)
|
4
|
+
return false if val == false || val.nil? || val.to_s =~ (/^(false|f|no|n|0|off)$/i)
|
5
|
+
raise ArgumentError.new("invalid value for Boolean: \"#{val}\"")
|
6
|
+
end
|
7
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
# DiffChecker module is used to compare diff among
|
7
|
+
# application.yml application.yml.sample and default.rb.
|
8
|
+
module DiffChecker
|
9
|
+
|
10
|
+
def self.check_diff(deployment_file_path, sample_file_path, default_rb_file)
|
11
|
+
content = YAML.safe_load(File.read(deployment_file_path))
|
12
|
+
content_sample = YAML.safe_load(File.read(sample_file_path))
|
13
|
+
raise "#{sample_file_path} cant be empty" if (content_sample.nil? || content_sample.empty?)
|
14
|
+
missing_keys = content.nil? ? content_sample.keys : content_sample.keys - content.keys
|
15
|
+
unless missing_keys.empty?
|
16
|
+
raise "Some keys are missing in application.yml, but present in application.yml.sample. missing_keys : #{missing_keys}"
|
17
|
+
end
|
18
|
+
default_content_type1 = File.readlines(default_rb_file)
|
19
|
+
.map{ |line| line.match(/Figaro.env.[a-z0-9\_\-]+/).to_s.split('.')[2]}.reject{|line| line.nil?}
|
20
|
+
default_content_type2 = File.readlines(default_rb_file)
|
21
|
+
.map{ |line| line.match(/Figaro.env[^\.\s]+/).to_s.match(/[\"\'][a-z]*[^\]]+/)
|
22
|
+
.to_s.gsub!(/\A"|"\Z/, '')}.reject{|line| line.nil?}
|
23
|
+
default_file_keys = default_content_type1 + default_content_type2
|
24
|
+
missing_keys = content.nil? ? default_file_keys : default_file_keys - content.keys
|
25
|
+
unless missing_keys.empty?
|
26
|
+
raise "Some keys are missing in application.yml, but present in default.rb. missing_keys : #{missing_keys}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,140 @@
|
|
1
|
+
METRIC_PREFIXES = {milli: 10**-3, centi: 10**-2, deci: 10**-1, '': 1, deka: 10**1, hecto: 10**2, kilo: 10**3}
|
2
|
+
IMPERIAL_UNITS = {inches: 12**-1, feet: 1, yards: 3, miles: 5280}
|
3
|
+
DIMENSIONS = {'': 1, square_: 2, cubic_: 3}
|
4
|
+
FEET_TO_METRES_CONVERSION = 0.3048
|
5
|
+
|
6
|
+
require 'active_support/inflector'
|
7
|
+
|
8
|
+
Numeric.class_eval do
|
9
|
+
DIMENSIONS.each do |dimension_prefix, dimension|
|
10
|
+
METRIC_PREFIXES.keys.each do|prefix|
|
11
|
+
define_method("#{dimension_prefix}#{prefix}metres") do
|
12
|
+
distance_ob = MetricDistance.new(self, prefix.intern, dimension, dimension_prefix)
|
13
|
+
return distance_ob
|
14
|
+
end
|
15
|
+
alias_method "#{dimension_prefix}#{prefix}metre".intern, "#{dimension_prefix}#{prefix}metres".intern
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
DIMENSIONS.each do |dimension_prefix, dimension|
|
20
|
+
IMPERIAL_UNITS.keys.each do |base_unit|
|
21
|
+
define_method("#{dimension_prefix}#{base_unit}") do
|
22
|
+
distance_ob = ImperialDistance.new(self, base_unit, dimension, dimension_prefix)
|
23
|
+
return distance_ob
|
24
|
+
end
|
25
|
+
alias_method "#{dimension_prefix}#{base_unit.to_s.singularize}".intern, "#{dimension_prefix}#{base_unit}".intern
|
26
|
+
end
|
27
|
+
alias_method "#{dimension_prefix}foot".intern, "#{dimension_prefix}feet".intern
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
class DistanceUnit < Numeric
|
33
|
+
|
34
|
+
attr_reader :dimension, :dimension_prefix
|
35
|
+
attr_accessor :value, :base_unit
|
36
|
+
|
37
|
+
def initialize value, base_unit, dimension, dimension_prefix
|
38
|
+
@value = value
|
39
|
+
@base_unit = base_unit
|
40
|
+
@dimension = dimension
|
41
|
+
@dimension_prefix = dimension_prefix
|
42
|
+
end
|
43
|
+
|
44
|
+
def format_display
|
45
|
+
display_string = ''
|
46
|
+
base_distance = self.to_metres
|
47
|
+
if base_distance.value >= 1000
|
48
|
+
display_string = "#{base_distance.to_kilometres.value.round(1)} km"
|
49
|
+
else
|
50
|
+
display_string = "#{base_distance.value.round(0)} m"
|
51
|
+
end
|
52
|
+
return display_string
|
53
|
+
end
|
54
|
+
|
55
|
+
def unit
|
56
|
+
return "#{self.dimension_prefix}#{self.base_unit}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def value
|
60
|
+
return @value.to_f
|
61
|
+
end
|
62
|
+
|
63
|
+
DIMENSIONS.each do |dimension_prefix, dimension|
|
64
|
+
METRIC_PREFIXES.each do |prefix, scale|
|
65
|
+
define_method("to_#{dimension_prefix}#{prefix}metres") do
|
66
|
+
raise "Dimension Error" if self.dimension != dimension
|
67
|
+
base_imperial = self.send("to_#{dimension_prefix}feet")
|
68
|
+
metric_value = base_imperial.value * (FEET_TO_METRES_CONVERSION ** dimension)
|
69
|
+
base_metric = MetricDistance.new(metric_value, '', dimension, dimension_prefix)
|
70
|
+
metric_distance = base_metric.send("to_#{dimension_prefix}#{prefix}metres")
|
71
|
+
end
|
72
|
+
alias_method "to_#{dimension_prefix}#{prefix}metre".intern, "to_#{dimension_prefix}#{prefix}metres".intern
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
DIMENSIONS.each do |dimension_prefix, dimension|
|
77
|
+
IMPERIAL_UNITS.each do |base_unit, scale|
|
78
|
+
define_method("to_#{dimension_prefix}#{base_unit}") do
|
79
|
+
raise "Dimension Error" if self.dimension != dimension
|
80
|
+
base_metric = self.send("to_#{dimension_prefix}metres")
|
81
|
+
imperial_value = base_metric.value * (FEET_TO_METRES_CONVERSION ** -dimension)
|
82
|
+
base_imperial = ImperialDistance.new(imperial_value, :feet, dimension, dimension_prefix)
|
83
|
+
imperial_distance = base_imperial.send("to_#{dimension_prefix}#{base_unit}")
|
84
|
+
end
|
85
|
+
alias_method "to_#{dimension_prefix}#{base_unit.to_s.singularize}".intern, "to_#{dimension_prefix}#{base_unit}".intern
|
86
|
+
end
|
87
|
+
alias_method "to_#{dimension_prefix}foot".intern, "to_#{dimension_prefix}feet".intern
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
class MetricDistance < DistanceUnit
|
93
|
+
|
94
|
+
attr_accessor :metric_prefix
|
95
|
+
|
96
|
+
def initialize value, metric_prefix, dimension, dimension_prefix
|
97
|
+
@metric_prefix = metric_prefix
|
98
|
+
base_unit = "#{metric_prefix}metres".intern
|
99
|
+
super(value, base_unit, dimension, dimension_prefix)
|
100
|
+
end
|
101
|
+
|
102
|
+
def metric_prefix= metric_prefix
|
103
|
+
self.metric_prefix = metric_prefix
|
104
|
+
self.base_unit = "#{metric_prefix}metres".intern
|
105
|
+
end
|
106
|
+
|
107
|
+
DIMENSIONS.each do |dimension_prefix, dimension|
|
108
|
+
METRIC_PREFIXES.each do |prefix, scale|
|
109
|
+
define_method("to_#{dimension_prefix}#{prefix}metres") do
|
110
|
+
raise "Dimension Error" if self.dimension != dimension
|
111
|
+
current_value = self.value
|
112
|
+
current_prefix = self.metric_prefix
|
113
|
+
new_value = current_value * ((METRIC_PREFIXES[current_prefix.intern] * (scale**-1)) ** dimension)
|
114
|
+
return MetricDistance.new(new_value, prefix.intern, dimension, dimension_prefix)
|
115
|
+
end
|
116
|
+
alias_method "to_#{dimension_prefix}#{prefix}metre".intern, "to_#{dimension_prefix}#{prefix}metres".intern
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
class ImperialDistance < DistanceUnit
|
123
|
+
|
124
|
+
DIMENSIONS.each do |dimension_prefix, dimension|
|
125
|
+
IMPERIAL_UNITS.each do |base_unit, scale|
|
126
|
+
define_method("to_#{dimension_prefix}#{base_unit}") do
|
127
|
+
raise "Dimension Error" if self.dimension != dimension
|
128
|
+
current_value = self.value
|
129
|
+
current_unit = self.base_unit
|
130
|
+
new_value = current_value * ((IMPERIAL_UNITS[current_unit.intern] * (scale**-1)) ** dimension)
|
131
|
+
return ImperialDistance.new(new_value, base_unit, dimension, dimension_prefix)
|
132
|
+
end
|
133
|
+
alias_method "to_#{dimension_prefix}#{base_unit.to_s.singularize}".intern, "to_#{dimension_prefix}#{base_unit}".intern
|
134
|
+
end
|
135
|
+
alias_method "to_#{dimension_prefix}foot".intern, "to_#{dimension_prefix}feet".intern
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
|