couchrest_model-radiant 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/LICENSE +176 -0
  2. data/README.md +19 -0
  3. data/Rakefile +74 -0
  4. data/THANKS.md +21 -0
  5. data/history.txt +207 -0
  6. data/lib/couchrest/model.rb +10 -0
  7. data/lib/couchrest/model/associations.rb +223 -0
  8. data/lib/couchrest/model/base.rb +111 -0
  9. data/lib/couchrest/model/callbacks.rb +27 -0
  10. data/lib/couchrest/model/casted_array.rb +39 -0
  11. data/lib/couchrest/model/casted_model.rb +68 -0
  12. data/lib/couchrest/model/class_proxy.rb +122 -0
  13. data/lib/couchrest/model/collection.rb +263 -0
  14. data/lib/couchrest/model/configuration.rb +51 -0
  15. data/lib/couchrest/model/design_doc.rb +123 -0
  16. data/lib/couchrest/model/document_queries.rb +83 -0
  17. data/lib/couchrest/model/errors.rb +23 -0
  18. data/lib/couchrest/model/extended_attachments.rb +77 -0
  19. data/lib/couchrest/model/persistence.rb +155 -0
  20. data/lib/couchrest/model/properties.rb +208 -0
  21. data/lib/couchrest/model/property.rb +97 -0
  22. data/lib/couchrest/model/property_protection.rb +71 -0
  23. data/lib/couchrest/model/support/couchrest.rb +19 -0
  24. data/lib/couchrest/model/support/hash.rb +9 -0
  25. data/lib/couchrest/model/typecast.rb +175 -0
  26. data/lib/couchrest/model/validations.rb +68 -0
  27. data/lib/couchrest/model/validations/casted_model.rb +14 -0
  28. data/lib/couchrest/model/validations/locale/en.yml +5 -0
  29. data/lib/couchrest/model/validations/uniqueness.rb +44 -0
  30. data/lib/couchrest/model/views.rb +160 -0
  31. data/lib/couchrest/railtie.rb +12 -0
  32. data/lib/couchrest_model.rb +62 -0
  33. data/lib/rails/generators/couchrest_model.rb +16 -0
  34. data/lib/rails/generators/couchrest_model/model/model_generator.rb +27 -0
  35. data/lib/rails/generators/couchrest_model/model/templates/model.rb +2 -0
  36. data/spec/couchrest/assocations_spec.rb +196 -0
  37. data/spec/couchrest/attachment_spec.rb +176 -0
  38. data/spec/couchrest/base_spec.rb +463 -0
  39. data/spec/couchrest/casted_model_spec.rb +438 -0
  40. data/spec/couchrest/casted_spec.rb +75 -0
  41. data/spec/couchrest/class_proxy_spec.rb +132 -0
  42. data/spec/couchrest/configuration_spec.rb +78 -0
  43. data/spec/couchrest/inherited_spec.rb +40 -0
  44. data/spec/couchrest/persistence_spec.rb +415 -0
  45. data/spec/couchrest/property_protection_spec.rb +192 -0
  46. data/spec/couchrest/property_spec.rb +871 -0
  47. data/spec/couchrest/subclass_spec.rb +99 -0
  48. data/spec/couchrest/validations.rb +85 -0
  49. data/spec/couchrest/view_spec.rb +463 -0
  50. data/spec/fixtures/attachments/README +3 -0
  51. data/spec/fixtures/attachments/couchdb.png +0 -0
  52. data/spec/fixtures/attachments/test.html +11 -0
  53. data/spec/fixtures/base.rb +139 -0
  54. data/spec/fixtures/more/article.rb +35 -0
  55. data/spec/fixtures/more/card.rb +17 -0
  56. data/spec/fixtures/more/cat.rb +19 -0
  57. data/spec/fixtures/more/client.rb +6 -0
  58. data/spec/fixtures/more/course.rb +25 -0
  59. data/spec/fixtures/more/event.rb +8 -0
  60. data/spec/fixtures/more/invoice.rb +14 -0
  61. data/spec/fixtures/more/person.rb +9 -0
  62. data/spec/fixtures/more/question.rb +7 -0
  63. data/spec/fixtures/more/sale_entry.rb +9 -0
  64. data/spec/fixtures/more/sale_invoice.rb +13 -0
  65. data/spec/fixtures/more/service.rb +10 -0
  66. data/spec/fixtures/more/user.rb +22 -0
  67. data/spec/fixtures/views/lib.js +3 -0
  68. data/spec/fixtures/views/test_view/lib.js +3 -0
  69. data/spec/fixtures/views/test_view/only-map.js +4 -0
  70. data/spec/fixtures/views/test_view/test-map.js +3 -0
  71. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  72. data/spec/spec_helper.rb +48 -0
  73. metadata +263 -0
@@ -0,0 +1,10 @@
1
+
2
+ module CouchRest
3
+
4
+ module Model
5
+
6
+ VERSION = "1.0.0.beta8"
7
+
8
+ end
9
+
10
+ end
@@ -0,0 +1,223 @@
1
+ module CouchRest
2
+ module Model
3
+ module Associations
4
+
5
+ # Basic support for relationships between CouchRest::Model::Base
6
+
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ module ClassMethods
12
+
13
+ # Define an association that this object belongs to.
14
+ #
15
+ def belongs_to(attrib, *options)
16
+ opts = {
17
+ :foreign_key => attrib.to_s + '_id',
18
+ :class_name => attrib.to_s.camelcase,
19
+ :proxy => nil
20
+ }
21
+ case options.first
22
+ when Hash
23
+ opts.merge!(options.first)
24
+ end
25
+
26
+ begin
27
+ opts[:class] = opts[:class_name].constantize
28
+ rescue
29
+ raise "Unable to convert class name into Constant for #{self.name}##{attrib}"
30
+ end
31
+
32
+ prop = property(opts[:foreign_key], opts)
33
+
34
+ create_belongs_to_getter(attrib, prop, opts)
35
+ create_belongs_to_setter(attrib, prop, opts)
36
+
37
+ prop
38
+ end
39
+
40
+ # Provide access to a collection of objects where the associated
41
+ # property contains a list of the collection item ids.
42
+ #
43
+ # The following:
44
+ #
45
+ # collection_of :groups
46
+ #
47
+ # creates a pseudo property called "groups" which allows access
48
+ # to a CollectionOfProxy object. Adding, replacing or removing entries in this
49
+ # proxy will cause the matching property array, in this case "group_ids", to
50
+ # be kept in sync.
51
+ #
52
+ # Any manual changes made to the collection ids property (group_ids), unless replaced, will require
53
+ # a reload of the CollectionOfProxy for the two sets of data to be in sync:
54
+ #
55
+ # group_ids = ['123']
56
+ # groups == [Group.get('123')]
57
+ # group_ids << '321'
58
+ # groups == [Group.get('123')]
59
+ # groups(true) == [Group.get('123'), Group.get('321')]
60
+ #
61
+ # Of course, saving the parent record will store the collection ids as they are
62
+ # found.
63
+ #
64
+ # The CollectionOfProxy supports the following array functions, anything else will cause
65
+ # a mismatch between the collection objects and collection ids:
66
+ #
67
+ # groups << obj
68
+ # groups.push obj
69
+ # groups.unshift obj
70
+ # groups[0] = obj
71
+ # groups.pop == obj
72
+ # groups.shift == obj
73
+ #
74
+ # Addtional options match those of the the belongs_to method.
75
+ #
76
+ # NOTE: This method is *not* recommended for large collections or collections that change
77
+ # frequently! Use with prudence.
78
+ #
79
+ def collection_of(attrib, *options)
80
+ opts = {
81
+ :foreign_key => attrib.to_s.singularize + '_ids',
82
+ :class_name => attrib.to_s.singularize.camelcase,
83
+ :proxy => nil
84
+ }
85
+ case options.first
86
+ when Hash
87
+ opts.merge!(options.first)
88
+ end
89
+ begin
90
+ opts[:class] = opts[:class_name].constantize
91
+ rescue
92
+ raise "Unable to convert class name into Constant for #{self.name}##{attrib}"
93
+ end
94
+ opts[:readonly] = true
95
+
96
+ prop = property(opts[:foreign_key], [], opts)
97
+
98
+ create_collection_of_property_setter(attrib, prop, opts)
99
+ create_collection_of_getter(attrib, prop, opts)
100
+ create_collection_of_setter(attrib, prop, opts)
101
+
102
+ prop
103
+ end
104
+
105
+
106
+ private
107
+
108
+ def create_belongs_to_getter(attrib, property, options)
109
+ base = options[:proxy] || options[:class_name]
110
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
111
+ def #{attrib}
112
+ @#{attrib} ||= #{options[:foreign_key]}.nil? ? nil : #{base}.get(self.#{options[:foreign_key]})
113
+ end
114
+ EOS
115
+ end
116
+
117
+ def create_belongs_to_setter(attrib, property, options)
118
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
119
+ def #{attrib}=(value)
120
+ self.#{options[:foreign_key]} = value.nil? ? nil : value.id
121
+ @#{attrib} = value
122
+ end
123
+ EOS
124
+ end
125
+
126
+ ### collection_of support methods
127
+
128
+ def create_collection_of_property_setter(attrib, property, options)
129
+ # ensure CollectionOfProxy is nil, ready to be reloaded on request
130
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
131
+ def #{options[:foreign_key]}=(value)
132
+ @#{attrib} = nil
133
+ write_attribute("#{options[:foreign_key]}", value)
134
+ end
135
+ EOS
136
+ end
137
+
138
+ def create_collection_of_getter(attrib, property, options)
139
+ base = options[:proxy] || options[:class_name]
140
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
141
+ def #{attrib}(reload = false)
142
+ return @#{attrib} unless @#{attrib}.nil? or reload
143
+ ary = self.#{options[:foreign_key]}.collect{|i| #{base}.get(i)}
144
+ @#{attrib} = ::CouchRest::CollectionOfProxy.new(ary, self, '#{options[:foreign_key]}')
145
+ end
146
+ EOS
147
+ end
148
+
149
+ def create_collection_of_setter(attrib, property, options)
150
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
151
+ def #{attrib}=(value)
152
+ @#{attrib} = ::CouchRest::CollectionOfProxy.new(value, self, '#{options[:foreign_key]}')
153
+ end
154
+ EOS
155
+ end
156
+
157
+ end
158
+
159
+ end
160
+ end
161
+
162
+ # Special proxy for a collection of items so that adding and removing
163
+ # to the list automatically updates the associated property.
164
+ class CollectionOfProxy < Array
165
+ attr_accessor :property
166
+ attr_accessor :casted_by
167
+
168
+ def initialize(array, casted_by, property)
169
+ self.property = property
170
+ self.casted_by = casted_by
171
+ (array ||= []).compact!
172
+ casted_by[property.to_s] = [] # replace the original array!
173
+ array.compact.each do |obj|
174
+ check_obj(obj)
175
+ casted_by[property.to_s] << obj.id
176
+ end
177
+ super(array)
178
+ end
179
+
180
+ def << obj
181
+ check_obj(obj)
182
+ casted_by[property.to_s] << obj.id
183
+ super(obj)
184
+ end
185
+
186
+ def push(obj)
187
+ check_obj(obj)
188
+ casted_by[property.to_s].push obj.id
189
+ super(obj)
190
+ end
191
+
192
+ def unshift(obj)
193
+ check_obj(obj)
194
+ casted_by[property.to_s].unshift obj.id
195
+ super(obj)
196
+ end
197
+
198
+ def []= index, obj
199
+ check_obj(obj)
200
+ casted_by[property.to_s][index] = obj.id
201
+ super(index, obj)
202
+ end
203
+
204
+ def pop
205
+ casted_by[property.to_s].pop
206
+ super
207
+ end
208
+
209
+ def shift
210
+ casted_by[property.to_s].shift
211
+ super
212
+ end
213
+
214
+ protected
215
+
216
+ def check_obj(obj)
217
+ raise "Object cannot be added to #{casted_by.class.to_s}##{property.to_s} collection unless saved" if obj.new?
218
+ end
219
+
220
+ end
221
+
222
+
223
+ end
@@ -0,0 +1,111 @@
1
+ module CouchRest
2
+ module Model
3
+ class Base < Document
4
+
5
+ extend ActiveModel::Naming
6
+
7
+ include CouchRest::Model::Configuration
8
+ include CouchRest::Model::Persistence
9
+ include CouchRest::Model::Callbacks
10
+ include CouchRest::Model::DocumentQueries
11
+ include CouchRest::Model::Views
12
+ include CouchRest::Model::DesignDoc
13
+ include CouchRest::Model::ExtendedAttachments
14
+ include CouchRest::Model::ClassProxy
15
+ include CouchRest::Model::Collection
16
+ include CouchRest::Model::PropertyProtection
17
+ include CouchRest::Model::Associations
18
+ include CouchRest::Model::Validations
19
+
20
+ def self.subclasses
21
+ @subclasses ||= []
22
+ end
23
+
24
+ def self.inherited(subklass)
25
+ super
26
+ subklass.send(:include, CouchRest::Model::Properties)
27
+ subklass.class_eval <<-EOS, __FILE__, __LINE__ + 1
28
+ def self.inherited(subklass)
29
+ super
30
+ subklass.properties = self.properties.dup
31
+ # This is nasty:
32
+ subklass._validators = self._validators.dup
33
+ end
34
+ EOS
35
+ subclasses << subklass
36
+ end
37
+
38
+ # Accessors
39
+ attr_accessor :casted_by
40
+
41
+
42
+ # Instantiate a new CouchRest::Model::Base by preparing all properties
43
+ # using the provided document hash.
44
+ #
45
+ # Options supported:
46
+ #
47
+ # * :directly_set_attributes: true when data comes directly from database
48
+ #
49
+ def initialize(doc = {}, options = {})
50
+ doc = prepare_all_attributes(doc, options)
51
+ super(doc)
52
+ unless self['_id'] && self['_rev']
53
+ self[self.model_type_key] = self.class.to_s
54
+ end
55
+ after_initialize if respond_to?(:after_initialize)
56
+ end
57
+
58
+
59
+ # Temp solution to make the view_by methods available
60
+ def self.method_missing(m, *args, &block)
61
+ if has_view?(m)
62
+ query = args.shift || {}
63
+ return view(m, query, *args, &block)
64
+ elsif m.to_s =~ /^find_(by_.+)/
65
+ view_name = $1
66
+ if has_view?(view_name)
67
+ return first_from_view(view_name, *args)
68
+ end
69
+ end
70
+ super
71
+ end
72
+
73
+ ### instance methods
74
+
75
+ # Gets a reference to the actual document in the DB
76
+ # Calls up to the next document if there is one,
77
+ # Otherwise we're at the top and we return self
78
+ def base_doc
79
+ return self if base_doc?
80
+ @casted_by.base_doc
81
+ end
82
+
83
+ # Checks if we're the top document
84
+ def base_doc?
85
+ !@casted_by
86
+ end
87
+
88
+ ## Compatibility with ActiveSupport and older frameworks
89
+
90
+ # Hack so that CouchRest::Document, which descends from Hash,
91
+ # doesn't appear to Rails routing as a Hash of options
92
+ def is_a?(klass)
93
+ return false if klass == Hash
94
+ super
95
+ end
96
+ alias :kind_of? :is_a?
97
+
98
+ def persisted?
99
+ !new?
100
+ end
101
+
102
+ def to_key
103
+ new? ? nil : [id]
104
+ end
105
+
106
+ alias :to_param :id
107
+ alias :new_record? :new?
108
+ alias :new_document? :new?
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+
3
+ module CouchRest #:nodoc:
4
+ module Model #:nodoc:
5
+
6
+ module Callbacks
7
+ extend ActiveSupport::Concern
8
+ included do
9
+ extend ActiveModel::Callbacks
10
+
11
+ define_model_callbacks \
12
+ :create,
13
+ :destroy,
14
+ :save,
15
+ :update,
16
+ :validate
17
+
18
+ end
19
+
20
+ def valid?(*) #nodoc
21
+ _run_validation_callbacks { super }
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+ #
2
+ # Wrapper around Array so that the casted_by attribute is set in all
3
+ # elements of the array.
4
+ #
5
+
6
+ module CouchRest::Model
7
+ class CastedArray < Array
8
+ attr_accessor :casted_by
9
+ attr_accessor :property
10
+
11
+ def initialize(array, property)
12
+ self.property = property
13
+ super(array)
14
+ end
15
+
16
+ def << obj
17
+ super(instantiate_and_cast(obj))
18
+ end
19
+
20
+ def push(obj)
21
+ super(instantiate_and_cast(obj))
22
+ end
23
+
24
+ def []= index, obj
25
+ super(index, instantiate_and_cast(obj))
26
+ end
27
+
28
+ protected
29
+
30
+ def instantiate_and_cast(obj)
31
+ if self.casted_by && self.property && obj.class != self.property.type_class
32
+ self.property.cast_value(self.casted_by, obj)
33
+ else
34
+ obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
35
+ obj
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,68 @@
1
+ module CouchRest::Model
2
+ module CastedModel
3
+
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include CouchRest::Model::Configuration
8
+ include CouchRest::Model::Callbacks
9
+ include CouchRest::Model::Properties
10
+ include CouchRest::Model::PropertyProtection
11
+ include CouchRest::Model::Associations
12
+ include CouchRest::Model::Validations
13
+ attr_accessor :casted_by
14
+ end
15
+
16
+ def initialize(keys = {})
17
+ raise StandardError unless self.is_a? Hash
18
+ prepare_all_attributes(keys)
19
+ super()
20
+ end
21
+
22
+ def []= key, value
23
+ super(key.to_s, value)
24
+ end
25
+
26
+ def [] key
27
+ super(key.to_s)
28
+ end
29
+
30
+ # Gets a reference to the top level extended
31
+ # document that a model is saved inside of
32
+ def base_doc
33
+ return nil unless @casted_by
34
+ @casted_by.base_doc
35
+ end
36
+
37
+ # False if the casted model has already
38
+ # been saved in the containing document
39
+ def new?
40
+ @casted_by.nil? ? true : @casted_by.new?
41
+ end
42
+ alias :new_record? :new?
43
+
44
+ def persisted?
45
+ !new?
46
+ end
47
+
48
+ # The to_param method is needed for rails to generate resourceful routes.
49
+ # In your controller, remember that it's actually the id of the document.
50
+ def id
51
+ return nil if base_doc.nil?
52
+ base_doc.id
53
+ end
54
+ alias :to_key :id
55
+ alias :to_param :id
56
+
57
+ # Sets the attributes from a hash
58
+ def update_attributes_without_saving(hash)
59
+ hash.each do |k, v|
60
+ raise NoMethodError, "#{k}= method not available, use property :#{k}" unless self.respond_to?("#{k}=")
61
+ end
62
+ hash.each do |k, v|
63
+ self.send("#{k}=",v)
64
+ end
65
+ end
66
+ alias :attributes= :update_attributes_without_saving
67
+ end
68
+ end