housing_misc 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
+