marketo-api-ruby 0.8
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
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +2 -0
- data/.gemtest +0 -0
- data/Contributing.rdoc +65 -0
- data/Gemfile +9 -0
- data/History.rdoc +3 -0
- data/Licence.rdoc +24 -0
- data/Manifest.txt +32 -0
- data/README.rdoc +105 -0
- data/Rakefile +65 -0
- data/lib/marketo-api-ruby.rb +1 -0
- data/lib/marketo_api.rb +39 -0
- data/lib/marketo_api/campaigns.rb +194 -0
- data/lib/marketo_api/client.rb +169 -0
- data/lib/marketo_api/client_proxy.rb +104 -0
- data/lib/marketo_api/lead.rb +277 -0
- data/lib/marketo_api/leads.rb +150 -0
- data/lib/marketo_api/lists.rb +109 -0
- data/lib/marketo_api/mobject.rb +272 -0
- data/lib/marketo_api/mobjects.rb +115 -0
- data/spec/marketo/authentication_header_spec.rb +66 -0
- data/spec/marketo/client_spec.rb +363 -0
- data/spec/marketo/lead_key_spec.rb +40 -0
- data/spec/marketo/lead_record_spec.rb +86 -0
- data/spec/spec_helper.rb +4 -0
- data/test/marketo_api/test_campaigns.rb +173 -0
- data/test/marketo_api/test_client.rb +83 -0
- data/test/marketo_api/test_lead.rb +158 -0
- data/test/marketo_api/test_leads.rb +133 -0
- data/test/marketo_api/test_lists.rb +95 -0
- data/test/marketo_api/test_mobject.rb +273 -0
- data/test/marketo_api/test_mobjects.rb +158 -0
- data/test/minitest_helper.rb +69 -0
- data/test/test_marketo_api.rb +38 -0
- metadata +302 -0
- metadata.gz.sig +3 -0
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'savon'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
##
|
5
|
+
# The client to the Marketo SOAP API.
|
6
|
+
class MarketoAPI::Client
|
7
|
+
DEFAULT_CONFIG = {
|
8
|
+
api_subdomain: '123-ABC-456',
|
9
|
+
api_version: '2_3',
|
10
|
+
user_id: nil,
|
11
|
+
encryption_key: nil,
|
12
|
+
read_timeout: 90,
|
13
|
+
open_timeout: 90,
|
14
|
+
headers: { 'Connection' => 'Keep-Alive' },
|
15
|
+
env_namespace: 'SOAP-ENV',
|
16
|
+
namespaces: { 'xmlns:ns1' => 'http://www.marketo.com/mktows/' },
|
17
|
+
pretty_print_xml: true,
|
18
|
+
ssl_verify_mode: :none,
|
19
|
+
}.freeze
|
20
|
+
DEFAULT_CONFIG.values.each(&:freeze)
|
21
|
+
private_constant :DEFAULT_CONFIG
|
22
|
+
|
23
|
+
# Sets the logger.
|
24
|
+
attr_writer :logger
|
25
|
+
|
26
|
+
# The targeted Marketo SOAP API version.
|
27
|
+
attr_reader :api_version
|
28
|
+
# The subdomain for interacting with Marketo.
|
29
|
+
attr_reader :subdomain
|
30
|
+
# The WSDL used for interacting with Marketo.
|
31
|
+
attr_reader :wsdl
|
32
|
+
# The computed endpoint for Marketo.
|
33
|
+
attr_reader :endpoint
|
34
|
+
# If the most recent call resulted in an exception, it will be captured
|
35
|
+
# here.
|
36
|
+
attr_reader :error
|
37
|
+
|
38
|
+
# Creates a client to talk to the Marketo SOAP API.
|
39
|
+
#
|
40
|
+
# === Required Configuration Parameters
|
41
|
+
#
|
42
|
+
# The required configuration parameters can be found in your Marketo
|
43
|
+
# dashboard, under Admin / Integration / SOAP API.
|
44
|
+
#
|
45
|
+
# +api_subdomain+:: The endpoint subdomain.
|
46
|
+
# +api_version+:: The endpoint version.
|
47
|
+
# +user_id+:: The user iD for SOAP integration.
|
48
|
+
# +encryption_key+:: The encryption key for SOAP integration.
|
49
|
+
#
|
50
|
+
# Version 1.0 will make these values defaultable through environment
|
51
|
+
# variables.
|
52
|
+
#
|
53
|
+
# === Savon Configuration Parameters
|
54
|
+
#
|
55
|
+
# These affect how Savon interacts with the HTTP server.
|
56
|
+
#
|
57
|
+
# +read_timeout+:: The timeout for reading from the server. Defaults
|
58
|
+
# to 90.
|
59
|
+
# +open_timeout+:: The timeout for opening the connection. Defaults to
|
60
|
+
# 90.
|
61
|
+
# +pretty_print_xml+:: How the SOAP XML should be written. Defaults to
|
62
|
+
# +true+.
|
63
|
+
# +ssl_verify_mode+:: How to verify SSL keys. This version defaults to
|
64
|
+
# +none+. Version 1.0 will default to normal
|
65
|
+
# verification.
|
66
|
+
# +headers+:: Headers to use. Defaults to Connection: Keep-Alive.
|
67
|
+
# Version 1.0 will enforce at least this value.
|
68
|
+
#
|
69
|
+
# Version 1.0 will require that these options be provided under a +savon+
|
70
|
+
# key.
|
71
|
+
def initialize(config = {})
|
72
|
+
config = DEFAULT_CONFIG.merge(config)
|
73
|
+
@api_version = config.delete(:api_version).freeze
|
74
|
+
@subdomain = config.delete(:api_subdomain).freeze
|
75
|
+
|
76
|
+
@logger = config.delete(:logger)
|
77
|
+
|
78
|
+
user_id = config.delete(:user_id)
|
79
|
+
encryption_key = config.delete(:encryption_key)
|
80
|
+
@auth = AuthHeader.new(user_id, encryption_key)
|
81
|
+
|
82
|
+
@wsdl = "http://app.marketo.com/soap/mktows/#{api_version}?WSDL".freeze
|
83
|
+
@endpoint = "https://#{subdomain}.mktoapi.com/soap/mktows/#{api_version}".freeze
|
84
|
+
@savon = Savon.client(config.merge(wsdl: wsdl, endpoint: endpoint))
|
85
|
+
end
|
86
|
+
|
87
|
+
# Indicates the presence of an error from the last call.
|
88
|
+
def error?
|
89
|
+
!!@error
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# :attr_reader: campaigns
|
94
|
+
# The MarketoAPI::Campaigns instance using this Client.
|
95
|
+
|
96
|
+
##
|
97
|
+
# :attr_reader: leads
|
98
|
+
# The MarketoAPI::Leads instance using this Client.
|
99
|
+
|
100
|
+
##
|
101
|
+
# :attr_reader: lists
|
102
|
+
# The MarketoAPI::Lists instance using this Client.
|
103
|
+
|
104
|
+
##
|
105
|
+
# :attr_reader: mobjects
|
106
|
+
# The MarketoAPI::MObjects instance using this Client.
|
107
|
+
|
108
|
+
# Perform a SOAP API request with a properly formatted params message
|
109
|
+
# object.
|
110
|
+
#
|
111
|
+
# *Warning*: This method is for internal use by descendants of
|
112
|
+
# MarketoAPI::ClientProxy. It should not be called by external users.
|
113
|
+
def call(web_method, params) #:nodoc:
|
114
|
+
@error = nil
|
115
|
+
@savon.call(
|
116
|
+
web_method,
|
117
|
+
message: params,
|
118
|
+
soap_header: { 'ns1:AuthenticationHeader' => @auth.signature }
|
119
|
+
).to_hash
|
120
|
+
rescue Exception => e
|
121
|
+
@error = e
|
122
|
+
@logger.log(e) if @logger
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
# Implements the Marketo
|
128
|
+
# {Authentication Signature}[http://developers.marketo.com/documentation/soap/signature-algorithm/].
|
129
|
+
class AuthHeader #:nodoc:
|
130
|
+
DIGEST = OpenSSL::Digest.new('sha1')
|
131
|
+
private_constant :DIGEST
|
132
|
+
|
133
|
+
def initialize(user_id, encryption_key)
|
134
|
+
if user_id.nil? || encryption_key.nil?
|
135
|
+
raise ArgumentError, ":user_id and :encryption_key required"
|
136
|
+
end
|
137
|
+
|
138
|
+
@user_id = user_id
|
139
|
+
@encryption_key = encryption_key
|
140
|
+
end
|
141
|
+
|
142
|
+
attr_reader :user_id
|
143
|
+
|
144
|
+
# Compute the HMAC signature and return it.
|
145
|
+
def signature
|
146
|
+
time = Time.now
|
147
|
+
{
|
148
|
+
mktowsUserId: user_id,
|
149
|
+
requestTimestamp: time.to_s,
|
150
|
+
requestSignature: hmac(time),
|
151
|
+
}
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
def hmac(time)
|
156
|
+
OpenSSL::HMAC.hexdigest(
|
157
|
+
DIGEST,
|
158
|
+
@encryption_key,
|
159
|
+
"#{time}#{user_id}"
|
160
|
+
)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
private_constant :AuthHeader
|
164
|
+
end
|
165
|
+
|
166
|
+
require_relative 'campaigns'
|
167
|
+
require_relative 'leads'
|
168
|
+
require_relative 'lists'
|
169
|
+
require_relative 'mobjects'
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
# The ClientProxy is the base class for implementing Marketo APIs in a
|
4
|
+
# fluent manner. When a descendant class is implemented, a method will be
|
5
|
+
# added to MarketoAPI::Client based on the descendant class name that will
|
6
|
+
# provide an initialized instance of the descendant class using the
|
7
|
+
# MarketoAPI::Client instance.
|
8
|
+
#
|
9
|
+
# This base class does not provide any useful functionality for consumers of
|
10
|
+
# MarketoAPI, and is primarily provided for common functionality.
|
11
|
+
#
|
12
|
+
# As an example, MarketoAPI::Client#campaigns was generated when
|
13
|
+
# MarketoAPI::Campaigns was inherited from MarketoAPI::ClientProxy. It
|
14
|
+
# returns an instance of MarketoAPI::Campaigns intialized with the
|
15
|
+
# MarketoAPI::Client that generated it.
|
16
|
+
class MarketoAPI::ClientProxy
|
17
|
+
extend Forwardable
|
18
|
+
|
19
|
+
class << self
|
20
|
+
# Generates a new method on MarketoAPI::Client based on the inherited
|
21
|
+
# class name.
|
22
|
+
def inherited(klass)
|
23
|
+
name = klass.name.split(/::/).last.downcase.to_sym
|
24
|
+
|
25
|
+
MarketoAPI::Client.class_eval <<-EOS
|
26
|
+
def #{name}
|
27
|
+
@#{name} ||= #{klass}.new(self)
|
28
|
+
end
|
29
|
+
EOS
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(client)
|
34
|
+
@client = client
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# :attr_reader: error
|
39
|
+
#
|
40
|
+
# Reads the error attribute from the proxied client.
|
41
|
+
|
42
|
+
##
|
43
|
+
# :method: error?
|
44
|
+
#
|
45
|
+
# Reads the presence of an error from the proxied client.
|
46
|
+
|
47
|
+
def_delegators :@client, :error, :error?
|
48
|
+
|
49
|
+
private
|
50
|
+
# Performs a SOAP call and extracts the result from a successful result.
|
51
|
+
#
|
52
|
+
# The Marketo SOAP API always returns results in a fairly deep structure.
|
53
|
+
# For example, the SOAP response for requestCampaign looks something like:
|
54
|
+
#
|
55
|
+
# <successRequestCampaign>
|
56
|
+
# <result>
|
57
|
+
# </result>
|
58
|
+
# </successRequestCampaign>
|
59
|
+
def call(method, param, &block)
|
60
|
+
extract_from_response(
|
61
|
+
@client.call(method, param),
|
62
|
+
:"success_#{method}",
|
63
|
+
:result,
|
64
|
+
&block
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Takes a parameter list and calls #transform_param on each parameter.
|
69
|
+
def transform_param_list(method, param_list)
|
70
|
+
method = :"params_for_#{method}"
|
71
|
+
param_list.map { |param|
|
72
|
+
transform_param(nil, param, method)
|
73
|
+
}.compact
|
74
|
+
end
|
75
|
+
|
76
|
+
# Takes a provided a parameter and transforms it. If the parameter is a
|
77
|
+
# Hash or +nil+, there is no transformation performed; if it responds to a
|
78
|
+
# method <tt>params_for_#{method}</tt>, that method is called to transform
|
79
|
+
# the object.
|
80
|
+
#
|
81
|
+
# If neither of these is true, an ArgumentError is raised.
|
82
|
+
def transform_param(method, param, override = nil)
|
83
|
+
method = if override
|
84
|
+
override
|
85
|
+
else
|
86
|
+
:"params_for_#{method}"
|
87
|
+
end
|
88
|
+
if param.kind_of? Hash or param.nil?
|
89
|
+
param
|
90
|
+
elsif param.respond_to? method
|
91
|
+
param.send(method)
|
92
|
+
else
|
93
|
+
raise ArgumentError, "Invalid parameter: #{param.inspect}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Given a response hash (which is deeply nested), follows the key path
|
98
|
+
# down.
|
99
|
+
def extract_from_response(response, *paths)
|
100
|
+
paths.each { |path| response &&= response[path] }
|
101
|
+
response = yield response if response and block_given?
|
102
|
+
response
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,277 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
# An object representing a Marketo Lead record.
|
4
|
+
class MarketoAPI::Lead
|
5
|
+
extend Forwardable
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
NAMED_KEYS = { #:nodoc:
|
9
|
+
id: :IDNUM,
|
10
|
+
cookie: :COOKIE,
|
11
|
+
email: :EMAIL,
|
12
|
+
lead_owner_email: :LEADOWNEREMAIL,
|
13
|
+
salesforce_account_id: :SFDCACCOUNTID,
|
14
|
+
salesforce_contact_id: :SFDCCONTACTID,
|
15
|
+
salesforce_lead_id: :SFDCLEADID,
|
16
|
+
salesforce_lead_owner_id: :SFDCLEADOWNERID,
|
17
|
+
salesforce_opportunity_id: :SFDCOPPTYID
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
KEY_TYPES = MarketoAPI.freeze(*NAMED_KEYS.values) #:nodoc:
|
21
|
+
private_constant :KEY_TYPES
|
22
|
+
|
23
|
+
# The Marketo ID. This value cannot be set by consumers.
|
24
|
+
attr_reader :id
|
25
|
+
# The Marketo tracking cookie. Optional.
|
26
|
+
attr_accessor :cookie
|
27
|
+
|
28
|
+
# The attributes for the Lead.
|
29
|
+
attr_reader :attributes
|
30
|
+
# The types for the Lead attributes.
|
31
|
+
attr_reader :types
|
32
|
+
# The proxy object for this class.
|
33
|
+
attr_reader :proxy
|
34
|
+
|
35
|
+
def_delegators :@attributes, :[], :each, :each_pair, :each_key,
|
36
|
+
:each_value, :keys, :values
|
37
|
+
|
38
|
+
##
|
39
|
+
# :method: [](hash)
|
40
|
+
# :call-seq:
|
41
|
+
# lead[attribute_key]
|
42
|
+
#
|
43
|
+
# Looks up the provided attribute.
|
44
|
+
|
45
|
+
##
|
46
|
+
# :method: each
|
47
|
+
# :call-seq:
|
48
|
+
# each { |key, value| block }
|
49
|
+
#
|
50
|
+
# Iterates over the attributes.
|
51
|
+
|
52
|
+
##
|
53
|
+
# :method: each_pair
|
54
|
+
# :call-seq:
|
55
|
+
# each_pair { |key, value| block }
|
56
|
+
#
|
57
|
+
# Iterates over the attributes.
|
58
|
+
|
59
|
+
##
|
60
|
+
# :method: each_key
|
61
|
+
# :call-seq:
|
62
|
+
# each_key { |key| block }
|
63
|
+
#
|
64
|
+
# Iterates over the attribute keys.
|
65
|
+
|
66
|
+
##
|
67
|
+
# :method: each_value
|
68
|
+
# :call-seq:
|
69
|
+
# each_value { |value| block }
|
70
|
+
#
|
71
|
+
# Iterates over the attribute values.
|
72
|
+
|
73
|
+
##
|
74
|
+
# :method: keys
|
75
|
+
# :call-seq:
|
76
|
+
# keys() -> array
|
77
|
+
#
|
78
|
+
# Returns the attribute keys.
|
79
|
+
|
80
|
+
##
|
81
|
+
# :method: values
|
82
|
+
# :call-seq:
|
83
|
+
# values() -> array
|
84
|
+
#
|
85
|
+
# Returns the attribute values.
|
86
|
+
|
87
|
+
def initialize(options = {})
|
88
|
+
@id = options[:id]
|
89
|
+
@attributes = {}
|
90
|
+
@types = {}
|
91
|
+
@foreign = {}
|
92
|
+
self[:Email] = options[:email]
|
93
|
+
self.proxy = options[:proxy]
|
94
|
+
yield self if block_given?
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# :method: []=(hash, value)
|
99
|
+
# :call-seq:
|
100
|
+
# lead[key] = value -> value
|
101
|
+
#
|
102
|
+
# Looks up the provided attribute.
|
103
|
+
def []=(key, value)
|
104
|
+
@attributes[key] = value
|
105
|
+
@types[key] ||= infer_value_type(value)
|
106
|
+
end
|
107
|
+
|
108
|
+
# :attr_writer:
|
109
|
+
# :call-seq:
|
110
|
+
# lead.proxy = proxy -> proxy
|
111
|
+
#
|
112
|
+
# Assign a proxy object. Once set, the proxy cannot be unset, but it can be
|
113
|
+
# changed.
|
114
|
+
def proxy=(value)
|
115
|
+
@proxy = case value
|
116
|
+
when nil
|
117
|
+
defined?(@proxy) && @proxy
|
118
|
+
when MarketoAPI::Leads
|
119
|
+
value
|
120
|
+
when MarketoAPI::ClientProxy
|
121
|
+
value.instance_variable_get(:@client).leads
|
122
|
+
when MarketoAPI::Client
|
123
|
+
value.leads
|
124
|
+
else
|
125
|
+
raise ArgumentError, "Invalid proxy type"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# :call-seq:
|
130
|
+
# lead.foreign -> nil
|
131
|
+
# lead.foreign(type, id) -> { type: type, id: id }
|
132
|
+
# lead.foreign -> { type: type, id: id }
|
133
|
+
#
|
134
|
+
# Sets or returns the foreign system type and person ID.
|
135
|
+
def foreign(type = nil, id = nil)
|
136
|
+
@foreign = { type: type.to_sym, id: id } if type and id
|
137
|
+
@foreign
|
138
|
+
end
|
139
|
+
|
140
|
+
# :attr_reader: email
|
141
|
+
def email
|
142
|
+
self[:Email]
|
143
|
+
end
|
144
|
+
|
145
|
+
# :attr_writer: email
|
146
|
+
def email=(value)
|
147
|
+
self[:Email] = value
|
148
|
+
end
|
149
|
+
|
150
|
+
# Performs a Lead sync and returns the new Lead object, or +nil+ if the
|
151
|
+
# sync failed.
|
152
|
+
#
|
153
|
+
# Raises an ArgumentError if a proxy has not been configured with
|
154
|
+
# Lead#proxy=.
|
155
|
+
def sync
|
156
|
+
raise ArgumentError, "No proxy configured" unless proxy
|
157
|
+
proxy.sync(self)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Performs a Lead sync and updates this Lead object in-place, or +nil+ if
|
161
|
+
# the sync failed.
|
162
|
+
#
|
163
|
+
# Raises an ArgumentError if a proxy has not been configured with
|
164
|
+
# Lead#proxy=.
|
165
|
+
def sync!
|
166
|
+
if lead = sync
|
167
|
+
@id = lead.id
|
168
|
+
@cookie = lead.cookie
|
169
|
+
@foreign = lead.foreign
|
170
|
+
@proxy = lead.proxy
|
171
|
+
removed = self.keys - lead.keys
|
172
|
+
|
173
|
+
lead.each_pair { |k, v|
|
174
|
+
@attributes[k] = v
|
175
|
+
@types[k] = lead.types[k]
|
176
|
+
}
|
177
|
+
|
178
|
+
removed.each { |k|
|
179
|
+
@attributes.delete(k)
|
180
|
+
@types.delete(k)
|
181
|
+
}
|
182
|
+
self
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Returns a lead key structure suitable for use with
|
187
|
+
# MarketoAPI::Leads#get.
|
188
|
+
def params_for_get
|
189
|
+
self.class.key(:IDNUM, id)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Returns the parameters required for use with MarketoAPI::Leads#sync.
|
193
|
+
def params_for_sync
|
194
|
+
{
|
195
|
+
return_lead: true,
|
196
|
+
marketo_cookie: cookie,
|
197
|
+
lead_record: {
|
198
|
+
email: email,
|
199
|
+
id: id,
|
200
|
+
foreign_sys_person_id: foreign[:id],
|
201
|
+
foreign_sys_type: foreign[:type],
|
202
|
+
lead_attribute_list: {
|
203
|
+
attribute: attributes.map { |key, value|
|
204
|
+
{
|
205
|
+
attr_name: key.to_s,
|
206
|
+
attr_type: types[key],
|
207
|
+
attr_value: value
|
208
|
+
}
|
209
|
+
}
|
210
|
+
}
|
211
|
+
}.delete_if(&MarketoAPI::MINIMIZE_HASH)
|
212
|
+
}.delete_if(&MarketoAPI::MINIMIZE_HASH)
|
213
|
+
end
|
214
|
+
|
215
|
+
class << self
|
216
|
+
# Creates a new Lead from a SOAP response hash (from Leads#get,
|
217
|
+
# Leads#get_multiple, Leads#sync, or Leads#sync_multiple).
|
218
|
+
def from_soap_hash(hash) #:nodoc:
|
219
|
+
lead = new(id: hash[:id].to_i, email: hash[:email]) do |lr|
|
220
|
+
if type = hash[:foreign_sys_type]
|
221
|
+
lr.foreign(type, hash[:foreign_sys_person_id])
|
222
|
+
end
|
223
|
+
hash[:lead_attribute_list][:attribute].each do |attribute|
|
224
|
+
name = attribute[:attr_name].to_sym
|
225
|
+
lr.attributes[name] = attribute[:attr_value]
|
226
|
+
lr.types[name] = attribute[:attr_type]
|
227
|
+
end
|
228
|
+
end
|
229
|
+
yield lead if block_given?
|
230
|
+
lead
|
231
|
+
end
|
232
|
+
|
233
|
+
# Creates a new Lead key hash suitable for use in a number of Marketo
|
234
|
+
# API calls.
|
235
|
+
def key(key, value)
|
236
|
+
{
|
237
|
+
lead_key: {
|
238
|
+
key_type: key_type(key),
|
239
|
+
key_value: value
|
240
|
+
}
|
241
|
+
}
|
242
|
+
end
|
243
|
+
|
244
|
+
private
|
245
|
+
def key_type(key)
|
246
|
+
key = key.to_sym
|
247
|
+
res = if KEY_TYPES.include? key
|
248
|
+
key
|
249
|
+
else
|
250
|
+
NAMED_KEYS[key]
|
251
|
+
end
|
252
|
+
raise ArgumentError, "Invalid key #{key}" unless res
|
253
|
+
res
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def ==(other)
|
258
|
+
id == other.id && cookie == other.cookie && foreign == other.foreign &&
|
259
|
+
attributes == other.attributes && types == other.types
|
260
|
+
end
|
261
|
+
|
262
|
+
def inspect
|
263
|
+
"#<#{self.class} id=#{id} cookie=#{cookie} foreign=#{foreign.inspect} attributes=#{attributes.inspect} types=#{types.inspect}>"
|
264
|
+
end
|
265
|
+
|
266
|
+
private
|
267
|
+
def infer_value_type(value)
|
268
|
+
case value
|
269
|
+
when Integer
|
270
|
+
'integer'
|
271
|
+
when Time, DateTime
|
272
|
+
'datetime'
|
273
|
+
else
|
274
|
+
'string'
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|