samlown-couchrest_extended_document 1.0.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 +176 -0
- data/README.md +46 -0
- data/Rakefile +67 -0
- data/THANKS.md +19 -0
- data/examples/model/example.rb +144 -0
- data/history.txt +130 -0
- data/lib/couchrest/casted_array.rb +25 -0
- data/lib/couchrest/casted_model.rb +59 -0
- data/lib/couchrest/extended_document.rb +318 -0
- data/lib/couchrest/mixins.rb +12 -0
- data/lib/couchrest/mixins/attribute_protection.rb +74 -0
- data/lib/couchrest/mixins/callbacks.rb +532 -0
- data/lib/couchrest/mixins/class_proxy.rb +120 -0
- data/lib/couchrest/mixins/collection.rb +260 -0
- data/lib/couchrest/mixins/design_doc.rb +121 -0
- data/lib/couchrest/mixins/document_queries.rb +80 -0
- data/lib/couchrest/mixins/extended_attachments.rb +70 -0
- data/lib/couchrest/mixins/properties.rb +154 -0
- data/lib/couchrest/mixins/validation.rb +245 -0
- data/lib/couchrest/mixins/views.rb +148 -0
- data/lib/couchrest/monkeypatches.rb +5 -0
- data/lib/couchrest/property.rb +50 -0
- data/lib/couchrest/support/couchrest.rb +56 -0
- data/lib/couchrest/support/rails.rb +42 -0
- data/lib/couchrest/typecast.rb +175 -0
- data/lib/couchrest/validation/auto_validate.rb +156 -0
- data/lib/couchrest/validation/contextual_validators.rb +78 -0
- data/lib/couchrest/validation/validation_errors.rb +125 -0
- data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
- data/lib/couchrest/validation/validators/confirmation_validator.rb +107 -0
- data/lib/couchrest/validation/validators/format_validator.rb +122 -0
- data/lib/couchrest/validation/validators/formats/email.rb +66 -0
- data/lib/couchrest/validation/validators/formats/url.rb +43 -0
- data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
- data/lib/couchrest/validation/validators/length_validator.rb +139 -0
- data/lib/couchrest/validation/validators/method_validator.rb +89 -0
- data/lib/couchrest/validation/validators/numeric_validator.rb +109 -0
- data/lib/couchrest/validation/validators/required_field_validator.rb +114 -0
- data/spec/couchrest/attribute_protection_spec.rb +150 -0
- data/spec/couchrest/casted_extended_doc_spec.rb +79 -0
- data/spec/couchrest/casted_model_spec.rb +406 -0
- data/spec/couchrest/extended_doc_attachment_spec.rb +135 -0
- data/spec/couchrest/extended_doc_inherited_spec.rb +40 -0
- data/spec/couchrest/extended_doc_spec.rb +824 -0
- data/spec/couchrest/extended_doc_subclass_spec.rb +99 -0
- data/spec/couchrest/extended_doc_view_spec.rb +473 -0
- data/spec/couchrest/property_spec.rb +628 -0
- data/spec/fixtures/attachments/README +3 -0
- data/spec/fixtures/attachments/couchdb.png +0 -0
- data/spec/fixtures/attachments/test.html +11 -0
- data/spec/fixtures/more/article.rb +35 -0
- data/spec/fixtures/more/card.rb +22 -0
- data/spec/fixtures/more/cat.rb +22 -0
- data/spec/fixtures/more/course.rb +22 -0
- data/spec/fixtures/more/event.rb +8 -0
- data/spec/fixtures/more/invoice.rb +17 -0
- data/spec/fixtures/more/person.rb +9 -0
- data/spec/fixtures/more/question.rb +6 -0
- data/spec/fixtures/more/service.rb +12 -0
- data/spec/fixtures/more/user.rb +22 -0
- data/spec/fixtures/views/lib.js +3 -0
- data/spec/fixtures/views/test_view/lib.js +3 -0
- data/spec/fixtures/views/test_view/only-map.js +4 -0
- data/spec/fixtures/views/test_view/test-map.js +3 -0
- data/spec/fixtures/views/test_view/test-reduce.js +3 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +49 -0
- data/utils/remap.rb +27 -0
- data/utils/subset.rb +30 -0
- metadata +181 -0
@@ -0,0 +1,148 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module Mixins
|
3
|
+
module Views
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
# Define a CouchDB view. The name of the view will be the concatenation
|
11
|
+
# of <tt>by</tt> and the keys joined by <tt>_and_</tt>
|
12
|
+
#
|
13
|
+
# ==== Example views:
|
14
|
+
#
|
15
|
+
# class Post
|
16
|
+
# # view with default options
|
17
|
+
# # query with Post.by_date
|
18
|
+
# view_by :date, :descending => true
|
19
|
+
#
|
20
|
+
# # view with compound sort-keys
|
21
|
+
# # query with Post.by_user_id_and_date
|
22
|
+
# view_by :user_id, :date
|
23
|
+
#
|
24
|
+
# # view with custom map/reduce functions
|
25
|
+
# # query with Post.by_tags :reduce => true
|
26
|
+
# view_by :tags,
|
27
|
+
# :map =>
|
28
|
+
# "function(doc) {
|
29
|
+
# if (doc['couchrest-type'] == 'Post' && doc.tags) {
|
30
|
+
# doc.tags.forEach(function(tag){
|
31
|
+
# emit(doc.tag, 1);
|
32
|
+
# });
|
33
|
+
# }
|
34
|
+
# }",
|
35
|
+
# :reduce =>
|
36
|
+
# "function(keys, values, rereduce) {
|
37
|
+
# return sum(values);
|
38
|
+
# }"
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# <tt>view_by :date</tt> will create a view defined by this Javascript
|
42
|
+
# function:
|
43
|
+
#
|
44
|
+
# function(doc) {
|
45
|
+
# if (doc['couchrest-type'] == 'Post' && doc.date) {
|
46
|
+
# emit(doc.date, null);
|
47
|
+
# }
|
48
|
+
# }
|
49
|
+
#
|
50
|
+
# It can be queried by calling <tt>Post.by_date</tt> which accepts all
|
51
|
+
# valid options for CouchRest::Database#view. In addition, calling with
|
52
|
+
# the <tt>:raw => true</tt> option will return the view rows
|
53
|
+
# themselves. By default <tt>Post.by_date</tt> will return the
|
54
|
+
# documents included in the generated view.
|
55
|
+
#
|
56
|
+
# Calling with :database => [instance of CouchRest::Database] will
|
57
|
+
# send the query to a specific database, otherwise it will go to
|
58
|
+
# the model's default database (use_database)
|
59
|
+
#
|
60
|
+
# CouchRest::Database#view options can be applied at view definition
|
61
|
+
# time as defaults, and they will be curried and used at view query
|
62
|
+
# time. Or they can be overridden at query time.
|
63
|
+
#
|
64
|
+
# Custom views can be queried with <tt>:reduce => true</tt> to return
|
65
|
+
# reduce results. The default for custom views is to query with
|
66
|
+
# <tt>:reduce => false</tt>.
|
67
|
+
#
|
68
|
+
# Views are generated (on a per-model basis) lazily on first-access.
|
69
|
+
# This means that if you are deploying changes to a view, the views for
|
70
|
+
# that model won't be available until generation is complete. This can
|
71
|
+
# take some time with large databases. Strategies are in the works.
|
72
|
+
#
|
73
|
+
# To understand the capabilities of this view system more completely,
|
74
|
+
# it is recommended that you read the RSpec file at
|
75
|
+
# <tt>spec/couchrest/more/extended_doc_spec.rb</tt>.
|
76
|
+
|
77
|
+
def view_by(*keys)
|
78
|
+
opts = keys.pop if keys.last.is_a?(Hash)
|
79
|
+
opts ||= {}
|
80
|
+
ducktype = opts.delete(:ducktype)
|
81
|
+
unless ducktype || opts[:map]
|
82
|
+
opts[:guards] ||= []
|
83
|
+
opts[:guards].push "(doc['couchrest-type'] == '#{self.to_s}')"
|
84
|
+
end
|
85
|
+
keys.push opts
|
86
|
+
design_doc.view_by(*keys)
|
87
|
+
req_design_doc_refresh
|
88
|
+
end
|
89
|
+
|
90
|
+
# returns stored defaults if there is a view named this in the design doc
|
91
|
+
def has_view?(view)
|
92
|
+
view = view.to_s
|
93
|
+
design_doc && design_doc['views'] && design_doc['views'][view]
|
94
|
+
end
|
95
|
+
|
96
|
+
# Dispatches to any named view.
|
97
|
+
def view(name, query={}, &block)
|
98
|
+
db = query.delete(:database) || database
|
99
|
+
refresh_design_doc(db)
|
100
|
+
query[:raw] = true if query[:reduce]
|
101
|
+
raw = query.delete(:raw)
|
102
|
+
fetch_view_with_docs(db, name, query, raw, &block)
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def fetch_view_with_docs(db, name, opts, raw=false, &block)
|
108
|
+
if raw || (opts.has_key?(:include_docs) && opts[:include_docs] == false)
|
109
|
+
fetch_view(db, name, opts, &block)
|
110
|
+
else
|
111
|
+
begin
|
112
|
+
if block.nil?
|
113
|
+
collection_proxy_for(design_doc, name, opts.merge({:include_docs => true}))
|
114
|
+
else
|
115
|
+
view = fetch_view db, name, opts.merge({:include_docs => true}), &block
|
116
|
+
view['rows'].collect{|r|create_from_database(r['doc'])} if view['rows']
|
117
|
+
end
|
118
|
+
rescue
|
119
|
+
# fallback for old versions of couchdb that don't
|
120
|
+
# have include_docs support
|
121
|
+
view = fetch_view(db, name, opts, &block)
|
122
|
+
view['rows'].collect{|r|create_from_database(db.get(r['id']))} if view['rows']
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def fetch_view(db, view_name, opts, &block)
|
128
|
+
raise "A view needs a database to operate on (specify :database option, or use_database in the #{self.class} class)" unless db
|
129
|
+
retryable = true
|
130
|
+
begin
|
131
|
+
design_doc.view_on(db, view_name, opts, &block)
|
132
|
+
# the design doc may not have been saved yet on this database
|
133
|
+
rescue HttpAbstraction::ResourceNotFound => e
|
134
|
+
if retryable
|
135
|
+
save_design_doc(db)
|
136
|
+
retryable = false
|
137
|
+
retry
|
138
|
+
else
|
139
|
+
raise e
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
end # module ClassMethods
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module CouchRest
|
2
|
+
|
3
|
+
# Basic attribute support for adding getter/setter + validation
|
4
|
+
class Property
|
5
|
+
attr_reader :name, :type, :read_only, :alias, :default, :casted, :init_method, :options
|
6
|
+
|
7
|
+
# attribute to define
|
8
|
+
def initialize(name, type = nil, options = {})
|
9
|
+
@name = name.to_s
|
10
|
+
parse_type(type)
|
11
|
+
parse_options(options)
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def parse_type(type)
|
18
|
+
if type.nil?
|
19
|
+
@type = String
|
20
|
+
elsif type.is_a?(Array) && type.empty?
|
21
|
+
@type = [Object]
|
22
|
+
else
|
23
|
+
base_type = type.is_a?(Array) ? type.first : type
|
24
|
+
if base_type.is_a?(String)
|
25
|
+
if base_type.downcase == 'boolean'
|
26
|
+
base_type = TrueClass
|
27
|
+
else
|
28
|
+
begin
|
29
|
+
base_type = ::CouchRest.constantize(base_type)
|
30
|
+
rescue # leave base type as a string and convert in more/typecast
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
@type = type.is_a?(Array) ? [base_type] : base_type
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def parse_options(options)
|
39
|
+
return if options.empty?
|
40
|
+
@validation_format = options.delete(:format) if options[:format]
|
41
|
+
@read_only = options.delete(:read_only) if options[:read_only]
|
42
|
+
@alias = options.delete(:alias) if options[:alias]
|
43
|
+
@default = options.delete(:default) unless options[:default].nil?
|
44
|
+
@casted = options[:casted] ? true : false
|
45
|
+
@init_method = options[:init_method] ? options.delete(:init_method) : 'new'
|
46
|
+
@options = options
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
|
2
|
+
module CouchRest
|
3
|
+
|
4
|
+
|
5
|
+
# The CouchRest module methods handle the basic JSON serialization
|
6
|
+
# and deserialization, as well as query parameters. The module also includes
|
7
|
+
# some helpers for tasks like instantiating a new Database or Server instance.
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# extracted from Extlib
|
11
|
+
#
|
12
|
+
# Constantize tries to find a declared constant with the name specified
|
13
|
+
# in the string. It raises a NameError when the name is not in CamelCase
|
14
|
+
# or is not initialized.
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# "Module".constantize #=> Module
|
18
|
+
# "Class".constantize #=> Class
|
19
|
+
def constantize(camel_cased_word)
|
20
|
+
unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
|
21
|
+
raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
|
22
|
+
end
|
23
|
+
|
24
|
+
Object.module_eval("::#{$1}", __FILE__, __LINE__)
|
25
|
+
end
|
26
|
+
|
27
|
+
# extracted from Extlib
|
28
|
+
#
|
29
|
+
# Capitalizes the first word and turns underscores into spaces and strips _id.
|
30
|
+
# Like titleize, this is meant for creating pretty output.
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# "employee_salary" #=> "Employee salary"
|
34
|
+
# "author_id" #=> "Author"
|
35
|
+
def humanize(lower_case_and_underscored_word)
|
36
|
+
lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
class Database
|
42
|
+
|
43
|
+
alias :delete_old! :delete!
|
44
|
+
def delete!
|
45
|
+
clear_extended_doc_fresh_cache
|
46
|
+
delete_old!
|
47
|
+
end
|
48
|
+
|
49
|
+
# If the database is deleted, ensure that the design docs will be refreshed.
|
50
|
+
def clear_extended_doc_fresh_cache
|
51
|
+
::CouchRest::ExtendedDocument.subclasses.each{|klass| klass.req_design_doc_refresh if klass.respond_to?(:req_design_doc_refresh)}
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# This file contains various hacks for Rails compatibility.
|
2
|
+
class Hash
|
3
|
+
# Hack so that CouchRest::Document, which descends from Hash,
|
4
|
+
# doesn't appear to Rails routing as a Hash of options
|
5
|
+
def self.===(other)
|
6
|
+
return false if self == Hash && other.is_a?(CouchRest::Document)
|
7
|
+
super
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
CouchRest::Document.class_eval do
|
12
|
+
# Need this when passing doc to a resourceful route
|
13
|
+
alias_method :to_param, :id
|
14
|
+
|
15
|
+
# Hack so that CouchRest::Document, which descends from Hash,
|
16
|
+
# doesn't appear to Rails routing as a Hash of options
|
17
|
+
def is_a?(o)
|
18
|
+
return false if o == Hash
|
19
|
+
super
|
20
|
+
end
|
21
|
+
alias_method :kind_of?, :is_a?
|
22
|
+
end
|
23
|
+
|
24
|
+
CouchRest::CastedModel.class_eval do
|
25
|
+
# The to_param method is needed for rails to generate resourceful routes.
|
26
|
+
# In your controller, remember that it's actually the id of the document.
|
27
|
+
def id
|
28
|
+
return nil if base_doc.nil?
|
29
|
+
base_doc.id
|
30
|
+
end
|
31
|
+
alias_method :to_param, :id
|
32
|
+
end
|
33
|
+
|
34
|
+
require Pathname.new(File.dirname(__FILE__)).join('..', 'validation', 'validation_errors')
|
35
|
+
|
36
|
+
CouchRest::Validation::ValidationErrors.class_eval do
|
37
|
+
# Returns the total number of errors added. Two errors added to the same attribute will be counted as such.
|
38
|
+
# This method is called by error_messages_for
|
39
|
+
def count
|
40
|
+
errors.values.inject(0) { |error_count, errors_for_attribute| error_count + errors_for_attribute.size }
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'bigdecimal'
|
3
|
+
require 'bigdecimal/util'
|
4
|
+
require File.join(File.dirname(__FILE__), 'property')
|
5
|
+
|
6
|
+
class Time
|
7
|
+
# returns a local time value much faster than Time.parse
|
8
|
+
def self.mktime_with_offset(string)
|
9
|
+
string =~ /(\d{4})[\-|\/](\d{2})[\-|\/](\d{2})[T|\s](\d{2}):(\d{2}):(\d{2})([\+|\s|\-])*(\d{2}):?(\d{2})/
|
10
|
+
# $1 = year
|
11
|
+
# $2 = month
|
12
|
+
# $3 = day
|
13
|
+
# $4 = hours
|
14
|
+
# $5 = minutes
|
15
|
+
# $6 = seconds
|
16
|
+
# $7 = time zone direction
|
17
|
+
# $8 = tz difference
|
18
|
+
# utc time with wrong TZ info:
|
19
|
+
time = mktime($1, RFC2822_MONTH_NAME[$2.to_i - 1], $3, $4, $5, $6, $7)
|
20
|
+
tz_difference = ("#{$7 == '-' ? '+' : '-'}#{$8}".to_i * 3600)
|
21
|
+
time + tz_difference + zone_offset(time.zone)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module CouchRest
|
26
|
+
module More
|
27
|
+
module Typecast
|
28
|
+
|
29
|
+
def typecast_value(value, klass, init_method)
|
30
|
+
return nil if value.nil?
|
31
|
+
klass = ::CouchRest.constantize(klass) unless klass.is_a?(Class)
|
32
|
+
if value.instance_of?(klass) || klass == Object
|
33
|
+
value
|
34
|
+
elsif [String, TrueClass, Integer, Float, BigDecimal, DateTime, Time, Date, Class].include?(klass)
|
35
|
+
send('typecast_to_'+klass.to_s.downcase, value)
|
36
|
+
else
|
37
|
+
# Allow the init_method to be defined as a Proc for advanced conversion
|
38
|
+
init_method.is_a?(Proc) ? init_method.call(value) : klass.send(init_method, value)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
# Typecast a value to an Integer
|
45
|
+
def typecast_to_integer(value)
|
46
|
+
typecast_to_numeric(value, :to_i)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Typecast a value to a String
|
50
|
+
def typecast_to_string(value)
|
51
|
+
value.to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
# Typecast a value to a true or false
|
55
|
+
def typecast_to_trueclass(value)
|
56
|
+
if value.kind_of?(Integer)
|
57
|
+
return true if value == 1
|
58
|
+
return false if value == 0
|
59
|
+
elsif value.respond_to?(:to_s)
|
60
|
+
return true if %w[ true 1 t ].include?(value.to_s.downcase)
|
61
|
+
return false if %w[ false 0 f ].include?(value.to_s.downcase)
|
62
|
+
end
|
63
|
+
value
|
64
|
+
end
|
65
|
+
|
66
|
+
# Typecast a value to a BigDecimal
|
67
|
+
def typecast_to_bigdecimal(value)
|
68
|
+
if value.kind_of?(Integer)
|
69
|
+
value.to_s.to_d
|
70
|
+
else
|
71
|
+
typecast_to_numeric(value, :to_d)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Typecast a value to a Float
|
76
|
+
def typecast_to_float(value)
|
77
|
+
typecast_to_numeric(value, :to_f)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Match numeric string
|
81
|
+
def typecast_to_numeric(value, method)
|
82
|
+
if value.respond_to?(:to_str)
|
83
|
+
if value.to_str =~ /\A(-?(?:0|[1-9]\d*)(?:\.\d+)?|(?:\.\d+))\z/
|
84
|
+
$1.send(method)
|
85
|
+
else
|
86
|
+
value
|
87
|
+
end
|
88
|
+
elsif value.respond_to?(method)
|
89
|
+
value.send(method)
|
90
|
+
else
|
91
|
+
value
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Typecasts an arbitrary value to a DateTime.
|
96
|
+
# Handles both Hashes and DateTime instances.
|
97
|
+
# This is slow!! Use Time instead.
|
98
|
+
def typecast_to_datetime(value)
|
99
|
+
if value.is_a?(Hash)
|
100
|
+
typecast_hash_to_datetime(value)
|
101
|
+
else
|
102
|
+
DateTime.parse(value.to_s)
|
103
|
+
end
|
104
|
+
rescue ArgumentError
|
105
|
+
value
|
106
|
+
end
|
107
|
+
|
108
|
+
# Typecasts an arbitrary value to a Date
|
109
|
+
# Handles both Hashes and Date instances.
|
110
|
+
def typecast_to_date(value)
|
111
|
+
if value.is_a?(Hash)
|
112
|
+
typecast_hash_to_date(value)
|
113
|
+
elsif value.is_a?(Time) # sometimes people think date is time!
|
114
|
+
value.to_date
|
115
|
+
elsif value.to_s =~ /(\d{4})[\-|\/](\d{2})[\-|\/](\d{2})/
|
116
|
+
# Faster than parsing the date
|
117
|
+
Date.new($1.to_i, $2.to_i, $3.to_i)
|
118
|
+
else
|
119
|
+
Date.parse(value)
|
120
|
+
end
|
121
|
+
rescue ArgumentError
|
122
|
+
value
|
123
|
+
end
|
124
|
+
|
125
|
+
# Typecasts an arbitrary value to a Time
|
126
|
+
# Handles both Hashes and Time instances.
|
127
|
+
def typecast_to_time(value)
|
128
|
+
if value.is_a?(Hash)
|
129
|
+
typecast_hash_to_time(value)
|
130
|
+
else
|
131
|
+
Time.mktime_with_offset(value.to_s)
|
132
|
+
end
|
133
|
+
rescue ArgumentError
|
134
|
+
value
|
135
|
+
rescue TypeError
|
136
|
+
value
|
137
|
+
end
|
138
|
+
|
139
|
+
# Creates a DateTime instance from a Hash with keys :year, :month, :day,
|
140
|
+
# :hour, :min, :sec
|
141
|
+
def typecast_hash_to_datetime(value)
|
142
|
+
DateTime.new(*extract_time(value))
|
143
|
+
end
|
144
|
+
|
145
|
+
# Creates a Date instance from a Hash with keys :year, :month, :day
|
146
|
+
def typecast_hash_to_date(value)
|
147
|
+
Date.new(*extract_time(value)[0, 3])
|
148
|
+
end
|
149
|
+
|
150
|
+
# Creates a Time instance from a Hash with keys :year, :month, :day,
|
151
|
+
# :hour, :min, :sec
|
152
|
+
def typecast_hash_to_time(value)
|
153
|
+
Time.local(*extract_time(value))
|
154
|
+
end
|
155
|
+
|
156
|
+
# Extracts the given args from the hash. If a value does not exist, it
|
157
|
+
# uses the value of Time.now.
|
158
|
+
def extract_time(value)
|
159
|
+
now = Time.now
|
160
|
+
[:year, :month, :day, :hour, :min, :sec].map do |segment|
|
161
|
+
typecast_to_numeric(value.fetch(segment, now.send(segment)), :to_i)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Typecast a value to a Class
|
166
|
+
def typecast_to_class(value)
|
167
|
+
::CouchRest.constantize(value.to_s)
|
168
|
+
rescue NameError
|
169
|
+
value
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|