dm-json-search 0.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/.gitignore +1 -0
- data/README +75 -0
- data/Rakefile +14 -0
- data/VERSION +1 -0
- data/dm-json-search.gemspec +44 -0
- data/lib/dm-json-search.rb +3 -0
- data/lib/dm-json-search/query_ext.rb +161 -0
- data/lib/dm-json-search/searchable.rb +29 -0
- metadata +62 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg/*
|
data/README
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
Search your models with JSON!
|
2
|
+
=============================
|
3
|
+
|
4
|
+
How to use
|
5
|
+
----------
|
6
|
+
|
7
|
+
gem install gemcutter # unless you already have
|
8
|
+
gem tumble # unless you already have
|
9
|
+
gem install dm-json-search
|
10
|
+
|
11
|
+
Then, in your model:
|
12
|
+
|
13
|
+
class Book
|
14
|
+
include JsonSearch::Searchable
|
15
|
+
end
|
16
|
+
|
17
|
+
Wala:
|
18
|
+
|
19
|
+
Book.search('[{"eql":{"title":"Psycho"}}]')
|
20
|
+
# Should this be mixed in automagically to every model?
|
21
|
+
|
22
|
+
|
23
|
+
Another example query and it's json
|
24
|
+
-----------------------------------
|
25
|
+
|
26
|
+
puts Ticket.select { |t|
|
27
|
+
t.title == "foo" || (t.user_id == 6 && t.account_id == 9)
|
28
|
+
}.query.to_json
|
29
|
+
|
30
|
+
Becomes:
|
31
|
+
|
32
|
+
{
|
33
|
+
"model": "Ticket",
|
34
|
+
"repository": "default",
|
35
|
+
"conditions": {
|
36
|
+
"and": [{
|
37
|
+
"or": [{
|
38
|
+
"eql": { "Ticket.title": "foo" }
|
39
|
+
},
|
40
|
+
{
|
41
|
+
"and": [{
|
42
|
+
"eql": { "Ticket.user_id": 6 }
|
43
|
+
},
|
44
|
+
{
|
45
|
+
"eql": { "Ticket.account_id": 9 }
|
46
|
+
}]
|
47
|
+
}]
|
48
|
+
}]
|
49
|
+
},
|
50
|
+
"reload": false,
|
51
|
+
"order": ["Ticket.id.asc"],
|
52
|
+
"fields": [...],
|
53
|
+
"links": [], // these don't work yet
|
54
|
+
"unique": false,
|
55
|
+
"limit": null,
|
56
|
+
"offset": 0
|
57
|
+
}
|
58
|
+
|
59
|
+
|
60
|
+
Explanation
|
61
|
+
-----------
|
62
|
+
|
63
|
+
Operations are objects with a key for the Operation#slug and an array of operands. Comparisons are object with a key for the Comparison#slug and an object with a property key and a value for the comparison.
|
64
|
+
|
65
|
+
So, you can send in a huge json string like the above, or you can just send in the conditions part: { "and": [...] }. Or, if you just send in an array, it will wrap that array in an "and" hash.
|
66
|
+
|
67
|
+
All DM Operations and Conditions are permitted on any Property of any Model. This probably needs to be changed to only search some fields.
|
68
|
+
|
69
|
+
|
70
|
+
TODO
|
71
|
+
----
|
72
|
+
|
73
|
+
* Spec it out and make sure there are no vectors for destructive actions
|
74
|
+
* Make links work
|
75
|
+
* Make it better?
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |gemspec|
|
4
|
+
gemspec.name = "dm-json-search"
|
5
|
+
gemspec.summary = "Search your models with JSON!"
|
6
|
+
gemspec.description = "Use JSON and Hashes to create complex queries on your DM models."
|
7
|
+
gemspec.email = "nathan@myobie.com"
|
8
|
+
gemspec.homepage = "http://github.com/myobie/dm-json-search"
|
9
|
+
gemspec.authors = ["Nathan Herald"]
|
10
|
+
end
|
11
|
+
Jeweler::GemcutterTasks.new
|
12
|
+
rescue LoadError
|
13
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
14
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{dm-json-search}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Nathan Herald"]
|
12
|
+
s.date = %q{2009-10-22}
|
13
|
+
s.description = %q{Use JSON and Hashes to create complex queries on your DM models.}
|
14
|
+
s.email = %q{nathan@myobie.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
"README",
|
21
|
+
"Rakefile",
|
22
|
+
"VERSION",
|
23
|
+
"dm-json-search.gemspec",
|
24
|
+
"lib/dm-json-search.rb",
|
25
|
+
"lib/dm-json-search/query_ext.rb",
|
26
|
+
"lib/dm-json-search/searchable.rb"
|
27
|
+
]
|
28
|
+
s.homepage = %q{http://github.com/myobie/dm-json-search}
|
29
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
30
|
+
s.require_paths = ["lib"]
|
31
|
+
s.rubygems_version = %q{1.3.5}
|
32
|
+
s.summary = %q{Search your models with JSON!}
|
33
|
+
|
34
|
+
if s.respond_to? :specification_version then
|
35
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
36
|
+
s.specification_version = 3
|
37
|
+
|
38
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
39
|
+
else
|
40
|
+
end
|
41
|
+
else
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
@@ -0,0 +1,161 @@
|
|
1
|
+
class DataMapper::Query
|
2
|
+
|
3
|
+
def to_hash
|
4
|
+
{
|
5
|
+
"repository" => self.repository.name,
|
6
|
+
"model" => self.model.name,
|
7
|
+
"fields" => self.fields.collect { |p| "#{p.model}.#{p.name}" },
|
8
|
+
"links" => [], # I need to see an example of this,
|
9
|
+
"order" => self.order.collect { |o| "#{o.target.model}.#{o.target.name}.#{o.operator}" },
|
10
|
+
"limit" => self.limit,
|
11
|
+
"offset" => self.offset,
|
12
|
+
"reload" => @reload,
|
13
|
+
"unique" => @unique,
|
14
|
+
"conditions" => operation_to_hash(self.conditions)
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_json
|
19
|
+
to_hash.to_json
|
20
|
+
end
|
21
|
+
|
22
|
+
def all
|
23
|
+
repository.scope { model.all(self) }
|
24
|
+
end
|
25
|
+
|
26
|
+
class << self
|
27
|
+
|
28
|
+
def from_json(json)
|
29
|
+
from_hash(JSON.parse(json))
|
30
|
+
end
|
31
|
+
|
32
|
+
def from_hash(hash)
|
33
|
+
|
34
|
+
respository = hash["repository"].to_sym
|
35
|
+
model = string_to_model(hash["model"])
|
36
|
+
options = {}
|
37
|
+
|
38
|
+
# fields
|
39
|
+
unless hash["fields"].blank? || !hash["fields"].is_a?(Array)
|
40
|
+
options[:fields] = hash["fields"].collect { |f| string_to_property(f) }
|
41
|
+
end
|
42
|
+
|
43
|
+
# links, again, I don't know enough about, I've never used them
|
44
|
+
|
45
|
+
# order
|
46
|
+
unless hash["order"].blank? || !hash["order"].is_a?(Array)
|
47
|
+
options[:order] = hash["order"].collect { |p| string_to_order(p) }
|
48
|
+
end
|
49
|
+
|
50
|
+
options[:limit] = hash["limit"] unless hash["limit"].blank?
|
51
|
+
options[:offset] = hash["offset"] unless hash["offset"].blank?
|
52
|
+
|
53
|
+
unless hash["conditions"].blank?
|
54
|
+
options[:conditions] = string_to_conditions(hash["conditions"])
|
55
|
+
end
|
56
|
+
|
57
|
+
new(repository, model, options)
|
58
|
+
|
59
|
+
end#from_hash
|
60
|
+
|
61
|
+
private
|
62
|
+
def string_to_model(string)
|
63
|
+
DataMapper::Model.descendants.find { |m| m.name == string }
|
64
|
+
end
|
65
|
+
|
66
|
+
def string_to_property(string)
|
67
|
+
strings = string.split(".")
|
68
|
+
|
69
|
+
if model = string_to_model(strings[0])
|
70
|
+
model.properties[strings[1]]
|
71
|
+
else
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def string_to_relationship(string)
|
77
|
+
strings = string.split(".")
|
78
|
+
|
79
|
+
if model = string_to_model(strings[0])
|
80
|
+
model.relationships[strings[1]]
|
81
|
+
else
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def string_to_order(string)
|
87
|
+
property = string_to_property(string)
|
88
|
+
direction = string.split(".")[2] || :asc
|
89
|
+
DataMapper::Query::Direction.new(property, direction.to_sym)
|
90
|
+
end
|
91
|
+
|
92
|
+
def string_to_conditions(conditions)
|
93
|
+
if DataMapper::Query::Conditions::Operation.slugs.include?(conditions.keys.first.to_sym)
|
94
|
+
string_to_operation(conditions)
|
95
|
+
elsif DataMapper::Query::Conditions::Comparison.slugs.include?(conditions.keys.first.to_sym)
|
96
|
+
string_to_comparison(conditions)
|
97
|
+
else
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def string_to_operation(operation)
|
103
|
+
slug = operation.keys.first.to_sym
|
104
|
+
operands = operation.values.first.collect { |o| string_to_conditions(o) }
|
105
|
+
DataMapper::Query::Conditions::Operation.new(slug, *operands)
|
106
|
+
end
|
107
|
+
|
108
|
+
def string_to_comparison(comparison)
|
109
|
+
slug = comparison.keys.first.to_sym
|
110
|
+
compare = comparison.values.first
|
111
|
+
subject = string_to_subject(compare.keys.first)
|
112
|
+
value = compare.values.first
|
113
|
+
DataMapper::Query::Conditions::Comparison.new(slug, subject, value)
|
114
|
+
end
|
115
|
+
|
116
|
+
def string_to_subject(subject)
|
117
|
+
if property = string_to_property(subject)
|
118
|
+
property
|
119
|
+
else
|
120
|
+
string_to_relationship(subject)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end#class << self
|
125
|
+
|
126
|
+
private
|
127
|
+
def operation_to_hash(operation)
|
128
|
+
{
|
129
|
+
operation.slug => operation.operands.collect { |o| operand_to_hash(o) }
|
130
|
+
}
|
131
|
+
end
|
132
|
+
|
133
|
+
def operand_to_hash(operand)
|
134
|
+
# TODO: figure out a better way to detect what type of object it is
|
135
|
+
|
136
|
+
if operand.respond_to?(:subject) # it's a comparison
|
137
|
+
comparison_to_hash(operand)
|
138
|
+
elsif operand.respond_to?(:operands) # it's an operation
|
139
|
+
operation_to_hash(operand)
|
140
|
+
else # it must be some other kind of object
|
141
|
+
operand
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def comparison_to_hash(comparison)
|
146
|
+
{
|
147
|
+
comparison.slug => {
|
148
|
+
subject_to_hash(comparison.subject) => comparison.value
|
149
|
+
}
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
def subject_to_hash(subject)
|
154
|
+
if subject.respond_to?(:parent_model)
|
155
|
+
"#{subject.source_model}.#{subject.name}"
|
156
|
+
else
|
157
|
+
"#{subject.model}.#{subject.name}"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module JsonSearch
|
2
|
+
module Searchable
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def search(json = {})
|
10
|
+
json = JSON.parse(json) if json.is_a?(String)
|
11
|
+
|
12
|
+
if json.is_a?(Array)
|
13
|
+
json = { "conditions" => { "and" => json } }
|
14
|
+
end
|
15
|
+
|
16
|
+
if json["conditions"].blank? && !json["and"].blank?
|
17
|
+
json = { "conditions" => json }
|
18
|
+
end
|
19
|
+
|
20
|
+
json["model"] = name
|
21
|
+
json["repository"] = default_repository_name
|
22
|
+
|
23
|
+
DataMapper::Query.from_hash(json).all
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
JSONSearch = JsonSearch
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dm-json-search
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nathan Herald
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-22 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Use JSON and Hashes to create complex queries on your DM models.
|
17
|
+
email: nathan@myobie.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
files:
|
25
|
+
- .gitignore
|
26
|
+
- README
|
27
|
+
- Rakefile
|
28
|
+
- VERSION
|
29
|
+
- dm-json-search.gemspec
|
30
|
+
- lib/dm-json-search.rb
|
31
|
+
- lib/dm-json-search/query_ext.rb
|
32
|
+
- lib/dm-json-search/searchable.rb
|
33
|
+
has_rdoc: true
|
34
|
+
homepage: http://github.com/myobie/dm-json-search
|
35
|
+
licenses: []
|
36
|
+
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options:
|
39
|
+
- --charset=UTF-8
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
version:
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
requirements: []
|
55
|
+
|
56
|
+
rubyforge_project:
|
57
|
+
rubygems_version: 1.3.5
|
58
|
+
signing_key:
|
59
|
+
specification_version: 3
|
60
|
+
summary: Search your models with JSON!
|
61
|
+
test_files: []
|
62
|
+
|