plucky 0.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.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 John Nunemaker
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,21 @@
1
+ = Plucky
2
+
3
+ Thin layer over the ruby driver that allows you to quickly grab hold of your data (pluck it!).
4
+
5
+ *Still rough around the edges and changing rapidly.*
6
+
7
+ == Install
8
+
9
+ $ gem install plucky
10
+
11
+ == Note on Patches/Pull Requests
12
+
13
+ * Fork the project.
14
+ * Make your feature addition or bug fix.
15
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
16
+ * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
17
+ * Send me a pull request. Bonus points for topic branches.
18
+
19
+ == Copyright
20
+
21
+ Copyright (c) 2010 John Nunemaker. See LICENSE for details.
@@ -0,0 +1,113 @@
1
+ module Plucky
2
+ class CriteriaHash
3
+ attr_reader :source
4
+
5
+ def initialize(hash={}, options={})
6
+ @source, @options = {}, options
7
+ hash.each { |key, value| self[key] = value }
8
+ end
9
+
10
+ def []=(key, value)
11
+ normalized_key = normalized_key(key)
12
+ if key.is_a?(SymbolOperator)
13
+ operator = "$#{key.operator}"
14
+ normalized_value = normalized_value(normalized_key, operator, value)
15
+ source[normalized_key] ||= {}
16
+ source[normalized_key][operator] = normalized_value
17
+ else
18
+ if key == :conditions
19
+ value.each { |k, v| self[k] = v }
20
+ else
21
+ normalized_value = normalized_value(normalized_key, normalized_key, value)
22
+ source[normalized_key] = normalized_value
23
+ end
24
+ end
25
+ end
26
+
27
+ def ==(other)
28
+ source == other.source
29
+ end
30
+
31
+ def to_hash
32
+ source
33
+ end
34
+
35
+ def merge(other)
36
+ target = source.dup
37
+ other.source.each_key do |key|
38
+ value, other_value = target[key], other[key]
39
+ target[key] =
40
+ if target.key?(key)
41
+ value_is_hash = value.is_a?(Hash)
42
+ other_is_hash = other_value.is_a?(Hash)
43
+
44
+ if value_is_hash && other_is_hash
45
+ value.update(other_value) do |key, old_value, new_value|
46
+ Array(old_value).concat(Array(new_value)).uniq
47
+ end
48
+ elsif value_is_hash && !other_is_hash
49
+ if modifier_key = value.keys.detect { |k| k.to_s[0, 1] == '$' }
50
+ value[modifier_key].concat(Array(other_value)).uniq!
51
+ else
52
+ # kaboom! Array(value).concat(Array(other_value)).uniq
53
+ end
54
+ elsif other_is_hash && !value_is_hash
55
+ if modifier_key = other_value.keys.detect { |k| k.to_s[0, 1] == '$' }
56
+ other_value[modifier_key].concat(Array(value)).uniq!
57
+ else
58
+ # kaboom! Array(value).concat(Array(other_value)).uniq
59
+ end
60
+ else
61
+ Array(value).concat(Array(other_value)).uniq
62
+ end
63
+ else
64
+ other_value
65
+ end
66
+ end
67
+ self.class.new(target)
68
+ end
69
+
70
+ def object_ids
71
+ @options[:object_ids] ||= []
72
+ end
73
+
74
+ def object_ids=(value)
75
+ raise ArgumentError unless value.respond_to?(:flatten)
76
+ @options[:object_ids] = value.flatten
77
+ end
78
+
79
+ private
80
+ def method_missing(method, *args, &block)
81
+ @source.send(method, *args, &block)
82
+ end
83
+
84
+ def object_id?(key)
85
+ object_ids.include?(key.to_sym)
86
+ end
87
+
88
+ def normalized_key(key)
89
+ key = key.to_sym if key.respond_to?(:to_sym)
90
+ return normalized_key(key.field) if key.respond_to?(:field)
91
+ return :_id if key == :id
92
+ key
93
+ end
94
+
95
+ def normalized_value(parent_key, key, value)
96
+ case value
97
+ when Array, Set
98
+ value.map! { |v| Plucky.to_object_id(v) } if object_id?(parent_key)
99
+ parent_key == key ? {'$in' => value.to_a} : value.to_a
100
+ when Time
101
+ value.utc
102
+ when String
103
+ return Plucky.to_object_id(value) if object_id?(key)
104
+ value
105
+ when Hash
106
+ value.each { |k, v| value[k] = normalized_value(key, k, v) }
107
+ value
108
+ else
109
+ value
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,15 @@
1
+ class SymbolOperator
2
+ attr_reader :field, :operator
3
+
4
+ def initialize(field, operator, options={})
5
+ @field, @operator = field, operator
6
+ end unless method_defined?(:initialize)
7
+ end
8
+
9
+ class Symbol
10
+ %w(gt lt gte lte ne in nin mod all size exists asc desc).each do |operator|
11
+ define_method(operator) do
12
+ SymbolOperator.new(self, operator)
13
+ end unless method_defined?(operator)
14
+ end
15
+ end
@@ -0,0 +1,97 @@
1
+ module Plucky
2
+ class OptionsHash
3
+ attr_reader :source
4
+
5
+ def initialize(hash={})
6
+ @source = {}
7
+ hash.each { |key, value| self[key] = value }
8
+ end
9
+
10
+ def []=(key, value)
11
+ key = normalized_key(key)
12
+ source[key] = normalized_value(key, value)
13
+ end
14
+
15
+ def ==(other)
16
+ source == other.source
17
+ end
18
+
19
+ def to_hash
20
+ source
21
+ end
22
+
23
+ private
24
+ def method_missing(method, *args, &block)
25
+ @source.send(method, *args, &block)
26
+ end
27
+
28
+ NormalizedKeys = {
29
+ :order => :sort,
30
+ :select => :fields,
31
+ :offset => :skip,
32
+ :id => :_id,
33
+ }
34
+
35
+ def normalized_key(key)
36
+ NormalizedKeys.default = key
37
+ NormalizedKeys[key.to_sym]
38
+ end
39
+
40
+ def normalized_value(key, value)
41
+ case key
42
+ when :fields
43
+ normalized_fields(value)
44
+ when :sort
45
+ normalized_sort(value)
46
+ when :limit, :skip
47
+ value.nil? ? nil : value.to_i
48
+ else
49
+ value
50
+ end
51
+ end
52
+
53
+ def normalized_fields(value)
54
+ return nil if value.respond_to?(:empty?) && value.empty?
55
+ case value
56
+ when Array
57
+ value.flatten
58
+ when Symbol
59
+ [value]
60
+ when String
61
+ value.split(',').map { |v| v.strip }
62
+ else
63
+ value
64
+ end
65
+ end
66
+
67
+ def normalized_sort(value)
68
+ case value
69
+ when Array
70
+ value.compact.map { |v| normalized_sort_piece(v).flatten }
71
+ else
72
+ normalized_sort_piece(value)
73
+ end
74
+ end
75
+
76
+ def normalized_sort_piece(value)
77
+ case value
78
+ when SymbolOperator
79
+ [normalized_direction(value.field, value.operator)]
80
+ when String
81
+ value.split(',').map do |piece|
82
+ normalized_direction(*piece.split(' '))
83
+ end
84
+ when Symbol
85
+ [normalized_direction(value)]
86
+ else
87
+ value
88
+ end
89
+ end
90
+
91
+ def normalized_direction(field, direction=nil)
92
+ direction ||= 'ASC'
93
+ direction = direction.upcase == 'ASC' ? 1 : -1
94
+ [normalized_key(field).to_s, direction]
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,113 @@
1
+ module Plucky
2
+ class Query
3
+ OptionKeys = [
4
+ :select, :offset, :order, # MM
5
+ :fields, :skip, :limit, :sort, :hint, :snapshot, :batch_size, :timeout # Ruby Driver
6
+ ]
7
+
8
+ attr_reader :criteria, :options, :collection
9
+
10
+ def initialize(collection, opts={})
11
+ @collection, @options, @criteria = collection, OptionsHash.new, CriteriaHash.new
12
+ opts.each { |key, value| self[key] = value }
13
+ end
14
+
15
+ def object_ids(*keys)
16
+ return criteria.object_ids if keys.empty?
17
+ criteria.object_ids = *keys
18
+ self
19
+ end
20
+
21
+ def find(opts={})
22
+ update(opts).collection.find(criteria.to_hash, options.to_hash)
23
+ end
24
+
25
+ def find_one(opts={})
26
+ update(opts).collection.find_one(criteria.to_hash, options.to_hash)
27
+ end
28
+
29
+ def all(opts={})
30
+ [].tap do |docs|
31
+ update(opts).find(to_hash).each { |doc| docs << doc }
32
+ end
33
+ end
34
+
35
+ def first(opts={})
36
+ update(opts).find_one(to_hash)
37
+ end
38
+
39
+ def last(opts={})
40
+ update(opts).reverse.find_one(to_hash)
41
+ end
42
+
43
+ def remove(opts={})
44
+ update(opts).collection.remove(criteria.to_hash)
45
+ end
46
+
47
+ def count(opts={})
48
+ update(opts).find(to_hash).count
49
+ end
50
+
51
+ def update(opts={})
52
+ opts.each { |key, value| self[key] = value }
53
+ self
54
+ end
55
+
56
+ def fields(*args)
57
+ self[:fields] = args
58
+ self
59
+ end
60
+
61
+ def limit(count=nil)
62
+ self[:limit] = count
63
+ self
64
+ end
65
+
66
+ def reverse
67
+ self[:sort].map! { |s| [s[0], -s[1]] }
68
+ self
69
+ end
70
+
71
+ def skip(count=nil)
72
+ self[:skip] = count
73
+ self
74
+ end
75
+
76
+ def sort(*args)
77
+ self[:sort] = *args
78
+ self
79
+ end
80
+
81
+ def where(hash={})
82
+ criteria.merge(CriteriaHash.new(hash)).to_hash.each { |key, value| self[key] = value }
83
+ self
84
+ end
85
+
86
+ def [](key)
87
+ key = key.to_sym if key.respond_to?(:to_sym)
88
+ if OptionKeys.include?(key)
89
+ @options[key]
90
+ else
91
+ @criteria[key]
92
+ end
93
+ end
94
+
95
+ def []=(key, value)
96
+ key = key.to_sym if key.respond_to?(:to_sym)
97
+ if OptionKeys.include?(key)
98
+ @options[key] = value
99
+ else
100
+ @criteria[key] = value
101
+ end
102
+ end
103
+
104
+ def merge(other)
105
+ merged = criteria.merge(other.criteria).to_hash.merge(options.to_hash.merge(other.options.to_hash))
106
+ clone.update(merged)
107
+ end
108
+
109
+ def to_hash
110
+ criteria.to_hash.merge(options.to_hash)
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,3 @@
1
+ module Plucky
2
+ Version = '0.1'
3
+ end
data/lib/plucky.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'set'
2
+ require 'mongo'
3
+ require 'plucky/extensions'
4
+
5
+ module Plucky
6
+ autoload :CriteriaHash, 'plucky/criteria_hash'
7
+ autoload :OptionsHash, 'plucky/options_hash'
8
+ autoload :Query, 'plucky/query'
9
+ autoload :Version, 'plucky/version'
10
+
11
+ def self.to_object_id(value)
12
+ if value.nil? || (value.respond_to?(:empty?) && value.empty?)
13
+ nil
14
+ elsif value.is_a?(BSON::ObjectID)
15
+ value
16
+ else
17
+ BSON::ObjectID.from_string(value.to_s)
18
+ end
19
+ end
20
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: plucky
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ version: "0.1"
9
+ platform: ruby
10
+ authors:
11
+ - John Nunemaker
12
+ autorequire:
13
+ bindir: bin
14
+ cert_chain: []
15
+
16
+ date: 2010-05-17 00:00:00 -04:00
17
+ default_executable:
18
+ dependencies:
19
+ - !ruby/object:Gem::Dependency
20
+ name: mongo
21
+ prerelease: false
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "="
25
+ - !ruby/object:Gem::Version
26
+ segments:
27
+ - 1
28
+ - 0
29
+ version: "1.0"
30
+ type: :runtime
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: shoulda
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 0
41
+ version: "0"
42
+ type: :development
43
+ version_requirements: *id002
44
+ - !ruby/object:Gem::Dependency
45
+ name: jnunemaker-matchy
46
+ prerelease: false
47
+ requirement: &id003 !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ segments:
52
+ - 0
53
+ version: "0"
54
+ type: :development
55
+ version_requirements: *id003
56
+ - !ruby/object:Gem::Dependency
57
+ name: mocha
58
+ prerelease: false
59
+ requirement: &id004 !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ type: :development
67
+ version_requirements: *id004
68
+ description:
69
+ email:
70
+ - nunemaker@gmail.com
71
+ executables: []
72
+
73
+ extensions: []
74
+
75
+ extra_rdoc_files: []
76
+
77
+ files:
78
+ - lib/plucky/criteria_hash.rb
79
+ - lib/plucky/extensions.rb
80
+ - lib/plucky/options_hash.rb
81
+ - lib/plucky/query.rb
82
+ - lib/plucky/version.rb
83
+ - lib/plucky.rb
84
+ - LICENSE
85
+ - README.rdoc
86
+ has_rdoc: true
87
+ homepage: http://github.com/jnunemaker/plucky
88
+ licenses: []
89
+
90
+ post_install_message:
91
+ rdoc_options: []
92
+
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ segments:
107
+ - 1
108
+ - 3
109
+ - 6
110
+ version: 1.3.6
111
+ requirements: []
112
+
113
+ rubyforge_project:
114
+ rubygems_version: 1.3.6
115
+ signing_key:
116
+ specification_version: 3
117
+ summary: Thin layer over the ruby driver that allows you to quickly grab hold of your data (pluck it!).
118
+ test_files: []
119
+