davber_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 +250 -0
- data/Rakefile +69 -0
- data/THANKS.md +22 -0
- data/examples/model/example.rb +144 -0
- data/history.txt +165 -0
- data/lib/couchrest/casted_array.rb +39 -0
- data/lib/couchrest/casted_model.rb +53 -0
- data/lib/couchrest/extended_document.rb +262 -0
- data/lib/couchrest/mixins.rb +12 -0
- data/lib/couchrest/mixins/attribute_protection.rb +74 -0
- data/lib/couchrest/mixins/attributes.rb +75 -0
- data/lib/couchrest/mixins/callbacks.rb +534 -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 +159 -0
- data/lib/couchrest/mixins/document_queries.rb +82 -0
- data/lib/couchrest/mixins/extended_attachments.rb +73 -0
- data/lib/couchrest/mixins/properties.rb +130 -0
- data/lib/couchrest/mixins/typecast.rb +174 -0
- data/lib/couchrest/mixins/views.rb +148 -0
- data/lib/couchrest/property.rb +96 -0
- data/lib/couchrest/support/couchrest.rb +19 -0
- data/lib/couchrest/support/rails.rb +42 -0
- data/lib/couchrest/validation.rb +246 -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/lib/couchrest_extended_document.rb +23 -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 +424 -0
- data/spec/couchrest/extended_doc_attachment_spec.rb +148 -0
- data/spec/couchrest/extended_doc_inherited_spec.rb +40 -0
- data/spec/couchrest/extended_doc_spec.rb +869 -0
- data/spec/couchrest/extended_doc_subclass_spec.rb +101 -0
- data/spec/couchrest/extended_doc_view_spec.rb +529 -0
- data/spec/couchrest/property_spec.rb +790 -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 +25 -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 +7 -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 +225 -0
@@ -0,0 +1,82 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module Mixins
|
3
|
+
module DocumentQueries
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
# Load all documents that have the type field equal to the
|
12
|
+
# name of the current class. Take the standard set of
|
13
|
+
# CouchRest::Database#view options.
|
14
|
+
def all(opts = {}, &block)
|
15
|
+
view(:all, opts, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the number of documents that have the type field
|
19
|
+
# equal to the name of the current class. Takes the standard set of
|
20
|
+
# CouchRest::Database#view options
|
21
|
+
def count(opts = {}, &block)
|
22
|
+
all({:raw => true, :limit => 0}.merge(opts), &block)['total_rows']
|
23
|
+
end
|
24
|
+
|
25
|
+
# Load the first document that have the type field equal to
|
26
|
+
# the name of the current class.
|
27
|
+
#
|
28
|
+
# ==== Returns
|
29
|
+
# Object:: The first object instance available
|
30
|
+
# or
|
31
|
+
# Nil:: if no instances available
|
32
|
+
#
|
33
|
+
# ==== Parameters
|
34
|
+
# opts<Hash>::
|
35
|
+
# View options, see <tt>CouchRest::Database#view</tt> options for more info.
|
36
|
+
def first(opts = {})
|
37
|
+
first_instance = self.all(opts.merge!(:limit => 1))
|
38
|
+
first_instance.empty? ? nil : first_instance.first
|
39
|
+
end
|
40
|
+
|
41
|
+
# Load a document from the database by id
|
42
|
+
# No exceptions will be raised if the document isn't found
|
43
|
+
#
|
44
|
+
# ==== Returns
|
45
|
+
# Object:: if the document was found
|
46
|
+
# or
|
47
|
+
# Nil::
|
48
|
+
#
|
49
|
+
# === Parameters
|
50
|
+
# id<String, Integer>:: Document ID
|
51
|
+
# db<Database>:: optional option to pass a custom database to use
|
52
|
+
def get(id, db = database)
|
53
|
+
begin
|
54
|
+
get!(id, db)
|
55
|
+
rescue
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
alias :find :get
|
60
|
+
|
61
|
+
# Load a document from the database by id
|
62
|
+
# An exception will be raised if the document isn't found
|
63
|
+
#
|
64
|
+
# ==== Returns
|
65
|
+
# Object:: if the document was found
|
66
|
+
# or
|
67
|
+
# Exception
|
68
|
+
#
|
69
|
+
# === Parameters
|
70
|
+
# id<String, Integer>:: Document ID
|
71
|
+
# db<Database>:: optional option to pass a custom database to use
|
72
|
+
def get!(id, db = database)
|
73
|
+
doc = db.get id
|
74
|
+
create_from_database(doc)
|
75
|
+
end
|
76
|
+
alias :find! :get!
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module Mixins
|
3
|
+
module ExtendedAttachments
|
4
|
+
|
5
|
+
# Add a file attachment to the current document. Expects
|
6
|
+
# :file and :name to be included in the arguments.
|
7
|
+
def create_attachment(args={})
|
8
|
+
raise ArgumentError unless args[:file] && args[:name]
|
9
|
+
return if has_attachment?(args[:name])
|
10
|
+
self['_attachments'] ||= {}
|
11
|
+
set_attachment_attr(args)
|
12
|
+
rescue ArgumentError => e
|
13
|
+
raise ArgumentError, 'You must specify :file and :name'
|
14
|
+
end
|
15
|
+
|
16
|
+
# reads the data from an attachment
|
17
|
+
def read_attachment(attachment_name)
|
18
|
+
database.fetch_attachment(self, attachment_name)
|
19
|
+
end
|
20
|
+
|
21
|
+
# modifies a file attachment on the current doc
|
22
|
+
def update_attachment(args={})
|
23
|
+
raise ArgumentError unless args[:file] && args[:name]
|
24
|
+
return unless has_attachment?(args[:name])
|
25
|
+
delete_attachment(args[:name])
|
26
|
+
set_attachment_attr(args)
|
27
|
+
rescue ArgumentError => e
|
28
|
+
raise ArgumentError, 'You must specify :file and :name'
|
29
|
+
end
|
30
|
+
|
31
|
+
# deletes a file attachment from the current doc
|
32
|
+
def delete_attachment(attachment_name)
|
33
|
+
return unless self['_attachments']
|
34
|
+
self['_attachments'].delete attachment_name
|
35
|
+
end
|
36
|
+
|
37
|
+
# returns true if attachment_name exists
|
38
|
+
def has_attachment?(attachment_name)
|
39
|
+
!!(self['_attachments'] && self['_attachments'][attachment_name] && !self['_attachments'][attachment_name].empty?)
|
40
|
+
end
|
41
|
+
|
42
|
+
# returns URL to fetch the attachment from
|
43
|
+
def attachment_url(attachment_name)
|
44
|
+
return unless has_attachment?(attachment_name)
|
45
|
+
"#{database.root}/#{self.id}/#{attachment_name}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# returns URI to fetch the attachment from
|
49
|
+
def attachment_uri(attachment_name)
|
50
|
+
return unless has_attachment?(attachment_name)
|
51
|
+
"#{database.uri}/#{self.id}/#{attachment_name}"
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def get_mime_type(path)
|
57
|
+
return nil if path.nil?
|
58
|
+
type = ::MIME::Types.type_for(path)
|
59
|
+
type.empty? ? nil : type.first.content_type
|
60
|
+
end
|
61
|
+
|
62
|
+
def set_attachment_attr(args)
|
63
|
+
content_type = args[:content_type] ? args[:content_type] : get_mime_type(args[:file].path)
|
64
|
+
content_type ||= (get_mime_type(args[:name]) || 'text/plain')
|
65
|
+
self['_attachments'][args[:name]] = {
|
66
|
+
'content_type' => content_type,
|
67
|
+
'data' => args[:file].read
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
end # module ExtendedAttachments
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'time'
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'property')
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'casted_array')
|
4
|
+
|
5
|
+
module CouchRest
|
6
|
+
module Mixins
|
7
|
+
module Properties
|
8
|
+
|
9
|
+
class IncludeError < StandardError; end
|
10
|
+
|
11
|
+
def self.included(base)
|
12
|
+
base.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
13
|
+
extend CouchRest::InheritableAttributes
|
14
|
+
couchrest_inheritable_accessor(:properties) unless self.respond_to?(:properties)
|
15
|
+
self.properties ||= []
|
16
|
+
EOS
|
17
|
+
base.extend(ClassMethods)
|
18
|
+
raise CouchRest::Mixins::Properties::IncludeError, "You can only mixin Properties in a class responding to [] and []=, if you tried to mixin CastedModel, make sure your class inherits from Hash or responds to the proper methods" unless (base.new.respond_to?(:[]) && base.new.respond_to?(:[]=))
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the Class properties
|
22
|
+
#
|
23
|
+
# ==== Returns
|
24
|
+
# Array:: the list of properties for model's class
|
25
|
+
def properties
|
26
|
+
self.class.properties
|
27
|
+
end
|
28
|
+
|
29
|
+
def read_attribute(property)
|
30
|
+
self[property.to_s]
|
31
|
+
end
|
32
|
+
|
33
|
+
def write_attribute(property, value)
|
34
|
+
prop = property.is_a?(::CouchRest::Property) ? property : self.class.properties.detect {|p| p.to_s == property.to_s}
|
35
|
+
raise "Missing property definition for #{property.to_s}" unless prop
|
36
|
+
self[prop.to_s] = prop.cast(self, value)
|
37
|
+
end
|
38
|
+
|
39
|
+
def apply_all_property_defaults
|
40
|
+
return if self.respond_to?(:new?) && (new? == false)
|
41
|
+
# TODO: cache the default object
|
42
|
+
self.class.properties.each do |property|
|
43
|
+
write_attribute(property, property.default_value)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module ClassMethods
|
48
|
+
|
49
|
+
def property(name, *options, &block)
|
50
|
+
opts = { }
|
51
|
+
type = options.shift
|
52
|
+
if type.class != Hash
|
53
|
+
opts[:type] = type
|
54
|
+
opts.merge!(options.shift || {})
|
55
|
+
else
|
56
|
+
opts.update(type)
|
57
|
+
end
|
58
|
+
existing_property = self.properties.find{|p| p.name == name.to_s}
|
59
|
+
if existing_property.nil? || (existing_property.default != opts[:default])
|
60
|
+
define_property(name, opts, &block)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
protected
|
65
|
+
|
66
|
+
# This is not a thread safe operation, if you have to set new properties at runtime
|
67
|
+
# make sure a mutex is used.
|
68
|
+
def define_property(name, options={}, &block)
|
69
|
+
# check if this property is going to casted
|
70
|
+
type = options.delete(:type) || options.delete(:cast_as)
|
71
|
+
if block_given?
|
72
|
+
type = Class.new(Hash) do
|
73
|
+
include CastedModel
|
74
|
+
end
|
75
|
+
type.class_eval { yield type }
|
76
|
+
type = [type] # inject as an array
|
77
|
+
end
|
78
|
+
property = CouchRest::Property.new(name, type, options)
|
79
|
+
create_property_getter(property)
|
80
|
+
create_property_setter(property) unless property.read_only == true
|
81
|
+
properties << property
|
82
|
+
property
|
83
|
+
end
|
84
|
+
|
85
|
+
# defines the getter for the property (and optional aliases)
|
86
|
+
def create_property_getter(property)
|
87
|
+
# meth = property.name
|
88
|
+
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
89
|
+
def #{property.name}
|
90
|
+
read_attribute('#{property.name}')
|
91
|
+
end
|
92
|
+
EOS
|
93
|
+
|
94
|
+
if ['boolean', TrueClass.to_s.downcase].include?(property.type.to_s.downcase)
|
95
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
96
|
+
def #{property.name}?
|
97
|
+
value = read_attribute('#{property.name}')
|
98
|
+
!(value.nil? || value == false)
|
99
|
+
end
|
100
|
+
EOS
|
101
|
+
end
|
102
|
+
|
103
|
+
if property.alias
|
104
|
+
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
105
|
+
alias #{property.alias.to_sym} #{property.name.to_sym}
|
106
|
+
EOS
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# defines the setter for the property (and optional aliases)
|
111
|
+
def create_property_setter(property)
|
112
|
+
property_name = property.name
|
113
|
+
class_eval <<-EOS
|
114
|
+
def #{property_name}=(value)
|
115
|
+
write_attribute('#{property_name}', value)
|
116
|
+
end
|
117
|
+
EOS
|
118
|
+
|
119
|
+
if property.alias
|
120
|
+
class_eval <<-EOS
|
121
|
+
alias #{property.alias.to_sym}= #{property_name.to_sym}=
|
122
|
+
EOS
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
end # module ClassMethods
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'bigdecimal'
|
3
|
+
require 'bigdecimal/util'
|
4
|
+
|
5
|
+
class Time
|
6
|
+
# returns a local time value much faster than Time.parse
|
7
|
+
def self.mktime_with_offset(string)
|
8
|
+
string =~ /(\d{4})[\-|\/](\d{2})[\-|\/](\d{2})[T|\s](\d{2}):(\d{2}):(\d{2})([\+|\s|\-])*(\d{2}):?(\d{2})/
|
9
|
+
# $1 = year
|
10
|
+
# $2 = month
|
11
|
+
# $3 = day
|
12
|
+
# $4 = hours
|
13
|
+
# $5 = minutes
|
14
|
+
# $6 = seconds
|
15
|
+
# $7 = time zone direction
|
16
|
+
# $8 = tz difference
|
17
|
+
# utc time with wrong TZ info:
|
18
|
+
time = mktime($1, RFC2822_MONTH_NAME[$2.to_i - 1], $3, $4, $5, $6, $7)
|
19
|
+
tz_difference = ("#{$7 == '-' ? '+' : '-'}#{$8}".to_i * 3600)
|
20
|
+
time + tz_difference + zone_offset(time.zone)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module CouchRest
|
25
|
+
module Mixins
|
26
|
+
module Typecast
|
27
|
+
|
28
|
+
def typecast_value(value, property) # klass, init_method)
|
29
|
+
return nil if value.nil?
|
30
|
+
klass = property.type_class
|
31
|
+
if value.instance_of?(klass) || klass == Object
|
32
|
+
value
|
33
|
+
elsif [String, TrueClass, Integer, Float, BigDecimal, DateTime, Time, Date, Class].include?(klass)
|
34
|
+
send('typecast_to_'+klass.to_s.downcase, value)
|
35
|
+
else
|
36
|
+
# Allow the init_method to be defined as a Proc for advanced conversion
|
37
|
+
property.init_method.is_a?(Proc) ? property.init_method.call(value) : klass.send(property.init_method, value)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
# Typecast a value to an Integer
|
44
|
+
def typecast_to_integer(value)
|
45
|
+
typecast_to_numeric(value, :to_i)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Typecast a value to a String
|
49
|
+
def typecast_to_string(value)
|
50
|
+
value.to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
# Typecast a value to a true or false
|
54
|
+
def typecast_to_trueclass(value)
|
55
|
+
if value.kind_of?(Integer)
|
56
|
+
return true if value == 1
|
57
|
+
return false if value == 0
|
58
|
+
elsif value.respond_to?(:to_s)
|
59
|
+
return true if %w[ true 1 t ].include?(value.to_s.downcase)
|
60
|
+
return false if %w[ false 0 f ].include?(value.to_s.downcase)
|
61
|
+
end
|
62
|
+
value
|
63
|
+
end
|
64
|
+
|
65
|
+
# Typecast a value to a BigDecimal
|
66
|
+
def typecast_to_bigdecimal(value)
|
67
|
+
if value.kind_of?(Integer)
|
68
|
+
value.to_s.to_d
|
69
|
+
else
|
70
|
+
typecast_to_numeric(value, :to_d)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Typecast a value to a Float
|
75
|
+
def typecast_to_float(value)
|
76
|
+
typecast_to_numeric(value, :to_f)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Match numeric string
|
80
|
+
def typecast_to_numeric(value, method)
|
81
|
+
if value.respond_to?(:to_str)
|
82
|
+
if value.to_str =~ /\A(-?(?:0|[1-9]\d*)(?:\.\d+)?|(?:\.\d+))\z/
|
83
|
+
$1.send(method)
|
84
|
+
else
|
85
|
+
value
|
86
|
+
end
|
87
|
+
elsif value.respond_to?(method)
|
88
|
+
value.send(method)
|
89
|
+
else
|
90
|
+
value
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Typecasts an arbitrary value to a DateTime.
|
95
|
+
# Handles both Hashes and DateTime instances.
|
96
|
+
# This is slow!! Use Time instead.
|
97
|
+
def typecast_to_datetime(value)
|
98
|
+
if value.is_a?(Hash)
|
99
|
+
typecast_hash_to_datetime(value)
|
100
|
+
else
|
101
|
+
DateTime.parse(value.to_s)
|
102
|
+
end
|
103
|
+
rescue ArgumentError
|
104
|
+
value
|
105
|
+
end
|
106
|
+
|
107
|
+
# Typecasts an arbitrary value to a Date
|
108
|
+
# Handles both Hashes and Date instances.
|
109
|
+
def typecast_to_date(value)
|
110
|
+
if value.is_a?(Hash)
|
111
|
+
typecast_hash_to_date(value)
|
112
|
+
elsif value.is_a?(Time) # sometimes people think date is time!
|
113
|
+
value.to_date
|
114
|
+
elsif value.to_s =~ /(\d{4})[\-|\/](\d{2})[\-|\/](\d{2})/
|
115
|
+
# Faster than parsing the date
|
116
|
+
Date.new($1.to_i, $2.to_i, $3.to_i)
|
117
|
+
else
|
118
|
+
Date.parse(value)
|
119
|
+
end
|
120
|
+
rescue ArgumentError
|
121
|
+
value
|
122
|
+
end
|
123
|
+
|
124
|
+
# Typecasts an arbitrary value to a Time
|
125
|
+
# Handles both Hashes and Time instances.
|
126
|
+
def typecast_to_time(value)
|
127
|
+
if value.is_a?(Hash)
|
128
|
+
typecast_hash_to_time(value)
|
129
|
+
else
|
130
|
+
Time.mktime_with_offset(value.to_s)
|
131
|
+
end
|
132
|
+
rescue ArgumentError
|
133
|
+
value
|
134
|
+
rescue TypeError
|
135
|
+
value
|
136
|
+
end
|
137
|
+
|
138
|
+
# Creates a DateTime instance from a Hash with keys :year, :month, :day,
|
139
|
+
# :hour, :min, :sec
|
140
|
+
def typecast_hash_to_datetime(value)
|
141
|
+
DateTime.new(*extract_time(value))
|
142
|
+
end
|
143
|
+
|
144
|
+
# Creates a Date instance from a Hash with keys :year, :month, :day
|
145
|
+
def typecast_hash_to_date(value)
|
146
|
+
Date.new(*extract_time(value)[0, 3])
|
147
|
+
end
|
148
|
+
|
149
|
+
# Creates a Time instance from a Hash with keys :year, :month, :day,
|
150
|
+
# :hour, :min, :sec
|
151
|
+
def typecast_hash_to_time(value)
|
152
|
+
Time.local(*extract_time(value))
|
153
|
+
end
|
154
|
+
|
155
|
+
# Extracts the given args from the hash. If a value does not exist, it
|
156
|
+
# uses the value of Time.now.
|
157
|
+
def extract_time(value)
|
158
|
+
now = Time.now
|
159
|
+
[:year, :month, :day, :hour, :min, :sec].map do |segment|
|
160
|
+
typecast_to_numeric(value.fetch(segment, now.send(segment)), :to_i)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Typecast a value to a Class
|
165
|
+
def typecast_to_class(value)
|
166
|
+
value.to_s.constantize
|
167
|
+
rescue NameError
|
168
|
+
value
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|