yssk22-couch_resource 0.1.0
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.
- data/LICENSE +2 -0
- data/MIT_LICENSE +8 -0
- data/README.rdoc +7 -0
- data/lib/couch_resource.rb +25 -0
- data/lib/couch_resource/base.rb +705 -0
- data/lib/couch_resource/callbacks.rb +103 -0
- data/lib/couch_resource/connection.rb +194 -0
- data/lib/couch_resource/error.rb +3 -0
- data/lib/couch_resource/struct.rb +340 -0
- data/lib/couch_resource/validations.rb +520 -0
- data/lib/couch_resource/view.rb +228 -0
- data/test/test_base.rb +384 -0
- data/test/test_callbacks.rb +115 -0
- data/test/test_connection.rb +39 -0
- data/test/test_struct.rb +332 -0
- data/test/test_validations.rb +186 -0
- metadata +70 -0
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module CouchResource
|
5
|
+
# Callbacks are hooks similar to ActiveRecord::Callbacks
|
6
|
+
#
|
7
|
+
# * (-) <tt>save</tt>
|
8
|
+
# * (-) <tt>valid</tt>
|
9
|
+
# * (1) <tt>before_validation</tt>
|
10
|
+
# * (2) <tt>before_validation_on_create</tt>
|
11
|
+
# * (-) <tt>validate</tt>
|
12
|
+
# * (-) <tt>validate_on_create</tt>
|
13
|
+
# * (3) <tt>after_validation</tt>
|
14
|
+
# * (4) <tt>after_validation_on_create</tt>
|
15
|
+
# * (5) <tt>before_save</tt>
|
16
|
+
# * (6) <tt>before_create</tt>
|
17
|
+
# * (-) <tt>create</tt>
|
18
|
+
# * (7) <tt>after_create</tt>
|
19
|
+
# * (8) <tt>after_save</tt>
|
20
|
+
#
|
21
|
+
module Callbacks
|
22
|
+
CALLBACKS = %w(
|
23
|
+
after_find after_initialize before_save after_save before_create after_create before_update after_update before_validation
|
24
|
+
after_validation before_validation_on_create after_validation_on_create before_validation_on_update
|
25
|
+
after_validation_on_update before_destroy after_destroy
|
26
|
+
)
|
27
|
+
|
28
|
+
def self.included(base)
|
29
|
+
[:create_or_update, :valid?, :create, :update, :destroy].each do |method|
|
30
|
+
base.send :alias_method_chain, method, :callbacks
|
31
|
+
end
|
32
|
+
|
33
|
+
base.send(:include, ActiveSupport::Callbacks)
|
34
|
+
[:save, :create, :update, :validation, :validation_on_create, :validation_on_update, :destroy].each do |method|
|
35
|
+
base.define_callbacks "before_#{method}".to_sym, "after_#{method}".to_sym
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
[:save, :create, :update, :validation, :validation_on_create, :validation_on_update, :destroy].each do |method|
|
41
|
+
module_eval <<-EOS
|
42
|
+
def before_#{method}; end
|
43
|
+
def after_#{method}; end
|
44
|
+
EOS
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def create_or_update_with_callbacks
|
49
|
+
return false if invoke_callbacks(:before_save) == false
|
50
|
+
result = create_or_update_without_callbacks
|
51
|
+
invoke_callbacks(:after_save)
|
52
|
+
result
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_with_callbacks
|
56
|
+
return false if invoke_callbacks(:before_create) == false
|
57
|
+
result = create_without_callbacks
|
58
|
+
invoke_callbacks(:after_create)
|
59
|
+
result
|
60
|
+
end
|
61
|
+
|
62
|
+
def update_with_callbacks
|
63
|
+
return false if invoke_callbacks(:before_update) == false
|
64
|
+
result = update_without_callbacks
|
65
|
+
invoke_callbacks(:after_update)
|
66
|
+
result
|
67
|
+
end
|
68
|
+
|
69
|
+
def valid_with_callbacks?
|
70
|
+
return false if invoke_callbacks(:before_validation) == false
|
71
|
+
if new_record? then
|
72
|
+
return false if invoke_callbacks(:before_validation_on_create) == false
|
73
|
+
else
|
74
|
+
return false if invoke_callbacks(:before_validation_on_update) == false
|
75
|
+
end
|
76
|
+
|
77
|
+
result = valid_without_callbacks?
|
78
|
+
|
79
|
+
invoke_callbacks(:after_validation)
|
80
|
+
if new_record? then
|
81
|
+
invoke_callbacks(:after_validation_on_create)
|
82
|
+
else
|
83
|
+
invoke_callbacks(:after_validation_on_update)
|
84
|
+
end
|
85
|
+
return result
|
86
|
+
end
|
87
|
+
|
88
|
+
def destroy_with_callbacks
|
89
|
+
return false if invoke_callbacks(:before_destroy) == false
|
90
|
+
result = destroy_without_callbacks
|
91
|
+
invoke_callbacks(:after_destroy)
|
92
|
+
result
|
93
|
+
end
|
94
|
+
|
95
|
+
def invoke_callbacks(callback_name)
|
96
|
+
# invoke member method
|
97
|
+
return false if send(callback_name) == false
|
98
|
+
# invoke callbacks defined by using ActiveSupport::Callbacks
|
99
|
+
run_callbacks(callback_name)
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_support'
|
3
|
+
require 'json'
|
4
|
+
require 'net/https'
|
5
|
+
require 'uri'
|
6
|
+
require File.join(File.dirname(__FILE__), "error")
|
7
|
+
|
8
|
+
module CouchResource
|
9
|
+
class ConnectionError < CouchResourceError # :nodoc:
|
10
|
+
attr_reader :request
|
11
|
+
attr_reader :response
|
12
|
+
attr_reader :json
|
13
|
+
|
14
|
+
def initialize(request, response, message = nil)
|
15
|
+
@request = request
|
16
|
+
@response = response
|
17
|
+
@json = JSON(response.body) rescue nil
|
18
|
+
@message = message
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"Failed with #{response.code} #{response.message if response.respond_to?(:message)}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# 3xx Redirection
|
27
|
+
class Redirection < ConnectionError # :nodoc:
|
28
|
+
def to_s; response['Location'] ? "#{super} => #{response['Location']}" : super; end
|
29
|
+
end
|
30
|
+
|
31
|
+
# 4xx Client Error
|
32
|
+
class ClientError < ConnectionError; end # :nodoc:
|
33
|
+
|
34
|
+
# 400 Bad Request
|
35
|
+
class BadRequest < ClientError; end # :nodoc
|
36
|
+
|
37
|
+
# 401 Unauthorized
|
38
|
+
class UnauthorizedAccess < ClientError; end # :nodoc
|
39
|
+
|
40
|
+
# 403 Forbidden
|
41
|
+
class ForbiddenAccess < ClientError; end # :nodoc
|
42
|
+
|
43
|
+
# 404 Not Found
|
44
|
+
class ResourceNotFound < ClientError; end # :nodoc:
|
45
|
+
|
46
|
+
# 409 Conflict
|
47
|
+
class ResourceConflict < ClientError; end # :nodoc:
|
48
|
+
|
49
|
+
# 412 Precondition Failed
|
50
|
+
class PreconditionFailed < ClientError; end
|
51
|
+
|
52
|
+
# 5xx Server Error
|
53
|
+
class ServerError < ConnectionError; end # :nodoc:
|
54
|
+
|
55
|
+
# 405 Method Not Allowed
|
56
|
+
class MethodNotAllowed < ClientError # :nodoc:
|
57
|
+
def allowed_methods
|
58
|
+
@response['Allow'].split(',').map { |verb| verb.strip.downcase.to_sym }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class Connection
|
63
|
+
attr_reader :site, :user, :password, :timeout
|
64
|
+
attr_writer :user, :password, :timeout
|
65
|
+
|
66
|
+
def initialize(site)
|
67
|
+
raise ArgumentError, 'Missing site URI' unless site
|
68
|
+
@user = @password = nil
|
69
|
+
self.site = site
|
70
|
+
end
|
71
|
+
|
72
|
+
def site=(uri_string)
|
73
|
+
@site = uri_string.is_a?(URI) ? uri_string : URI.parse(uri_string)
|
74
|
+
@user = URI.decode(@site.user) if @site.user
|
75
|
+
@password = URI.decode(@site.password) if @site.password
|
76
|
+
end
|
77
|
+
|
78
|
+
def get(path, headers = {})
|
79
|
+
req = Net::HTTP::Get.new(path)
|
80
|
+
set_request_headers(req, headers)
|
81
|
+
request(req)
|
82
|
+
end
|
83
|
+
|
84
|
+
def delete(path, headers = {})
|
85
|
+
req = Net::HTTP::Delete.new(path)
|
86
|
+
set_request_headers(req, headers)
|
87
|
+
request(req)
|
88
|
+
end
|
89
|
+
|
90
|
+
def put(path, body='', headers = {})
|
91
|
+
req = Net::HTTP::Put.new(path)
|
92
|
+
set_request_headers(req, headers)
|
93
|
+
req.body = body
|
94
|
+
request(req)
|
95
|
+
end
|
96
|
+
|
97
|
+
def post(path, body='', headers = {})
|
98
|
+
req = Net::HTTP::Post.new(path)
|
99
|
+
set_request_headers(req, headers)
|
100
|
+
req.body = body
|
101
|
+
request(req)
|
102
|
+
end
|
103
|
+
|
104
|
+
def head(path, body='', headers = {})
|
105
|
+
req = Net::HTTP::Head.new(path)
|
106
|
+
request(req)
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
def request(req)
|
111
|
+
res = http.request(req)
|
112
|
+
handle_response(req, res)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Handles response and error codes from remote service.
|
116
|
+
def handle_response(request, response)
|
117
|
+
case response.code.to_i
|
118
|
+
when 301,302
|
119
|
+
raise(Redirection.new(request, response))
|
120
|
+
when 200...400
|
121
|
+
response
|
122
|
+
when 400
|
123
|
+
raise(BadRequest.new(request, response))
|
124
|
+
when 401
|
125
|
+
raise(UnauthorizedAccess.new(request, response))
|
126
|
+
when 403
|
127
|
+
raise(ForbiddenAccess.new(request, response))
|
128
|
+
when 404
|
129
|
+
raise(ResourceNotFound.new(request, response))
|
130
|
+
when 405
|
131
|
+
raise(MethodNotAllowed.new(request, response))
|
132
|
+
when 409
|
133
|
+
raise(ResourceConflict.new(request, response))
|
134
|
+
when 412
|
135
|
+
raise(PreconditionFailed.new(request, response))
|
136
|
+
when 422
|
137
|
+
raise(ResourceInvalid.new(request, response))
|
138
|
+
when 401...500
|
139
|
+
raise(ClientError.new(request, response))
|
140
|
+
when 500...600
|
141
|
+
raise(ServerError.new(request, response))
|
142
|
+
else
|
143
|
+
raise(ConnectionError.new(request, response, "Unknown response code: #{response.code}"))
|
144
|
+
end
|
145
|
+
begin
|
146
|
+
if response.body.blank?
|
147
|
+
nil
|
148
|
+
else
|
149
|
+
hash = JSON(response.body)
|
150
|
+
normalize_hash(hash)
|
151
|
+
end
|
152
|
+
rescue JSON::ParserError => e
|
153
|
+
raise(ConnectionError.new(request, response, "Invalid json response: #{e.body}"))
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def normalize_hash(hash)
|
158
|
+
hash.inject(HashWithIndifferentAccess.new({})) do |normalized, (k, v)|
|
159
|
+
v = normalize_hash(v) if v.is_a?(Hash)
|
160
|
+
normalized[k.to_sym] = v
|
161
|
+
normalized
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def http
|
166
|
+
http = Net::HTTP.new(@site.host, @site.port)
|
167
|
+
http.use_ssl = @site.is_a?(URI::HTTPS)
|
168
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl
|
169
|
+
http.read_timeout = @timeout if @timeout
|
170
|
+
http
|
171
|
+
end
|
172
|
+
|
173
|
+
def default_header
|
174
|
+
@default_header ||= {
|
175
|
+
"Accept" => "application/json",
|
176
|
+
'Content-Type' => "application/json"
|
177
|
+
}
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
def set_request_headers(request, headers={})
|
182
|
+
headers = authorization_header.update(default_header).update(headers)
|
183
|
+
headers.each do |k,v|
|
184
|
+
request[k.to_s] = v
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Sets authorization header
|
189
|
+
def authorization_header
|
190
|
+
(@user || @password ? { 'Authorization' => 'Basic ' + ["#{@user}:#{ @password}"].pack('m').delete("\r\n") } : {})
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,340 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_support'
|
3
|
+
require 'json'
|
4
|
+
module CouchResource
|
5
|
+
module Struct
|
6
|
+
def self.included(base)
|
7
|
+
base.send(:extend, ClassMethods)
|
8
|
+
base.send(:include, InstanceMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
#
|
13
|
+
# define a string attribtue
|
14
|
+
# options are :
|
15
|
+
# * <tt>:validates</tt> - see CouchResource::Validations
|
16
|
+
#
|
17
|
+
def string(name, option={})
|
18
|
+
option[:is_a] = :string
|
19
|
+
register_attribute_member(name, option)
|
20
|
+
define_attribute_accessor(name, option)
|
21
|
+
define_validations(name, option)
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# define a number attribute
|
26
|
+
# options are :
|
27
|
+
# * <tt>:validates</tt> - see CouchResource::Validations
|
28
|
+
#
|
29
|
+
def number(name, option={})
|
30
|
+
option[:is_a] = :number
|
31
|
+
register_attribute_member(name, option)
|
32
|
+
define_attribute_accessor(name, option)
|
33
|
+
define_validations(name, option)
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# define a boolean attribute
|
38
|
+
# options are :
|
39
|
+
# * <tt>:validates</tt> - see CouchResource::Validations
|
40
|
+
#
|
41
|
+
def boolean(name, option={})
|
42
|
+
option[:is_a] = :boolean
|
43
|
+
register_attribute_member(name, option)
|
44
|
+
define_attribute_accessor(name, option)
|
45
|
+
method = <<-EOS
|
46
|
+
def #{name}?
|
47
|
+
get_attribute(:#{name})
|
48
|
+
end
|
49
|
+
EOS
|
50
|
+
class_eval(method, __FILE__, __LINE__)
|
51
|
+
define_validations(name, option)
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# define a array attribute, each of which elements is a primitive (one of string, number, array, boolean or hash) object
|
56
|
+
# options are :
|
57
|
+
#
|
58
|
+
def array(name, option={})
|
59
|
+
option[:is_a] = :array
|
60
|
+
register_attribute_member(name, option)
|
61
|
+
define_attribute_accessor(name, option)
|
62
|
+
define_validations(name, option)
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# define a collection attribute, each of which elements is an object specified by the <tt>:is_a</tt> option.
|
67
|
+
# options are :
|
68
|
+
# * <tt>:each</tt> - set the class to encode/decode each of Hash object (default is :hash, which means no encoding/decoding will be processed)
|
69
|
+
# * <tt>:validates</tt> - see CouchResource::Validations
|
70
|
+
#
|
71
|
+
def collection(name, option={})
|
72
|
+
option = {
|
73
|
+
:each => :hash
|
74
|
+
}.update(option)
|
75
|
+
option[:is_a] = :collection
|
76
|
+
register_attribute_member(name, option)
|
77
|
+
define_attribute_accessor(name, option)
|
78
|
+
define_validations(name, option)
|
79
|
+
end
|
80
|
+
|
81
|
+
#
|
82
|
+
# define a object attribute
|
83
|
+
# <tt>options</tt> are :
|
84
|
+
# * <tt>:is_a</tt> - set the class to encode/decode Hash object (default is :hash, which means no encoding/decoding will be processed)
|
85
|
+
# * <tt>:validates</tt> - see CouchResource::Validations
|
86
|
+
#
|
87
|
+
def object(name, option={})
|
88
|
+
unless option.has_key?(:is_a)
|
89
|
+
option[:is_a] = :hash
|
90
|
+
end
|
91
|
+
register_attribute_member(name, option)
|
92
|
+
define_attribute_accessor(name, option)
|
93
|
+
define_validations(name, option)
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
# define a datetime object (extension of string)
|
98
|
+
# options are :
|
99
|
+
# * <tt>:validates</tt> - see CouchResource::Validations
|
100
|
+
#
|
101
|
+
def datetime(name, option={})
|
102
|
+
option[:is_a] = :datetime
|
103
|
+
register_attribute_member(name, option)
|
104
|
+
define_attribute_accessor(name, option)
|
105
|
+
define_validations(name, option)
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
def from_hash(hash)
|
110
|
+
hash ||= {}
|
111
|
+
hash.symbolize_keys!
|
112
|
+
instance = self.new
|
113
|
+
(read_inheritable_attribute(:attribute_members) || {}).each do |name, option|
|
114
|
+
instance.set_attribute(name, hash[name.to_sym])
|
115
|
+
end
|
116
|
+
instance
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
def register_attribute_member(name, option = {})
|
121
|
+
attribute_members = read_inheritable_attribute(:attribute_members)
|
122
|
+
attribute_members ||= HashWithIndifferentAccess.new({})
|
123
|
+
attribute_members[name] = option
|
124
|
+
write_inheritable_attribute(:attribute_members, attribute_members)
|
125
|
+
end
|
126
|
+
|
127
|
+
def define_attribute_accessor(name, option={})
|
128
|
+
define_attribute_read_accessor(name, option)
|
129
|
+
define_attribute_write_accessor(name, option)
|
130
|
+
end
|
131
|
+
|
132
|
+
def define_attribute_read_accessor(name, option={})
|
133
|
+
method = <<-EOS
|
134
|
+
def #{name}
|
135
|
+
get_attribute(:#{name})
|
136
|
+
end
|
137
|
+
def #{name}_before_type_cast
|
138
|
+
get_attribute_before_type_cast(:#{name})
|
139
|
+
end
|
140
|
+
EOS
|
141
|
+
class_eval(method, __FILE__, __LINE__)
|
142
|
+
end
|
143
|
+
|
144
|
+
def define_attribute_write_accessor(name, option={})
|
145
|
+
method = <<-EOS
|
146
|
+
def #{name}=(value)
|
147
|
+
set_attribute(:#{name}, value)
|
148
|
+
end
|
149
|
+
EOS
|
150
|
+
class_eval(method, __FILE__, __LINE__)
|
151
|
+
end
|
152
|
+
|
153
|
+
def define_validations(name, option={})
|
154
|
+
(option[:validates] || []).each do |validation_type, validate_option|
|
155
|
+
args = validate_option.nil? ? [name] : [name, validate_option]
|
156
|
+
case validation_type.to_sym
|
157
|
+
when :each
|
158
|
+
proc = validate_option[:proc]
|
159
|
+
send("validates_each", *args) do |record, attr, value|
|
160
|
+
proc.call(record, attr, value) if proc
|
161
|
+
end
|
162
|
+
when :confirmation_of, :presense_of, :length_of, :size_of, :format_of,
|
163
|
+
:inclusion_of, :exclusion_of, :numericality_of, :children_of
|
164
|
+
send("validates_#{validation_type}", *args)
|
165
|
+
else
|
166
|
+
raise ArgumentError, "invalid validation type (#{validation_type})"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
module InstanceMethods
|
173
|
+
def [](name)
|
174
|
+
get_attribute(name)
|
175
|
+
end
|
176
|
+
|
177
|
+
def []=(name, value)
|
178
|
+
set_attribute(name, value)
|
179
|
+
end
|
180
|
+
|
181
|
+
def get_attribute_option(attr_name)
|
182
|
+
(self.class.read_inheritable_attribute(:attribute_members) || {})[attr_name]
|
183
|
+
end
|
184
|
+
|
185
|
+
def set_attribute(name, value)
|
186
|
+
@attributes ||= HashWithIndifferentAccess.new({})
|
187
|
+
# inplicit type cast
|
188
|
+
attribute_members = self.class.read_inheritable_attribute(:attribute_members) || {}
|
189
|
+
if attribute_members.has_key?(name)
|
190
|
+
option = attribute_members[name]
|
191
|
+
if value.nil?
|
192
|
+
@attributes[name] = nil
|
193
|
+
else
|
194
|
+
if option[:allow_nil] && value.blank?
|
195
|
+
@attributes[name] = nil
|
196
|
+
else
|
197
|
+
klass = option[:is_a]
|
198
|
+
@attributes[name] = case klass
|
199
|
+
when :string, :number, :boolean, :array, :hash, :datetime
|
200
|
+
self.send("type_cast_for_#{klass}_attributes", value)
|
201
|
+
when :collection
|
202
|
+
self.send("type_cast_for_collection_attributes", value, option[:each])
|
203
|
+
else
|
204
|
+
self.send("type_cast_for_object_attributes", value, klass)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
else
|
209
|
+
@attributes[name] = nil
|
210
|
+
end
|
211
|
+
value
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
def get_attribute(name)
|
216
|
+
value = get_attribute_before_type_cast(name)
|
217
|
+
attribute_members = self.class.read_inheritable_attribute(:attribute_members) || {}
|
218
|
+
if attribute_members.has_key?(name)
|
219
|
+
value = @attributes[name]
|
220
|
+
option = attribute_members[name]
|
221
|
+
value
|
222
|
+
#if value.nil?
|
223
|
+
# nil
|
224
|
+
#else
|
225
|
+
# klass = option[:is_a]
|
226
|
+
# case klass
|
227
|
+
# when :string, :number, :boolean, :array, :hash, :datetime
|
228
|
+
# self.send("type_cast_for_#{klass}_attributes", value)
|
229
|
+
# when :collection
|
230
|
+
# self.send("type_cast_for_collection_attributes", value, option[:each])
|
231
|
+
# else
|
232
|
+
# self.send("type_cast_for_object_attributes", value, klass)
|
233
|
+
# end
|
234
|
+
#end
|
235
|
+
else
|
236
|
+
nil
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def get_attribute_before_type_cast(name)
|
241
|
+
@attributes ||= HashWithIndifferentAccess.new({})
|
242
|
+
@attributes[name]
|
243
|
+
end
|
244
|
+
|
245
|
+
def to_hash
|
246
|
+
hash = HashWithIndifferentAccess.new({ :class => self.class.name })
|
247
|
+
(self.class.read_inheritable_attribute(:attribute_members) || {}).each do |name, option|
|
248
|
+
klass = option[:is_a]
|
249
|
+
value = get_attribute(name)
|
250
|
+
case klass
|
251
|
+
when :string, :number, :boolean, :array, :hash, :datetime
|
252
|
+
hash[name] = value
|
253
|
+
when :collection
|
254
|
+
hash[name] = value.map(&:to_hash)
|
255
|
+
else
|
256
|
+
if value
|
257
|
+
hash[name] = value.to_hash
|
258
|
+
else
|
259
|
+
hash[name] = nil
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
hash
|
264
|
+
end
|
265
|
+
|
266
|
+
private
|
267
|
+
def type_cast_for_string_attributes(value)
|
268
|
+
value.is_a?(String) ? value : value.to_s
|
269
|
+
end
|
270
|
+
|
271
|
+
def type_cast_for_number_attributes(value)
|
272
|
+
if value.is_a?(Numeric)
|
273
|
+
value
|
274
|
+
else
|
275
|
+
v = value.to_s
|
276
|
+
if v =~ /^\d+$/
|
277
|
+
v.to_i
|
278
|
+
else
|
279
|
+
v.to_f
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def type_cast_for_boolean_attributes(value)
|
285
|
+
value && true
|
286
|
+
end
|
287
|
+
|
288
|
+
def type_cast_for_array_attributes(value)
|
289
|
+
if value.is_a?(Array)
|
290
|
+
value
|
291
|
+
else
|
292
|
+
if value.respond_to?(:to_a)
|
293
|
+
value.to_a
|
294
|
+
else
|
295
|
+
[value]
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def type_cast_for_collection_attributes(v1, each)
|
301
|
+
type_cast_for_array_attributes(v1).map { |v2|
|
302
|
+
type_cast_for_object_attributes(v2, each)
|
303
|
+
}.reject { |v3|
|
304
|
+
v3.nil?
|
305
|
+
}
|
306
|
+
end
|
307
|
+
|
308
|
+
def type_cast_for_hash_attributes(value)
|
309
|
+
if value.is_a?(Hash)
|
310
|
+
value
|
311
|
+
else
|
312
|
+
if value.respond_to?(:to_hash)
|
313
|
+
value.to_hash
|
314
|
+
else
|
315
|
+
nil
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def type_cast_for_object_attributes(value, klass)
|
321
|
+
if value.is_a?(klass)
|
322
|
+
value
|
323
|
+
elsif value.is_a?(Hash)
|
324
|
+
klass.from_hash(value)
|
325
|
+
else
|
326
|
+
nil
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
def type_cast_for_datetime_attributes(value)
|
331
|
+
case value
|
332
|
+
when Date, Time, DateTime
|
333
|
+
value
|
334
|
+
else
|
335
|
+
DateTime.parse(value.to_s) rescue nil
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|