forecast 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.README.md.html +480 -0
- data/.gitignore +19 -0
- data/.project +11 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +32 -0
- data/Rakefile +1 -0
- data/dump.rdb +1 -0
- data/forecast.gemspec +27 -0
- data/lib/forecast/adapter.rb +204 -0
- data/lib/forecast/adapters/open_weather_map_adapter.rb +95 -0
- data/lib/forecast/adapters/open_weather_map_adapter.yml +18 -0
- data/lib/forecast/adapters/wunderground_adapter.rb +70 -0
- data/lib/forecast/adapters/yahoo_adapter.rb +90 -0
- data/lib/forecast/cache.rb +7 -0
- data/lib/forecast/collection.rb +28 -0
- data/lib/forecast/conditions.yml +12 -0
- data/lib/forecast/config.rb +74 -0
- data/lib/forecast/config.yml +6 -0
- data/lib/forecast/model.rb +71 -0
- data/lib/forecast/themes/weather_icons.yml +10 -0
- data/lib/forecast/utils.rb +32 -0
- data/lib/forecast/version.rb +3 -0
- data/lib/forecast.rb +134 -0
- data/spec/forecast_spec.rb +41 -0
- metadata +126 -0
@@ -0,0 +1,204 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'rexml/document'
|
3
|
+
require 'open-uri'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
class Forecast
|
7
|
+
module Adapter
|
8
|
+
|
9
|
+
@options = nil
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def slug
|
13
|
+
self.name.split('::').last.gsub(/Adapter$/, '').gsub(/(.)([A-Z])/,'\1_\2').downcase
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.included(base)
|
18
|
+
base.extend(ClassMethods)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.instance
|
22
|
+
provider = Forecast.config.provider
|
23
|
+
if provider.is_a?(Hash)
|
24
|
+
adapter_name = provider['adapter'].to_s
|
25
|
+
options = provider.clone
|
26
|
+
options.delete('adapter')
|
27
|
+
else
|
28
|
+
adapter_name = provider
|
29
|
+
options = {}
|
30
|
+
end
|
31
|
+
if adapter_name
|
32
|
+
adapter_classname = (adapter_name.to_s << "_adapter").split('_').collect!{ |w| w.capitalize }.join
|
33
|
+
adapter_class = Object.const_get('Forecast').const_get("Adapters").const_get(adapter_classname)
|
34
|
+
adapter_class.new(options)
|
35
|
+
else
|
36
|
+
puts 'no adapter provided'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize(options = {})
|
41
|
+
@options = options
|
42
|
+
end
|
43
|
+
|
44
|
+
def options
|
45
|
+
@options
|
46
|
+
end
|
47
|
+
|
48
|
+
def config
|
49
|
+
Forecast.config.adapters[self.class.slug] || {}
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def get_json(url, params = {})
|
55
|
+
if params.keys.count > 0
|
56
|
+
query_string = URI.encode_www_form(params)
|
57
|
+
url = url + "?" + query_string
|
58
|
+
end
|
59
|
+
resp = Net::HTTP.get_response(URI.parse(url))
|
60
|
+
data = resp.body
|
61
|
+
result = JSON.parse(data)
|
62
|
+
if result && result['cod'] != "404"
|
63
|
+
return result
|
64
|
+
end
|
65
|
+
return nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_doc(url, params = {})
|
69
|
+
if params.keys.count > 0
|
70
|
+
query_string = URI.encode_www_form(params)
|
71
|
+
url = url + "?" + query_string
|
72
|
+
end
|
73
|
+
xml_data = Net::HTTP.get_response(URI.parse(url)).body
|
74
|
+
doc = REXML::Document.new(xml_data)
|
75
|
+
return doc
|
76
|
+
end
|
77
|
+
|
78
|
+
def metric(fahrenheit)
|
79
|
+
((fahrenheit - 32) / 1.800).round()
|
80
|
+
end
|
81
|
+
|
82
|
+
def get_temp(fahrenheit)
|
83
|
+
fahrenheit = fahrenheit.to_i.round()
|
84
|
+
Forecast.config.temp_scale.to_sym == :metric ? metric(fahrenheit) : fahrenheit
|
85
|
+
end
|
86
|
+
|
87
|
+
def similar_words(first, second)
|
88
|
+
similar_words = 0.0
|
89
|
+
first_words = first.downcase.split(/\s+/)
|
90
|
+
second_words = second.downcase.split(/\s+/)
|
91
|
+
first_words.each do |first_word|
|
92
|
+
second_words.each do |second_word|
|
93
|
+
similar = 0.0
|
94
|
+
if first_word == second_word
|
95
|
+
similar = 1.0
|
96
|
+
else
|
97
|
+
l1 = levenshtein(first_word, second_word)
|
98
|
+
if l1 > 0 && l1 < 3
|
99
|
+
similar = 1.0
|
100
|
+
end
|
101
|
+
end
|
102
|
+
similar_words+= similar
|
103
|
+
end
|
104
|
+
end
|
105
|
+
count = first_words.concat(second_words).uniq.length
|
106
|
+
similarity = similar_words / count
|
107
|
+
return similarity
|
108
|
+
end
|
109
|
+
|
110
|
+
def levenshtein(first, second)
|
111
|
+
matrix = [(0..first.length).to_a]
|
112
|
+
(1..second.length).each do |j|
|
113
|
+
matrix << [j] + [0] * (first.length)
|
114
|
+
end
|
115
|
+
(1..second.length).each do |i|
|
116
|
+
(1..first.length).each do |j|
|
117
|
+
if first[j-1] == second[i-1]
|
118
|
+
matrix[i][j] = matrix[i-1][j-1]
|
119
|
+
else
|
120
|
+
matrix[i][j] = [
|
121
|
+
matrix[i-1][j],
|
122
|
+
matrix[i][j-1],
|
123
|
+
matrix[i-1][j-1],
|
124
|
+
].min + 1
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
return matrix.last.last
|
129
|
+
end
|
130
|
+
|
131
|
+
def get_condition_by_similarity(name)
|
132
|
+
conditions = Forecast.config.conditions
|
133
|
+
c = conditions.values.sort { |a, b| similar_words(name, a) <=> similar_words(name, b) }.reverse
|
134
|
+
if c.first && similar_words(name, c.first) > 0
|
135
|
+
return c.first
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def get_condition_name(match)
|
140
|
+
if match == nil
|
141
|
+
return nil
|
142
|
+
end
|
143
|
+
conditions = Forecast.config.conditions
|
144
|
+
condition = "Unknown"
|
145
|
+
if conditions.keys.include?(match)
|
146
|
+
condition = conditions[match]
|
147
|
+
elsif conditions.values.include?(match)
|
148
|
+
condition = match
|
149
|
+
end
|
150
|
+
return condition
|
151
|
+
end
|
152
|
+
|
153
|
+
def match_adapter_condition(api_condition)
|
154
|
+
match = nil
|
155
|
+
conditions = config['conditions']
|
156
|
+
if conditions != nil
|
157
|
+
conditions.each do |key, value|
|
158
|
+
# puts "match key #{api_condition} -> #{key.to_s}, #{value.to_s}"
|
159
|
+
if is_numeric?(api_condition) && key.is_a?(String)
|
160
|
+
if key.include? ".."
|
161
|
+
range = key.split(/\.{2}/)
|
162
|
+
if api_condition.to_i >= range[0].to_i && api_condition.to_i <= range[1].to_i
|
163
|
+
match = value
|
164
|
+
end
|
165
|
+
end
|
166
|
+
elsif key.to_s == api_condition.to_s
|
167
|
+
match = value;
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
return match
|
172
|
+
end
|
173
|
+
|
174
|
+
def get_condition(api_conditions)
|
175
|
+
if !api_conditions.is_a?(Array)
|
176
|
+
api_conditions = [api_conditions]
|
177
|
+
end
|
178
|
+
condition = nil
|
179
|
+
api_conditions.each do |api_condition|
|
180
|
+
match = match_adapter_condition(api_condition)
|
181
|
+
if condition == nil
|
182
|
+
condition = get_condition_by_similarity(api_condition)
|
183
|
+
end
|
184
|
+
if condition
|
185
|
+
break
|
186
|
+
end
|
187
|
+
end
|
188
|
+
get_condition_name(condition)
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
def is_numeric?(s)
|
194
|
+
begin
|
195
|
+
Float(s)
|
196
|
+
rescue
|
197
|
+
false # not numeric
|
198
|
+
else
|
199
|
+
true # numeric
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
class Forecast
|
2
|
+
module Adapters
|
3
|
+
class OpenWeatherMapAdapter
|
4
|
+
|
5
|
+
include Forecast::Adapter
|
6
|
+
|
7
|
+
def current(latitude, longitude)
|
8
|
+
forecast = nil
|
9
|
+
result = get_json(api_url('weather', latitude, longitude))
|
10
|
+
if result
|
11
|
+
forecast = Forecast.new(latitude: latitude, longitude: longitude)
|
12
|
+
forecast.date = Time.at(result['dt']).to_datetime
|
13
|
+
forecast.temp = get_temp(kelvin_to_fahrenheit(result['main']['temp']))
|
14
|
+
result['weather'].each do |obj|
|
15
|
+
condition = get_condition(obj['description'])
|
16
|
+
if condition != nil
|
17
|
+
forecast.condition = condition
|
18
|
+
break
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
return forecast
|
23
|
+
end
|
24
|
+
|
25
|
+
def hourly(latitude, longitude)
|
26
|
+
forecasts = Forecast::Collection.new
|
27
|
+
result = get_json(api_url('forecast', latitude, longitude))
|
28
|
+
if result
|
29
|
+
result['list'].each do |item|
|
30
|
+
forecast = Forecast.new(latitude: latitude, longitude:longitude)
|
31
|
+
forecast.date = Time.at(item['dt']).to_datetime
|
32
|
+
forecast.temp = get_temp(kelvin_to_fahrenheit(item['main']['temp']))
|
33
|
+
item['weather'].each do |obj|
|
34
|
+
condition = get_condition([obj['description'], obj['id']])
|
35
|
+
if condition != nil
|
36
|
+
forecast.condition = condition
|
37
|
+
break
|
38
|
+
end
|
39
|
+
end
|
40
|
+
# forecast.temp_min = item['main']['temp_min']
|
41
|
+
# forecast.temp_max = item['main']['temp_max']
|
42
|
+
forecasts << forecast
|
43
|
+
end
|
44
|
+
end
|
45
|
+
return forecasts
|
46
|
+
end
|
47
|
+
|
48
|
+
def daily(latitude, longitude)
|
49
|
+
forecasts = Forecast::Collection.new
|
50
|
+
result = get_json(api_url('forecast/daily', latitude, longitude))
|
51
|
+
result['list'].each do |item|
|
52
|
+
forecast = Forecast.new(latitude: latitude, longitude:longitude)
|
53
|
+
forecast.date = Time.at(item['dt'])
|
54
|
+
forecast.temp_min = get_temp(kelvin_to_fahrenheit(item['temp']['min']))
|
55
|
+
forecast.temp_max = get_temp(kelvin_to_fahrenheit(item['temp']['max']))
|
56
|
+
forecast.temp = (forecast.temp_min + forecast.temp_max) / 2
|
57
|
+
item['weather'].each do |obj|
|
58
|
+
condition = get_condition(obj['description'])
|
59
|
+
if condition != nil
|
60
|
+
forecast.condition = condition
|
61
|
+
break
|
62
|
+
end
|
63
|
+
end
|
64
|
+
forecasts << forecast
|
65
|
+
end
|
66
|
+
return forecasts
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def api_url(action, latitude, longitude)
|
72
|
+
url = "http://api.openweathermap.org/data/2.5/#{action}"
|
73
|
+
params = {
|
74
|
+
lat: latitude,
|
75
|
+
lon: longitude
|
76
|
+
}
|
77
|
+
if options[:api_key]
|
78
|
+
params['APPID'] = options[:api_key]
|
79
|
+
end
|
80
|
+
query_string = URI.encode_www_form(params)
|
81
|
+
return url + "?" + query_string
|
82
|
+
end
|
83
|
+
|
84
|
+
def kelvin_to_fahrenheit(kelvin)
|
85
|
+
return ((kelvin - 273.15) * 1.8000 + 32).round
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
forecast:
|
2
|
+
adapters:
|
3
|
+
open_weather_map:
|
4
|
+
conditions:
|
5
|
+
200..210: Windy
|
6
|
+
211: Storm
|
7
|
+
212..299: 'Heavy Storm'
|
8
|
+
300..399: 'Light Rain'
|
9
|
+
500: 'Light Rain'
|
10
|
+
501: 'Rain'
|
11
|
+
502..599: 'Heavy Rain'
|
12
|
+
600: 'Light Snow'
|
13
|
+
601: 'Snow'
|
14
|
+
602..699: 'Heavy Snow'
|
15
|
+
800: 'Clear'
|
16
|
+
801: 'Partly Cloudy'
|
17
|
+
802..803: 210
|
18
|
+
804..899: 'Mostly Cloudy'
|
@@ -0,0 +1,70 @@
|
|
1
|
+
class Forecast
|
2
|
+
module Adapters
|
3
|
+
class WundergroundAdapter
|
4
|
+
|
5
|
+
include Forecast::Adapter
|
6
|
+
|
7
|
+
def current(latitude, longitude)
|
8
|
+
forecast = nil
|
9
|
+
result = get_json(api_url('conditions', latitude, longitude))
|
10
|
+
if result
|
11
|
+
item = result['current_observation']
|
12
|
+
forecast = Forecast.new(latitude: latitude, longitude: longitude)
|
13
|
+
forecast.date = Time.rfc822(item['observation_time_rfc822'])
|
14
|
+
forecast.temp = get_temp(item['temp_f'])
|
15
|
+
forecast.condition = get_condition([item['weather']])
|
16
|
+
forecast.orig_condition = item['weather']
|
17
|
+
end
|
18
|
+
return forecast
|
19
|
+
end
|
20
|
+
|
21
|
+
def hourly(latitude, longitude)
|
22
|
+
forecasts = Forecast::Collection.new
|
23
|
+
result = get_json(api_url('hourly', latitude, longitude))
|
24
|
+
if result
|
25
|
+
items = result['hourly_forecast']
|
26
|
+
items.each do |item|
|
27
|
+
forecast = Forecast.new(latitude: latitude, longitude:longitude)
|
28
|
+
forecast.date = Time.at(item['FCTTIME']['epoch'].to_i).to_datetime
|
29
|
+
forecast.temp = get_temp(item['temp']['english'])
|
30
|
+
forecast.condition = get_condition([item['condition']])
|
31
|
+
forecast.orig_condition = item['condition']
|
32
|
+
forecasts << forecast
|
33
|
+
end
|
34
|
+
end
|
35
|
+
return forecasts
|
36
|
+
end
|
37
|
+
|
38
|
+
def daily(latitude, longitude)
|
39
|
+
forecasts = Forecast::Collection.new
|
40
|
+
result = get_json(api_url('forecast', latitude, longitude))
|
41
|
+
if result
|
42
|
+
items = result['forecast']['simpleforecast']['forecastday']
|
43
|
+
items.each do |item|
|
44
|
+
forecast = Forecast.new(latitude: latitude, longitude:longitude)
|
45
|
+
forecast.date = Time.at(item['date']['epoch'].to_i).to_datetime
|
46
|
+
forecast.temp_min = get_temp(item['low']['fahrenheit'])
|
47
|
+
forecast.temp_max = get_temp(item['high']['fahrenheit'])
|
48
|
+
forecast.temp = (forecast.temp_min + forecast.temp_max) / 2
|
49
|
+
forecast.condition = get_condition([item['conditions']])
|
50
|
+
forecast.orig_condition = item['conditions']
|
51
|
+
forecasts << forecast
|
52
|
+
end
|
53
|
+
end
|
54
|
+
return forecasts
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def api_url(action, latitude, longitude)
|
60
|
+
url = "http://api.wunderground.com/api/#{options['api_key']}/#{action}/q/#{latitude},#{longitude}.json"
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
|
@@ -0,0 +1,90 @@
|
|
1
|
+
class Forecast
|
2
|
+
module Adapters
|
3
|
+
class YahooAdapter
|
4
|
+
|
5
|
+
include Forecast::Adapter
|
6
|
+
|
7
|
+
URL_YQL = 'http://query.yahooapis.com/v1/public/yql'
|
8
|
+
URL_RSS = 'http://weather.yahooapis.com/forecastrss'
|
9
|
+
|
10
|
+
def current(latitude, longitude)
|
11
|
+
forecast = nil
|
12
|
+
doc = get_rss(latitude, longitude)
|
13
|
+
if doc
|
14
|
+
forecast = Forecast.new
|
15
|
+
doc.elements.each('rss/channel/item/yweather:condition') do |elem|
|
16
|
+
elem.attributes.each() do |attr|
|
17
|
+
name = attr[0]
|
18
|
+
value = attr[1]
|
19
|
+
case name
|
20
|
+
when 'date'
|
21
|
+
forecast.date = DateTime.parse(value)
|
22
|
+
when 'temp'
|
23
|
+
forecast.temp = value.to_i
|
24
|
+
when 'text'
|
25
|
+
forecast.condition = get_condition(value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
return forecast
|
31
|
+
end
|
32
|
+
|
33
|
+
def hourly(latitude, longitude)
|
34
|
+
# not supported
|
35
|
+
return []
|
36
|
+
end
|
37
|
+
|
38
|
+
def daily(latitude, longitude)
|
39
|
+
doc = get_rss(latitude, longitude)
|
40
|
+
forecasts = Forecast::Collection.new
|
41
|
+
if doc
|
42
|
+
doc.elements.each('rss/channel/item/yweather:forecast') do |elem|
|
43
|
+
forecast = Forecast.new
|
44
|
+
elem.attributes.each() do |attr|
|
45
|
+
puts 'attr' + attr.to_s
|
46
|
+
name = attr[0]
|
47
|
+
value = attr[1]
|
48
|
+
case name
|
49
|
+
when 'date'
|
50
|
+
forecast.date = DateTime.parse(value)
|
51
|
+
when 'low'
|
52
|
+
forecast.temp_min = get_temp(value)
|
53
|
+
when 'high'
|
54
|
+
forecast.temp_max = get_temp(value)
|
55
|
+
when 'text'
|
56
|
+
forecast.condition = get_condition(value)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
forecast.temp = (forecast.temp_min + forecast.temp_max) / 2
|
60
|
+
forecasts << forecast
|
61
|
+
end
|
62
|
+
end
|
63
|
+
return forecasts
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def get_woeid(latitude, longitude)
|
69
|
+
woeid = nil
|
70
|
+
query = "SELECT * FROM geo.placefinder WHERE text='#{latitude}, #{longitude}' and gflags='R'"
|
71
|
+
url = URL_YQL + "?q=" + URI::encode(query)
|
72
|
+
doc = get_doc(url)
|
73
|
+
doc.elements.each('query/results/Result/woeid') do |elem|
|
74
|
+
woeid = elem.text
|
75
|
+
end
|
76
|
+
return woeid
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_rss(latitude, longitude)
|
80
|
+
woeid = get_woeid(latitude, longitude)
|
81
|
+
if woeid
|
82
|
+
doc = get_doc(URL_RSS, {w: woeid})
|
83
|
+
return doc
|
84
|
+
end
|
85
|
+
return nil
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Forecast
|
2
|
+
class Collection < Array
|
3
|
+
def select_date(date)
|
4
|
+
result = nil
|
5
|
+
date_forecasts = self.select do |obj|
|
6
|
+
obj.date.to_date == date.to_date
|
7
|
+
end
|
8
|
+
if date_forecasts.length == 0
|
9
|
+
return nil
|
10
|
+
else
|
11
|
+
hour_forecasts = date_forecasts.select do |obj|
|
12
|
+
obj.date.hour == obj.date.hour
|
13
|
+
end
|
14
|
+
if hour_forecasts.length > 0
|
15
|
+
return hour_forecasts.first
|
16
|
+
end
|
17
|
+
return date_forecasts.first
|
18
|
+
end
|
19
|
+
return nil
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def seconds_between(date1, date2)
|
25
|
+
((Time.parse(date1.to_s) - Time.parse(date2.to_s)) / 3600).abs
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
class Forecast
|
2
|
+
class Config
|
3
|
+
|
4
|
+
attr_accessor :adapters, :provider, :temp_scale, :conditions, :cache, :themes, :theme
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
|
8
|
+
@config_file = File.dirname(File.dirname(File.dirname(__FILE__))) + "/config/forecast.yml"
|
9
|
+
|
10
|
+
self.load(File.dirname(__FILE__) + '/**/*.yml')
|
11
|
+
self.load(@config_file)
|
12
|
+
|
13
|
+
def theme
|
14
|
+
if @theme != nil
|
15
|
+
if @theme.is_a?(Hash)
|
16
|
+
return @theme
|
17
|
+
end
|
18
|
+
if themes[@theme] != nil
|
19
|
+
return themes[@theme]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
return @theme
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
def load(pattern)
|
28
|
+
Dir.glob(pattern).sort{ |a, b| a.split(/\//).length <=> b.split(/\//).length}.reverse.each do |f|
|
29
|
+
obj = YAML.load_file(f)
|
30
|
+
if obj['forecast'] != nil
|
31
|
+
obj['forecast'].each do |k, v|
|
32
|
+
if respond_to?("#{k}")
|
33
|
+
o = send("#{k}")
|
34
|
+
if o.is_a?(Hash)
|
35
|
+
v = deep_merge(o, v)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
send("#{k}=", v) if respond_to?("#{k}=")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def deep_merge(hash, other_hash, &block)
|
47
|
+
other_hash.each_pair do |k,v|
|
48
|
+
tv = hash[k]
|
49
|
+
if tv.is_a?(Hash) && v.is_a?(Hash)
|
50
|
+
hash[k] = deep_merge(tv, v, &block)
|
51
|
+
else
|
52
|
+
hash[k] = block && tv ? block.call(k, tv, v) : v
|
53
|
+
end
|
54
|
+
end
|
55
|
+
hash
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.config
|
61
|
+
@@config ||= Config.new
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.configure
|
65
|
+
yield self.config
|
66
|
+
# puts 'configured'
|
67
|
+
# if self.config.config_file != nil
|
68
|
+
# puts 'load config from file'
|
69
|
+
# self.config.load(@config_file)
|
70
|
+
# end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
class Forecast
|
2
|
+
module Model
|
3
|
+
|
4
|
+
def initialize(object_attribute_hash = {})
|
5
|
+
object_attribute_hash.map do |(k, v)|
|
6
|
+
send("#{k}=", v) if respond_to?("#{k}=")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def icon
|
11
|
+
if Forecast.config.theme.is_a? Hash
|
12
|
+
icon = Forecast.config.theme[self.condition]
|
13
|
+
return icon unless icon == nil
|
14
|
+
end
|
15
|
+
return slugify(self.condition)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.included(base)
|
19
|
+
base.extend(ClassMethods)
|
20
|
+
end
|
21
|
+
|
22
|
+
def as_json options = {}
|
23
|
+
serialized = Hash.new
|
24
|
+
if self.class.attributes != nil
|
25
|
+
self.class.attributes.each do |attribute|
|
26
|
+
serialized[attribute] = self.public_send attribute
|
27
|
+
end
|
28
|
+
end
|
29
|
+
serialized
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_json *a
|
33
|
+
as_json.to_json a
|
34
|
+
end
|
35
|
+
|
36
|
+
def from_json(json)
|
37
|
+
self.class.attributes.each do |attribute|
|
38
|
+
writer_m = "#{attribute}="
|
39
|
+
value = json[attribute.to_s]
|
40
|
+
if attribute == :date
|
41
|
+
value = DateTime.parse(value)
|
42
|
+
end
|
43
|
+
send(writer_m, value) if respond_to?(writer_m)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def slugify(string)
|
52
|
+
string.downcase.strip.gsub(' ', '-').gsub(/[^\w-]/, '')
|
53
|
+
end
|
54
|
+
|
55
|
+
module ClassMethods
|
56
|
+
|
57
|
+
@attributes = []
|
58
|
+
|
59
|
+
def attributes
|
60
|
+
@attributes
|
61
|
+
end
|
62
|
+
|
63
|
+
def attr_accessor *attrs
|
64
|
+
@attributes = Array attrs
|
65
|
+
super
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|