openlogic-couchrest_model 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. data/.gitignore +11 -0
  2. data/.rspec +4 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +176 -0
  5. data/README.md +137 -0
  6. data/Rakefile +38 -0
  7. data/THANKS.md +21 -0
  8. data/VERSION +1 -0
  9. data/benchmarks/dirty.rb +118 -0
  10. data/couchrest_model.gemspec +36 -0
  11. data/history.md +309 -0
  12. data/init.rb +1 -0
  13. data/lib/couchrest/model.rb +10 -0
  14. data/lib/couchrest/model/associations.rb +231 -0
  15. data/lib/couchrest/model/base.rb +129 -0
  16. data/lib/couchrest/model/callbacks.rb +28 -0
  17. data/lib/couchrest/model/casted_array.rb +83 -0
  18. data/lib/couchrest/model/casted_by.rb +33 -0
  19. data/lib/couchrest/model/casted_hash.rb +84 -0
  20. data/lib/couchrest/model/class_proxy.rb +135 -0
  21. data/lib/couchrest/model/collection.rb +273 -0
  22. data/lib/couchrest/model/configuration.rb +67 -0
  23. data/lib/couchrest/model/connection.rb +70 -0
  24. data/lib/couchrest/model/core_extensions/hash.rb +9 -0
  25. data/lib/couchrest/model/core_extensions/time_parsing.rb +66 -0
  26. data/lib/couchrest/model/design_doc.rb +128 -0
  27. data/lib/couchrest/model/designs.rb +91 -0
  28. data/lib/couchrest/model/designs/view.rb +513 -0
  29. data/lib/couchrest/model/dirty.rb +39 -0
  30. data/lib/couchrest/model/document_queries.rb +99 -0
  31. data/lib/couchrest/model/embeddable.rb +78 -0
  32. data/lib/couchrest/model/errors.rb +25 -0
  33. data/lib/couchrest/model/extended_attachments.rb +83 -0
  34. data/lib/couchrest/model/persistence.rb +178 -0
  35. data/lib/couchrest/model/properties.rb +228 -0
  36. data/lib/couchrest/model/property.rb +114 -0
  37. data/lib/couchrest/model/property_protection.rb +71 -0
  38. data/lib/couchrest/model/proxyable.rb +183 -0
  39. data/lib/couchrest/model/support/couchrest_database.rb +13 -0
  40. data/lib/couchrest/model/support/couchrest_design.rb +33 -0
  41. data/lib/couchrest/model/typecast.rb +154 -0
  42. data/lib/couchrest/model/validations.rb +80 -0
  43. data/lib/couchrest/model/validations/casted_model.rb +16 -0
  44. data/lib/couchrest/model/validations/locale/en.yml +5 -0
  45. data/lib/couchrest/model/validations/uniqueness.rb +69 -0
  46. data/lib/couchrest/model/views.rb +151 -0
  47. data/lib/couchrest/railtie.rb +24 -0
  48. data/lib/couchrest_model.rb +66 -0
  49. data/lib/rails/generators/couchrest_model.rb +16 -0
  50. data/lib/rails/generators/couchrest_model/config/config_generator.rb +18 -0
  51. data/lib/rails/generators/couchrest_model/config/templates/couchdb.yml +21 -0
  52. data/lib/rails/generators/couchrest_model/model/model_generator.rb +27 -0
  53. data/lib/rails/generators/couchrest_model/model/templates/model.rb +2 -0
  54. data/spec/.gitignore +1 -0
  55. data/spec/fixtures/attachments/README +3 -0
  56. data/spec/fixtures/attachments/couchdb.png +0 -0
  57. data/spec/fixtures/attachments/test.html +11 -0
  58. data/spec/fixtures/config/couchdb.yml +10 -0
  59. data/spec/fixtures/models/article.rb +36 -0
  60. data/spec/fixtures/models/base.rb +164 -0
  61. data/spec/fixtures/models/card.rb +19 -0
  62. data/spec/fixtures/models/cat.rb +23 -0
  63. data/spec/fixtures/models/client.rb +6 -0
  64. data/spec/fixtures/models/course.rb +27 -0
  65. data/spec/fixtures/models/event.rb +8 -0
  66. data/spec/fixtures/models/invoice.rb +14 -0
  67. data/spec/fixtures/models/key_chain.rb +5 -0
  68. data/spec/fixtures/models/membership.rb +4 -0
  69. data/spec/fixtures/models/person.rb +11 -0
  70. data/spec/fixtures/models/project.rb +6 -0
  71. data/spec/fixtures/models/question.rb +7 -0
  72. data/spec/fixtures/models/sale_entry.rb +9 -0
  73. data/spec/fixtures/models/sale_invoice.rb +14 -0
  74. data/spec/fixtures/models/service.rb +10 -0
  75. data/spec/fixtures/models/user.rb +22 -0
  76. data/spec/fixtures/views/lib.js +3 -0
  77. data/spec/fixtures/views/test_view/lib.js +3 -0
  78. data/spec/fixtures/views/test_view/only-map.js +4 -0
  79. data/spec/fixtures/views/test_view/test-map.js +3 -0
  80. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  81. data/spec/functional/validations_spec.rb +8 -0
  82. data/spec/spec_helper.rb +60 -0
  83. data/spec/unit/active_model_lint_spec.rb +30 -0
  84. data/spec/unit/assocations_spec.rb +242 -0
  85. data/spec/unit/attachment_spec.rb +176 -0
  86. data/spec/unit/base_spec.rb +537 -0
  87. data/spec/unit/casted_spec.rb +72 -0
  88. data/spec/unit/class_proxy_spec.rb +167 -0
  89. data/spec/unit/collection_spec.rb +86 -0
  90. data/spec/unit/configuration_spec.rb +77 -0
  91. data/spec/unit/connection_spec.rb +148 -0
  92. data/spec/unit/core_extensions/time_parsing.rb +77 -0
  93. data/spec/unit/design_doc_spec.rb +241 -0
  94. data/spec/unit/designs/view_spec.rb +831 -0
  95. data/spec/unit/designs_spec.rb +134 -0
  96. data/spec/unit/dirty_spec.rb +436 -0
  97. data/spec/unit/embeddable_spec.rb +498 -0
  98. data/spec/unit/inherited_spec.rb +33 -0
  99. data/spec/unit/persistence_spec.rb +481 -0
  100. data/spec/unit/property_protection_spec.rb +192 -0
  101. data/spec/unit/property_spec.rb +481 -0
  102. data/spec/unit/proxyable_spec.rb +376 -0
  103. data/spec/unit/subclass_spec.rb +85 -0
  104. data/spec/unit/typecast_spec.rb +521 -0
  105. data/spec/unit/validations_spec.rb +140 -0
  106. data/spec/unit/view_spec.rb +367 -0
  107. metadata +301 -0
@@ -0,0 +1,228 @@
1
+ # encoding: utf-8
2
+ module CouchRest
3
+ module Model
4
+ module Properties
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute(:properties) unless self.respond_to?(:properties)
9
+ class_attribute(:properties_by_name) unless self.respond_to?(:properties_by_name)
10
+ self.properties ||= []
11
+ self.properties_by_name ||= {}
12
+ 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?(:[]=))
13
+ end
14
+
15
+ # Provide an attribute hash ready to be sent to CouchDB but with
16
+ # all the nil attributes removed.
17
+ def as_couch_json
18
+ super.delete_if{|k,v| v.nil?}
19
+ end
20
+
21
+ # Returns the Class properties with their values
22
+ #
23
+ # ==== Returns
24
+ # Array:: the list of properties with their values
25
+ def properties_with_values
26
+ props = {}
27
+ properties.each { |property| props[property.name] = read_attribute(property.name) }
28
+ props
29
+ end
30
+
31
+ # Read the casted value of an attribute defined with a property.
32
+ #
33
+ # ==== Returns
34
+ # Object:: the casted attibutes value.
35
+ def read_attribute(property)
36
+ self[find_property!(property).to_s]
37
+ end
38
+
39
+ # Store a casted value in the current instance of an attribute defined
40
+ # with a property and update dirty status
41
+ def write_attribute(property, value)
42
+ prop = find_property!(property)
43
+ value = prop.is_a?(String) ? value : prop.cast(self, value)
44
+ couchrest_attribute_will_change!(prop.name) if use_dirty? && self[prop.name] != value
45
+ self[prop.name] = value
46
+ end
47
+
48
+ # Takes a hash as argument, and applies the values by using writer methods
49
+ # for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
50
+ # missing. In case of error, no attributes are changed.
51
+ def update_attributes_without_saving(hash)
52
+ # Remove any protected and update all the rest. Any attributes
53
+ # which do not have a property will simply be ignored.
54
+ attrs = remove_protected_attributes(hash)
55
+ directly_set_attributes(attrs)
56
+ end
57
+ alias :attributes= :update_attributes_without_saving
58
+
59
+ # 'attributes' needed for Dirty
60
+ alias :attributes :properties_with_values
61
+
62
+ def set_attributes(hash)
63
+ attrs = remove_protected_attributes(hash)
64
+ directly_set_attributes(attrs)
65
+ end
66
+
67
+ protected
68
+
69
+ def find_property(property)
70
+ property.is_a?(Property) ? property : self.class.properties_by_name[property.to_s]
71
+ end
72
+
73
+ # The following methods should be accessable by the Model::Base Class, but not by anything else!
74
+ def apply_all_property_defaults
75
+ return if self.respond_to?(:new?) && (new? == false)
76
+ # TODO: cache the default object
77
+ # Never mark default options as dirty!
78
+ dirty, self.disable_dirty = self.disable_dirty, true
79
+ self.class.properties.each do |property|
80
+ write_attribute(property, property.default_value)
81
+ end
82
+ self.disable_dirty = dirty
83
+ end
84
+
85
+ def prepare_all_attributes(attrs = {}, options = {})
86
+ self.disable_dirty = !!options[:directly_set_attributes]
87
+ apply_all_property_defaults
88
+ if options[:directly_set_attributes]
89
+ directly_set_read_only_attributes(attrs)
90
+ directly_set_attributes(attrs, true)
91
+ else
92
+ attrs = remove_protected_attributes(attrs)
93
+ directly_set_attributes(attrs)
94
+ end
95
+ self.disable_dirty = false
96
+ self
97
+ end
98
+
99
+ def find_property!(property)
100
+ prop = find_property(property)
101
+ raise ArgumentError, "Missing property definition for #{property.to_s}" if prop.nil?
102
+ prop
103
+ end
104
+
105
+ # Set all the attributes and return a hash with the attributes
106
+ # that have not been accepted.
107
+ def directly_set_attributes(hash, mass_assign = false)
108
+ return if hash.nil?
109
+ hash.reject do |key, value|
110
+ if self.respond_to?("#{key}=")
111
+ self.send("#{key}=", value)
112
+ elsif mass_assign || mass_assign_any_attribute
113
+ self[key] = value
114
+ end
115
+ end
116
+ end
117
+
118
+ def directly_set_read_only_attributes(hash)
119
+ property_list = self.properties.map{|p| p.name}
120
+ hash.each do |attribute_name, attribute_value|
121
+ next if self.respond_to?("#{attribute_name}=")
122
+ if property_list.include?(attribute_name)
123
+ write_attribute(attribute_name, hash.delete(attribute_name))
124
+ end
125
+ end
126
+ end
127
+
128
+
129
+
130
+ module ClassMethods
131
+
132
+ def property(name, *options, &block)
133
+ raise "Invalid property definition, '#{name}' already used for CouchRest Model type field" if name.to_s == model_type_key.to_s && CouchRest::Model::Base >= self
134
+ opts = { }
135
+ type = options.shift
136
+ if type.class != Hash
137
+ opts[:type] = type
138
+ opts.merge!(options.shift || {})
139
+ else
140
+ opts.update(type)
141
+ end
142
+ existing_property = self.properties.find{|p| p.name == name.to_s}
143
+ if existing_property.nil? || (existing_property.default != opts[:default])
144
+ define_property(name, opts, &block)
145
+ end
146
+ end
147
+
148
+ # Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
149
+ # on the document whenever saving occurs.
150
+ #
151
+ # These properties are casted as Time objects, so they should always
152
+ # be set to UTC.
153
+ def timestamps!
154
+ property(:updated_at, Time, :read_only => true, :protected => true, :auto_validation => false)
155
+ property(:created_at, Time, :read_only => true, :protected => true, :auto_validation => false)
156
+
157
+ set_callback :save, :before do |object|
158
+ write_attribute('updated_at', Time.now)
159
+ write_attribute('created_at', Time.now) if object.new?
160
+ end
161
+ end
162
+
163
+ protected
164
+
165
+ # This is not a thread safe operation, if you have to set new properties at runtime
166
+ # make sure a mutex is used.
167
+ def define_property(name, options={}, &block)
168
+ # check if this property is going to casted
169
+ type = options.delete(:type) || options.delete(:cast_as)
170
+ if block_given?
171
+ type = Class.new do
172
+ include Embeddable
173
+ end
174
+ if block.arity == 1 # Traditional, with options
175
+ type.class_eval { yield type }
176
+ else
177
+ type.instance_exec(&block)
178
+ end
179
+ type = [type] # inject as an array
180
+ end
181
+ property = Property.new(name, type, options)
182
+ create_property_getter(property)
183
+ create_property_setter(property) unless property.read_only == true
184
+ if property.type_class.respond_to?(:validates_casted_model)
185
+ validates_casted_model property.name
186
+ end
187
+ properties << property
188
+ properties_by_name[property.to_s] = property
189
+ property
190
+ end
191
+
192
+ # defines the getter for the property (and optional aliases)
193
+ def create_property_getter(property)
194
+ define_method(property.name) do
195
+ read_attribute(property.name)
196
+ end
197
+
198
+ if ['boolean', TrueClass.to_s.downcase].include?(property.type.to_s.downcase)
199
+ define_method("#{property.name}?") do
200
+ value = read_attribute(property.name)
201
+ !(value.nil? || value == false)
202
+ end
203
+ end
204
+
205
+ if property.alias
206
+ alias_method(property.alias, property.name.to_sym)
207
+ end
208
+ end
209
+
210
+ # defines the setter for the property (and optional aliases)
211
+ def create_property_setter(property)
212
+ name = property.name
213
+
214
+ define_method("#{name}=") do |value|
215
+ write_attribute(name, value)
216
+ end
217
+
218
+ if property.alias
219
+ alias_method "#{property.alias}=", "#{name}="
220
+ end
221
+ end
222
+
223
+ end # module ClassMethods
224
+
225
+ end
226
+ end
227
+ end
228
+
@@ -0,0 +1,114 @@
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 params hash where key is index
31
+ value = parameter_hash_to_array(value)
32
+ elsif !value.is_a?(Array)
33
+ raise "Expecting an array or keyed hash for property #{parent.class.name}##{self.name}"
34
+ end
35
+ arr = value.collect { |data| cast_value(parent, data) }
36
+ # allow casted_by calls to be passed up chain by wrapping in CastedArray
37
+ CastedArray.new(arr, self, parent)
38
+ elsif (type == Object || type == Hash) && (value.is_a?(Hash))
39
+ # allow casted_by calls to be passed up chain by wrapping in CastedHash
40
+ CastedHash[value, self, parent]
41
+ elsif !value.nil?
42
+ cast_value(parent, value)
43
+ end
44
+ end
45
+
46
+ # Cast an individual value
47
+ def cast_value(parent, value)
48
+ value = typecast_value(value, self)
49
+ associate_casted_value_to_parent(parent, value)
50
+ end
51
+
52
+ def default_value
53
+ return if default.nil?
54
+ if default.class == Proc
55
+ default.call
56
+ else
57
+ # TODO identify cause of mutex errors
58
+ Marshal.load(Marshal.dump(default))
59
+ end
60
+ end
61
+
62
+ # Initialize a new instance of a property's type ready to be
63
+ # used. If a proc is defined for the init method, it will be used instead of
64
+ # a normal call to the class.
65
+ def build(*args)
66
+ raise StandardError, "Cannot build property without a class" if @type_class.nil?
67
+ if @init_method.is_a?(Proc)
68
+ @init_method.call(*args)
69
+ else
70
+ @type_class.send(@init_method, *args)
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def parameter_hash_to_array(source)
77
+ value = [ ]
78
+ source.keys.each do |k|
79
+ value[k.to_i] = source[k]
80
+ end
81
+ value.compact
82
+ end
83
+
84
+ def associate_casted_value_to_parent(parent, value)
85
+ value.casted_by = parent if value.respond_to?(:casted_by)
86
+ value.casted_by_property = self if value.respond_to?(:casted_by_property)
87
+ value
88
+ end
89
+
90
+ def parse_type(type)
91
+ if type.nil?
92
+ @casted = false
93
+ @type = nil
94
+ @type_class = nil
95
+ else
96
+ base = type.is_a?(Array) ? type.first : type
97
+ base = Object if base.nil?
98
+ raise "Defining a property type as a #{type.class.name.humanize} is not supported in CouchRest Model!" if base.class != Class
99
+ @type_class = base
100
+ @type = type
101
+ end
102
+ end
103
+
104
+ def parse_options(options)
105
+ @validation_format = options.delete(:format) if options[:format]
106
+ @read_only = options.delete(:read_only) if options[:read_only]
107
+ @alias = options.delete(:alias) if options[:alias]
108
+ @default = options.delete(:default) unless options[:default].nil?
109
+ @init_method = options[:init_method] ? options.delete(:init_method) : 'new'
110
+ @options = options
111
+ end
112
+
113
+ end
114
+ end
@@ -0,0 +1,71 @@
1
+ module CouchRest
2
+ module Model
3
+ module PropertyProtection
4
+ extend ActiveSupport::Concern
5
+
6
+ # Property protection from mass assignment to CouchRest::Model properties
7
+ #
8
+ # Protected methods will be removed from
9
+ # * new
10
+ # * update_attributes
11
+ # * upate_attributes_without_saving
12
+ # * attributes=
13
+ #
14
+ # There are two modes of protection
15
+ # 1) Declare accessible poperties, and assume all unspecified properties are protected
16
+ # property :name, :accessible => true
17
+ # property :admin # this will be automatically protected
18
+ #
19
+ # 2) Declare protected properties, and assume all unspecified properties are accessible
20
+ # property :name # this will not be protected
21
+ # property :admin, :protected => true
22
+ #
23
+ # 3) Mix and match, and assume all unspecified properties are protected.
24
+ # property :name, :accessible => true
25
+ # property :admin, :protected => true # ignored
26
+ # property :phone # this will be automatically protected
27
+ #
28
+ # Note: the timestamps! method protectes the created_at and updated_at properties
29
+
30
+
31
+ def self.included(base)
32
+ base.extend(ClassMethods)
33
+ end
34
+
35
+ module ClassMethods
36
+ def accessible_properties
37
+ props = properties.select { |prop| prop.options[:accessible] }
38
+ if props.empty?
39
+ props = properties.select { |prop| !prop.options[:protected] }
40
+ end
41
+ props
42
+ end
43
+
44
+ def protected_properties
45
+ accessibles = accessible_properties
46
+ properties.reject { |prop| accessibles.include?(prop) }
47
+ end
48
+ end
49
+
50
+ def accessible_properties
51
+ self.class.accessible_properties
52
+ end
53
+
54
+ def protected_properties
55
+ self.class.protected_properties
56
+ end
57
+
58
+ # Return a new copy of the attributes hash with protected attributes
59
+ # removed.
60
+ def remove_protected_attributes(attributes)
61
+ protected_names = protected_properties.map { |prop| prop.name }
62
+ return attributes if protected_names.empty? or attributes.nil?
63
+
64
+ attributes.reject do |property_name, property_value|
65
+ protected_names.include?(property_name.to_s)
66
+ end
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,183 @@
1
+ module CouchRest
2
+ module Model
3
+ # :nodoc: Because I like inventing words
4
+ module Proxyable
5
+ extend ActiveSupport::Concern
6
+
7
+ def proxy_database
8
+ raise StandardError, "Please set the #proxy_database_method" if self.class.proxy_database_method.nil?
9
+ @proxy_database ||= self.class.prepare_database(self.send(self.class.proxy_database_method))
10
+ end
11
+
12
+ module ClassMethods
13
+
14
+
15
+ # Define a collection that will use the base model for the database connection
16
+ # details.
17
+ def proxy_for(assoc_name, options = {})
18
+ db_method = options[:database_method] || "proxy_database"
19
+ options[:class_name] ||= assoc_name.to_s.singularize.camelize
20
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
21
+ def #{assoc_name}
22
+ @#{assoc_name} ||= CouchRest::Model::Proxyable::ModelProxy.new(::#{options[:class_name]}, self, self.class.to_s.underscore, #{db_method})
23
+ end
24
+ EOS
25
+ end
26
+
27
+ # Tell this model which other model to use a base for the database
28
+ # connection to use.
29
+ def proxied_by(model_name, options = {})
30
+ raise "Model can only be proxied once or ##{model_name} already defined" if method_defined?(model_name) || !proxy_owner_method.nil?
31
+ self.proxy_owner_method = model_name
32
+ attr_accessor :model_proxy
33
+ attr_accessor model_name
34
+ overwrite_database_reader(model_name)
35
+ end
36
+
37
+ # Define an a class variable accessor ready to be inherited and unique
38
+ # for each Class using the base.
39
+ # Perhaps there is a shorter way of writing this.
40
+ def proxy_owner_method=(name); @proxy_owner_method = name; end
41
+ def proxy_owner_method; @proxy_owner_method; end
42
+
43
+ # Define the name of a method to call to determine the name of
44
+ # the database to use as a proxy.
45
+ def proxy_database_method(name = nil)
46
+ @proxy_database_method = name if name
47
+ @proxy_database_method
48
+ end
49
+
50
+ private
51
+
52
+ # Ensure that no attempt is made to autoload a database connection
53
+ # by overwriting it to provide a basic accessor.
54
+ def overwrite_database_reader(model_name)
55
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
56
+ def self.database
57
+ raise StandardError, "#{self.to_s} database must be accessed via '#{model_name}' proxy"
58
+ end
59
+ EOS
60
+ end
61
+
62
+ end
63
+
64
+ class ModelProxy
65
+
66
+ attr_reader :model, :owner, :owner_name, :database
67
+
68
+ def initialize(model, owner, owner_name, database)
69
+ @model = model
70
+ @owner = owner
71
+ @owner_name = owner_name
72
+ @database = database
73
+ end
74
+
75
+ # Base
76
+ def new(attrs = {}, options = {}, &block)
77
+ proxy_block_update(:new, attrs, options, &block)
78
+ end
79
+
80
+ def build_from_database(attrs = {}, options = {}, &block)
81
+ proxy_block_update(:build_from_database, attrs, options, &block)
82
+ end
83
+
84
+ def method_missing(m, *args, &block)
85
+ if has_view?(m)
86
+ if model.respond_to?(m)
87
+ return model.send(m, *args).proxy(self)
88
+ else
89
+ query = args.shift || {}
90
+ return view(m, query, *args, &block)
91
+ end
92
+ elsif m.to_s =~ /^find_(by_.+)/
93
+ view_name = $1
94
+ if has_view?(view_name)
95
+ return first_from_view(view_name, *args)
96
+ end
97
+ end
98
+ super
99
+ end
100
+
101
+ # DocumentQueries
102
+
103
+ def all(opts = {}, &block)
104
+ proxy_update_all(@model.all({:database => @database}.merge(opts), &block))
105
+ end
106
+
107
+ def count(opts = {})
108
+ @model.count({:database => @database}.merge(opts))
109
+ end
110
+
111
+ def first(opts = {})
112
+ proxy_update(@model.first({:database => @database}.merge(opts)))
113
+ end
114
+
115
+ def last(opts = {})
116
+ proxy_update(@model.last({:database => @database}.merge(opts)))
117
+ end
118
+
119
+ def get(id)
120
+ proxy_update(@model.get(id, @database))
121
+ end
122
+ alias :find :get
123
+
124
+ # Views
125
+
126
+ def has_view?(view)
127
+ @model.has_view?(view)
128
+ end
129
+
130
+ def view_by(*args)
131
+ @model.view_by(*args)
132
+ end
133
+
134
+ def view(name, query={}, &block)
135
+ proxy_update_all(@model.view(name, {:database => @database}.merge(query), &block))
136
+ end
137
+
138
+ def first_from_view(name, *args)
139
+ # add to first hash available, or add to end
140
+ (args.last.is_a?(Hash) ? args.last : (args << {}).last)[:database] = @database
141
+ proxy_update(@model.first_from_view(name, *args))
142
+ end
143
+
144
+ # DesignDoc
145
+ def design_doc
146
+ @model.design_doc
147
+ end
148
+
149
+ def save_design_doc(db = nil)
150
+ @model.save_design_doc(db || @database)
151
+ end
152
+
153
+
154
+ protected
155
+
156
+ # Update the document's proxy details, specifically, the fields that
157
+ # link back to the original document.
158
+ def proxy_update(doc)
159
+ if doc && doc.is_a?(model)
160
+ doc.database = @database
161
+ doc.model_proxy = self
162
+ doc.send("#{owner_name}=", owner)
163
+ end
164
+ doc
165
+ end
166
+
167
+ def proxy_update_all(docs)
168
+ docs.each do |doc|
169
+ proxy_update(doc)
170
+ end
171
+ end
172
+
173
+ def proxy_block_update(method, *args, &block)
174
+ model.send(method, *args) do |doc|
175
+ proxy_update(doc)
176
+ yield doc if block_given?
177
+ end
178
+ end
179
+
180
+ end
181
+ end
182
+ end
183
+ end