couchrest_model 1.0.0.beta7
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +176 -0
- data/README.md +320 -0
- data/Rakefile +71 -0
- data/THANKS.md +19 -0
- data/examples/model/example.rb +144 -0
- data/history.txt +180 -0
- data/lib/couchrest/model.rb +10 -0
- data/lib/couchrest/model/associations.rb +207 -0
- data/lib/couchrest/model/attribute_protection.rb +74 -0
- data/lib/couchrest/model/attributes.rb +75 -0
- data/lib/couchrest/model/base.rb +111 -0
- data/lib/couchrest/model/callbacks.rb +27 -0
- data/lib/couchrest/model/casted_array.rb +39 -0
- data/lib/couchrest/model/casted_model.rb +68 -0
- data/lib/couchrest/model/class_proxy.rb +122 -0
- data/lib/couchrest/model/collection.rb +260 -0
- data/lib/couchrest/model/design_doc.rb +126 -0
- data/lib/couchrest/model/document_queries.rb +82 -0
- data/lib/couchrest/model/errors.rb +23 -0
- data/lib/couchrest/model/extended_attachments.rb +73 -0
- data/lib/couchrest/model/persistence.rb +141 -0
- data/lib/couchrest/model/properties.rb +144 -0
- data/lib/couchrest/model/property.rb +96 -0
- data/lib/couchrest/model/support/couchrest.rb +19 -0
- data/lib/couchrest/model/support/hash.rb +9 -0
- data/lib/couchrest/model/typecast.rb +170 -0
- data/lib/couchrest/model/validations.rb +68 -0
- data/lib/couchrest/model/validations/casted_model.rb +14 -0
- data/lib/couchrest/model/validations/locale/en.yml +5 -0
- data/lib/couchrest/model/validations/uniqueness.rb +45 -0
- data/lib/couchrest/model/views.rb +167 -0
- data/lib/couchrest_model.rb +56 -0
- data/spec/couchrest/assocations_spec.rb +213 -0
- data/spec/couchrest/attachment_spec.rb +148 -0
- data/spec/couchrest/attribute_protection_spec.rb +153 -0
- data/spec/couchrest/base_spec.rb +463 -0
- data/spec/couchrest/casted_model_spec.rb +424 -0
- data/spec/couchrest/casted_spec.rb +75 -0
- data/spec/couchrest/class_proxy_spec.rb +132 -0
- data/spec/couchrest/inherited_spec.rb +40 -0
- data/spec/couchrest/persistence_spec.rb +409 -0
- data/spec/couchrest/property_spec.rb +804 -0
- data/spec/couchrest/subclass_spec.rb +99 -0
- data/spec/couchrest/validations.rb +73 -0
- data/spec/couchrest/view_spec.rb +463 -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/base.rb +139 -0
- data/spec/fixtures/more/article.rb +35 -0
- data/spec/fixtures/more/card.rb +17 -0
- data/spec/fixtures/more/cat.rb +19 -0
- data/spec/fixtures/more/course.rb +25 -0
- data/spec/fixtures/more/event.rb +8 -0
- data/spec/fixtures/more/invoice.rb +14 -0
- data/spec/fixtures/more/person.rb +9 -0
- data/spec/fixtures/more/question.rb +7 -0
- data/spec/fixtures/more/service.rb +10 -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 +48 -0
- data/utils/remap.rb +27 -0
- data/utils/subset.rb +30 -0
- metadata +232 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module CouchRest
|
3
|
+
module Model
|
4
|
+
module Errors
|
5
|
+
|
6
|
+
class CouchRestModelError < StandardError; end
|
7
|
+
|
8
|
+
# Raised when a persisence method ending in ! fails validation. The message
|
9
|
+
# will contain the full error messages from the +Document+ in question.
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
#
|
13
|
+
# <tt>Validations.new(person.errors)</tt>
|
14
|
+
class Validations < CouchRestModelError
|
15
|
+
attr_reader :document
|
16
|
+
def initialize(document)
|
17
|
+
@document = document
|
18
|
+
super("Validation Failed: #{@document.errors.full_messages.join(", ")}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module Model
|
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,141 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module Model
|
3
|
+
module Persistence
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
# Create the document. Validation is enabled by default and will return
|
7
|
+
# false if the document is not valid. If all goes well, the document will
|
8
|
+
# be returned.
|
9
|
+
def create(options = {})
|
10
|
+
return false unless perform_validations(options)
|
11
|
+
_run_create_callbacks do
|
12
|
+
_run_save_callbacks do
|
13
|
+
set_unique_id if new? && self.respond_to?(:set_unique_id)
|
14
|
+
result = database.save_doc(self)
|
15
|
+
(result["ok"] == true) ? self : false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Creates the document in the db. Raises an exception
|
21
|
+
# if the document is not created properly.
|
22
|
+
def create!
|
23
|
+
self.class.fail_validate!(self) unless self.create
|
24
|
+
end
|
25
|
+
|
26
|
+
# Trigger the callbacks (before, after, around)
|
27
|
+
# only if the document isn't new
|
28
|
+
def update(options = {})
|
29
|
+
raise "Calling #{self.class.name}#update on document that has not been created!" if self.new?
|
30
|
+
return false unless perform_validations(options)
|
31
|
+
_run_update_callbacks do
|
32
|
+
_run_save_callbacks do
|
33
|
+
result = database.save_doc(self)
|
34
|
+
result["ok"] == true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Trigger the callbacks (before, after, around) and save the document
|
40
|
+
def save(options = {})
|
41
|
+
self.new? ? create(options) : update(options)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Saves the document to the db using save. Raises an exception
|
45
|
+
# if the document is not saved properly.
|
46
|
+
def save!
|
47
|
+
self.class.fail_validate!(self) unless self.save
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
# Deletes the document from the database. Runs the :destroy callbacks.
|
52
|
+
# Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
|
53
|
+
# document to be saved to a new <tt>_id</tt> if required.
|
54
|
+
def destroy
|
55
|
+
_run_destroy_callbacks do
|
56
|
+
result = database.delete_doc(self)
|
57
|
+
if result['ok']
|
58
|
+
self.delete('_rev')
|
59
|
+
self.delete('_id')
|
60
|
+
end
|
61
|
+
result['ok']
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
66
|
+
|
67
|
+
def perform_validations(options = {})
|
68
|
+
perform_validation = case options
|
69
|
+
when Hash
|
70
|
+
options[:validate] != false
|
71
|
+
else
|
72
|
+
options
|
73
|
+
end
|
74
|
+
perform_validation ? valid? : true
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
module ClassMethods
|
79
|
+
|
80
|
+
# Creates a new instance, bypassing attribute protection
|
81
|
+
#
|
82
|
+
#
|
83
|
+
# ==== Returns
|
84
|
+
# a document instance
|
85
|
+
def create_from_database(doc = {})
|
86
|
+
base = (doc['couchrest-type'].blank? || doc['couchrest-type'] == self.to_s) ? self : doc['couchrest-type'].constantize
|
87
|
+
base.new(doc, :directly_set_attributes => true)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Defines an instance and save it directly to the database
|
91
|
+
#
|
92
|
+
# ==== Returns
|
93
|
+
# returns the reloaded document
|
94
|
+
def create(attributes = {})
|
95
|
+
instance = new(attributes)
|
96
|
+
instance.create
|
97
|
+
instance
|
98
|
+
end
|
99
|
+
|
100
|
+
# Defines an instance and save it directly to the database
|
101
|
+
#
|
102
|
+
# ==== Returns
|
103
|
+
# returns the reloaded document or raises an exception
|
104
|
+
def create!(attributes = {})
|
105
|
+
instance = new(attributes)
|
106
|
+
instance.create!
|
107
|
+
instance
|
108
|
+
end
|
109
|
+
|
110
|
+
# Name a method that will be called before the document is first saved,
|
111
|
+
# which returns a string to be used for the document's <tt>_id</tt>.
|
112
|
+
#
|
113
|
+
# Because CouchDB enforces a constraint that each id must be unique,
|
114
|
+
# this can be used to enforce eg: uniq usernames. Note that this id
|
115
|
+
# must be globally unique across all document types which share a
|
116
|
+
# database, so if you'd like to scope uniqueness to this class, you
|
117
|
+
# should use the class name as part of the unique id.
|
118
|
+
def unique_id method = nil, &block
|
119
|
+
if method
|
120
|
+
define_method :set_unique_id do
|
121
|
+
self['_id'] ||= self.send(method)
|
122
|
+
end
|
123
|
+
elsif block
|
124
|
+
define_method :set_unique_id do
|
125
|
+
uniqid = block.call(self)
|
126
|
+
raise ArgumentError, "unique_id block must not return nil" if uniqid.nil?
|
127
|
+
self['_id'] ||= uniqid
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Raise an error if validation failed.
|
133
|
+
def fail_validate!(document)
|
134
|
+
raise Errors::Validations.new(document)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module CouchRest
|
3
|
+
module Model
|
4
|
+
module Properties
|
5
|
+
|
6
|
+
class IncludeError < StandardError; end
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
10
|
+
extlib_inheritable_accessor(:properties) unless self.respond_to?(:properties)
|
11
|
+
self.properties ||= []
|
12
|
+
EOS
|
13
|
+
base.extend(ClassMethods)
|
14
|
+
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?(:[]=))
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns the Class properties
|
18
|
+
#
|
19
|
+
# ==== Returns
|
20
|
+
# Array:: the list of properties for model's class
|
21
|
+
def properties
|
22
|
+
self.class.properties
|
23
|
+
end
|
24
|
+
|
25
|
+
def read_attribute(property)
|
26
|
+
self[property.to_s]
|
27
|
+
end
|
28
|
+
|
29
|
+
def write_attribute(property, value)
|
30
|
+
prop = property.is_a?(Property) ? property : self.class.properties.detect {|p| p.to_s == property.to_s}
|
31
|
+
raise "Missing property definition for #{property.to_s}" unless prop
|
32
|
+
self[prop.to_s] = prop.cast(self, value)
|
33
|
+
end
|
34
|
+
|
35
|
+
def apply_all_property_defaults
|
36
|
+
return if self.respond_to?(:new?) && (new? == false)
|
37
|
+
# TODO: cache the default object
|
38
|
+
self.class.properties.each do |property|
|
39
|
+
write_attribute(property, property.default_value)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module ClassMethods
|
44
|
+
|
45
|
+
def property(name, *options, &block)
|
46
|
+
opts = { }
|
47
|
+
type = options.shift
|
48
|
+
if type.class != Hash
|
49
|
+
opts[:type] = type
|
50
|
+
opts.merge!(options.shift || {})
|
51
|
+
else
|
52
|
+
opts.update(type)
|
53
|
+
end
|
54
|
+
existing_property = self.properties.find{|p| p.name == name.to_s}
|
55
|
+
if existing_property.nil? || (existing_property.default != opts[:default])
|
56
|
+
define_property(name, opts, &block)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
|
61
|
+
# on the document whenever saving occurs. CouchRest uses a pretty
|
62
|
+
# decent time format by default. See Time#to_json
|
63
|
+
def timestamps!
|
64
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
65
|
+
property(:updated_at, Time, :read_only => true, :protected => true, :auto_validation => false)
|
66
|
+
property(:created_at, Time, :read_only => true, :protected => true, :auto_validation => false)
|
67
|
+
|
68
|
+
set_callback :save, :before do |object|
|
69
|
+
write_attribute('updated_at', Time.now)
|
70
|
+
write_attribute('created_at', Time.now) if object.new?
|
71
|
+
end
|
72
|
+
EOS
|
73
|
+
end
|
74
|
+
|
75
|
+
protected
|
76
|
+
|
77
|
+
# This is not a thread safe operation, if you have to set new properties at runtime
|
78
|
+
# make sure a mutex is used.
|
79
|
+
def define_property(name, options={}, &block)
|
80
|
+
# check if this property is going to casted
|
81
|
+
type = options.delete(:type) || options.delete(:cast_as)
|
82
|
+
if block_given?
|
83
|
+
type = Class.new(Hash) do
|
84
|
+
include CastedModel
|
85
|
+
end
|
86
|
+
type.class_eval { yield type }
|
87
|
+
type = [type] # inject as an array
|
88
|
+
end
|
89
|
+
property = Property.new(name, type, options)
|
90
|
+
create_property_getter(property)
|
91
|
+
create_property_setter(property) unless property.read_only == true
|
92
|
+
if property.type_class.respond_to?(:validates_casted_model)
|
93
|
+
validates_casted_model property.name
|
94
|
+
end
|
95
|
+
properties << property
|
96
|
+
property
|
97
|
+
end
|
98
|
+
|
99
|
+
# defines the getter for the property (and optional aliases)
|
100
|
+
def create_property_getter(property)
|
101
|
+
# meth = property.name
|
102
|
+
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
103
|
+
def #{property.name}
|
104
|
+
read_attribute('#{property.name}')
|
105
|
+
end
|
106
|
+
EOS
|
107
|
+
|
108
|
+
if ['boolean', TrueClass.to_s.downcase].include?(property.type.to_s.downcase)
|
109
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
110
|
+
def #{property.name}?
|
111
|
+
value = read_attribute('#{property.name}')
|
112
|
+
!(value.nil? || value == false)
|
113
|
+
end
|
114
|
+
EOS
|
115
|
+
end
|
116
|
+
|
117
|
+
if property.alias
|
118
|
+
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
119
|
+
alias #{property.alias.to_sym} #{property.name.to_sym}
|
120
|
+
EOS
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# defines the setter for the property (and optional aliases)
|
125
|
+
def create_property_setter(property)
|
126
|
+
property_name = property.name
|
127
|
+
class_eval <<-EOS
|
128
|
+
def #{property_name}=(value)
|
129
|
+
write_attribute('#{property_name}', value)
|
130
|
+
end
|
131
|
+
EOS
|
132
|
+
|
133
|
+
if property.alias
|
134
|
+
class_eval <<-EOS
|
135
|
+
alias #{property.alias.to_sym}= #{property_name.to_sym}=
|
136
|
+
EOS
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
end # module ClassMethods
|
141
|
+
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module CouchRest::Model
|
3
|
+
class Property
|
4
|
+
|
5
|
+
include ::CouchRest::Model::Typecast
|
6
|
+
|
7
|
+
attr_reader :name, :type, :type_class, :read_only, :alias, :default, :casted, :init_method, :options
|
8
|
+
|
9
|
+
# Attribute to define.
|
10
|
+
# All Properties are assumed casted unless the type is nil.
|
11
|
+
def initialize(name, type = nil, options = {})
|
12
|
+
@name = name.to_s
|
13
|
+
@casted = true
|
14
|
+
parse_type(type)
|
15
|
+
parse_options(options)
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
name
|
21
|
+
end
|
22
|
+
|
23
|
+
# Cast the provided value using the properties details.
|
24
|
+
def cast(parent, value)
|
25
|
+
return value unless casted
|
26
|
+
if type.is_a?(Array)
|
27
|
+
if value.nil?
|
28
|
+
value = []
|
29
|
+
elsif [Hash, HashWithIndifferentAccess].include?(value.class)
|
30
|
+
# Assume provided as a Hash where key is index!
|
31
|
+
data = value
|
32
|
+
value = [ ]
|
33
|
+
data.keys.sort.each do |k|
|
34
|
+
value << data[k]
|
35
|
+
end
|
36
|
+
elsif value.class != Array
|
37
|
+
raise "Expecting an array or keyed hash for property #{parent.class.name}##{self.name}"
|
38
|
+
end
|
39
|
+
arr = value.collect { |data| cast_value(parent, data) }
|
40
|
+
# allow casted_by calls to be passed up chain by wrapping in CastedArray
|
41
|
+
value = type_class != String ? CastedArray.new(arr, self) : arr
|
42
|
+
value.casted_by = parent if value.respond_to?(:casted_by)
|
43
|
+
elsif !value.nil?
|
44
|
+
value = cast_value(parent, value)
|
45
|
+
end
|
46
|
+
value
|
47
|
+
end
|
48
|
+
|
49
|
+
# Cast an individual value, not an array
|
50
|
+
def cast_value(parent, value)
|
51
|
+
raise "An array inside an array cannot be casted, use CastedModel" if value.is_a?(Array)
|
52
|
+
value = typecast_value(value, self)
|
53
|
+
associate_casted_value_to_parent(parent, value)
|
54
|
+
end
|
55
|
+
|
56
|
+
def default_value
|
57
|
+
return if default.nil?
|
58
|
+
if default.class == Proc
|
59
|
+
default.call
|
60
|
+
else
|
61
|
+
Marshal.load(Marshal.dump(default))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def associate_casted_value_to_parent(parent, value)
|
68
|
+
value.casted_by = parent if value.respond_to?(:casted_by)
|
69
|
+
value
|
70
|
+
end
|
71
|
+
|
72
|
+
def parse_type(type)
|
73
|
+
if type.nil?
|
74
|
+
@casted = false
|
75
|
+
@type = nil
|
76
|
+
@type_class = nil
|
77
|
+
else
|
78
|
+
base = type.is_a?(Array) ? type.first : type
|
79
|
+
base = Object if base.nil?
|
80
|
+
raise "Defining a property type as a #{type.class.name.humanize} is not supported in CouchRest Model!" if base.class != Class
|
81
|
+
@type_class = base
|
82
|
+
@type = type
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def parse_options(options)
|
87
|
+
@validation_format = options.delete(:format) if options[:format]
|
88
|
+
@read_only = options.delete(:read_only) if options[:read_only]
|
89
|
+
@alias = options.delete(:alias) if options[:alias]
|
90
|
+
@default = options.delete(:default) unless options[:default].nil?
|
91
|
+
@init_method = options[:init_method] ? options.delete(:init_method) : 'new'
|
92
|
+
@options = options
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|