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,129 @@
1
+ module CouchRest
2
+ module Model
3
+ class Base < CouchRest::Document
4
+
5
+ extend ActiveModel::Naming
6
+
7
+ include CouchRest::Model::Configuration
8
+ include CouchRest::Model::Connection
9
+ include CouchRest::Model::Persistence
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::Proxyable
16
+ include CouchRest::Model::Collection
17
+ include CouchRest::Model::PropertyProtection
18
+ include CouchRest::Model::Associations
19
+ include CouchRest::Model::Validations
20
+ include CouchRest::Model::Callbacks
21
+ include CouchRest::Model::Designs
22
+ include CouchRest::Model::CastedBy
23
+ include CouchRest::Model::Dirty
24
+ include CouchRest::Model::Callbacks
25
+
26
+ def self.subclasses
27
+ @subclasses ||= []
28
+ end
29
+
30
+ def self.inherited(subklass)
31
+ super
32
+ subklass.send(:include, CouchRest::Model::Properties)
33
+
34
+ subklass.class_eval <<-EOS, __FILE__, __LINE__ + 1
35
+ def self.inherited(subklass)
36
+ super
37
+ subklass.properties = self.properties.dup
38
+ # This is nasty:
39
+ subklass._validators = self._validators.dup
40
+ end
41
+ EOS
42
+ subclasses << subklass
43
+ end
44
+
45
+ # Instantiate a new CouchRest::Model::Base by preparing all properties
46
+ # using the provided document hash.
47
+ #
48
+ # Options supported:
49
+ #
50
+ # * :directly_set_attributes, true when data comes directly from database
51
+ # * :database, provide an alternative database
52
+ #
53
+ # If a block is provided the new model will be passed into the
54
+ # block so that it can be populated.
55
+ def initialize(attributes = {}, options = {})
56
+ super()
57
+ prepare_all_attributes(attributes, options)
58
+ # set the instance's database, if provided
59
+ self.database = options[:database] unless options[:database].nil?
60
+ unless self['_id'] && self['_rev']
61
+ self[self.model_type_key] = self.class.to_s
62
+ end
63
+
64
+ yield self if block_given?
65
+
66
+ after_initialize if respond_to?(:after_initialize)
67
+ run_callbacks(:initialize) { self }
68
+ end
69
+
70
+
71
+ # Temp solution to make the view_by methods available
72
+ def self.method_missing(m, *args, &block)
73
+ if has_view?(m)
74
+ query = args.shift || {}
75
+ return view(m, query, *args, &block)
76
+ elsif m.to_s =~ /^find_(by_.+)/
77
+ view_name = $1
78
+ if has_view?(view_name)
79
+ return first_from_view(view_name, *args)
80
+ end
81
+ end
82
+ super
83
+ end
84
+
85
+ # compatbility for 1.8, it does not use respond_to_missing?
86
+ # thing is, when using it like this only, doing method(:find_by_view)
87
+ # will throw an error
88
+ def self.respond_to?(m, include_private = false)
89
+ super || respond_to_missing?(m, include_private)
90
+ end
91
+
92
+ # ruby 1.9 feature
93
+ # this allows ruby to know that the method is defined using
94
+ # method_missing, and as such, method(:find_by_view) will actually
95
+ # give a Method back, and not throw an error like in 1.8!
96
+ def self.respond_to_missing?(m, include_private = false)
97
+ has_view?(m) || has_view?(m.to_s[/^find_(by_.+)/, 1])
98
+ end
99
+
100
+ def to_key
101
+ new? ? nil : [id]
102
+ end
103
+
104
+ alias :to_param :id
105
+ alias :new_record? :new?
106
+ alias :new_document? :new?
107
+
108
+ # Compare this model with another by confirming to see
109
+ # if the IDs and their databases match!
110
+ #
111
+ # Camparison of the database is required in case the
112
+ # model has been proxied or loaded elsewhere.
113
+ #
114
+ # A Basic CouchRest document will only ever compare using
115
+ # a Hash comparison on the attributes.
116
+ def == other
117
+ return false unless other.is_a?(Base)
118
+ if id.nil? && other.id.nil?
119
+ # no ids? assume comparing nested and revert to hash comparison
120
+ to_hash == other.to_hash
121
+ else
122
+ database == other.database && id == other.id
123
+ end
124
+ end
125
+ alias :eql? :==
126
+
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ module CouchRest #:nodoc:
4
+ module Model #:nodoc:
5
+
6
+ module Callbacks
7
+ extend ActiveSupport::Concern
8
+
9
+ CALLBACKS = [
10
+ :before_validation, :after_validation,
11
+ :after_initialize,
12
+ :before_create, :around_create, :after_create,
13
+ :before_destroy, :around_destroy, :after_destroy,
14
+ :before_save, :around_save, :after_save,
15
+ :before_update, :around_update, :after_update,
16
+ ]
17
+
18
+ included do
19
+ extend ActiveModel::Callbacks
20
+ include ActiveModel::Validations::Callbacks
21
+
22
+ define_model_callbacks :initialize, :only => :after
23
+ define_model_callbacks :create, :destroy, :save, :update
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,83 @@
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
+ include CouchRest::Model::CastedBy
9
+ include CouchRest::Model::Dirty
10
+ attr_accessor :casted_by_property
11
+
12
+ def initialize(array, property, parent = nil)
13
+ self.casted_by_property = property
14
+ self.casted_by = parent unless parent.nil?
15
+ super(array)
16
+ end
17
+
18
+ # Adding new entries
19
+
20
+ def << obj
21
+ super(instantiate_and_cast(obj))
22
+ end
23
+
24
+ def push(obj)
25
+ super(instantiate_and_cast(obj))
26
+ end
27
+
28
+ def unshift(obj)
29
+ super(instantiate_and_cast(obj))
30
+ end
31
+
32
+ def []= index, obj
33
+ value = instantiate_and_cast(obj, false)
34
+ couchrest_parent_will_change! if use_dirty? && value != self[index]
35
+ super(index, value)
36
+ end
37
+
38
+ def pop
39
+ couchrest_parent_will_change! if use_dirty? && self.length > 0
40
+ super
41
+ end
42
+
43
+ def shift
44
+ couchrest_parent_will_change! if use_dirty? && self.length > 0
45
+ super
46
+ end
47
+
48
+ def clear
49
+ couchrest_parent_will_change! if use_dirty? && self.length > 0
50
+ super
51
+ end
52
+
53
+ def delete(obj)
54
+ couchrest_parent_will_change! if use_dirty? && self.length > 0
55
+ super(obj)
56
+ end
57
+
58
+ def delete_at(index)
59
+ couchrest_parent_will_change! if use_dirty? && self.length > 0
60
+ super(index)
61
+ end
62
+
63
+ def build(*args)
64
+ obj = casted_by_property.build(*args)
65
+ self.push(obj)
66
+ obj
67
+ end
68
+
69
+ protected
70
+
71
+ def instantiate_and_cast(obj, change = true)
72
+ property = casted_by_property
73
+ couchrest_parent_will_change! if change && use_dirty?
74
+ if casted_by && property && obj.class != property.type_class
75
+ property.cast_value(casted_by, obj)
76
+ else
77
+ obj.casted_by = casted_by if obj.respond_to?(:casted_by)
78
+ obj.casted_by_property = casted_by_property if obj.respond_to?(:casted_by_property)
79
+ obj
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,33 @@
1
+
2
+ module CouchRest::Model
3
+ module CastedBy
4
+ extend ActiveSupport::Concern
5
+ included do
6
+ self.send(:attr_accessor, :casted_by)
7
+ self.send(:attr_accessor, :casted_by_property)
8
+ end
9
+
10
+ # Gets a reference to the actual document in the DB
11
+ # Calls up to the next document if there is one,
12
+ # Otherwise we're at the top and we return self
13
+ def base_doc
14
+ return self if base_doc?
15
+ casted_by ? casted_by.base_doc : nil
16
+ end
17
+
18
+ # Checks if we're the top document
19
+ def base_doc?
20
+ !casted_by
21
+ end
22
+
23
+ # Provide the property this casted model instance has been
24
+ # used by. If it has not been set, search through the
25
+ # casted_by objects properties to try and find it.
26
+ #def casted_by_property
27
+ # return nil unless casted_by
28
+ # attrs = casted_by.attributes
29
+ # @casted_by_property ||= casted_by.properties.detect{ |k| attrs[k.to_s] === self }
30
+ #end
31
+
32
+ end
33
+ end
@@ -0,0 +1,84 @@
1
+ #
2
+ # Wrapper around Hash so that the casted_by attribute is set.
3
+
4
+ module CouchRest::Model
5
+ class CastedHash < Hash
6
+ include CouchRest::Model::CastedBy
7
+ include CouchRest::Model::Dirty
8
+ attr_accessor :casted_by_property
9
+
10
+ def self.[](hash, property, parent = nil)
11
+ obj = super(hash)
12
+ obj.casted_by_property = property
13
+ obj.casted_by = parent unless parent.nil?
14
+ obj
15
+ end
16
+
17
+ # needed for dirty
18
+ def attributes
19
+ self
20
+ end
21
+
22
+ def []= key, obj
23
+ couchrest_attribute_will_change!(key) if use_dirty? && obj != self[key]
24
+ super(key, obj)
25
+ end
26
+
27
+ def delete(key)
28
+ couchrest_attribute_will_change!(key) if use_dirty? && include?(key)
29
+ super(key)
30
+ end
31
+
32
+ def merge!(other_hash)
33
+ if use_dirty? && other_hash && other_hash.kind_of?(Hash)
34
+ other_hash.keys.each do |key|
35
+ if self[key] != other_hash[key] || !include?(key)
36
+ couchrest_attribute_will_change!(key)
37
+ end
38
+ end
39
+ end
40
+ super(other_hash)
41
+ end
42
+
43
+ def replace(other_hash)
44
+ if use_dirty? && other_hash && other_hash.kind_of?(Hash)
45
+ # new keys and changed keys
46
+ other_hash.keys.each do |key|
47
+ if self[key] != other_hash[key] || !include?(key)
48
+ couchrest_attribute_will_change!(key)
49
+ end
50
+ end
51
+ # old keys
52
+ old_keys = self.keys.reject { |key| other_hash.include?(key) }
53
+ old_keys.each { |key| couchrest_attribute_will_change!(key) }
54
+ end
55
+
56
+ super(other_hash)
57
+ end
58
+
59
+ def clear
60
+ self.keys.each { |key| couchrest_attribute_will_change!(key) } if use_dirty?
61
+ super
62
+ end
63
+
64
+ def delete_if
65
+ if use_dirty? && block_given?
66
+ self.keys.each do |key|
67
+ couchrest_attribute_will_change!(key) if yield key, self[key]
68
+ end
69
+ end
70
+ super
71
+ end
72
+
73
+ # ruby 1.9
74
+ def keep_if
75
+ if use_dirty? && block_given?
76
+ self.keys.each do |key|
77
+ couchrest_attribute_will_change!(key) if !yield key, self[key]
78
+ end
79
+ end
80
+ super
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,135 @@
1
+ module CouchRest
2
+ module Model
3
+ module ClassProxy
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+
11
+ # Return a proxy object which represents a model class on a
12
+ # chosen database instance. This allows you to DRY operations
13
+ # where a database is chosen dynamically.
14
+ #
15
+ # ==== Example:
16
+ #
17
+ # db = CouchRest::Database.new(...)
18
+ # articles = Article.on(db)
19
+ #
20
+ # articles.all { ... }
21
+ # articles.by_title { ... }
22
+ #
23
+ # u = articles.get("someid")
24
+ #
25
+ # u = articles.new(:title => "I like plankton")
26
+ # u.save # saved on the correct database
27
+
28
+ def on(database)
29
+ Proxy.new(self, database)
30
+ end
31
+ end
32
+
33
+ class Proxy #:nodoc:
34
+ def initialize(klass, database)
35
+ @klass = klass
36
+ @database = database
37
+ end
38
+
39
+ # Base
40
+
41
+ def new(*args)
42
+ doc = @klass.new(*args)
43
+ doc.database = @database
44
+ doc
45
+ end
46
+
47
+ def method_missing(m, *args, &block)
48
+ if has_view?(m)
49
+ query = args.shift || {}
50
+ return view(m, query, *args, &block)
51
+ elsif m.to_s =~ /^find_(by_.+)/
52
+ view_name = $1
53
+ if has_view?(view_name)
54
+ return first_from_view(view_name, *args)
55
+ end
56
+ end
57
+ super
58
+ end
59
+
60
+ # DocumentQueries
61
+
62
+ def all(opts = {}, &block)
63
+ docs = @klass.all({:database => @database}.merge(opts), &block)
64
+ docs.each { |doc| doc.database = @database if doc.respond_to?(:database) } if docs
65
+ docs
66
+ end
67
+
68
+ def count(opts = {}, &block)
69
+ @klass.all({:database => @database, :raw => true, :limit => 0}.merge(opts), &block)['total_rows']
70
+ end
71
+
72
+ def first(opts = {})
73
+ doc = @klass.first({:database => @database}.merge(opts))
74
+ doc.database = @database if doc && doc.respond_to?(:database)
75
+ doc
76
+ end
77
+
78
+ def last(opts = {})
79
+ doc = @klass.last({:database => @database}.merge(opts))
80
+ doc.database = @database if doc && doc.respond_to?(:database)
81
+ doc
82
+ end
83
+
84
+ def get(id)
85
+ doc = @klass.get(id, @database)
86
+ doc.database = @database if doc && doc.respond_to?(:database)
87
+ doc
88
+ end
89
+ alias :find :get
90
+
91
+ def get!(id)
92
+ doc = @klass.get!(id, @database)
93
+ doc.database = @database if doc && doc.respond_to?(:database)
94
+ doc
95
+ end
96
+ alias :find! :get!
97
+
98
+ # Views
99
+
100
+ def has_view?(view)
101
+ @klass.has_view?(view)
102
+ end
103
+
104
+ def view(name, query={}, &block)
105
+ docs = @klass.view(name, {:database => @database}.merge(query), &block)
106
+ docs.each { |doc| doc.database = @database if doc.respond_to?(:database) } if docs
107
+ docs
108
+ end
109
+
110
+ def first_from_view(name, *args)
111
+ # add to first hash available, or add to end
112
+ (args.last.is_a?(Hash) ? args.last : (args << {}).last)[:database] = @database
113
+ doc = @klass.first_from_view(name, *args)
114
+ doc.database = @database if doc && doc.respond_to?(:database)
115
+ doc
116
+ end
117
+
118
+ # DesignDoc
119
+
120
+ def design_doc
121
+ @klass.design_doc
122
+ end
123
+
124
+ def refresh_design_doc
125
+ @klass.refresh_design_doc(@database)
126
+ end
127
+
128
+ def save_design_doc
129
+ @klass.save_design_doc(@database)
130
+ end
131
+
132
+ end
133
+ end
134
+ end
135
+ end