andrewtimberlake-couch_potato 0.2.8.1

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 (47) hide show
  1. data/MIT-LICENSE.txt +19 -0
  2. data/README.md +279 -0
  3. data/VERSION.yml +4 -0
  4. data/init.rb +3 -0
  5. data/lib/core_ext/date.rb +10 -0
  6. data/lib/core_ext/object.rb +5 -0
  7. data/lib/core_ext/string.rb +19 -0
  8. data/lib/core_ext/symbol.rb +15 -0
  9. data/lib/core_ext/time.rb +11 -0
  10. data/lib/couch_potato.rb +40 -0
  11. data/lib/couch_potato/database.rb +105 -0
  12. data/lib/couch_potato/persistence.rb +96 -0
  13. data/lib/couch_potato/persistence/belongs_to_property.rb +58 -0
  14. data/lib/couch_potato/persistence/callbacks.rb +60 -0
  15. data/lib/couch_potato/persistence/dirty_attributes.rb +27 -0
  16. data/lib/couch_potato/persistence/json.rb +46 -0
  17. data/lib/couch_potato/persistence/magic_timestamps.rb +13 -0
  18. data/lib/couch_potato/persistence/properties.rb +57 -0
  19. data/lib/couch_potato/persistence/simple_property.rb +83 -0
  20. data/lib/couch_potato/persistence/validation.rb +18 -0
  21. data/lib/couch_potato/view/base_view_spec.rb +24 -0
  22. data/lib/couch_potato/view/custom_view_spec.rb +27 -0
  23. data/lib/couch_potato/view/custom_views.rb +44 -0
  24. data/lib/couch_potato/view/model_view_spec.rb +63 -0
  25. data/lib/couch_potato/view/properties_view_spec.rb +39 -0
  26. data/lib/couch_potato/view/raw_view_spec.rb +25 -0
  27. data/lib/couch_potato/view/view_query.rb +44 -0
  28. data/rails/init.rb +7 -0
  29. data/spec/callbacks_spec.rb +271 -0
  30. data/spec/create_spec.rb +22 -0
  31. data/spec/custom_view_spec.rb +134 -0
  32. data/spec/destroy_spec.rb +29 -0
  33. data/spec/fixtures/address.rb +9 -0
  34. data/spec/fixtures/person.rb +6 -0
  35. data/spec/property_spec.rb +83 -0
  36. data/spec/spec.opts +4 -0
  37. data/spec/spec_helper.rb +31 -0
  38. data/spec/unit/attributes_spec.rb +26 -0
  39. data/spec/unit/callbacks_spec.rb +33 -0
  40. data/spec/unit/create_spec.rb +58 -0
  41. data/spec/unit/customs_views_spec.rb +15 -0
  42. data/spec/unit/database_spec.rb +38 -0
  43. data/spec/unit/dirty_attributes_spec.rb +113 -0
  44. data/spec/unit/string_spec.rb +13 -0
  45. data/spec/unit/view_query_spec.rb +9 -0
  46. data/spec/update_spec.rb +40 -0
  47. metadata +144 -0
@@ -0,0 +1,96 @@
1
+ require 'digest/md5'
2
+ require File.dirname(__FILE__) + '/database'
3
+ require File.dirname(__FILE__) + '/persistence/properties'
4
+ require File.dirname(__FILE__) + '/persistence/magic_timestamps'
5
+ require File.dirname(__FILE__) + '/persistence/callbacks'
6
+ require File.dirname(__FILE__) + '/persistence/json'
7
+ require File.dirname(__FILE__) + '/persistence/dirty_attributes'
8
+ require File.dirname(__FILE__) + '/persistence/validation'
9
+ require File.dirname(__FILE__) + '/view/custom_views'
10
+ require File.dirname(__FILE__) + '/view/view_query'
11
+
12
+
13
+ module CouchPotato
14
+ module Persistence
15
+
16
+ def self.included(base)
17
+ base.send :include, Properties, Callbacks, Validation, Json, CouchPotato::View::CustomViews
18
+ base.send :include, DirtyAttributes
19
+ base.send :include, MagicTimestamps
20
+ base.class_eval do
21
+ attr_accessor :_id, :_rev, :_attachments, :_deleted, :database
22
+ alias_method :id, :_id
23
+ end
24
+ end
25
+
26
+ # initialize a new instance of the model optionally passing it a hash of attributes.
27
+ # the attributes have to be declared using the #property method
28
+ #
29
+ # example:
30
+ # class Book
31
+ # include CouchPotato::Persistence
32
+ # property :title
33
+ # end
34
+ # book = Book.new :title => 'Time to Relax'
35
+ # book.title # => 'Time to Relax'
36
+ def initialize(attributes = {})
37
+ attributes.each do |name, value|
38
+ self.send("#{name}=", value)
39
+ end if attributes
40
+ end
41
+
42
+ # assign multiple attributes at once.
43
+ # the attributes have to be declared using the #property method
44
+ #
45
+ # example:
46
+ # class Book
47
+ # include CouchPotato::Persistence
48
+ # property :title
49
+ # property :year
50
+ # end
51
+ # book = Book.new
52
+ # book.attributes = {:title => 'Time to Relax', :year => 2009}
53
+ # book.title # => 'Time to Relax'
54
+ # book.year # => 2009
55
+ def attributes=(hash)
56
+ hash.each do |attribute, value|
57
+ self.send "#{attribute}=", value
58
+ end
59
+ end
60
+
61
+ # returns all of a model's attributes that have been defined using the #property method as a Hash
62
+ #
63
+ # example:
64
+ # class Book
65
+ # include CouchPotato::Persistence
66
+ # property :title
67
+ # property :year
68
+ # end
69
+ # book = Book.new :year => 2009
70
+ # book.attributes # => {:title => nil, :year => 2009}
71
+ def attributes
72
+ self.class.properties.inject({}) do |res, property|
73
+ property.serialize(res, self)
74
+ res
75
+ end
76
+ end
77
+
78
+ # returns true if a model hasn't been saved yet, false otherwise
79
+ def new?
80
+ _rev.nil?
81
+ end
82
+ alias_method :new_record?, :new?
83
+
84
+ # returns the document id
85
+ # this is used by rails to construct URLs
86
+ # can be overridden to for example use slugs for URLs instead if ids
87
+ def to_param
88
+ _id
89
+ end
90
+
91
+ def ==(other) #:nodoc:
92
+ other.class == self.class && self.to_json == other.to_json
93
+ end
94
+
95
+ end
96
+ end
@@ -0,0 +1,58 @@
1
+ module CouchPotato
2
+ module Persistence
3
+ class BelongsToProperty #:nodoc:
4
+ attr_accessor :name
5
+
6
+ def initialize(owner_clazz, name, options = {})
7
+ self.name = name
8
+ accessors = <<-ACCESSORS
9
+ def #{name}
10
+ return @#{name} if instance_variable_defined?(:@#{name})
11
+ @#{name} = @#{name}_id ? #{item_class_name}.find(@#{name}_id) : nil
12
+ end
13
+
14
+ def #{name}=(value)
15
+ @#{name} = value
16
+ if value.nil?
17
+ @#{name}_id = nil
18
+ else
19
+ @#{name}_id = value.id
20
+ end
21
+ end
22
+
23
+ def #{name}_id=(id)
24
+ remove_instance_variable(:@#{name}) if instance_variable_defined?(:@#{name})
25
+ @#{name}_id = id
26
+ end
27
+ ACCESSORS
28
+ owner_clazz.class_eval accessors
29
+ owner_clazz.send :attr_reader, "#{name}_id"
30
+ end
31
+
32
+ def save(object)
33
+
34
+ end
35
+
36
+ def dirty?(object)
37
+ false
38
+ end
39
+
40
+ def destroy(object)
41
+
42
+ end
43
+
44
+ def build(object, json)
45
+ object.send "#{name}_id=", json["#{name}_id"]
46
+ end
47
+
48
+ def serialize(json, object)
49
+ json["#{name}_id"] = object.send("#{name}_id") if object.send("#{name}_id")
50
+ end
51
+
52
+ def item_class_name
53
+ @name.to_s.camelize
54
+ end
55
+
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,60 @@
1
+ module CouchPotato
2
+ module Persistence
3
+ module Callbacks
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+
7
+ base.class_eval do
8
+ attr_accessor :skip_callbacks
9
+ def self.callbacks
10
+ @callbacks ||= {}
11
+ @callbacks[self.name] ||= {:before_validation => [], :before_validation_on_create => [],
12
+ :before_validation_on_update => [], :before_validation_on_save => [], :before_create => [],
13
+ :after_create => [], :before_update => [], :after_update => [],
14
+ :before_save => [], :after_save => [],
15
+ :before_destroy => [], :after_destroy => []}
16
+ end
17
+ end
18
+ end
19
+
20
+ # Runs all callbacks on a model with the given name, i.g. :after_create.
21
+ #
22
+ # This method is called by the CouchPotato::Database object when saving/destroying an object
23
+ def run_callbacks(name)
24
+ return if skip_callbacks
25
+ self.class.callbacks[name].uniq.each do |callback|
26
+ if callback.is_a?(Symbol)
27
+ send callback
28
+ elsif callback.is_a?(Proc)
29
+ callback.call self
30
+ else
31
+ raise "Don't know how to handle callback of type #{name.class.name}"
32
+ end
33
+ end
34
+ end
35
+
36
+ module ClassMethods
37
+ [
38
+ :before_validation,
39
+ :before_validation_on_create,
40
+ :before_validation_on_update,
41
+ :before_validation_on_save,
42
+ :before_create,
43
+ :before_save,
44
+ :before_update,
45
+ :before_destroy,
46
+ :after_update,
47
+ :after_save,
48
+ :after_create,
49
+ :after_destroy
50
+ ].each do |callback|
51
+ define_method callback do |*names|
52
+ names.each do |name|
53
+ callbacks[callback] << name
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,27 @@
1
+ module CouchPotato
2
+ module Persistence
3
+ module DirtyAttributes
4
+
5
+ def self.included(base)
6
+ base.class_eval do
7
+ after_save :reset_dirty_attributes
8
+ end
9
+ end
10
+
11
+ # returns true if a model has dirty attributes, i.e. their value has changed since the last save
12
+ def dirty?
13
+ new? || self.class.properties.inject(false) do |res, property|
14
+ res || property.dirty?(self)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def reset_dirty_attributes
21
+ self.class.properties.each do |property|
22
+ instance_variable_set("@#{property.name}_was", send(property.name))
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,46 @@
1
+ module CouchPotato
2
+ module Persistence
3
+ module Json
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ # returns a JSON representation of a model in order to store it in CouchDB
9
+ def to_json(*args)
10
+ to_hash.to_json(*args)
11
+ end
12
+
13
+ # returns all the attributes, the ruby class and the _id and _rev of a model as a Hash
14
+ def to_hash
15
+ (self.class.properties).inject({}) do |props, property|
16
+ property.serialize(props, self)
17
+ props
18
+ end.merge('ruby_class' => self.class.name).merge(id_and_rev_json)
19
+ end
20
+
21
+ private
22
+
23
+ def id_and_rev_json
24
+ ['_id', '_rev', '_deleted'].inject({}) do |hash, key|
25
+ hash[key] = self.send(key) unless self.send(key).nil?
26
+ hash
27
+ end
28
+ end
29
+
30
+ module ClassMethods
31
+
32
+ # creates a model instance from JSON
33
+ def json_create(json)
34
+ return if json.nil?
35
+ instance = self.new
36
+ instance._id = json[:_id] || json['_id']
37
+ instance._rev = json[:_rev] || json['_rev']
38
+ properties.each do |property|
39
+ property.build(instance, json)
40
+ end
41
+ instance
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,13 @@
1
+ module CouchPotato
2
+ module MagicTimestamps #:nodoc:
3
+ def self.included(base)
4
+ base.instance_eval do
5
+ property :created_at, :type => Time
6
+ property :updated_at, :type => Time
7
+
8
+ before_create lambda {|model| model.created_at = Time.now; model.created_at_not_changed}
9
+ before_save lambda {|model| model.updated_at = Time.now; model.updated_at_not_changed}
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,57 @@
1
+ require File.dirname(__FILE__) + '/simple_property'
2
+ require File.dirname(__FILE__) + '/belongs_to_property'
3
+
4
+ module CouchPotato
5
+ module Persistence
6
+ module Properties
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ base.class_eval do
10
+ def self.properties
11
+ @properties ||= {}
12
+ @properties[self.name] ||= []
13
+ end
14
+ end
15
+ end
16
+
17
+ module ClassMethods
18
+ # returns all the property names of a model class that have been defined using the #property method
19
+ #
20
+ # example:
21
+ # class Book
22
+ # property :title
23
+ # property :year
24
+ # end
25
+ # Book.property_names # => [:title, :year]
26
+ def property_names
27
+ properties.map(&:name)
28
+ end
29
+
30
+ def json_create(json) #:nodoc:
31
+ return if json.nil?
32
+ instance = super
33
+ instance.send(:assign_attribute_copies_for_dirty_tracking)
34
+ instance
35
+ end
36
+
37
+ # Declare a proprty on a model class. properties are not typed by default. You can use any of the basic types by JSON (String, Integer, Fixnum, Array, Hash). If you want a property to be of a custom class you have to define it using the :class option.
38
+ #
39
+ # example:
40
+ # class Book
41
+ # property :title
42
+ # property :year
43
+ # property :publisher, :class => Publisher
44
+ # end
45
+ def property(name, options = {})
46
+ clazz = options.delete(:class)
47
+ properties << (clazz || SimpleProperty).new(self, name, options)
48
+ end
49
+
50
+ def belongs_to(name) #:nodoc:
51
+ property name, :class => BelongsToProperty
52
+ end
53
+
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,83 @@
1
+ module CouchPotato
2
+ module Persistence
3
+ class SimpleProperty #:nodoc:
4
+ attr_accessor :name, :type
5
+
6
+ def initialize(owner_clazz, name, options = {})
7
+ self.name = name
8
+ self.type = options[:type]
9
+ owner_clazz.class_eval do
10
+ attr_reader "#{name}_was"
11
+
12
+ def initialize(attributes = {})
13
+ super attributes
14
+ assign_attribute_copies_for_dirty_tracking
15
+ end
16
+
17
+ def assign_attribute_copies_for_dirty_tracking
18
+ attributes.each do |name, value|
19
+ self.instance_variable_set("@#{name}_was", clone_attribute(value))
20
+ end if attributes
21
+ end
22
+ private :assign_attribute_copies_for_dirty_tracking
23
+
24
+ def clone_attribute(value)
25
+ if [Fixnum, Symbol, TrueClass, FalseClass, NilClass, Float].include?(value.class)
26
+ value
27
+ else
28
+ value.clone
29
+ end
30
+ end
31
+
32
+ define_method "#{name}" do
33
+ value = self.instance_variable_get("@#{name}")
34
+ default = options[:default]
35
+ value.blank? ? default : value
36
+ end
37
+
38
+ define_method "#{name}=" do |value|
39
+ self.instance_variable_set("@#{name}", value)
40
+ end
41
+
42
+ define_method "#{name}?" do
43
+ !self.send(name).nil? && !self.send(name).try(:blank?)
44
+ end
45
+
46
+ define_method "#{name}_changed?" do
47
+ !self.instance_variable_get("@#{name}_not_changed") && self.send(name) != self.send("#{name}_was")
48
+ end
49
+
50
+ define_method "#{name}_not_changed" do
51
+ self.instance_variable_set("@#{name}_not_changed", true)
52
+ end
53
+ end
54
+ end
55
+
56
+ def build(object, json)
57
+ value = json[name.to_s] || json[name.to_sym]
58
+ typecasted_value = if type
59
+ type.json_create value
60
+ else
61
+ value
62
+ end
63
+ object.send "#{name}=", typecasted_value
64
+ end
65
+
66
+ def dirty?(object)
67
+ object.send("#{name}_changed?")
68
+ end
69
+
70
+ def save(object)
71
+
72
+ end
73
+
74
+ def destroy(object)
75
+
76
+ end
77
+
78
+ def serialize(json, object)
79
+ json[name] = object.send name
80
+ end
81
+ end
82
+ end
83
+ end