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.
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