medea 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,20 +1,21 @@
1
- require 'rake'
2
-
3
- begin
4
- require 'jeweler'
5
- Jeweler::Tasks.new do |s|
6
- s.name = "medea"
7
- s.summary = "Simple wrapper for persisting objects to JasonDB"
8
- s.email = "michaelj@jasondb.com"
9
- s.homepage = "https://github.com/rob-linton/Medea"
10
- s.description = "Simple wrapper for persisting objects to JasonDB"
11
- s.authors = ["Michael Jensen"]
12
- s.files = FileList["[A-Z]*", "{lib}/medea.rb", "{lib}/medea/*"]
13
- s.files.exclude '{lib}/test*'
14
- s.add_dependency 'json'
15
- s.add_dependency 'rest-client'
16
- end
17
- rescue LoadError
18
- puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
19
- end
20
-
1
+ require 'rake'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |s|
6
+ s.name = "medea"
7
+ s.summary = "Simple wrapper for persisting objects to JasonDB"
8
+ s.email = "michaelj@jasondb.com"
9
+ s.homepage = "https://github.com/rob-linton/Medea"
10
+ s.description = "Simple wrapper for persisting objects to JasonDB"
11
+ s.authors = ["Michael Jensen"]
12
+ s.files = FileList["[A-Z]*", "{lib}/medea.rb", "{lib}/medea/*"]
13
+ s.files.exclude '{lib}/test*'
14
+ s.add_dependency 'json'
15
+ s.add_dependency 'rest-client'
16
+ s.add_dependency 'uuidtools'
17
+ end
18
+ rescue LoadError
19
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
20
+ end
21
+
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.3
1
+ 0.2.4
@@ -1,8 +1,8 @@
1
-
2
- module Medea
3
- require 'medea/inheritable_attributes'
4
- require 'medea/jasonobject'
5
- require 'medea/jasondeferredquery'
6
- require 'medea/jasonlistproperty'
7
- require 'medea/jasondb'
1
+
2
+ module Medea
3
+ require 'medea/inheritable_attributes'
4
+ require 'medea/jasonobject'
5
+ require 'medea/jasondeferredquery'
6
+ require 'medea/jasonlistproperty'
7
+ require 'medea/jasondb'
8
8
  end
@@ -1,31 +1,31 @@
1
- #This module allows for an attribute to be defined on a superclass and carry down into sub-classes with its default.
2
- #Taken from http://railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/
3
-
4
- module ClassLevelInheritableAttributes
5
- def self.included(base)
6
- base.extend(ClassMethods)
7
- end
8
-
9
- module ClassMethods
10
- def inheritable_attributes(*args)
11
- #for some reason, in rails, @inheritable_attributes is set to be an empty hash here...
12
- #check for this strange case and account for it.
13
- @inheritable_attributes = [:inheritable_attributes] if @inheritable_attributes == {} ||
14
- @inheritable_attributes == nil
15
- @inheritable_attributes += args
16
- args.each do |arg|
17
- class_eval %(
18
- class << self; attr_accessor :#{arg} end
19
- )
20
- end
21
- @inheritable_attributes
22
- end
23
-
24
- def inherited(subclass)
25
- @inheritable_attributes.each do |inheritable_attribute|
26
- instance_var = "@#{inheritable_attribute}"
27
- subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
28
- end
29
- end
30
- end
1
+ #This module allows for an attribute to be defined on a superclass and carry down into sub-classes with its default.
2
+ #Taken from http://railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/
3
+
4
+ module ClassLevelInheritableAttributes
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ def inheritable_attributes(*args)
11
+ #for some reason, in rails, @inheritable_attributes is set to be an empty hash here...
12
+ #check for this strange case and account for it.
13
+ @inheritable_attributes = [:inheritable_attributes] if @inheritable_attributes == {} ||
14
+ @inheritable_attributes == nil
15
+ @inheritable_attributes += args
16
+ args.each do |arg|
17
+ class_eval %(
18
+ class << self; attr_accessor :#{arg} end
19
+ )
20
+ end
21
+ @inheritable_attributes
22
+ end
23
+
24
+ def inherited(subclass)
25
+ @inheritable_attributes.each do |inheritable_attribute|
26
+ instance_var = "@#{inheritable_attribute}"
27
+ subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
28
+ end
29
+ end
30
+ end
31
31
  end
@@ -1,20 +1,20 @@
1
- module JasonDB
2
- #jason_url here doesn't include the http[s]:// part, but does include the domain and a trailing '/'
3
- #( so it's "rest.jasondb.com/<domain>/" )
4
-
5
- def JasonDB::db_auth_url mode=:secure
6
- config = Rails.configuration.database_configuration[Rails.env]
7
- user = config["user"]
8
- topic = config["topic"]
9
- password = config["password"]
10
- if config["jason_host"]
11
- host = config["jason_host"]
12
- else
13
- host = "rest.jasondb.com"
14
- end
15
- protocol = "http"
16
- protocol << "s" if mode == :secure
17
- "#{protocol}://#{user}:#{password}@#{host}/#{topic}/"
18
- end
19
-
20
- end
1
+ module JasonDB
2
+ #jason_url here doesn't include the http[s]:// part, but does include the domain and a trailing '/'
3
+ #( so it's "rest.jasondb.com/<domain>/" )
4
+
5
+ def JasonDB::db_auth_url mode=:secure
6
+ config = Rails.configuration.database_configuration[Rails.env]
7
+ user = config["user"]
8
+ topic = config["topic"]
9
+ password = config["password"]
10
+ if config["jason_host"]
11
+ host = config["jason_host"]
12
+ else
13
+ host = "rest.jasondb.com"
14
+ end
15
+ protocol = "http"
16
+ protocol << "s" if mode == :secure
17
+ "#{protocol}://#{user}:#{password}@#{host}/#{topic}/"
18
+ end
19
+
20
+ end
@@ -1,125 +1,125 @@
1
- module Medea
2
- class JasonDeferredQuery
3
- require 'rest_client'
4
-
5
- attr_accessor :time_limit, :result_format, :type, :result_format, :time_limit, :state, :contents, :filters
6
-
7
- def initialize a_class, format=:json
8
- @type = a_class
9
- @filters = {:FILTER => {:HTTP_X_CLASS => a_class.name.to_s}}
10
- @result_format = format
11
- @time_limit = 0
12
- @state = :prefetch
13
- @contents = []
14
- end
15
-
16
- #Here we're going to put the "query" interface
17
-
18
- #here we will capture:
19
- #members_of(object) (where object is an instance of a class that this class can be a member of)
20
- #members_of_<classname>(key)
21
- #find_by_<property>(value)
22
- #Will return a JasonDeferredQuery for this class with the appropriate data filter set
23
- def method_missing(name, *args, &block)
24
- #if we are postfetch, we throw away all our cached results
25
- if self.state == :postfetch
26
- self.state = :prefetch
27
- self.contents = []
28
- end
29
-
30
- if name =~ /^members_of$/
31
- #use the type and key of the first arg (being a JasonObject)
32
- #args[0] must be a JasonObject (or child)
33
- raise ArgumentError, "When looking for members, you must pass a JasonObject" unless args[0].is_a? JasonObject
34
-
35
- self.filters[:DATA_FILTER] ||= {}
36
- self.filters[:DATA_FILTER]["__member_of"] ||= []
37
- self.filters[:DATA_FILTER]["__member_of"] << args[0].jason_key
38
- elsif name =~ /^find_by_(.*)$/
39
- #use the property name from the name variable, and the value from the first arg
40
- add_data_filter $1, args[0].to_s
41
- else
42
- #no method!
43
- super
44
- return
45
- end
46
- #return self, so that we can chain up query refinements
47
- self
48
- end
49
- #end query interface
50
-
51
- def add_data_filter property, value
52
- self.filters[:DATA_FILTER] ||= {}
53
- self.filters[:DATA_FILTER][property] = value
54
- end
55
-
56
- def to_url
57
- url = "#{JasonDB::db_auth_url}@#{self.time_limit}.#{self.result_format}?"
58
- filter_array = []
59
- self.filters.each do |name, val|
60
- if not val
61
- filter_array << name.to_s
62
- next
63
- else
64
- #FILTER's value is a hash (to avoid dupes)
65
- #DATA_FILTER's value is a hash
66
- if val.is_a? Hash
67
- #for each k/v in the hash, we want to add an entry to filter_array
68
- val.each do |field ,value|
69
- if value.is_a? Array
70
- value.each do |i|
71
- filter_array << "#{name.to_s}=#{field}:#{i}"
72
- end
73
- else
74
- filter_array << "#{name.to_s}=#{field.to_s}:#{value.to_s}"
75
- end
76
- end
77
- end
78
- end
79
- end
80
-
81
- url + filter_array.join("&")
82
- end
83
-
84
- #array access interface
85
- def [](index)
86
- execute_query unless self.state == :postfetch
87
- self.contents[index]
88
- end
89
-
90
- def each(&block)
91
- execute_query unless self.state == :postfetch
92
- self.contents.each &block
93
- end
94
-
95
- def count
96
- execute_query unless self.state == :postfetch
97
- self.contents.count
98
- end
99
- #end array interface
100
-
101
- def execute_query
102
- #hit the URL
103
- #fill self.contents with :ghost versions of JasonObjects
104
- begin
105
- #puts " = Executing #{type.name} deferred query! (#{to_url})"
106
- result = JSON.parse(RestClient.get to_url)
107
-
108
- #results are in a hash, their keys are just numbers
109
- result.keys.each do |k|
110
- if k =~ /^[0-9]+$/
111
- #this is a result! get the key
112
- /\/([^\/]*)\/([^\/]*)$/.match result[k]["POST_TO"]
113
- #$1 is the class name, $2 is the key
114
- item = type.new($2, :lazy)
115
- self.contents << item
116
- end
117
- end
118
- rescue
119
- self.contents = []
120
- ensure
121
- self.state = :postfetch
122
- end
123
- end
124
- end
1
+ module Medea
2
+ class JasonDeferredQuery
3
+ require 'rest_client'
4
+
5
+ attr_accessor :time_limit, :result_format, :type, :result_format, :time_limit, :state, :contents, :filters
6
+
7
+ def initialize a_class, format=:json
8
+ @type = a_class
9
+ @filters = {:FILTER => {:HTTP_X_CLASS => a_class.name.to_s}}
10
+ @result_format = format
11
+ @time_limit = 0
12
+ @state = :prefetch
13
+ @contents = []
14
+ end
15
+
16
+ #Here we're going to put the "query" interface
17
+
18
+ #here we will capture:
19
+ #members_of(object) (where object is an instance of a class that this class can be a member of)
20
+ #members_of_<classname>(key)
21
+ #find_by_<property>(value)
22
+ #Will return a JasonDeferredQuery for this class with the appropriate data filter set
23
+ def method_missing(name, *args, &block)
24
+ #if we are postfetch, we throw away all our cached results
25
+ if self.state == :postfetch
26
+ self.state = :prefetch
27
+ self.contents = []
28
+ end
29
+
30
+ if name =~ /^members_of$/
31
+ #use the type and key of the first arg (being a JasonObject)
32
+ #args[0] must be a JasonObject (or child)
33
+ raise ArgumentError, "When looking for members, you must pass a JasonObject" unless args[0].is_a? JasonObject
34
+
35
+ self.filters[:DATA_FILTER] ||= {}
36
+ self.filters[:DATA_FILTER]["__member_of"] ||= []
37
+ self.filters[:DATA_FILTER]["__member_of"] << args[0].jason_key
38
+ elsif name =~ /^find_by_(.*)$/
39
+ #use the property name from the name variable, and the value from the first arg
40
+ add_data_filter $1, args[0].to_s
41
+ else
42
+ #no method!
43
+ super
44
+ return
45
+ end
46
+ #return self, so that we can chain up query refinements
47
+ self
48
+ end
49
+ #end query interface
50
+
51
+ def add_data_filter property, value
52
+ self.filters[:DATA_FILTER] ||= {}
53
+ self.filters[:DATA_FILTER][property] = value
54
+ end
55
+
56
+ def to_url
57
+ url = "#{JasonDB::db_auth_url}@#{self.time_limit}.#{self.result_format}?"
58
+ filter_array = []
59
+ self.filters.each do |name, val|
60
+ if not val
61
+ filter_array << name.to_s
62
+ next
63
+ else
64
+ #FILTER's value is a hash (to avoid dupes)
65
+ #DATA_FILTER's value is a hash
66
+ if val.is_a? Hash
67
+ #for each k/v in the hash, we want to add an entry to filter_array
68
+ val.each do |field ,value|
69
+ if value.is_a? Array
70
+ value.each do |i|
71
+ filter_array << "#{name.to_s}=#{field}:#{i}"
72
+ end
73
+ else
74
+ filter_array << "#{name.to_s}=#{field.to_s}:#{value.to_s}"
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ url + filter_array.join("&")
82
+ end
83
+
84
+ #array access interface
85
+ def [](index)
86
+ execute_query unless self.state == :postfetch
87
+ self.contents[index]
88
+ end
89
+
90
+ def each(&block)
91
+ execute_query unless self.state == :postfetch
92
+ self.contents.each &block
93
+ end
94
+
95
+ def count
96
+ execute_query unless self.state == :postfetch
97
+ self.contents.count
98
+ end
99
+ #end array interface
100
+
101
+ def execute_query
102
+ #hit the URL
103
+ #fill self.contents with :ghost versions of JasonObjects
104
+ begin
105
+ #puts " = Executing #{type.name} deferred query! (#{to_url})"
106
+ result = JSON.parse(RestClient.get to_url)
107
+
108
+ #results are in a hash, their keys are just numbers
109
+ result.keys.each do |k|
110
+ if k =~ /^[0-9]+$/
111
+ #this is a result! get the key
112
+ /\/([^\/]*)\/([^\/]*)$/.match result[k]["POST_TO"]
113
+ #$1 is the class name, $2 is the key
114
+ item = type.new($2, :lazy)
115
+ self.contents << item
116
+ end
117
+ end
118
+ rescue
119
+ self.contents = []
120
+ ensure
121
+ self.state = :postfetch
122
+ end
123
+ end
124
+ end
125
125
  end
@@ -1,102 +1,102 @@
1
- module Medea
2
- require 'uri'
3
- require 'rest_client'
4
- class JasonListProperty < JasonDeferredQuery
5
-
6
- attr_accessor :list_name, :parent, :list_type
7
-
8
- def initialize parent, list_name, list_class, list_type
9
- @type = list_class
10
- @list_name = list_name
11
- @list_type = list_type
12
- @parent = parent
13
- @result_format = :json
14
- @time_limit = 0
15
- @state = :prefetch
16
- @contents = []
17
- end
18
-
19
- def method_missing name, *args, &block
20
- #is this a list property on the base class?
21
- if (@type.class_variable_defined? :@@lists) && (@type.class_variable_get(:@@lists).has_key? name)
22
- #if so, we'll just return a new ListProperty with my query as the parent
23
- new_list_class, new_list_type = @type.class_variable_get(:@@lists)[name]
24
- base_query = self.clone
25
- base_query.result_format = :keylist
26
- JasonListProperty.new base_query, name.to_sym, new_list_class, new_list_type
27
- else
28
- #no method, let JasonDeferredQuery handle it
29
- super
30
- end
31
- end
32
-
33
- def add! member, save=true
34
- raise RuntimeError, "You can only add an item if you are accessing this list from an object." unless @parent.is_a? JasonObject
35
- raise ArgumentError, "You can only add #{@type.name} items to this collection!" unless member.is_a? @type
36
-
37
- if @list_type == :value
38
- member.jason_parent = @parent
39
- member.jason_parent_list = @list_name
40
- elsif @list_type == :reference
41
-
42
- #post to JasonDB::db_auth_url/a_class.name/
43
- url = "#{JasonDB::db_auth_url}#{@type.name}/#{@parent.jason_key}/#{@list_name}/#{member.jason_key}"
44
- post_headers = {
45
- :content_type => 'application/json',
46
- "X-CLASS" => @type.name,
47
- "X-KEY" => member.jason_key,
48
- "X-PARENT" => @parent.jason_key,
49
- "X-LIST" => @list_name.to_s
50
- }
51
- content = {
52
- "_id" => member.jason_key,
53
- "_parent" => @parent.jason_key
54
- }
55
- #puts " = " + url
56
- #puts " = #{post_headers}"
57
- response = RestClient.post url, content.to_json, post_headers
58
-
59
- if response.code == 201
60
- #save successful!
61
- #store the new eTag for this object
62
- #puts response.raw_headers
63
- #@__jason_etag = response.headers[:location] + ":" + response.headers[:content_md5]
64
- else
65
- raise "POST failed! Could not save membership"
66
- end
67
- else
68
- #parent is a JasonObject, but this list is something other than :value or :reference??
69
- raise "Invalid list type or trying to add an item to a subquery list!"
70
- end
71
-
72
- if member.jason_state == :new
73
- #we want to save it? probably...
74
- member.save! if save
75
- end
76
-
77
- @state = :prefetch
78
- end
79
-
80
- def to_url
81
- url = "#{JasonDB::db_auth_url}@#{@time_limit}.#{@result_format}?"
82
- params = ["VERSION0"]
83
-
84
- params << "FILTER=HTTP_X_CLASS:#{@type.name}"
85
-
86
- if @parent.is_a? JasonObject
87
- params << "FILTER=HTTP_X_PARENT:#{@parent.jason_key}"
88
- else # @parent.is_a? JasonListProperty ##(or DeferredQuery?)
89
- #we can get the insecure url here, because it will be resolved and executed at JasonDB - on a secure subnet.
90
-
91
- #subquery = "<%@LANGUAGE=\"URL\" #{@parent.to_url}%>"
92
- #puts " = Fetching subquery stupidly. (#{@parent.to_url})"
93
-
94
- subquery = (RestClient.get @parent.to_url).strip
95
- #puts " = Result: #{subquery}"
96
- params << URI.escape("FILTER={HTTP_X_PARENT:#{subquery}}", Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
97
- end
98
-
99
- url << params.join("&")
100
- end
101
- end
1
+ module Medea
2
+ require 'uri'
3
+ require 'rest_client'
4
+ class JasonListProperty < JasonDeferredQuery
5
+
6
+ attr_accessor :list_name, :parent, :list_type
7
+
8
+ def initialize parent, list_name, list_class, list_type
9
+ @type = list_class
10
+ @list_name = list_name
11
+ @list_type = list_type
12
+ @parent = parent
13
+ @result_format = :json
14
+ @time_limit = 0
15
+ @state = :prefetch
16
+ @contents = []
17
+ end
18
+
19
+ def method_missing name, *args, &block
20
+ #is this a list property on the base class?
21
+ if (@type.class_variable_defined? :@@lists) && (@type.class_variable_get(:@@lists).has_key? name)
22
+ #if so, we'll just return a new ListProperty with my query as the parent
23
+ new_list_class, new_list_type = @type.class_variable_get(:@@lists)[name]
24
+ base_query = self.clone
25
+ base_query.result_format = :keylist
26
+ JasonListProperty.new base_query, name.to_sym, new_list_class, new_list_type
27
+ else
28
+ #no method, let JasonDeferredQuery handle it
29
+ super
30
+ end
31
+ end
32
+
33
+ def add! member, save=true
34
+ raise RuntimeError, "You can only add an item if you are accessing this list from an object." unless @parent.is_a? JasonObject
35
+ raise ArgumentError, "You can only add #{@type.name} items to this collection!" unless member.is_a? @type
36
+
37
+ if @list_type == :value
38
+ member.jason_parent = @parent
39
+ member.jason_parent_list = @list_name
40
+ elsif @list_type == :reference
41
+
42
+ #post to JasonDB::db_auth_url/a_class.name/
43
+ url = "#{JasonDB::db_auth_url}#{@type.name}/#{@parent.jason_key}/#{@list_name}/#{member.jason_key}"
44
+ post_headers = {
45
+ :content_type => 'application/json',
46
+ "X-CLASS" => @type.name,
47
+ "X-KEY" => member.jason_key,
48
+ "X-PARENT" => @parent.jason_key,
49
+ "X-LIST" => @list_name.to_s
50
+ }
51
+ content = {
52
+ "_id" => member.jason_key,
53
+ "_parent" => @parent.jason_key
54
+ }
55
+ #puts " = " + url
56
+ #puts " = #{post_headers}"
57
+ response = RestClient.post url, content.to_json, post_headers
58
+
59
+ if response.code == 201
60
+ #save successful!
61
+ #store the new eTag for this object
62
+ #puts response.raw_headers
63
+ #@__jason_etag = response.headers[:location] + ":" + response.headers[:content_md5]
64
+ else
65
+ raise "POST failed! Could not save membership"
66
+ end
67
+ else
68
+ #parent is a JasonObject, but this list is something other than :value or :reference??
69
+ raise "Invalid list type or trying to add an item to a subquery list!"
70
+ end
71
+
72
+ if member.jason_state == :new
73
+ #we want to save it? probably...
74
+ member.save! if save
75
+ end
76
+
77
+ @state = :prefetch
78
+ end
79
+
80
+ def to_url
81
+ url = "#{JasonDB::db_auth_url}@#{@time_limit}.#{@result_format}?"
82
+ params = ["VERSION0"]
83
+
84
+ params << "FILTER=HTTP_X_CLASS:#{@type.name}"
85
+
86
+ if @parent.is_a? JasonObject
87
+ params << "FILTER=HTTP_X_PARENT:#{@parent.jason_key}"
88
+ else # @parent.is_a? JasonListProperty ##(or DeferredQuery?)
89
+ #we can get the insecure url here, because it will be resolved and executed at JasonDB - on a secure subnet.
90
+
91
+ #subquery = "<%@LANGUAGE=\"URL\" #{@parent.to_url}%>"
92
+ #puts " = Fetching subquery stupidly. (#{@parent.to_url})"
93
+
94
+ subquery = (RestClient.get @parent.to_url).strip
95
+ #puts " = Result: #{subquery}"
96
+ params << URI.escape("FILTER={HTTP_X_PARENT:#{subquery}}", Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
97
+ end
98
+
99
+ url << params.join("&")
100
+ end
101
+ end
102
102
  end