openlogic-couchrest_model 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 (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