couchrest_model-radiant 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 +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
|