eventhub-processor 0.0.6 → 0.0.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dea7fe8b1b08a77c68d736c1c2e2e72325df218a
4
- data.tar.gz: bc476eac97e284575a684ed0cfe61850d4ea9b8f
3
+ metadata.gz: d6896414dd331b83418d8bbf1e7ed6f6efc78f35
4
+ data.tar.gz: c7b8614fc58ba0cdef80a3532d94a645564d08ee
5
5
  SHA512:
6
- metadata.gz: dcce6e241aefab6b054f79d942969722fa04ea9162e86fbcefce05e396d39c69062785ae0a2eca6409dd4e0864d604bbf84092f977f80d7a7068b531cc0c044b
7
- data.tar.gz: be828faa80aaa01901a29918413f265956835686b5120402726eec913eded92203fe57760f1b4f744da63ebbc9f307efe22175c4382662ab6a4322c0d9ee5d09
6
+ metadata.gz: 46bc0b39fb711bab9835d507bd6bb1410a154d262c0a25fc9239eda0f7bdf79e70480adba332b5e6f4201b9e31ac293ac7861525ea4689807dc8b991203d3916
7
+ data.tar.gz: b67fe54f620f45b063721dd6510258b520ea5ec37c7edc83f8e4bbf2ed343588c864726a0f3bb2f6052b3b3a936840b4fb377856a1ae7fa31d4e3bd9ec480f26
@@ -4,18 +4,21 @@ module EventHub
4
4
  include Singleton
5
5
  include Helper
6
6
 
7
- attr_accessor :data
7
+ attr_accessor :data, :folder, :environment
8
8
 
9
9
  def initialize
10
10
  @data = nil
11
+ @folder = Dir.pwd
12
+ @environment = 'development'
11
13
  end
12
14
 
13
15
  def load_file(input, env='development')
14
16
  tmp = JSON.parse( IO.read(input))
15
17
  @data = tmp[env]
18
+ @environment = env
16
19
  true
17
20
  rescue => e
18
- EventHub.logger.info("Unexpected exception while loading configuration [#{input}]: #{format_raw_string(e.message)}")
21
+ EventHub.logger.info("Unexpected exception while loading configuration [#{input}]: #{format_string(e.message)}")
19
22
  false
20
23
  end
21
24
 
@@ -1,11 +1,11 @@
1
1
  module EventHub
2
2
 
3
- EVENT_HUB_QUEUE_INBOUND = 'inbound'
3
+ EH_X_INBOUND = 'event_hub.inbound'
4
4
 
5
5
  STATUS_INITIAL = 0 # initial status code
6
6
  STATUS_SUCCESS = 200 # compare with HTTP Status Code: Success
7
7
  STATUS_RETRIED = 300 # compare with HTTP Status Code: Multiple Choices
8
8
  STATUS_INVALID = 400 # compare with HTTP Status Code: Bad request
9
9
  STATUS_UNDELIVERABLE = 500 # comapre with HTTP Status Code: Server Error
10
-
10
+ STATUS_ERROR = 600 # Business Process Error
11
11
  end
@@ -0,0 +1,48 @@
1
+ module HashExtensions
2
+
3
+ module ClassMethods
4
+ end
5
+
6
+ module InstanceMethods
7
+ # get value from provided key path, e.g. hash.get(%w(event_hub plate.queue1 retry_s))
8
+ # "a" => { "b" => { "c" => { "value"}}}
9
+ def get(arg)
10
+ path = arg.is_a?(String) ? arg.split('.') : arg
11
+ path.inject(self,:[])
12
+ rescue NoMethodError
13
+ return nil
14
+ end
15
+
16
+ # set value from provided key path, e.h. hash.set('a.b.c','new value')
17
+ # if overwrite is false, value will be set if it was nil previously
18
+ def set(arg,value,overwrite=true)
19
+ *key_path, last = arg.is_a?(String) ? arg.split(".") : arg
20
+ if overwrite
21
+ key_path.inject(self) { |h,key| h.has_key?(key) ? h[key] : h[key]={}} [last] = value
22
+ else
23
+ key_path.inject(self) { |h,key| h.has_key?(key) ? h[key] : h[key]={}} [last] ||= value
24
+ end
25
+ end
26
+
27
+ # get all keys path, { 'a' => 'value1', 'b' => { 'c' => 'value2'}}.all_keys_with_path => ['a','b.c']
28
+ def all_keys_with_path(parent=nil)
29
+ a = []
30
+ each do |k,v|
31
+ if v.is_a?(Hash)
32
+ a << v.all_keys_with_path([parent,k].compact.join('.'))
33
+ else
34
+ a << "#{[parent,k].compact.join(".")}"
35
+ end
36
+ end
37
+ a.flatten
38
+ end
39
+
40
+ end
41
+
42
+ def self.included(receiver)
43
+ receiver.extend ClassMethods
44
+ receiver.send :include, InstanceMethods
45
+ end
46
+ end
47
+
48
+ HashExtensions.included(Hash)
@@ -2,18 +2,54 @@ module EventHub
2
2
 
3
3
  module Helper
4
4
 
5
- # converts a base class name, Whatever::MyClassName => my_class_name
6
- def class_to_string(class_name)
7
- class_name.to_s.split("::")[-1].gsub(/[A-Z]/) { |c| "_#{c}"}.gsub(/^_/,"").downcase
5
+ # converts a class like EventHub::PlateStore::MyClassName to an array ['event_hub','plate_store','my_class_name']
6
+ def class_to_array(class_name)
7
+ class_name.to_s.split("::").map{ |m| m.gsub(/[A-Z]/) { |c| "_#{c}"}.gsub(/^_/,"").downcase }
8
8
  end
9
9
 
10
- def format_raw_string(message,max_characters=80)
10
+ # replaces CR, LF, CRLF with ";" and cut's string to requied length by adding "..." if string would be longer
11
+ def format_string(message,max_characters=80)
11
12
  max_characters = 5 if max_characters < 5
12
13
  m = message.gsub(/\r\n|\n|\r/m,";")
13
- return (m[0..max_characters-3] + "...") if m.size > max_characters
14
+ return (m[0..max_characters-4] + "...") if m.size > max_characters
14
15
  return m
15
16
  end
16
17
 
18
+ def get_host
19
+ Socket.gethostname
20
+ end
21
+
22
+ def get_ip_adresses
23
+ list = Socket.ip_address_list.map { |i| i.ip_address unless i.ipv4_loopback? || i.ipv6_loopback? }.compact
24
+ return list.size == 0 ? ["no ip address found (loopback excluded)"] : list
25
+ end
26
+
27
+ def now_stamp(now=nil)
28
+ now ||= Time.now
29
+ now.utc.strftime("%Y-%m-%dT%H:%M:%S.#{now.usec}Z")
30
+ end
31
+
32
+ def duration(difference)
33
+ rest, secs = difference.divmod( 60 ) # self is the time difference t2 - t1
34
+ rest, mins = rest.divmod( 60 )
35
+ days, hours = rest.divmod( 24 )
36
+ secs = secs.truncate
37
+ milliseconds = ((difference - difference.truncate)*1000).round
38
+
39
+ result = []
40
+ result << "#{days} days" if days > 1
41
+ result << "#{days} day" if days == 1
42
+ result << "#{hours} hours" if hours > 1
43
+ result << "#{hours} hour" if hours == 1
44
+ result << "#{mins} minutes" if mins > 1
45
+ result << "#{mins} minute" if mins == 1
46
+ result << "#{secs} seconds" if secs > 1
47
+ result << "#{secs} second" if secs == 1
48
+ result << "#{milliseconds} milliseconds" if milliseconds > 1
49
+ result << "#{milliseconds} millisecond" if milliseconds == 1
50
+ return result.join(' ')
51
+ end
52
+
17
53
  end
18
54
 
19
55
  end
@@ -1,6 +1,7 @@
1
1
  module EventHub
2
2
 
3
3
  class Message
4
+ include Helper
4
5
 
5
6
  VERSION = '1.0.0'
6
7
 
@@ -50,23 +51,27 @@ module EventHub
50
51
  @raw = raw
51
52
 
52
53
  # set message defaults, that we have required headers
53
- now = Time.now
54
54
  @header.set('message_id',UUIDTools::UUID.timestamp_create.to_s,false)
55
55
  @header.set('version',VERSION,false)
56
- @header.set('created_at',now.utc.strftime("%Y-%m-%dT%H:%M:%S.#{now.usec/1000}Z"),false)
56
+ @header.set('created_at',now_stamp,false)
57
57
 
58
- @header.set('process.name',nil,false)
58
+ @header.set('origin.module_id','undefined',false)
59
+ @header.set('origin.type','undefined',false)
60
+ @header.set('origin.site_id','undefined',false)
61
+
62
+ @header.set('process.name','undefined',false)
59
63
  @header.set('process.execution_id',UUIDTools::UUID.timestamp_create.to_s,false)
60
64
  @header.set('process.step_position',0,false)
61
65
 
62
66
  @header.set('status.retried_count',0,false)
63
67
  @header.set('status.code',STATUS_INITIAL,false)
64
- @header.set('status.message',nil,false)
68
+ @header.set('status.message','',false)
65
69
 
66
70
  end
67
71
 
68
72
  def valid?
69
- REQUIRED_HEADERS.all? { |key| @header.all_keys_with_path.include?(key) }
73
+ # check for existence and defined value
74
+ REQUIRED_HEADERS.all? { |key| @header.all_keys_with_path.include?(key) && !!self.send(key.gsub(/\./,"_").to_sym)}
70
75
  end
71
76
 
72
77
  def success?
@@ -90,14 +95,19 @@ module EventHub
90
95
  end
91
96
 
92
97
  def to_s
93
- "Message: message_id [#{self.message_id}], status.code [#{status_code}], status.message [#{status_message}], status.retried_count [#{status_retried_count}] "
98
+ "Msg: process [#{self.process_name},#{self.process_step_position},#{self.process_execution_id}], status [#{self.status_code},#{self.status_message},#{self.status_retried_count}]"
94
99
  end
95
100
 
96
- def copy(args={})
97
- copied_header = self.header.dup
101
+ # copies the message and set's provided status code (default: success), actual stamp, and a new message id
102
+ def copy(status_code=STATUS_SUCCESS)
103
+
104
+ copied_header = self.header.dup
98
105
  copied_body = self.body.dup
99
106
 
100
- args.each { |key,value| copied_header.set(key,value) } if args.is_a?(Hash)
107
+ copied_header.set("message_id",UUIDTools::UUID.timestamp_create.to_s)
108
+ copied_header.set("created_at",now_stamp)
109
+ copied_header.set("status.code",status_code)
110
+
101
111
  Message.new(copied_header, copied_body)
102
112
  end
103
113
 
@@ -6,7 +6,7 @@ module EventHub
6
6
  include Helper
7
7
 
8
8
  def initialize(name=nil)
9
- @name = name || class_to_string(self.class)
9
+ @name = name || class_to_array(self.class)[1..-1].join(".")
10
10
  @folder = Dir.pwd
11
11
 
12
12
  @started = Time.now
@@ -21,28 +21,32 @@ module EventHub
21
21
  EventHub::Configuration.instance.data
22
22
  end
23
23
 
24
- def host
24
+ def server_host
25
25
  configuration.get('server.host') || 'localhost'
26
26
  end
27
27
 
28
- def user
28
+ def server_user
29
29
  configuration.get('server.user') || 'admin'
30
30
  end
31
31
 
32
- def password
32
+ def server_password
33
33
  configuration.get('server.password') || 'admin'
34
34
  end
35
35
 
36
- def management_port
36
+ def server_management_port
37
37
  configuration.get('server.management_port') || 15672
38
38
  end
39
39
 
40
- def listener_queue
41
- configuration.get('processor.listener_queue') || 'inbound'
40
+ def server_vhost
41
+ configuration.get('server.vhost') || 'event_hub'
42
42
  end
43
43
 
44
- def vhost
45
- configuration.get('server.vhost') || nil
44
+ def connection_settings
45
+ { user: server_user, password: server_password, host: server_host, vhost: server_vhost }
46
+ end
47
+
48
+ def listener_queue
49
+ configuration.get('processor.listener_queue') || 'undefined_listener_queue'
46
50
  end
47
51
 
48
52
  def watchdog_cycle_in_s
@@ -60,42 +64,64 @@ module EventHub
60
64
  def start(detached=false)
61
65
  daemonize if detached
62
66
 
67
+ EventHub.logger.info("Processor [#{@name}] base folder [#{@folder}]")
68
+
69
+ channel = nil
70
+
63
71
  while @restart
64
72
 
65
73
  begin
66
- AMQP.start(configuration.get('server')) do |connection, open_ok|
74
+ AMQP.start(self.connection_settings) do |connection, open_ok|
67
75
 
68
76
  @connection = connection
69
77
 
70
78
  # deal with tcp connection issues
71
79
  @connection.on_tcp_connection_loss do |conn, settings|
72
- EventHub.logger.warn("Processor lost tcp connection. Trying to restart in #{@restart_in_s} seconds...")
80
+ EventHub.logger.warn("Processor lost tcp connection. Trying to restart in #{self.restart_in_s} seconds...")
73
81
  stop_processor(true)
74
82
  end
75
83
 
76
84
  # create channel
77
- @channel = AMQP::Channel.new(@connection)
78
- @channel.auto_recovery = true
85
+ channel = AMQP::Channel.new(@connection)
79
86
 
80
87
  # connect to queue
81
- @queue = @channel.queue(self.listener_queue, durable: true, auto_delete: false)
88
+ @queue = channel.queue(self.listener_queue, durable: true, auto_delete: false)
82
89
 
83
90
  # subscribe to queue
84
- @queue.subscribe(:ack => true) do |metadata, payload|
91
+ @queue.subscribe(:ack => true) do |metadata, payload|
92
+
93
+
85
94
  begin
86
- if handle_message(metadata,payload)
87
- raise
88
- metadata.ack
89
- else
90
- metadata.nack
95
+ messages_to_send = []
96
+
97
+ # try to convert to Evenhub message
98
+ message = Message.from_json(payload)
99
+ EventHub.logger.info("-> #{message.to_s}")
100
+
101
+ if message.status_code == STATUS_INVALID
102
+ messages_to_send << message
103
+ EventHub.logger.info("-> #{message.to_s} => Put to queue [#{EH_X_INBOUND}].")
104
+ else
105
+ # pass received message to handler or dervied handler
106
+ messages_to_send = Array(handle_message(message))
107
+ end
108
+
109
+ # forward invalid or returned messages to dispatcher
110
+ messages_to_send.each do |message|
111
+ send_message(message)
91
112
  end
113
+ channel.acknowledge(metadata.delivery_tag)
114
+ @messages_successful += 1
115
+
92
116
  rescue => e
93
- EventHub.logger.error("Unexpected exception in handle_message method: #{e}")
117
+ channel.reject(metadata.delivery_tag,false)
118
+ @messages_unsuccessful += 1
119
+ EventHub.logger.error("Unexpected exception in handle_message method: #{e}. Message dead lettered.")
94
120
  EventHub.logger.save_detailed_error(e)
95
121
  end
96
122
  end
97
123
 
98
- EventHub.logger.info("Processor [#{@name}] is listening to queue [#{self.listener_queue}], base folder [#{@folder}]")
124
+ EventHub.logger.info("Processor [#{@name}] is listening to vhost [#{self.server_vhost}], queue [#{self.listener_queue}]")
99
125
 
100
126
  # Singnal Listening
101
127
  Signal.trap("TERM") {stop_processor}
@@ -111,7 +137,7 @@ module EventHub
111
137
  Signal.trap("INT") { stop_processor }
112
138
 
113
139
  id = EventHub.logger.save_detailed_error(e)
114
- EventHub.logger.error("Unexpected exception: #{e}, see => #{id}")
140
+ EventHub.logger.error("Unexpected exception: #{e}, see => #{id}. Trying to restart in #{self.restart_in_s} seconds...")
115
141
 
116
142
  sleep_break self.restart_in_s
117
143
  end
@@ -134,15 +160,16 @@ module EventHub
134
160
 
135
161
  def watchdog
136
162
  begin
137
- response = RestClient.get "http://#{self.user}:#{self.password}@#{self.host}:#{self.management_port}/api/queues/#{self.vhost}/#{self.listener_queue}/bindings", { :content_type => :json}
163
+ response = RestClient.get "http://#{self.server_user}:#{self.server_password}@#{self.server_host}:#{self.server_management_port}/api/queues/#{self.server_vhost}/#{self.listener_queue}/bindings", { :content_type => :json}
138
164
  data = JSON.parse(response.body)
139
165
 
140
166
  if response.code != 200
141
167
  EventHub.logger.warn("Watchdog: Server did not answered properly. Trying to restart in #{self.restart_in_s} seconds...")
142
- stop_processor
168
+ EventMachine.add_timer(self.restart_in_s) { stop_processor(true) }
143
169
  elsif data.size == 0
144
170
  EventHub.logger.warn("Watchdog: Something is wrong with the vhost, queue, and/or bindings. Trying to restart in #{self.restart_in_s} seconds...")
145
- stop_processor
171
+ EventMachine.add_timer(self.restart_in_s) { stop_processor(true) }
172
+ # does it make sence ? Needs maybe more checks in future
146
173
  else
147
174
  # Watchdog is happy :-)
148
175
  # add timer for next check
@@ -159,16 +186,20 @@ module EventHub
159
186
  message = Message.new
160
187
  message.origin_module_id = @name
161
188
  message.origin_type = "processor"
162
- message.origin_site_id = 'chbs'
189
+ message.origin_site_id = 'global'
163
190
 
164
191
  message.process_name = 'event_hub.heartbeat'
165
192
 
193
+ now = Time.now
166
194
  message.body = {
167
195
  heartbeat: {
168
- started: @started,
169
- stamp_last_beat: Time.now,
196
+ started: now_stamp(@started),
197
+ stamp_last_beat: now_stamp(now),
198
+ uptime: duration(now-@started),
170
199
  heartbeat_cycle_in_s: self.heartbeat_cycle_in_s,
171
200
  served_queues: [self.listener_queue],
201
+ host: get_host,
202
+ ip_adresses: get_ip_adresses,
172
203
  messages: {
173
204
  total: @messages_successful+@messages_unsuccessful,
174
205
  successful: @messages_successful,
@@ -177,35 +208,21 @@ module EventHub
177
208
  }
178
209
  }
179
210
 
180
- send_to_dispatcher(message.to_json)
211
+ # send heartbeat message
212
+ send_message(message)
181
213
 
182
214
  EventMachine.add_timer(self.heartbeat_cycle_in_s) { heartbeat }
183
215
  end
184
216
 
185
- def send_to_dispatcher(payload)
186
- confirmed = true
187
-
188
- connection = Bunny.new({hostname: self.host, user: self.user, password: self.password, vhost: "event_hub"})
189
- connection.start
190
-
191
- channel = connection.create_channel
192
- channel.confirm_select
193
-
194
- channel.default_exchange.publish(payload,routing_key: EVENT_HUB_QUEUE_INBOUND, persistent: true)
195
- success = channel.wait_for_confirms
196
-
197
- if !success
198
- EventHub.logger.error("Message has not been confirmed by the server to be received !!!")
199
- confirmed = false
200
- @messages_unsuccessful += 1
201
- else
202
- @messages_successful += 1
203
- end
204
-
205
- channel.close
206
- connection.close
207
-
208
- confirmed
217
+ # send message
218
+ def send_message(message,exchange_name=EH_X_INBOUND)
219
+ AMQP::Channel.new(@connection) do |channel|
220
+ channel.direct(exchange_name, :durable => true, :auto_delete => false) do |exchange, declare_ok|
221
+ exchange.publish(message.to_json, :persistent => true)
222
+ end
223
+ end
224
+ rescue => e
225
+ EventHub.logger.error("Unexpected exception while sending message to [#{exchange_name}]: #{e}")
209
226
  end
210
227
 
211
228
  def sleep_break( seconds ) # breaks after n seconds or after interrupt
@@ -221,11 +238,11 @@ module EventHub
221
238
  def stop_processor(restart=false)
222
239
  @restart = restart
223
240
 
224
- # stop event loop
225
- @connection.disconnect {
226
- EventHub.logger.info("Processor [#{@name}] is stopping main event loop")
227
- EventMachine.stop
228
- }
241
+ # stop connection and event loop
242
+ if @connection
243
+ @connection.disconnect if @connection.connected?
244
+ EventMachine.stop if EventMachine.reactor_running?
245
+ end
229
246
  end
230
247
 
231
248
  def daemonize
@@ -1,3 +1,3 @@
1
1
  module EventHub
2
- VERSION = "0.0.6"
2
+ VERSION = "0.0.7"
3
3
  end
@@ -1,10 +1,10 @@
1
1
  require 'amqp'
2
- require 'bunny'
3
2
  require 'rest-client'
4
3
  require 'json'
5
4
  require 'singleton'
6
5
  require 'uuidtools'
7
6
  require 'base64'
7
+ require 'socket'
8
8
 
9
9
  require_relative 'eventhub/version'
10
10
  require_relative 'eventhub/constant'
@@ -12,7 +12,7 @@ require_relative 'eventhub/helper'
12
12
  require_relative 'eventhub/multi_logger'
13
13
 
14
14
  require_relative 'eventhub/configuration'
15
- require_relative 'eventhub/hash'
15
+ require_relative 'eventhub/hash_extensions'
16
16
  require_relative 'eventhub/processor'
17
17
  require_relative 'eventhub/message'
18
18
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eventhub-processor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Steiner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-04-03 00:00:00.000000000 Z
11
+ date: 2014-04-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -39,13 +39,13 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rest-client
42
+ name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
- type: :runtime
48
+ type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
@@ -53,7 +53,7 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: amqp
56
+ name: rest-client
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
@@ -67,7 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: bunny
70
+ name: amqp
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
@@ -102,11 +102,10 @@ extensions: []
102
102
  extra_rdoc_files: []
103
103
  files:
104
104
  - LICENSE.txt
105
- - Rakefile
106
105
  - lib/eventhub-processor.rb
107
106
  - lib/eventhub/configuration.rb
108
107
  - lib/eventhub/constant.rb
109
- - lib/eventhub/hash.rb
108
+ - lib/eventhub/hash_extensions.rb
110
109
  - lib/eventhub/helper.rb
111
110
  - lib/eventhub/message.rb
112
111
  - lib/eventhub/multi_logger.rb
data/Rakefile DELETED
@@ -1 +0,0 @@
1
- require "bundler/gem_tasks"
data/lib/eventhub/hash.rb DELETED
@@ -1,34 +0,0 @@
1
- class ::Hash
2
-
3
- # get value from provided key path, e.g. hash.get('a.b.c')
4
- def get(key_path)
5
- key_path.split('.').inject(self,:[])
6
- rescue NoMethodError
7
- return nil
8
- end
9
-
10
- # set value from provided key path, e.h. hash.set('a.b.c','new value')
11
- # if overwrite is false, value will be set if it was nil previously
12
- def set(key_path,value,overwrite=true)
13
- *key_path, last = key_path.split(".")
14
- if overwrite
15
- key_path.inject(self) { |h,key| h.has_key?(key) ? h[key] : h[key]={}} [last] = value
16
- else
17
- key_path.inject(self) { |h,key| h.has_key?(key) ? h[key] : h[key]={}} [last] ||= value
18
- end
19
- end
20
-
21
- # get all keys path, { 'a' => 'value1', 'b' => { 'c' => 'value2'}}.all_keys_with_path => ['a','b.c']
22
- def all_keys_with_path(parent=nil)
23
- a = []
24
- each do |k,v|
25
- if v.is_a?(Hash)
26
- a << v.all_keys_with_path([parent,k].compact.join('.'))
27
- else
28
- a << "#{[parent,k].compact.join(".")}"
29
- end
30
- end
31
- a.flatten
32
- end
33
-
34
- end