couch_potato-rails2 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
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