flex 0.4.2 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -1
- data/README.md +46 -7
- data/VERSION +1 -1
- data/flex.gemspec +13 -19
- data/lib/flex.rb +66 -392
- data/lib/flex/api_stubs.rb +1268 -0
- data/lib/flex/api_templates/cluster_api.yml +94 -0
- data/lib/flex/api_templates/core_api.yml +202 -0
- data/lib/flex/api_templates/indices_api.yml +304 -0
- data/lib/flex/class_proxy/base.rb +20 -6
- data/lib/flex/class_proxy/templates.rb +97 -0
- data/lib/flex/class_proxy/templates/doc.rb +91 -0
- data/lib/flex/class_proxy/templates/search.rb +72 -0
- data/lib/flex/configuration.rb +17 -49
- data/lib/flex/deprecation.rb +153 -0
- data/lib/flex/errors.rb +2 -1
- data/lib/flex/http_clients/base.rb +15 -0
- data/lib/flex/http_clients/loader.rb +51 -0
- data/lib/flex/http_clients/patron.rb +9 -7
- data/lib/flex/http_clients/rest_client.rb +6 -8
- data/lib/flex/logger.rb +24 -3
- data/lib/flex/prog_bar.rb +11 -6
- data/lib/flex/rails.rb +1 -13
- data/lib/flex/result.rb +8 -2
- data/lib/flex/result/document.rb +42 -13
- data/lib/flex/result/multi_get.rb +24 -0
- data/lib/flex/result/search.rb +1 -24
- data/lib/flex/struct/array.rb +25 -0
- data/lib/flex/struct/hash.rb +105 -0
- data/lib/flex/{result/collection.rb → struct/paginable.rb} +16 -9
- data/lib/flex/struct/prunable.rb +60 -0
- data/lib/flex/struct/symbolize.rb +27 -0
- data/lib/flex/tasks.rb +26 -86
- data/lib/flex/template.rb +60 -120
- data/lib/flex/template/common.rb +42 -0
- data/lib/flex/template/logger.rb +66 -0
- data/lib/flex/template/partial.rb +12 -15
- data/lib/flex/template/search.rb +6 -6
- data/lib/flex/template/slim_search.rb +1 -1
- data/lib/flex/template/tags.rb +19 -9
- data/lib/flex/templates.rb +20 -0
- data/lib/flex/utility_methods.rb +80 -89
- data/lib/flex/utils.rb +68 -25
- data/lib/flex/variables.rb +55 -4
- data/lib/tasks.rake +28 -0
- metadata +61 -85
- data/bin/flexes +0 -174
- data/lib/flex/api_methods.yml +0 -108
- data/lib/flex/class_proxy/loader.rb +0 -102
- data/lib/flex/class_proxy/model.rb +0 -45
- data/lib/flex/class_proxy/model_sync.rb +0 -23
- data/lib/flex/class_proxy/related_model.rb +0 -23
- data/lib/flex/instance_proxy/base.rb +0 -29
- data/lib/flex/instance_proxy/model.rb +0 -102
- data/lib/flex/instance_proxy/related_model.rb +0 -7
- data/lib/flex/loader.rb +0 -18
- data/lib/flex/manager.rb +0 -61
- data/lib/flex/model.rb +0 -24
- data/lib/flex/rails/engine.rb +0 -23
- data/lib/flex/rails/helper.rb +0 -16
- data/lib/flex/related_model.rb +0 -16
- data/lib/flex/result/indifferent_access.rb +0 -11
- data/lib/flex/result/source_document.rb +0 -63
- data/lib/flex/result/source_search.rb +0 -32
- data/lib/flex/structure/indifferent_access.rb +0 -44
- data/lib/flex/structure/mergeable.rb +0 -21
- data/lib/flex/template/base.rb +0 -41
- data/lib/flex/template/info.rb +0 -68
- data/lib/generators/flex/setup/setup_generator.rb +0 -48
- data/lib/generators/flex/setup/templates/flex_config.yml +0 -16
- data/lib/generators/flex/setup/templates/flex_dir/es.rb.erb +0 -18
- data/lib/generators/flex/setup/templates/flex_dir/es.yml.erb +0 -19
- data/lib/generators/flex/setup/templates/flex_dir/es_extender.rb.erb +0 -17
- data/lib/generators/flex/setup/templates/flex_initializer.rb.erb +0 -44
- data/lib/tasks/index.rake +0 -17
- data/test/flex.irt +0 -143
- data/test/flex/configuration.irt +0 -53
- data/test/irt_helper.rb +0 -12
data/lib/flex/errors.rb
CHANGED
@@ -8,6 +8,7 @@ module Flex
|
|
8
8
|
class ExistingIndexError < StandardError; end
|
9
9
|
class MissingHttpClientError < StandardError; end
|
10
10
|
class MissingParentError < StandardError; end
|
11
|
+
class MissingVariableError < StandardError; end
|
11
12
|
|
12
13
|
class HttpError < StandardError
|
13
14
|
|
@@ -27,7 +28,7 @@ module Flex
|
|
27
28
|
end
|
28
29
|
|
29
30
|
def to_s
|
30
|
-
log = "
|
31
|
+
log = "#@caller_line\n" if @caller_line
|
31
32
|
"#{log}#{status}: #{body}"
|
32
33
|
end
|
33
34
|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Flex
|
2
|
+
module HttpClients
|
3
|
+
class Base
|
4
|
+
|
5
|
+
attr_accessor :options, :base_uri, :raise_proc
|
6
|
+
|
7
|
+
def initialize(base_uri='http://localhost:9200', options={})
|
8
|
+
@options = options
|
9
|
+
@base_uri = base_uri
|
10
|
+
@raise_proc = proc{|response| response.status >= 400}
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Flex
|
2
|
+
module HttpClients
|
3
|
+
|
4
|
+
class Dummy
|
5
|
+
def request(*)
|
6
|
+
raise MissingHttpClientError,
|
7
|
+
'you should install the gem "patron" (recommended for performances) or "rest-client", ' +
|
8
|
+
'or provide your own http-client interface and set Flex::Configuration.http_client'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Loader
|
13
|
+
|
14
|
+
extend self
|
15
|
+
|
16
|
+
def new_http_client
|
17
|
+
if Gem::Specification.respond_to?(:find_all_by_name)
|
18
|
+
case
|
19
|
+
# terrible way to check whether a gem is available.
|
20
|
+
# Gem.available? was just perfect: that's probably the reason it has been deprecated!
|
21
|
+
# https://github.com/rubygems/rubygems/issues/176
|
22
|
+
when Gem::Specification::find_all_by_name('patron').any? then require_patron
|
23
|
+
when Gem::Specification::find_all_by_name('rest-client').any? then require_rest_client
|
24
|
+
else Dummy.new
|
25
|
+
end
|
26
|
+
else
|
27
|
+
case
|
28
|
+
when Gem.available?('patron') then require_patron
|
29
|
+
when Gem.available?('rest-client') then require_rest_client
|
30
|
+
else Dummy.new
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def require_patron
|
38
|
+
require 'patron'
|
39
|
+
require 'flex/http_clients/patron'
|
40
|
+
Patron.new
|
41
|
+
end
|
42
|
+
|
43
|
+
def require_rest_client
|
44
|
+
require 'rest-client'
|
45
|
+
require 'flex/http_clients/rest_client'
|
46
|
+
RestClient.new
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -1,14 +1,16 @@
|
|
1
1
|
module Flex
|
2
2
|
module HttpClients
|
3
|
-
|
4
|
-
extend self
|
3
|
+
class Patron < Base
|
5
4
|
|
6
5
|
def request(method, path, data=nil)
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
# patron would raise an error for :post and :put requests with no data
|
7
|
+
# and elasticsearch ignores the data when it expects no data,
|
8
|
+
# so we silence patron by adding some dummy data
|
9
|
+
data = {} if (method == 'POST' || method == 'PUT') && data.nil?
|
10
|
+
opts = options.merge(:data => data)
|
11
|
+
session.request method.to_s.downcase.to_sym, path, {}, opts
|
10
12
|
rescue ::Patron::TimeoutError
|
11
|
-
session.request method.to_s.downcase.to_sym, path, {},
|
13
|
+
session.request method.to_s.downcase.to_sym, path, {}, opts
|
12
14
|
end
|
13
15
|
|
14
16
|
private
|
@@ -17,7 +19,7 @@ module Flex
|
|
17
19
|
Thread.current[:flex_patron_session] ||= begin
|
18
20
|
sess = ::Patron::Session.new
|
19
21
|
sess.headers['User-Agent'] = "flex-#{Flex::VERSION}"
|
20
|
-
sess.base_url =
|
22
|
+
sess.base_url = base_uri
|
21
23
|
sess
|
22
24
|
end
|
23
25
|
end
|
@@ -1,15 +1,13 @@
|
|
1
1
|
module Flex
|
2
2
|
module HttpClients
|
3
|
-
|
4
|
-
extend self
|
3
|
+
class RestClient < Base
|
5
4
|
|
6
5
|
def request(method, path, data=nil)
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
response = ::RestClient::Request.new( args ).execute
|
6
|
+
url = "#{base_uri}#{path}"
|
7
|
+
opts = options.merge( :method => method.to_s.downcase.to_sym,
|
8
|
+
:url => url,
|
9
|
+
:payload => data )
|
10
|
+
response = ::RestClient::Request.new( opts ).execute
|
13
11
|
extend_response(response, url)
|
14
12
|
|
15
13
|
rescue ::RestClient::ExceptionWithResponse => e
|
data/lib/flex/logger.rb
CHANGED
@@ -3,13 +3,34 @@ require 'logger'
|
|
3
3
|
module Flex
|
4
4
|
class Logger < ::Logger
|
5
5
|
|
6
|
+
|
7
|
+
attr_accessor :debug_variables, :debug_request, :debug_result, :curl_format
|
8
|
+
|
6
9
|
def initialize(*)
|
7
10
|
super
|
8
|
-
self.level = ::Logger::
|
9
|
-
self.progname =
|
11
|
+
self.level = ::Logger::WARN
|
12
|
+
self.progname = 'FLEX'
|
10
13
|
self.formatter = proc do |severity, datetime, progname, msg|
|
11
|
-
|
14
|
+
flex_format(severity, msg)
|
12
15
|
end
|
16
|
+
@debug_variables = true
|
17
|
+
@debug_request = true
|
18
|
+
@debug_result = true
|
19
|
+
@curl_format = false
|
20
|
+
end
|
21
|
+
|
22
|
+
def flex_format(severity, msg)
|
23
|
+
prefix = Dye.dye(" FLEX-#{severity} ", "FLEX-#{severity}:", :blue, :bold, :reversed) + ' '
|
24
|
+
msg.split("\n").map{|l| prefix + l}.join("\n") + "\n"
|
25
|
+
end
|
26
|
+
|
27
|
+
# force color in console (used with jruby)
|
28
|
+
def color=(bool)
|
29
|
+
Dye.color = bool
|
30
|
+
end
|
31
|
+
|
32
|
+
def color
|
33
|
+
Dye.color?
|
13
34
|
end
|
14
35
|
|
15
36
|
end
|
data/lib/flex/prog_bar.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Flex
|
2
2
|
class ProgBar
|
3
3
|
|
4
|
-
attr_reader :pbar
|
4
|
+
attr_reader :pbar, :total_count
|
5
5
|
|
6
6
|
def initialize(total_count, batch_size=nil, prefix_message=nil)
|
7
7
|
@total_count = total_count
|
@@ -18,16 +18,21 @@ module Flex
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def process_result(result, inc)
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
unless result.nil? || result.empty?
|
22
|
+
unless result.failed.size == 0
|
23
|
+
Conf.logger.error "Failed load:\n#{result.failed.to_yaml}"
|
24
|
+
@pbar.bar_mark = 'F'
|
25
|
+
end
|
26
|
+
@failed_count += result.failed.size
|
27
|
+
@successful_count += result.successful.size
|
28
|
+
end
|
24
29
|
@pbar.inc(inc)
|
25
30
|
end
|
26
31
|
|
27
32
|
def finish
|
28
33
|
@pbar.finish
|
29
|
-
puts "Processed
|
30
|
-
puts
|
34
|
+
puts "Processed #@total_count. Successful #@successful_count. Skipped #{@total_count - @successful_count - @failed_count}. Failed #@failed_count."
|
35
|
+
puts 'See the log for the details about the failures.' unless @failed_count == 0
|
31
36
|
end
|
32
37
|
|
33
38
|
end
|
data/lib/flex/rails.rb
CHANGED
@@ -1,13 +1 @@
|
|
1
|
-
require 'flex'
|
2
|
-
require 'rails'
|
3
|
-
require 'flex/rails/helper'
|
4
|
-
|
5
|
-
if ::Rails.respond_to?(:version) && ::Rails.version.to_i >= 3
|
6
|
-
require 'flex/rails/engine'
|
7
|
-
else
|
8
|
-
Flex::Configuration.configure do |c|
|
9
|
-
c.config_file = ::Rails.root.join('config', 'flex.yml').to_s
|
10
|
-
c.flex_dir = ::Rails.root.join('app', 'flex').to_s
|
11
|
-
c.debug = ::Rails.env.development?
|
12
|
-
end
|
13
|
-
end
|
1
|
+
raise NotImplementedError, %(The "flex/rails" file has been replaced by the "flex-rails" gem. Please, use "flex-rails" in place of "flex" in Rails applications (change "gem 'flex', require => 'flex/rails'" to "gem 'flex-rails'" in your Gemfile))
|
data/lib/flex/result.rb
CHANGED
@@ -1,18 +1,24 @@
|
|
1
1
|
module Flex
|
2
2
|
class Result < ::Hash
|
3
3
|
|
4
|
-
attr_reader :template, :
|
4
|
+
attr_reader :template, :response
|
5
|
+
attr_accessor :variables
|
5
6
|
|
6
7
|
def initialize(template, variables, response, result=nil)
|
7
8
|
@template = template
|
8
9
|
@variables = variables
|
9
10
|
@response = response
|
10
11
|
replace result || !response.body.empty? && MultiJson.decode(response.body) || return
|
11
|
-
|
12
|
+
Conf.result_extenders.each do |ext|
|
12
13
|
next if ext.respond_to?(:should_extend?) && !ext.should_extend?(self)
|
13
14
|
extend ext
|
14
15
|
end
|
15
16
|
end
|
16
17
|
|
18
|
+
def to_flex_result(force=false)
|
19
|
+
return self if variables[:context].nil? || variables[:raw_result] &&! force
|
20
|
+
variables[:context].respond_to?(:flex_result) ? variables[:context].flex_result(self) : self
|
21
|
+
end
|
22
|
+
|
17
23
|
end
|
18
24
|
end
|
data/lib/flex/result/document.rb
CHANGED
@@ -1,36 +1,65 @@
|
|
1
1
|
module Flex
|
2
2
|
class Result
|
3
3
|
|
4
|
-
# adds sugar to documents with the following structure:
|
4
|
+
# adds sugar to documents with the following structure (_source is optional):
|
5
5
|
#
|
6
6
|
# {
|
7
7
|
# "_index" : "twitter",
|
8
8
|
# "_type" : "tweet",
|
9
9
|
# "_id" : "1",
|
10
|
+
# "_source" : {
|
11
|
+
# "user" : "kimchy",
|
12
|
+
# "postDate" : "2009-11-15T14:12:12",
|
13
|
+
# "message" : "trying out Elastic Search"
|
14
|
+
# }
|
10
15
|
# }
|
11
16
|
|
12
17
|
module Document
|
13
18
|
|
14
|
-
META = %w[_index _type _id]
|
15
|
-
|
16
19
|
# extend if result has a structure like a document
|
17
20
|
def self.should_extend?(obj)
|
18
|
-
|
21
|
+
%w[_index _type _id].all? {|k| obj.has_key?(k)}
|
22
|
+
end
|
23
|
+
|
24
|
+
def respond_to?(meth, private=false)
|
25
|
+
smeth = meth.to_s
|
26
|
+
readers.has_key?(smeth) || has_key?(smeth) || has_key?("_#{smeth}") || super
|
19
27
|
end
|
20
28
|
|
21
|
-
|
22
|
-
|
29
|
+
# exposes _source and readers: automatically supply object-like reader access
|
30
|
+
# also expose meta readers like _id, _source, etc, also callable without the leading '_'
|
31
|
+
def method_missing(meth, *args, &block)
|
32
|
+
smeth = meth.to_s
|
33
|
+
case
|
34
|
+
# field name
|
35
|
+
when readers.has_key?(smeth)
|
36
|
+
readers[smeth]
|
37
|
+
# result item
|
38
|
+
when has_key?(smeth)
|
39
|
+
self[smeth]
|
40
|
+
# result item called without the '_' prefix
|
41
|
+
when has_key?("_#{smeth}")
|
42
|
+
self["_#{smeth}"]
|
43
|
+
else
|
44
|
+
super
|
45
|
+
end
|
23
46
|
end
|
24
47
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
raise DocumentMappingError, "the '#{_index}/#{_type}' document cannot be mapped to any class." \
|
29
|
-
if should_raise
|
48
|
+
# used to get the unprefixed (by live-reindex) index name
|
49
|
+
def index_basename
|
50
|
+
@index_basename ||= self['_index'].sub(/^\d{14}_/, '')
|
30
51
|
end
|
31
52
|
|
32
|
-
|
33
|
-
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def readers
|
57
|
+
@readers ||= begin
|
58
|
+
readers = (self['_source']||{}).merge(self['fields']||{})
|
59
|
+
# flattened reader for multi_readers or attachment readers
|
60
|
+
readers.keys.each{|k| readers[k.gsub('.','_')] = readers.delete(k) if k.include?('.')}
|
61
|
+
readers
|
62
|
+
end
|
34
63
|
end
|
35
64
|
|
36
65
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Flex
|
2
|
+
class Result
|
3
|
+
module MultiGet
|
4
|
+
|
5
|
+
# extend if result comes from a search url
|
6
|
+
def self.should_extend?(result)
|
7
|
+
result.response.url =~ /\b_mget\b/ && result['docs']
|
8
|
+
end
|
9
|
+
|
10
|
+
# extend the hits results on extended
|
11
|
+
def self.extended(result)
|
12
|
+
result['docs'].each { |h| h.extend(Document) }
|
13
|
+
result['docs'].extend Struct::Paginable
|
14
|
+
result['docs'].setup(result['docs'].size, result.variables)
|
15
|
+
end
|
16
|
+
|
17
|
+
def docs
|
18
|
+
self['docs']
|
19
|
+
end
|
20
|
+
alias_method :collection, :docs
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/flex/result/search.rb
CHANGED
@@ -10,7 +10,7 @@ module Flex
|
|
10
10
|
# extend the hits results on extended
|
11
11
|
def self.extended(result)
|
12
12
|
result['hits']['hits'].each { |h| h.extend(Document) }
|
13
|
-
result['hits']['hits'].extend
|
13
|
+
result['hits']['hits'].extend Struct::Paginable
|
14
14
|
result['hits']['hits'].setup(result['hits']['total'], result.variables)
|
15
15
|
end
|
16
16
|
|
@@ -23,29 +23,6 @@ module Flex
|
|
23
23
|
self['facets']
|
24
24
|
end
|
25
25
|
|
26
|
-
def loaded_collection
|
27
|
-
@loaded_collection ||= begin
|
28
|
-
records = []
|
29
|
-
# returns a structure like {Comment=>[{"_id"=>"123", ...}, {...}], BlogPost=>[...]}
|
30
|
-
h = Utils.group_array_by(collection) do |d|
|
31
|
-
d.mapped_class(should_raise=true)
|
32
|
-
end
|
33
|
-
h.each do |klass, docs|
|
34
|
-
records |= klass.find(docs.map(&:_id))
|
35
|
-
end
|
36
|
-
class_ids = collection.map { |d| [d.mapped_class.to_s, d._id] }
|
37
|
-
# Reorder records to preserve order from search results
|
38
|
-
records = class_ids.map do |class_str, id|
|
39
|
-
records.detect do |record|
|
40
|
-
record.class.to_s == class_str && record.id.to_s == id.to_s
|
41
|
-
end
|
42
|
-
end
|
43
|
-
records.extend Collection
|
44
|
-
records.setup(self['hits']['total'], variables)
|
45
|
-
records
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
26
|
end
|
50
27
|
end
|
51
28
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Flex
|
2
|
+
module Struct
|
3
|
+
class Array < ::Array
|
4
|
+
|
5
|
+
include Symbolize
|
6
|
+
|
7
|
+
def push(*vals)
|
8
|
+
super *symbolize(vals)
|
9
|
+
end
|
10
|
+
|
11
|
+
def <<(val)
|
12
|
+
super symbolize(val)
|
13
|
+
end
|
14
|
+
|
15
|
+
def insert(*vals)
|
16
|
+
super *symbolize(vals)
|
17
|
+
end
|
18
|
+
|
19
|
+
def unshift(*vals)
|
20
|
+
super *symbolize(vals)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Flex
|
2
|
+
module Struct
|
3
|
+
class Hash < ::Hash
|
4
|
+
include Symbolize
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
super do |hash, key|
|
8
|
+
if key[-1] == '!'
|
9
|
+
klass = (key[0] == '_' ? Array : Hash)
|
10
|
+
hash[clean_key(key)] = klass.new
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def merge(hash)
|
16
|
+
super symbolize(hash)
|
17
|
+
end
|
18
|
+
|
19
|
+
def merge!(hash)
|
20
|
+
super symbolize(hash)
|
21
|
+
end
|
22
|
+
|
23
|
+
def store(key, val)
|
24
|
+
if key[-1] == '='
|
25
|
+
super key[0..-2].to_sym, val.extend(AsIs)
|
26
|
+
else
|
27
|
+
super clean_key(key), symbolize(val)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
alias_method :[]=, :store
|
31
|
+
|
32
|
+
def fetch(key, *rest, &block)
|
33
|
+
cleaned = clean_key(key)
|
34
|
+
super has_key?(cleaned) ? cleaned : key.to_sym, *rest, &block
|
35
|
+
end
|
36
|
+
|
37
|
+
def [](key)
|
38
|
+
cleaned = clean_key(key)
|
39
|
+
super has_key?(cleaned) ? cleaned : key.to_sym
|
40
|
+
end
|
41
|
+
|
42
|
+
def deep_merge(*hashes)
|
43
|
+
dupe = deep_dup(self)
|
44
|
+
hashes.each {|h2| dupe.replace(deep_merge_hash(dupe,h2))}
|
45
|
+
dupe
|
46
|
+
end
|
47
|
+
|
48
|
+
def deep_merge!(*hashes)
|
49
|
+
replace deep_merge(*hashes)
|
50
|
+
end
|
51
|
+
|
52
|
+
module Nil
|
53
|
+
def method_missing(*)
|
54
|
+
self
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def try(key)
|
59
|
+
has_key?(key) ? self[key] : nil.extend(Nil)
|
60
|
+
end
|
61
|
+
|
62
|
+
def try_delete(key, *rest, &block)
|
63
|
+
val = delete clean_key(key), *rest, &block
|
64
|
+
val.nil? ? nil.extend(Nil) : val
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def clean_key(key)
|
71
|
+
key[-1] == '!' ? key[0..-2].to_sym : key.to_sym
|
72
|
+
end
|
73
|
+
|
74
|
+
def deep_merge_hash(h1, h2)
|
75
|
+
h2 ||= {}
|
76
|
+
h1.merge(h2) do |key, oldval, newval|
|
77
|
+
case
|
78
|
+
when oldval.is_a?(Hash) && newval.is_a?(Hash)
|
79
|
+
deep_merge_hash(oldval, newval)
|
80
|
+
when oldval.is_a?(Array) && newval.is_a?(Array)
|
81
|
+
oldval | newval
|
82
|
+
else
|
83
|
+
newval
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def deep_dup(obj)
|
89
|
+
case obj
|
90
|
+
when ::Hash, Flex::Struct::Hash
|
91
|
+
h = obj.dup
|
92
|
+
h.each_pair do |k,v|
|
93
|
+
h[k] = deep_dup(v)
|
94
|
+
end
|
95
|
+
h
|
96
|
+
when ::Array, Flex::Struct::Array
|
97
|
+
obj.map{|i| deep_dup(i)}
|
98
|
+
else
|
99
|
+
obj
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|