langalex-couch_potato 0.1.1 → 0.2.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 (61) hide show
  1. data/README.md +260 -0
  2. data/VERSION.yml +2 -2
  3. data/init.rb +3 -0
  4. data/lib/core_ext/date.rb +10 -0
  5. data/lib/core_ext/string.rb +15 -0
  6. data/lib/core_ext/time.rb +6 -9
  7. data/lib/couch_potato/database.rb +90 -0
  8. data/lib/couch_potato/persistence/belongs_to_property.rb +1 -1
  9. data/lib/couch_potato/persistence/callbacks.rb +14 -11
  10. data/lib/couch_potato/persistence/dirty_attributes.rb +13 -6
  11. data/lib/couch_potato/persistence/json.rb +9 -14
  12. data/lib/couch_potato/persistence/magic_timestamps.rb +13 -0
  13. data/lib/couch_potato/persistence/properties.rb +10 -8
  14. data/lib/couch_potato/persistence/simple_property.rb +14 -11
  15. data/lib/couch_potato/persistence.rb +11 -165
  16. data/lib/couch_potato/view/base_view_spec.rb +20 -0
  17. data/lib/couch_potato/view/custom_view_spec.rb +26 -0
  18. data/lib/couch_potato/view/custom_views.rb +30 -0
  19. data/lib/couch_potato/view/model_view_spec.rb +39 -0
  20. data/lib/couch_potato/view/properties_view_spec.rb +35 -0
  21. data/lib/couch_potato/view/raw_view_spec.rb +21 -0
  22. data/lib/couch_potato/view/view_query.rb +45 -0
  23. data/lib/couch_potato.rb +23 -6
  24. data/rails/init.rb +7 -0
  25. data/spec/callbacks_spec.rb +40 -43
  26. data/spec/create_spec.rb +9 -60
  27. data/spec/custom_view_spec.rb +93 -19
  28. data/spec/destroy_spec.rb +5 -4
  29. data/spec/property_spec.rb +22 -8
  30. data/spec/spec_helper.rb +12 -14
  31. data/spec/unit/attributes_spec.rb +26 -0
  32. data/spec/unit/create_spec.rb +58 -0
  33. data/spec/{dirty_attributes_spec.rb → unit/dirty_attributes_spec.rb} +31 -13
  34. data/spec/unit/string_spec.rb +13 -0
  35. data/spec/unit/view_query_spec.rb +2 -3
  36. data/spec/update_spec.rb +8 -7
  37. metadata +54 -32
  38. data/README.textile +0 -340
  39. data/lib/couch_potato/active_record/compatibility.rb +0 -9
  40. data/lib/couch_potato/ordering.rb +0 -84
  41. data/lib/couch_potato/persistence/bulk_save_queue.rb +0 -47
  42. data/lib/couch_potato/persistence/collection.rb +0 -51
  43. data/lib/couch_potato/persistence/custom_view.rb +0 -41
  44. data/lib/couch_potato/persistence/external_collection.rb +0 -83
  45. data/lib/couch_potato/persistence/external_has_many_property.rb +0 -72
  46. data/lib/couch_potato/persistence/find.rb +0 -21
  47. data/lib/couch_potato/persistence/finder.rb +0 -65
  48. data/lib/couch_potato/persistence/inline_has_many_property.rb +0 -43
  49. data/lib/couch_potato/persistence/view_query.rb +0 -81
  50. data/lib/couch_potato/versioning.rb +0 -46
  51. data/spec/attributes_spec.rb +0 -42
  52. data/spec/belongs_to_spec.rb +0 -55
  53. data/spec/find_spec.rb +0 -96
  54. data/spec/finder_spec.rb +0 -125
  55. data/spec/has_many_spec.rb +0 -241
  56. data/spec/inline_collection_spec.rb +0 -15
  57. data/spec/ordering_spec.rb +0 -95
  58. data/spec/reload_spec.rb +0 -50
  59. data/spec/unit/external_collection_spec.rb +0 -84
  60. data/spec/unit/finder_spec.rb +0 -10
  61. data/spec/versioning_spec.rb +0 -150
@@ -1,84 +0,0 @@
1
- module CouchPotato
2
- module Ordering
3
- def self.included(base)
4
- base.class_eval do
5
- property :position
6
- cattr_accessor :ordering_scope
7
-
8
- before_create :set_position
9
- before_create :update_positions
10
- before_destroy :update_lower_positions_after_destroy
11
- before_update :update_positions
12
-
13
- def self.set_ordering_scope(scope)
14
- self.ordering_scope = scope
15
- end
16
-
17
- def position=(new_position)
18
- @old_position = position
19
- @position = new_position
20
- end
21
- end
22
- end
23
-
24
- private
25
-
26
- MAX = 9999999
27
-
28
- def set_position
29
- self.position ||= self.class.count(scope_conditions) + 1
30
- end
31
-
32
- def update_positions
33
- @old_position = MAX if new_document?
34
- return unless @old_position
35
- if position < @old_position
36
- new_lower_items = find_in_positions self.position, @old_position - 1
37
- move new_lower_items, :down
38
- elsif position > @old_position
39
- new_higher_items = find_in_positions @old_position + 1, position
40
- move new_higher_items, :up
41
- end
42
- end
43
-
44
- def update_lower_positions_after_destroy
45
- lower_items = find_in_positions self.position + 1, MAX
46
- move lower_items, :up
47
- end
48
-
49
- def find_in_positions(from, to)
50
- self.class.all scope_conditions.merge(:position => from..to)
51
- end
52
-
53
- def scope_conditions
54
- if ordering_scope
55
- {ordering_scope => self.send(ordering_scope)}
56
- else
57
- {}
58
- end
59
- end
60
-
61
- def move(items, direction)
62
- items.each do |item|
63
- if direction == :up
64
- item.position -= 1
65
- else
66
- item.position += 1
67
- end
68
- self.bulk_save_queue << item
69
- end
70
- end
71
- end
72
-
73
- module ExternalCollectionOrderedFindExtension
74
- def items
75
- if @item_class.property_names.include?(:position)
76
- super @item_class, @owner_id_attribute_name => owner_id, :position => 1..CouchPotato::Ordering::MAX
77
- else
78
- super
79
- end
80
- end
81
- end
82
- CouchPotato::Persistence::ExternalCollection.send :include, ExternalCollectionOrderedFindExtension
83
- end
84
-
@@ -1,47 +0,0 @@
1
- module CouchPotato
2
- module Persistence
3
- class BulkSaveQueue
4
- attr_reader :callbacks
5
-
6
- def initialize
7
- @other_queues = []
8
- @callbacks = []
9
- @instances = []
10
- end
11
-
12
- def <<(instance)
13
- if own?
14
- @instances << instance
15
- else
16
- @other_queues.last << instance
17
- end
18
- end
19
-
20
- def push_queue(queue)
21
- @other_queues.push queue
22
- end
23
-
24
- def pop_queue
25
- @other_queues.pop
26
- end
27
-
28
- def own?
29
- @other_queues.empty?
30
- end
31
-
32
- def save(&callback)
33
- if own?
34
- @callbacks << callback if callback
35
- res = CouchPotato::Persistence.Db.bulk_save @instances
36
- @instances.clear
37
- @callbacks.each do |_callback|
38
- _callback.call res
39
- end
40
- @callbacks.clear
41
- else
42
- @other_queues.last.callbacks << callback if callback
43
- end
44
- end
45
- end
46
- end
47
- end
@@ -1,51 +0,0 @@
1
- module CouchPotato
2
- module Persistence
3
- class Collection
4
- attr_accessor :items, :item_class
5
-
6
- def initialize(item_class)
7
- @item_class = item_class
8
- @items = []
9
- end
10
-
11
- def build(attributes)
12
- item = @item_class.new(attributes)
13
- self.<< item
14
- item
15
- end
16
-
17
- def ==(other)
18
- other.class == self.class && other.items == items && other.item_class == item_class
19
- end
20
-
21
- def to_json(*args)
22
- raise 'implement me in a subclass'
23
- end
24
-
25
- def self.json_create(json)
26
- raise 'implement me in a subclass'
27
- end
28
-
29
- delegate :[], :<<, :empty?, :any?, :each, :+, :size, :first, :last, :map, :inject, :join, :clear, :select, :reject, :to => :items
30
-
31
- end
32
- end
33
- end
34
-
35
- if Object.const_defined? 'WillPaginate'
36
- module CouchPotato
37
- module Persistence
38
- class Collection
39
- def paginate(options = {})
40
- page = (options[:page] || 1).to_i
41
- per_page = options[:per_page] || 20
42
- collection = WillPaginate::Collection.new page, per_page, self.size
43
- items[((page - 1) * per_page)..(page * (per_page - 1))].each do |item|
44
- collection << item
45
- end
46
- collection
47
- end
48
- end
49
- end
50
- end
51
- end
@@ -1,41 +0,0 @@
1
- module CouchPotato
2
- module Persistence
3
- module CustomView
4
-
5
- def self.included(base)
6
- base.extend ClassMethods
7
- end
8
-
9
- module ClassMethods
10
-
11
- def view(name, options)
12
- (class << self; self; end).instance_eval do
13
- if options[:properties]
14
- define_method name do
15
- ViewQuery.new(self.name.underscore, name, map_function(options[:key], options[:properties]), nil, {}, {}).query_view!['rows'].map{|doc| self.new(doc['value'].merge(:_id => doc['id']))}
16
- end
17
- else
18
- define_method name do
19
- ViewQuery.new(self.name.underscore, name, map_function(options[:key], options[:properties]), nil, {}, {:include_docs => true}).query_view!['rows'].map{|doc| self.new(doc['doc'])}
20
- end
21
- end
22
- end
23
- end
24
-
25
- def map_function(key, properties)
26
- "function(doc) {
27
- emit(doc.#{key}, #{properties_for_map(properties)});
28
- }"
29
- end
30
-
31
- def properties_for_map(properties)
32
- if properties.nil?
33
- 'null'
34
- else
35
- '{' + properties.map{|p| "#{p}: doc.#{p}"}.join(', ') + '}'
36
- end
37
- end
38
- end
39
- end
40
- end
41
- end
@@ -1,83 +0,0 @@
1
- require File.dirname(__FILE__) + '/collection'
2
- require File.dirname(__FILE__) + '/finder'
3
-
4
- module CouchPotato
5
- module Persistence
6
- class ExternalCollection < Collection
7
-
8
- attr_accessor :item_ids, :owner_id
9
-
10
- def initialize(item_class, owner_id_attribute_name)
11
- super item_class
12
- @items = nil
13
- @owner_id_attribute_name = owner_id_attribute_name
14
- end
15
-
16
- def all(options = {}, view_options = {})
17
- if options.empty? && view_options.empty?
18
- items
19
- else
20
- Finder.new.find @item_class, options.merge(@owner_id_attribute_name => @owner_id), view_options
21
- end
22
- end
23
-
24
- def first(options = {}, view_options = {})
25
- if options.empty? && view_options.empty?
26
- items.first
27
- else
28
- Finder.new.find(@item_class, options.merge(@owner_id_attribute_name => @owner_id), view_options).first
29
- end
30
- end
31
-
32
- def count(options = {}, view_options = {})
33
- Finder.new.count @item_class, options.merge(@owner_id_attribute_name => @owner_id), view_options
34
- end
35
-
36
- def build(attributes = {})
37
- item = @item_class.new(attributes)
38
- self.<< item
39
- item.send "#{@owner_id_attribute_name}=", owner_id
40
- item
41
- end
42
-
43
- def create(attributes = {})
44
- item = build(attributes)
45
- item.save
46
- item
47
- end
48
-
49
- def create!(attributes = {})
50
- item = build(attributes)
51
- item.save!
52
- item
53
- end
54
-
55
- def dirty?
56
- return unless @items
57
- @original_item_ids != @items.map(&:_id) || @items.inject(false) {|res, item| res || item.dirty?}
58
- end
59
-
60
- def items
61
- unless @items
62
- @items = Finder.new.find @item_class, @owner_id_attribute_name => owner_id
63
- @original_item_ids = @items.map(&:_id)
64
- end
65
- @items
66
- end
67
-
68
- def save
69
- items.each do |item|
70
- item.send "#{@owner_id_attribute_name}=", owner_id
71
- item.save
72
- end
73
- end
74
-
75
- def destroy
76
- items.each do |item|
77
- item.destroy
78
- end
79
- end
80
- end
81
- end
82
- end
83
-
@@ -1,72 +0,0 @@
1
- module CouchPotato
2
- module Persistence
3
- class ExternalHasManyProperty
4
- attr_accessor :name, :dependent
5
- def initialize(owner_clazz, name, options = {})
6
- @name, @owner_clazz = name, owner_clazz
7
- @dependent = options[:dependent] || :nullify
8
- getter = <<-ACCESORS
9
- def #{name}
10
- @#{name} ||= CouchPotato::Persistence::ExternalCollection.new(#{item_class_name}, :#{owner_clazz.name.underscore}_id)
11
- end
12
-
13
- def #{name}=(items)
14
- items.each do |item|
15
- #{name} << item
16
- end
17
- end
18
- ACCESORS
19
- owner_clazz.class_eval getter
20
- end
21
-
22
- def dirty?(object)
23
- object.send("#{name}").dirty?
24
- end
25
-
26
- def save(object)
27
- object.send(name).owner_id = object._id
28
- object.send(name).each do |item|
29
- item.send("#{@owner_clazz.name.underscore}_id=", object.id)
30
- begin
31
- item.bulk_save_queue.push_queue object.bulk_save_queue
32
- item.save
33
- ensure
34
- item.bulk_save_queue.pop_queue
35
- end
36
- end
37
- end
38
-
39
- def destroy(object)
40
- object.send(name).each do |item|
41
- if dependent == :destroy
42
- begin
43
- item.bulk_save_queue.push_queue object.bulk_save_queue
44
- item.destroy
45
- ensure
46
- item.bulk_save_queue.pop_queue
47
- end
48
- else
49
- item.send("#{@owner_clazz.name.underscore}_id=", nil)
50
- end
51
- end
52
- end
53
-
54
- def build(object, json)
55
- collection = ExternalCollection.new(item_class_name.constantize, "#{@owner_clazz.name.underscore}_id")
56
- collection.owner_id = object.id
57
- object.send("#{name}").clear
58
- object.send "#{name}=", collection
59
- end
60
-
61
- def serialize(json, object)
62
- nil
63
- end
64
-
65
- private
66
-
67
- def item_class_name
68
- @name.to_s.singularize.camelcase
69
- end
70
- end
71
- end
72
- end
@@ -1,21 +0,0 @@
1
- module CouchPotato
2
- module Persistence
3
- module Find
4
- def first(options = {})
5
- Finder.new.find(self, options).first
6
- end
7
-
8
- def last(options = {})
9
- Finder.new.find(self, options, :descending => true).first
10
- end
11
-
12
- def all(options = {})
13
- Finder.new.find(self, options)
14
- end
15
-
16
- def count(options = {})
17
- Finder.new.count(self, options)
18
- end
19
- end
20
- end
21
- end
@@ -1,65 +0,0 @@
1
- require 'uri'
2
-
3
- module CouchPotato
4
- module Persistence
5
- class Finder
6
- # finds all objects of a given type by the given attribute/value pairs
7
- # options: attribute_name => value pairs to search for
8
- # value can also be a range which will do a range search with startkey/endkey
9
- # WARNING: calling this methods creates a new view in couchdb if it's not present already so don't overuse this
10
- def find(clazz, conditions = {}, view_options = {})
11
- to_instances clazz, ViewQuery.new(design_document(clazz), view(conditions), map_function(clazz, search_fields(conditions)), nil, conditions, view_options).query_view!
12
- end
13
-
14
- def count(clazz, conditions = {}, view_options = {})
15
- ViewQuery.new(design_document(clazz), view(conditions) + '_count', map_function(clazz, search_fields(conditions)), count_reduce_function, conditions, view_options).query_view!['rows'].first.try(:[], 'value') || 0
16
- end
17
-
18
- private
19
-
20
- def db(name = nil)
21
- ::CouchPotato::Persistence.Db(name)
22
- end
23
-
24
- def design_document(clazz)
25
- clazz.name.underscore
26
- end
27
-
28
- def map_function(clazz, search_fields)
29
- "function(doc) {
30
- if(doc.ruby_class == '#{clazz}') {
31
- emit(
32
- [#{search_fields.map{|attr| "doc[\"#{attr}\"]"}.join(', ')}], doc
33
- );
34
- }
35
- }"
36
- end
37
-
38
- def count_reduce_function
39
- "function(keys, values, combine) {
40
- if (combine) {
41
- return sum(values);
42
- } else {
43
- return values.length;
44
- }
45
- }"
46
- end
47
-
48
- def to_instances(clazz, query_result)
49
- query_result['rows'].map{|doc| doc['value']}.map{|json| clazz.json_create json}
50
- end
51
-
52
- def view(conditions)
53
- "by_#{view_name(conditions)}"
54
- end
55
-
56
- def search_fields(conditions)
57
- conditions.to_a.sort_by{|f| f.first.to_s}.map(&:first)
58
- end
59
-
60
- def view_name(options)
61
- options.to_a.sort_by{|f| f.first.to_s}.map(&:first).join('_and_')
62
- end
63
- end
64
- end
65
- end
@@ -1,43 +0,0 @@
1
- class InlineHasManyProperty
2
- attr_accessor :name
3
-
4
- def initialize(owner_clazz, name, options = {})
5
- @name = name
6
- getter = <<-GETTER
7
- def #{name}
8
- @#{name} ||= CouchPotato::Persistence::InlineCollection.new(#{item_class_name})
9
- end
10
- GETTER
11
- owner_clazz.class_eval getter
12
- end
13
-
14
- def build(object, json)
15
- object.send("#{name}").clear
16
- json[name.to_s].each do |item|
17
- item.delete 'ruby_class'
18
- object.send("#{name}").build item
19
- end
20
- end
21
-
22
- def dirty?(object)
23
- object.send("#{name}").dirty?
24
- end
25
-
26
- def save(object)
27
-
28
- end
29
-
30
- def serialize(json, object)
31
- json[name.to_s] = object.send(name)
32
- end
33
-
34
- def destroy(object)
35
-
36
- end
37
-
38
- private
39
-
40
- def item_class_name
41
- @name.to_s.singularize.camelcase
42
- end
43
- end
@@ -1,81 +0,0 @@
1
- module CouchPotato
2
- module Persistence
3
-
4
- class ViewQuery
5
- def initialize(design_document_name, view_name, map_function, reduce_function = nil, conditions = {}, view_options = {})
6
- @design_document_name = design_document_name
7
- @view_name = view_name
8
- @map_function = map_function
9
- @reduce_function = reduce_function
10
- @conditions = conditions
11
- @view_options = view_options
12
- end
13
-
14
- def query_view!
15
- begin
16
- query_view
17
- rescue RestClient::ResourceNotFound => e
18
- create_view
19
- query_view
20
- end
21
- end
22
-
23
- private
24
-
25
- def create_view
26
- # in couchdb 0.9 we could use only 1 view and pass reduce=false for find and count with reduce
27
- design_doc = db.get "_design/#{@design_document_name}" rescue nil
28
- design_doc ||= {'views' => {}, "_id" => "_design/#{@design_document_name}"}
29
- design_doc['views'][@view_name.to_s] = {
30
- 'map' => @map_function,
31
- 'reduce' => @reduce_function
32
- }
33
- db.save(design_doc)
34
- end
35
-
36
- def db(name = nil)
37
- ::CouchPotato::Persistence.Db(name)
38
- end
39
-
40
- def query_view
41
- db.view view_url, search_keys
42
- end
43
-
44
- def view_url
45
- "#{@design_document_name}/#{@view_name}"
46
- end
47
-
48
- def search_keys
49
- if search_values.select{|v| v.is_a?(Range)}.any?
50
- {:startkey => search_values.map{|v| v.is_a?(Range) ? v.first : v}, :endkey => search_values.map{|v| v.is_a?(Range) ? v.last : v}}.merge(view_options)
51
- elsif search_values.select{|v| v.is_a?(Array)}.any?
52
- {:keys => prepare_multi_key_search(search_values)}.merge(view_options)
53
- else
54
- view_options.merge(search_values.any? ? {:key => search_values} : {})
55
- end
56
- end
57
-
58
- def search_values
59
- conditions.to_a.sort_by{|f| f.first.to_s}.map(&:last)
60
- end
61
-
62
- def view_options
63
- @view_options
64
- end
65
-
66
- def conditions
67
- @conditions
68
- end
69
-
70
- def prepare_multi_key_search(values)
71
- array = values.select{|v| v.is_a?(Array)}.first
72
- index = values.index array
73
- array.map do |item|
74
- copy = values.dup
75
- copy[index] = item
76
- copy
77
- end
78
- end
79
- end
80
- end
81
- end
@@ -1,46 +0,0 @@
1
- module CouchPotato
2
- module Versioning
3
- def self.included(base)
4
- base.class_eval do
5
- property :version
6
- property :master_version_id
7
- cattr_accessor :new_version_condition
8
- before_create :set_version_default
9
- before_update :prepare_for_new_version
10
-
11
- def self.set_version_condition(lambda)
12
- self.new_version_condition = lambda
13
- end
14
- end
15
- end
16
-
17
- def versions(version_no = nil)
18
- if version_no
19
- CouchPotato::Persistence::Finder.new.find(self.class, :version => version_no).first
20
- elsif version == 1
21
- [self]
22
- else
23
- CouchPotato::Persistence::Finder.new.find(self.class, :master_version_id => _id).sort_by(&:version)
24
- end
25
- end
26
-
27
- private
28
-
29
- def set_version_default
30
- self.version ||= 1
31
- end
32
-
33
- def prepare_for_new_version
34
- if new_version_condition.nil? || new_version_condition.call(self)
35
- copy = self.class.get self._id
36
- copy._id = nil
37
- copy._rev = nil
38
- copy.master_version_id = self._id
39
- copy.save_without_callbacks
40
- self.master_version_id ||= self.id
41
- self.version += 1
42
- end
43
- end
44
-
45
- end
46
- end
@@ -1,42 +0,0 @@
1
- require File.dirname(__FILE__) + '/spec_helper'
2
-
3
- class Plant
4
- include CouchPotato::Persistence
5
- property :leaf_count
6
- end
7
-
8
- describe "attributes" do
9
- before(:all) do
10
- CouchPotato::Persistence.Db!
11
- end
12
-
13
- describe 'attributes=' do
14
- it "should assign the attributes" do
15
- plant = Plant.new
16
- plant.attributes = {:leaf_count => 1}
17
- plant.leaf_count.should == 1
18
- end
19
- end
20
-
21
- describe "attributes" do
22
- it "should return the attributes" do
23
- plant = Plant.new :leaf_count => 1
24
- plant.attributes .should == {:leaf_count => 1}
25
- end
26
- end
27
-
28
- describe 'update_attributes' do
29
- it "should assign the attributes" do
30
- plant = Plant.new
31
- plant.update_attributes :leaf_count => 1
32
- plant.leaf_count.should == 1
33
- end
34
-
35
- it "should save the object" do
36
- plant = Plant.new
37
- plant.update_attributes :leaf_count => 1
38
- plant.should_not be_new_document
39
- end
40
- end
41
- end
42
-