couchrest_model-radiant 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +176 -0
- data/README.md +19 -0
- data/Rakefile +74 -0
- data/THANKS.md +21 -0
- data/history.txt +207 -0
- data/lib/couchrest/model.rb +10 -0
- data/lib/couchrest/model/associations.rb +223 -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 +263 -0
- data/lib/couchrest/model/configuration.rb +51 -0
- data/lib/couchrest/model/design_doc.rb +123 -0
- data/lib/couchrest/model/document_queries.rb +83 -0
- data/lib/couchrest/model/errors.rb +23 -0
- data/lib/couchrest/model/extended_attachments.rb +77 -0
- data/lib/couchrest/model/persistence.rb +155 -0
- data/lib/couchrest/model/properties.rb +208 -0
- data/lib/couchrest/model/property.rb +97 -0
- data/lib/couchrest/model/property_protection.rb +71 -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 +175 -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 +44 -0
- data/lib/couchrest/model/views.rb +160 -0
- data/lib/couchrest/railtie.rb +12 -0
- data/lib/couchrest_model.rb +62 -0
- data/lib/rails/generators/couchrest_model.rb +16 -0
- data/lib/rails/generators/couchrest_model/model/model_generator.rb +27 -0
- data/lib/rails/generators/couchrest_model/model/templates/model.rb +2 -0
- data/spec/couchrest/assocations_spec.rb +196 -0
- data/spec/couchrest/attachment_spec.rb +176 -0
- data/spec/couchrest/base_spec.rb +463 -0
- data/spec/couchrest/casted_model_spec.rb +438 -0
- data/spec/couchrest/casted_spec.rb +75 -0
- data/spec/couchrest/class_proxy_spec.rb +132 -0
- data/spec/couchrest/configuration_spec.rb +78 -0
- data/spec/couchrest/inherited_spec.rb +40 -0
- data/spec/couchrest/persistence_spec.rb +415 -0
- data/spec/couchrest/property_protection_spec.rb +192 -0
- data/spec/couchrest/property_spec.rb +871 -0
- data/spec/couchrest/subclass_spec.rb +99 -0
- data/spec/couchrest/validations.rb +85 -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/client.rb +6 -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/sale_entry.rb +9 -0
- data/spec/fixtures/more/sale_invoice.rb +13 -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_helper.rb +48 -0
- metadata +263 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module Model
|
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 model_type_key's 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 model_type_key's 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 model_type_key's 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
|
+
raise "Missing or empty document ID" if id.to_s.empty?
|
74
|
+
doc = db.get id
|
75
|
+
create_from_database(doc)
|
76
|
+
end
|
77
|
+
alias :find! :get!
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -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,77 @@
|
|
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
|
+
set_attachment_attr(args)
|
11
|
+
rescue ArgumentError => e
|
12
|
+
raise ArgumentError, 'You must specify :file and :name'
|
13
|
+
end
|
14
|
+
|
15
|
+
# return all attachments
|
16
|
+
def attachments
|
17
|
+
self['_attachments'] ||= {}
|
18
|
+
end
|
19
|
+
|
20
|
+
# reads the data from an attachment
|
21
|
+
def read_attachment(attachment_name)
|
22
|
+
database.fetch_attachment(self, attachment_name)
|
23
|
+
end
|
24
|
+
|
25
|
+
# modifies a file attachment on the current doc
|
26
|
+
def update_attachment(args={})
|
27
|
+
raise ArgumentError unless args[:file] && args[:name]
|
28
|
+
return unless has_attachment?(args[:name])
|
29
|
+
delete_attachment(args[:name])
|
30
|
+
set_attachment_attr(args)
|
31
|
+
rescue ArgumentError => e
|
32
|
+
raise ArgumentError, 'You must specify :file and :name'
|
33
|
+
end
|
34
|
+
|
35
|
+
# deletes a file attachment from the current doc
|
36
|
+
def delete_attachment(attachment_name)
|
37
|
+
return unless attachments
|
38
|
+
attachments.delete attachment_name
|
39
|
+
end
|
40
|
+
|
41
|
+
# returns true if attachment_name exists
|
42
|
+
def has_attachment?(attachment_name)
|
43
|
+
!!(attachments && attachments[attachment_name] && !attachments[attachment_name].empty?)
|
44
|
+
end
|
45
|
+
|
46
|
+
# returns URL to fetch the attachment from
|
47
|
+
def attachment_url(attachment_name)
|
48
|
+
return unless has_attachment?(attachment_name)
|
49
|
+
"#{database.root}/#{self.id}/#{attachment_name}"
|
50
|
+
end
|
51
|
+
|
52
|
+
# returns URI to fetch the attachment from
|
53
|
+
def attachment_uri(attachment_name)
|
54
|
+
return unless has_attachment?(attachment_name)
|
55
|
+
"#{database.uri}/#{self.id}/#{attachment_name}"
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def get_mime_type(path)
|
61
|
+
return nil if path.nil?
|
62
|
+
type = ::MIME::Types.type_for(path)
|
63
|
+
type.empty? ? nil : type.first.content_type
|
64
|
+
end
|
65
|
+
|
66
|
+
def set_attachment_attr(args)
|
67
|
+
content_type = args[:content_type] ? args[:content_type] : get_mime_type(args[:file].path)
|
68
|
+
content_type ||= (get_mime_type(args[:name]) || 'text/plain')
|
69
|
+
attachments[args[:name]] = {
|
70
|
+
'content_type' => content_type,
|
71
|
+
'data' => args[:file].read
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
end # module ExtendedAttachments
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,155 @@
|
|
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
|
+
# Update the document's attributes and save. For example:
|
66
|
+
#
|
67
|
+
# doc.update_attributes :name => "Fred"
|
68
|
+
#
|
69
|
+
# Is the equivilent of doing the following:
|
70
|
+
#
|
71
|
+
# doc.attributes = { :name => "Fred" }
|
72
|
+
# doc.save
|
73
|
+
#
|
74
|
+
def update_attributes(hash)
|
75
|
+
update_attributes_without_saving hash
|
76
|
+
save
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
def perform_validations(options = {})
|
82
|
+
perform_validation = case options
|
83
|
+
when Hash
|
84
|
+
options[:validate] != false
|
85
|
+
else
|
86
|
+
options
|
87
|
+
end
|
88
|
+
perform_validation ? valid? : true
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
module ClassMethods
|
93
|
+
|
94
|
+
# Creates a new instance, bypassing attribute protection
|
95
|
+
#
|
96
|
+
#
|
97
|
+
# ==== Returns
|
98
|
+
# a document instance
|
99
|
+
def create_from_database(doc = {})
|
100
|
+
base = (doc[model_type_key].blank? || doc[model_type_key] == self.to_s) ? self : doc[model_type_key].constantize
|
101
|
+
base.new(doc, :directly_set_attributes => true)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Defines an instance and save it directly to the database
|
105
|
+
#
|
106
|
+
# ==== Returns
|
107
|
+
# returns the reloaded document
|
108
|
+
def create(attributes = {})
|
109
|
+
instance = new(attributes)
|
110
|
+
instance.create
|
111
|
+
instance
|
112
|
+
end
|
113
|
+
|
114
|
+
# Defines an instance and save it directly to the database
|
115
|
+
#
|
116
|
+
# ==== Returns
|
117
|
+
# returns the reloaded document or raises an exception
|
118
|
+
def create!(attributes = {})
|
119
|
+
instance = new(attributes)
|
120
|
+
instance.create!
|
121
|
+
instance
|
122
|
+
end
|
123
|
+
|
124
|
+
# Name a method that will be called before the document is first saved,
|
125
|
+
# which returns a string to be used for the document's <tt>_id</tt>.
|
126
|
+
#
|
127
|
+
# Because CouchDB enforces a constraint that each id must be unique,
|
128
|
+
# this can be used to enforce eg: uniq usernames. Note that this id
|
129
|
+
# must be globally unique across all document types which share a
|
130
|
+
# database, so if you'd like to scope uniqueness to this class, you
|
131
|
+
# should use the class name as part of the unique id.
|
132
|
+
def unique_id method = nil, &block
|
133
|
+
if method
|
134
|
+
define_method :set_unique_id do
|
135
|
+
self['_id'] ||= self.send(method)
|
136
|
+
end
|
137
|
+
elsif block
|
138
|
+
define_method :set_unique_id do
|
139
|
+
uniqid = block.call(self)
|
140
|
+
raise ArgumentError, "unique_id block must not return nil" if uniqid.nil?
|
141
|
+
self['_id'] ||= uniqid
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Raise an error if validation failed.
|
147
|
+
def fail_validate!(document)
|
148
|
+
raise Errors::Validations.new(document)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module CouchRest
|
3
|
+
module Model
|
4
|
+
module Properties
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
extlib_inheritable_accessor(:properties) unless self.respond_to?(:properties)
|
9
|
+
self.properties ||= []
|
10
|
+
raise "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 (method_defined?(:[]) && method_defined?(:[]=))
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns the Class properties
|
14
|
+
#
|
15
|
+
# ==== Returns
|
16
|
+
# Array:: the list of properties for model's class
|
17
|
+
def properties
|
18
|
+
self.class.properties
|
19
|
+
end
|
20
|
+
|
21
|
+
# Read the casted value of an attribute defined with a property.
|
22
|
+
#
|
23
|
+
# ==== Returns
|
24
|
+
# Object:: the casted attibutes value.
|
25
|
+
def read_attribute(property)
|
26
|
+
self[find_property!(property).to_s]
|
27
|
+
end
|
28
|
+
|
29
|
+
# Store a casted value in the current instance of an attribute defined
|
30
|
+
# with a property.
|
31
|
+
def write_attribute(property, value)
|
32
|
+
prop = find_property!(property)
|
33
|
+
self[prop.to_s] = prop.is_a?(String) ? value : prop.cast(self, value)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Takes a hash as argument, and applies the values by using writer methods
|
37
|
+
# for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
|
38
|
+
# missing. In case of error, no attributes are changed.
|
39
|
+
def update_attributes_without_saving(hash)
|
40
|
+
# Remove any protected and update all the rest. Any attributes
|
41
|
+
# which do not have a property will simply be ignored.
|
42
|
+
attrs = remove_protected_attributes(hash)
|
43
|
+
directly_set_attributes(attrs)
|
44
|
+
end
|
45
|
+
alias :attributes= :update_attributes_without_saving
|
46
|
+
|
47
|
+
|
48
|
+
private
|
49
|
+
# The following methods should be accessable by the Model::Base Class, but not by anything else!
|
50
|
+
|
51
|
+
def apply_all_property_defaults
|
52
|
+
return if self.respond_to?(:new?) && (new? == false)
|
53
|
+
# TODO: cache the default object
|
54
|
+
self.class.properties.each do |property|
|
55
|
+
write_attribute(property, property.default_value)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def prepare_all_attributes(doc = {}, options = {})
|
60
|
+
apply_all_property_defaults
|
61
|
+
if options[:directly_set_attributes]
|
62
|
+
directly_set_read_only_attributes(doc)
|
63
|
+
else
|
64
|
+
doc = remove_protected_attributes(doc)
|
65
|
+
end
|
66
|
+
directly_set_attributes(doc) unless doc.nil?
|
67
|
+
end
|
68
|
+
|
69
|
+
def find_property!(property)
|
70
|
+
prop = property.is_a?(Property) ? property : self.class.properties.detect {|p| p.to_s == property.to_s}
|
71
|
+
raise ArgumentError, "Missing property definition for #{property.to_s}" if prop.nil?
|
72
|
+
prop
|
73
|
+
end
|
74
|
+
|
75
|
+
# Set all the attributes and return a hash with the attributes
|
76
|
+
# that have not been accepted.
|
77
|
+
def directly_set_attributes(hash)
|
78
|
+
hash.reject do |attribute_name, attribute_value|
|
79
|
+
if self.respond_to?("#{attribute_name}=")
|
80
|
+
self.send("#{attribute_name}=", attribute_value)
|
81
|
+
true
|
82
|
+
elsif mass_assign_any_attribute # config option
|
83
|
+
self[attribute_name] = attribute_value
|
84
|
+
true
|
85
|
+
else
|
86
|
+
false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def directly_set_read_only_attributes(hash)
|
92
|
+
property_list = self.properties.map{|p| p.name}
|
93
|
+
hash.each do |attribute_name, attribute_value|
|
94
|
+
next if self.respond_to?("#{attribute_name}=")
|
95
|
+
if property_list.include?(attribute_name)
|
96
|
+
write_attribute(attribute_name, hash.delete(attribute_name))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def set_attributes(hash)
|
102
|
+
attrs = remove_protected_attributes(hash)
|
103
|
+
directly_set_attributes(attrs)
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
module ClassMethods
|
108
|
+
|
109
|
+
def property(name, *options, &block)
|
110
|
+
opts = { }
|
111
|
+
type = options.shift
|
112
|
+
if type.class != Hash
|
113
|
+
opts[:type] = type
|
114
|
+
opts.merge!(options.shift || {})
|
115
|
+
else
|
116
|
+
opts.update(type)
|
117
|
+
end
|
118
|
+
existing_property = self.properties.find{|p| p.name == name.to_s}
|
119
|
+
if existing_property.nil? || (existing_property.default != opts[:default])
|
120
|
+
define_property(name, opts, &block)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
|
125
|
+
# on the document whenever saving occurs. CouchRest uses a pretty
|
126
|
+
# decent time format by default. See Time#to_json
|
127
|
+
def timestamps!
|
128
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
129
|
+
property(:updated_at, Time, :read_only => true, :protected => true, :auto_validation => false)
|
130
|
+
property(:created_at, Time, :read_only => true, :protected => true, :auto_validation => false)
|
131
|
+
|
132
|
+
set_callback :save, :before do |object|
|
133
|
+
write_attribute('updated_at', Time.now)
|
134
|
+
write_attribute('created_at', Time.now) if object.new?
|
135
|
+
end
|
136
|
+
EOS
|
137
|
+
end
|
138
|
+
|
139
|
+
protected
|
140
|
+
|
141
|
+
# This is not a thread safe operation, if you have to set new properties at runtime
|
142
|
+
# make sure a mutex is used.
|
143
|
+
def define_property(name, options={}, &block)
|
144
|
+
# check if this property is going to casted
|
145
|
+
type = options.delete(:type) || options.delete(:cast_as)
|
146
|
+
if block_given?
|
147
|
+
type = Class.new(Hash) do
|
148
|
+
include CastedModel
|
149
|
+
end
|
150
|
+
type.class_eval { yield type }
|
151
|
+
type = [type] # inject as an array
|
152
|
+
end
|
153
|
+
property = Property.new(name, type, options)
|
154
|
+
create_property_getter(property)
|
155
|
+
create_property_setter(property) unless property.read_only == true
|
156
|
+
if property.type_class.respond_to?(:validates_casted_model)
|
157
|
+
validates_casted_model property.name
|
158
|
+
end
|
159
|
+
properties << property
|
160
|
+
property
|
161
|
+
end
|
162
|
+
|
163
|
+
# defines the getter for the property (and optional aliases)
|
164
|
+
def create_property_getter(property)
|
165
|
+
# meth = property.name
|
166
|
+
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
167
|
+
def #{property.name}
|
168
|
+
read_attribute('#{property.name}')
|
169
|
+
end
|
170
|
+
EOS
|
171
|
+
|
172
|
+
if ['boolean', TrueClass.to_s.downcase].include?(property.type.to_s.downcase)
|
173
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
174
|
+
def #{property.name}?
|
175
|
+
value = read_attribute('#{property.name}')
|
176
|
+
!(value.nil? || value == false)
|
177
|
+
end
|
178
|
+
EOS
|
179
|
+
end
|
180
|
+
|
181
|
+
if property.alias
|
182
|
+
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
183
|
+
alias #{property.alias.to_sym} #{property.name.to_sym}
|
184
|
+
EOS
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# defines the setter for the property (and optional aliases)
|
189
|
+
def create_property_setter(property)
|
190
|
+
property_name = property.name
|
191
|
+
class_eval <<-EOS
|
192
|
+
def #{property_name}=(value)
|
193
|
+
write_attribute('#{property_name}', value)
|
194
|
+
end
|
195
|
+
EOS
|
196
|
+
|
197
|
+
if property.alias
|
198
|
+
class_eval <<-EOS
|
199
|
+
alias #{property.alias.to_sym}= #{property_name.to_sym}=
|
200
|
+
EOS
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
end # module ClassMethods
|
205
|
+
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|