plucky 0.1

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