forecast 0.0.2
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 +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
|