langalex-couch_potato 0.1.1 → 0.2.0

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