marketo-api-ruby 0.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|