couch_potato-rails2 0.5.6

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 (88) hide show
  1. data/.gitignore +7 -0
  2. data/.travis.yml +5 -0
  3. data/CHANGES.md +148 -0
  4. data/CREDITS +6 -0
  5. data/Gemfile +4 -0
  6. data/MIT-LICENSE.txt +19 -0
  7. data/README.md +450 -0
  8. data/Rakefile +82 -0
  9. data/couch_potato.gemspec +27 -0
  10. data/init.rb +3 -0
  11. data/lib/core_ext/date.rb +14 -0
  12. data/lib/core_ext/object.rb +5 -0
  13. data/lib/core_ext/string.rb +12 -0
  14. data/lib/core_ext/symbol.rb +15 -0
  15. data/lib/core_ext/time.rb +23 -0
  16. data/lib/couch_potato.rb +48 -0
  17. data/lib/couch_potato/database.rb +179 -0
  18. data/lib/couch_potato/persistence.rb +124 -0
  19. data/lib/couch_potato/persistence/active_model_compliance.rb +44 -0
  20. data/lib/couch_potato/persistence/attachments.rb +31 -0
  21. data/lib/couch_potato/persistence/callbacks.rb +29 -0
  22. data/lib/couch_potato/persistence/dirty_attributes.rb +56 -0
  23. data/lib/couch_potato/persistence/ghost_attributes.rb +12 -0
  24. data/lib/couch_potato/persistence/json.rb +47 -0
  25. data/lib/couch_potato/persistence/magic_timestamps.rb +23 -0
  26. data/lib/couch_potato/persistence/properties.rb +79 -0
  27. data/lib/couch_potato/persistence/simple_property.rb +82 -0
  28. data/lib/couch_potato/persistence/type_caster.rb +40 -0
  29. data/lib/couch_potato/railtie.rb +25 -0
  30. data/lib/couch_potato/rspec.rb +2 -0
  31. data/lib/couch_potato/rspec/matchers.rb +39 -0
  32. data/lib/couch_potato/rspec/matchers/json2.js +482 -0
  33. data/lib/couch_potato/rspec/matchers/list_as_matcher.rb +54 -0
  34. data/lib/couch_potato/rspec/matchers/map_to_matcher.rb +49 -0
  35. data/lib/couch_potato/rspec/matchers/print_r.js +60 -0
  36. data/lib/couch_potato/rspec/matchers/reduce_to_matcher.rb +50 -0
  37. data/lib/couch_potato/rspec/stub_db.rb +46 -0
  38. data/lib/couch_potato/validation.rb +16 -0
  39. data/lib/couch_potato/validation/with_active_model.rb +27 -0
  40. data/lib/couch_potato/validation/with_validatable.rb +41 -0
  41. data/lib/couch_potato/version.rb +3 -0
  42. data/lib/couch_potato/view/base_view_spec.rb +84 -0
  43. data/lib/couch_potato/view/custom_view_spec.rb +42 -0
  44. data/lib/couch_potato/view/custom_views.rb +52 -0
  45. data/lib/couch_potato/view/lists.rb +23 -0
  46. data/lib/couch_potato/view/model_view_spec.rb +75 -0
  47. data/lib/couch_potato/view/properties_view_spec.rb +47 -0
  48. data/lib/couch_potato/view/raw_view_spec.rb +25 -0
  49. data/lib/couch_potato/view/view_query.rb +82 -0
  50. data/rails/init.rb +4 -0
  51. data/rails/reload_classes.rb +47 -0
  52. data/spec/attachments_spec.rb +23 -0
  53. data/spec/callbacks_spec.rb +297 -0
  54. data/spec/create_spec.rb +35 -0
  55. data/spec/custom_view_spec.rb +239 -0
  56. data/spec/default_property_spec.rb +38 -0
  57. data/spec/destroy_spec.rb +29 -0
  58. data/spec/fixtures/address.rb +10 -0
  59. data/spec/fixtures/person.rb +6 -0
  60. data/spec/property_spec.rb +323 -0
  61. data/spec/rails_spec.rb +50 -0
  62. data/spec/railtie_spec.rb +65 -0
  63. data/spec/spec.opts +2 -0
  64. data/spec/spec_helper.rb +44 -0
  65. data/spec/unit/active_model_compliance_spec.rb +98 -0
  66. data/spec/unit/attributes_spec.rb +135 -0
  67. data/spec/unit/base_view_spec_spec.rb +106 -0
  68. data/spec/unit/callbacks_spec.rb +46 -0
  69. data/spec/unit/couch_potato_spec.rb +39 -0
  70. data/spec/unit/create_spec.rb +69 -0
  71. data/spec/unit/custom_views_spec.rb +15 -0
  72. data/spec/unit/database_spec.rb +317 -0
  73. data/spec/unit/date_spec.rb +22 -0
  74. data/spec/unit/dirty_attributes_spec.rb +136 -0
  75. data/spec/unit/initialize_spec.rb +38 -0
  76. data/spec/unit/json_spec.rb +30 -0
  77. data/spec/unit/lists_spec.rb +20 -0
  78. data/spec/unit/model_view_spec_spec.rb +13 -0
  79. data/spec/unit/properties_view_spec_spec.rb +31 -0
  80. data/spec/unit/rspec_matchers_spec.rb +124 -0
  81. data/spec/unit/rspec_stub_db_spec.rb +35 -0
  82. data/spec/unit/string_spec.rb +7 -0
  83. data/spec/unit/time_spec.rb +15 -0
  84. data/spec/unit/validation_spec.rb +67 -0
  85. data/spec/unit/view_query_spec.rb +86 -0
  86. data/spec/update_spec.rb +40 -0
  87. data/spec/view_updates_spec.rb +28 -0
  88. metadata +243 -0
@@ -0,0 +1,44 @@
1
+ module CouchPotato
2
+ module Persistence
3
+ module ActiveModelCompliance
4
+ begin
5
+ require 'active_model'
6
+
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ def to_model
12
+ self
13
+ end
14
+
15
+ def errors
16
+ super || {}
17
+ end
18
+
19
+ def persisted?
20
+ !self.new?
21
+ end
22
+
23
+ def to_key
24
+ persisted? ? [to_param] : nil
25
+ end
26
+
27
+ def destroyed?
28
+ !!_deleted
29
+ end
30
+
31
+ module ClassMethods
32
+ def model_name
33
+ @model_name ||= ::ActiveModel::Name.new(self)
34
+ end
35
+ end
36
+
37
+ rescue LoadError, NameError
38
+ # if it's not installed you probably don't want to use it anyway
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+
@@ -0,0 +1,31 @@
1
+ module CouchPotato
2
+ module Attachments
3
+ def self.included(base) #:nodoc:
4
+ base.class_eval do
5
+ attr_writer :_attachments
6
+
7
+ def _attachments
8
+ @_attachments ||= {}
9
+ end
10
+
11
+ base.extend ClassMethods
12
+ end
13
+ end
14
+
15
+ def to_hash
16
+ if _attachments && _attachments.any?
17
+ super.merge('_attachments' => _attachments)
18
+ else
19
+ super
20
+ end
21
+ end
22
+
23
+ module ClassMethods
24
+ def json_create(json)
25
+ instance = super
26
+ instance._attachments = json['_attachments'] if json
27
+ instance
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,29 @@
1
+ require 'active_support/concern'
2
+ require 'active_model/callbacks'
3
+
4
+ module CouchPotato
5
+ module Persistence
6
+ module Callbacks
7
+ def self.included(base) #:nodoc:
8
+ base.extend ActiveModel::Callbacks
9
+
10
+ base.class_eval do
11
+ attr_accessor :skip_callbacks
12
+
13
+ define_model_callbacks :create, :save, :update, :destroy
14
+ define_model_callbacks *[:save, :create, :update].map {|c| :"validation_on_#{c}"}
15
+ define_model_callbacks :validation unless Config.validation_framework == :active_model
16
+ end
17
+ end
18
+
19
+ # Runs all callbacks on a model with the given name, e.g. :after_create.
20
+ #
21
+ # This method is called by the CouchPotato::Database object when saving/destroying an object
22
+ def run_callbacks(name, &block)
23
+ return if skip_callbacks
24
+
25
+ send(:"_run_#{name}_callbacks", &block)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,56 @@
1
+ require 'bigdecimal'
2
+ module CouchPotato
3
+ module Persistence
4
+ module DirtyAttributes
5
+
6
+ def self.included(base) #:nodoc:
7
+ base.send :include, ActiveModel::Dirty
8
+ base.class_eval do
9
+ after_save :reset_dirty_attributes
10
+ end
11
+ end
12
+
13
+ def initialize(attributes = {})
14
+ super
15
+ end
16
+
17
+ # returns true if a model has dirty attributes, i.e. their value has changed since the last save
18
+ def dirty?
19
+ changed? || @forced_dirty
20
+ end
21
+
22
+ # marks a model as dirty
23
+ def is_dirty
24
+ @forced_dirty = true
25
+ end
26
+
27
+ def method_missing(name, *args)
28
+ if(name.to_s.include?('_will_change!'))
29
+ self.class.define_attribute_methods self.class.property_names
30
+ send(name, *args)
31
+ else
32
+ super
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def reset_dirty_attributes
39
+ @previously_changed = changes
40
+ @changed_attributes.clear
41
+ @forced_dirty = nil
42
+ end
43
+
44
+ def clone_attribute(value)
45
+ if [Fixnum, Symbol, TrueClass, FalseClass, NilClass, Float, BigDecimal].include?(value.class)
46
+ value
47
+ elsif [Hash, Array].include?(value.class)
48
+ #Deep clone
49
+ Marshal::load(Marshal::dump(value))
50
+ else
51
+ value.clone
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,12 @@
1
+ module CouchPotato
2
+ module GhostAttributes #:nodoc:
3
+ def method_missing(name, *args)
4
+ if(value = _document && _document[name.to_s])
5
+ value
6
+ else
7
+ super
8
+ end
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,47 @@
1
+ module CouchPotato
2
+ module Persistence
3
+ module Json
4
+ def self.included(base) #:nodoc:
5
+ base.class_eval do
6
+ extend ClassMethods
7
+ attr_accessor :_document
8
+ end
9
+ end
10
+
11
+ # returns a JSON representation of a model in order to store it in CouchDB
12
+ def to_json(*args)
13
+ to_hash.to_json(*args)
14
+ end
15
+
16
+ # returns all the attributes, the ruby class and the _id and _rev of a model as a Hash
17
+ def to_hash
18
+ (self.class.properties).inject({}) do |props, property|
19
+ property.serialize(props, self)
20
+ props
21
+ end.merge(JSON.create_id => self.class.name).merge(id_and_rev_json)
22
+ end
23
+
24
+ private
25
+
26
+ def id_and_rev_json
27
+ ['_id', '_rev', '_deleted'].inject({}) do |hash, key|
28
+ hash[key] = self.send(key) unless self.send(key).nil?
29
+ hash
30
+ end
31
+ end
32
+
33
+ module ClassMethods
34
+
35
+ # creates a model instance from JSON
36
+ def json_create(json)
37
+ return if json.nil?
38
+ instance = self.new
39
+ instance._id = json[:_id] || json['_id']
40
+ instance._rev = json[:_rev] || json['_rev']
41
+ instance._document = HashWithIndifferentAccess.new(json)
42
+ instance
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,23 @@
1
+ require 'active_support/time'
2
+
3
+ module CouchPotato
4
+ module MagicTimestamps #:nodoc:
5
+ def self.included(base)
6
+ base.instance_eval do
7
+ property :created_at, :type => Time
8
+ property :updated_at, :type => Time
9
+
10
+ before_create lambda {|model|
11
+ model.created_at ||= (Time.zone || Time).now
12
+ @changed_attributes.delete 'created_at'
13
+ model.updated_at ||= (Time.zone || Time).now
14
+ @changed_attributes.delete 'updated_at'
15
+ }
16
+ before_update lambda {|model|
17
+ model.updated_at = (Time.zone || Time).now
18
+ @changed_attributes.delete 'updated_at'
19
+ }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,79 @@
1
+ require File.dirname(__FILE__) + '/simple_property'
2
+
3
+ module CouchPotato
4
+ module Persistence
5
+ module Properties
6
+ class PropertyList
7
+ include Enumerable
8
+
9
+ attr_accessor :list
10
+
11
+ def initialize(clazz)
12
+ @clazz = clazz
13
+ @list = []
14
+ end
15
+
16
+ def each
17
+ (list + inherited_properties).each {|property| yield property}
18
+ end
19
+
20
+ def <<(property)
21
+ @list << property
22
+ end
23
+
24
+ def inherited_properties
25
+ superclazz = @clazz.superclass
26
+ properties = []
27
+ while superclazz && superclazz.respond_to?(:properties)
28
+ properties << superclazz.properties.list
29
+ superclazz = superclazz.superclass
30
+ end
31
+ properties.flatten
32
+ end
33
+ end
34
+
35
+ def self.included(base) #:nodoc:
36
+ base.extend ClassMethods
37
+ base.class_eval do
38
+ def self.properties
39
+ @properties ||= {}
40
+ @properties[name] ||= PropertyList.new(self)
41
+ end
42
+ end
43
+ end
44
+
45
+ def type_caster #:nodoc:
46
+ @type_caster ||= TypeCaster.new
47
+ end
48
+
49
+ module ClassMethods
50
+ # returns all the property names of a model class that have been defined using the #property method
51
+ #
52
+ # example:
53
+ # class Book
54
+ # property :title
55
+ # property :year
56
+ # end
57
+ # Book.property_names # => [:title, :year]
58
+ def property_names
59
+ properties.map(&:name)
60
+ end
61
+
62
+ # Declare a property on a model class. Properties are not typed by default.
63
+ # You can store anything in a property that can be serialized into JSON.
64
+ # If you want a property to be of a custom class you have to define it using the :type option.
65
+ #
66
+ # example:
67
+ # class Book
68
+ # property :title
69
+ # property :year
70
+ # property :publisher, :type => Publisher
71
+ # end
72
+ def property(name, options = {})
73
+ properties << SimpleProperty.new(self, name, options)
74
+ end
75
+
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,82 @@
1
+ module CouchPotato
2
+ module Persistence
3
+ module PropertyMethods
4
+ private
5
+
6
+ def load_attribute_from_document(name)
7
+ if (_document || {}).has_key?(name.to_s)
8
+ property = self.class.properties.find{|property| property.name == name}
9
+ @skip_dirty_tracking = true
10
+ value = property.build(self, _document)
11
+ @skip_dirty_tracking = false
12
+ value
13
+ end
14
+ end
15
+ end
16
+
17
+ class SimpleProperty #:nodoc:
18
+ attr_accessor :name, :type
19
+
20
+ def initialize(owner_clazz, name, options = {})
21
+ self.name = name
22
+ self.type = options[:type]
23
+ @type_caster = TypeCaster.new
24
+ owner_clazz.send :include, PropertyMethods unless owner_clazz.ancestors.include?(PropertyMethods)
25
+
26
+ define_accessors accessors_module_for(owner_clazz), name, options
27
+ end
28
+
29
+ def build(object, json)
30
+ value = json[name.to_s].nil? ? json[name.to_sym] : json[name.to_s]
31
+ object.send "#{name}=", value
32
+ end
33
+
34
+ def dirty?(object)
35
+ object.send("#{name}_changed?")
36
+ end
37
+
38
+ def serialize(json, object)
39
+ json[name] = object.send name
40
+ end
41
+ alias :value :serialize
42
+
43
+ private
44
+
45
+ def accessors_module_for(clazz)
46
+ unless clazz.const_defined?('AccessorMethods')
47
+ accessors_module = clazz.const_set('AccessorMethods', Module.new)
48
+ clazz.send(:include, accessors_module)
49
+ end
50
+ clazz.const_get('AccessorMethods')
51
+ end
52
+
53
+ def define_accessors(base, name, options)
54
+ base.class_eval do
55
+ include PropertyMethods
56
+
57
+ define_method "#{name}" do
58
+ load_attribute_from_document(name) unless instance_variable_defined?("@#{name}")
59
+ value = instance_variable_get("@#{name}")
60
+ if value.nil? && options[:default]
61
+ default = clone_attribute(options[:default])
62
+ self.instance_variable_set("@#{name}", default)
63
+ default
64
+ else
65
+ value
66
+ end
67
+ end
68
+
69
+ define_method "#{name}=" do |value|
70
+ typecasted_value = type_caster.cast(value, options[:type])
71
+ send("#{name}_will_change!") unless @skip_dirty_tracking || typecasted_value == send(name)
72
+ self.instance_variable_set("@#{name}", typecasted_value)
73
+ end
74
+
75
+ define_method "#{name}?" do
76
+ !self.send(name).nil? && !self.send(name).try(:blank?)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,40 @@
1
+ module CouchPotato
2
+ module Persistence
3
+ class TypeCaster #:nodoc:
4
+ def cast(value, type)
5
+ if type == :boolean
6
+ cast_boolean(value)
7
+ else
8
+ cast_native(value, type)
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def cast_boolean(value)
15
+ if [FalseClass, TrueClass].include?(value.class) || value.nil?
16
+ value
17
+ elsif [0, '0', 'false'].include?(value)
18
+ false
19
+ else
20
+ true
21
+ end
22
+ end
23
+
24
+ def cast_native(value, type)
25
+ if type && !value.instance_of?(type)
26
+ if type == Fixnum
27
+ value.to_s.scan(/-?\d+/).join.to_i unless value.blank?
28
+ elsif type == Float
29
+ value.to_s.scan(/-?\d+\.?\d*/).join.to_f unless value.blank?
30
+ else
31
+ type.json_create value unless value.blank?
32
+ end
33
+ else
34
+ value
35
+ end
36
+ end
37
+
38
+ end
39
+ end
40
+ end