restful_objects 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.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YmE0NjFmNDUzNmFiODQ4MGM5YTNkZDUxZmNlM2EwOGQyZjExZmRiYg==
5
+ data.tar.gz: !binary |-
6
+ MDk2MDgzNTRkNWU5OGQ0YTlhYmJjMmNmNjIyOTBhZTc2NzJlYjFmMQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NTc2M2UyZGRiOTVmMzA5ZjU3M2Q4MGFiY2M3MzNiNTM2MWMzNDJkNDNkMDJl
10
+ ODU1ZjUwZGQ1MzFlMGQ2ZDIwZGY3NGY2Mzk4YjI4N2RiZDdjY2M3N2I1MDY5
11
+ ZmViODRjMjk0M2ZkMzEwMzUxMmIzODgzMmE4OGQ2YTQwMTNlOTQ=
12
+ data.tar.gz: !binary |-
13
+ NjA5OGE5YzYwYzFiMWNjYTBjYmEyMjZjYzljNzE0OGRiYTNmMjY1MGY5OTE0
14
+ ZDg5ZGJmMWE3MWQ2MzM0NTNmZDBmMTM3ZGE5NGZlMzQyMWNhZmJhNjQxYTAw
15
+ MTU4MDI5YzIyZDJjN2Y5NWE0OTE3OGIwMGM2NGZlM2YyOGU4MWE=
@@ -0,0 +1,82 @@
1
+ module RestfulObjects
2
+ class ActionDescription
3
+ include LinkGenerator
4
+
5
+ attr_reader :id, :kind_result_type, :result_type, :parameters
6
+ attr_accessor :friendly_name, :description, :member_order, :disabled_reason
7
+
8
+ def initialize(id, result_type, domain_type, parameters = {}, options = {})
9
+ @id = id
10
+
11
+ if result_type == :void
12
+ result_type = [:void, :void]
13
+ elsif result_type.is_a?(Symbol)
14
+ result_type = [:scalar, result_type]
15
+ end
16
+ raise "result type should be a symbol or an array" if not result_type.is_a?(Array)
17
+ raise "result type kind '#{result_type.last}' unssuported" if
18
+ not [:void, :scalar, :object, :proto_object, :list].include?(result_type.first)
19
+ @kind_result_type = result_type.first
20
+ @result_type = result_type.last
21
+ case @kind_result_type
22
+ when :scalar
23
+ raise "result type for scalar '#{result_type.last}' unssuported" if
24
+ not [:string, :int, :decimal, :date, :blob].include?(@result_type)
25
+ when :object, :proto_object, :list
26
+ raise "result type should be a class or a string with a class name" if
27
+ not (@result_type.is_a?(Class) || @result_type.is_a?(String))
28
+ end
29
+
30
+ @parameters = ParameterDescriptionList.new
31
+ parameters.each { |name, definition| @parameters.add(name, definition) }
32
+
33
+ @domain_type = domain_type
34
+ @friendly_name = options[:friendly_name] || id
35
+ @description = options[:description] || id
36
+ @member_order = options[:member_order] || 0
37
+ @disabled_reason = options[:disabled_reason] || ''
38
+ end
39
+
40
+ def get_representation
41
+ representation = {
42
+ 'id' => @id,
43
+ 'hasParams' => has_params,
44
+ 'memberOrder' => @member_order,
45
+ 'parameters' => parameters_list,
46
+ 'links' => [
47
+ link_to(:self, "/domain-types/#{@domain_type}/actions/#{@id}", :action_description),
48
+ link_to(:up, "/domain-types/#{@domain_type}", :domain_type),
49
+ link_to(:return_type, "/domain-types/#{result_type}", :domain_type)
50
+ ],
51
+ 'extensions' => {}
52
+ }
53
+
54
+ representation['friendlyName'] = friendly_name if friendly_name
55
+ representation['description'] = description if description
56
+
57
+ representation.to_json
58
+ end
59
+
60
+ def metadata
61
+ result = { 'friendlyName' => friendly_name,
62
+ 'description' => description,
63
+ 'returnType' => result_type,
64
+ 'hasParams' => has_params,
65
+ 'memberOrder' => member_order }
66
+ end
67
+
68
+ def has_params
69
+ not @parameters.empty?
70
+ end
71
+
72
+ def parameters_list
73
+ result = {}
74
+ parameters.each do |name, parameter|
75
+ result[name] = {
76
+ 'extension' => parameter.metadata
77
+ }
78
+ end
79
+ result
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,17 @@
1
+ module RestfulObjects
2
+ class ActionList
3
+ extend Forwardable
4
+
5
+ def initialize(domain_type)
6
+ @actions = Hash.new
7
+ @domain_type = domain_type
8
+ end
9
+
10
+ def add(id, result_type, parameters = {}, options = {})
11
+ options[:member_order] ||= count + 1
12
+ @actions[id] = ActionDescription.new(id, result_type, @domain_type, parameters, options)
13
+ end
14
+
15
+ def_delegators :@actions, :[], :each, :include?, :count, :empty?
16
+ end
17
+ end
@@ -0,0 +1,47 @@
1
+ module RestfulObjects
2
+ class CollectionDescription
3
+ include LinkGenerator
4
+ attr_reader :id, :type, :read_only
5
+ attr_accessor :friendly_name, :description, :plural_form, :member_order, :disabled_reason
6
+
7
+ def initialize(id, type, domain_type, options = {})
8
+ @id = id
9
+ @type = type
10
+ @domain_type = domain_type
11
+ @read_only = options[:read_only].nil? ? false : options[:read_only]
12
+ @disabled_reason = options[:disabled_reason] || 'read only collection' if read_only
13
+ @friendly_name = options[:friendly_name] || id
14
+ @description = options[:description] || id
15
+ @plural_form = options[:plural_form]
16
+ @member_order = options[:member_order]
17
+ end
18
+
19
+ def get_representation
20
+ representation = {
21
+ 'id' => id,
22
+ 'memberOrder' => member_order,
23
+ 'links' => [
24
+ link_to(:self, "/domain-types/#{@domain_type}/collections/#{@id}", :collection_description),
25
+ link_to(:up, "/domain-types/#{@domain_type}", :domain_type),
26
+ link_to(:return_type, "/domain-types/list", :domain_type),
27
+ link_to(:element_type, "/domain-types/#{@type}", :domain_type)
28
+ ],
29
+ 'extensions' => metadata
30
+ }
31
+
32
+ representation['friendlyName'] = friendly_name if friendly_name
33
+ representation['description'] = description if description
34
+
35
+ representation.to_json
36
+ end
37
+
38
+ def metadata
39
+ { 'friendlyName' => friendly_name,
40
+ 'description' => description,
41
+ 'returnType' => 'list',
42
+ 'elementType' => type,
43
+ 'memberOrder' => member_order,
44
+ 'pluralForm' => plural_form }
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,17 @@
1
+ module RestfulObjects
2
+ class CollectionList
3
+ extend Forwardable
4
+
5
+ def initialize(domain_type)
6
+ @domain_type = domain_type
7
+ @collections = Hash.new
8
+ end
9
+
10
+ def add(name, type, options = {})
11
+ options[:member_order] ||= count + 1
12
+ @collections[name] = CollectionDescription.new(name, type, @domain_type, options)
13
+ end
14
+
15
+ def_delegators :@collections, :[], :each, :each_key, :each_value, :include?, :count, :empty?, :clear
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ module RestfulObjects
2
+ class HttpResponse
3
+ attr_accessor :body, :content_type, :status
4
+
5
+ def initialize(body, content_type, status = 200)
6
+ @body = body
7
+ @content_type = content_type
8
+ @status = status
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,104 @@
1
+ module RestfulObjects
2
+ module LinkGenerator
3
+ def link_to(rel, href, type, options = {})
4
+ link = {
5
+ 'rel' => generate_rel(rel, options),
6
+ 'href' => RestfulObjects::DomainModel.current.base_url + href,
7
+ 'type' => generate_repr_type(type, options[:domain_type], options[:element_type]),
8
+ 'method' => options[:method] || 'GET' }
9
+
10
+ link['arguments'] = options[:arguments] if options[:arguments]
11
+
12
+ link
13
+ end
14
+
15
+ def generate_rel(rel, options = {})
16
+ if [:self, :up, :next, :previous, :icon, :help].include?(rel)
17
+ rel.to_s
18
+ elsif rel == :described_by
19
+ 'describedby'
20
+ else
21
+ if [:action, :action_param, :collection, :delete, :domain_type, :domain_types, :element, :element_type, :persist,
22
+ :property, :return_type, :services, :update, :user, :version].include?(rel)
23
+ 'urn:org.restfulobjects:rels/' + underscore_to_hyphen_string(rel)
24
+ else
25
+ 'urn:org.restfulobjects:rels/' +
26
+ case rel
27
+ when :add_to
28
+ 'add-to;collection="' + options[:collection] + '"'
29
+ when :attachment
30
+ 'attachment;property="' + options[:property] + '"'
31
+ when :choice
32
+ if options[:property]
33
+ 'choice;property="' + options[:property] + '"'
34
+ elsif options[:action]
35
+ 'choice;action="' + options[:action] + '"'
36
+ elsif options[:param]
37
+ 'choice;param="' + options[:param] + '"'
38
+ else
39
+ raise 'option not found for choice rel'
40
+ end
41
+ when :clear
42
+ 'clear;property="' + options[:property] + '"'
43
+ when :details
44
+ if options[:property]
45
+ 'details;property="' + options[:property] + '"'
46
+ elsif options[:collection]
47
+ 'details;collection="' + options[:collection] + '"'
48
+ elsif options[:action]
49
+ 'details;action="' + options[:action] + '"'
50
+ else
51
+ raise 'option not found for details rel'
52
+ end
53
+ when :invoke
54
+ if options[:action]
55
+ 'invoke;action="' + options[:action] + '"'
56
+ elsif options[:type_action]
57
+ 'invoke;typeaction="' + options[:type_action] + '"'
58
+ else
59
+ raise 'option not found for invoke rel'
60
+ end
61
+ when :modify
62
+ 'modify;property="' + options[:property] + '"'
63
+ when :remove_from
64
+ 'remove-from;collection="' + options[:collection] + '"'
65
+ when :service
66
+ 'service;serviceId="' + options[:service_id] + '"'
67
+ when :value
68
+ if options[:property]
69
+ 'value;property="' + options[:property] + '"'
70
+ elsif options[:collection]
71
+ 'value;collection="' + options[:collection] + '"'
72
+ else
73
+ raise 'option not found for value rel'
74
+ end
75
+ else
76
+ raise "rel invalid: #{rel}"
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ def generate_repr_type(type, domain_type = nil, element_type = nil)
83
+ valid_repr = [:homepage, :user, :version, :list, :object, :object_property, :object_collection, :object_action,
84
+ :object_result, :action_result, :type_list, :domain_type, :property_description, :collection_description,
85
+ :action_description, :action_param_description, :type_action_result, :error, :services]
86
+
87
+ raise "repr-type invalid: #{type.to_s}" if not valid_repr.include?(type)
88
+
89
+ repr = 'application/json;profile="urn:org.restfulobjects:repr-types/' + underscore_to_hyphen_string(type) + '"'
90
+
91
+ if domain_type && [:object, :action].include?(type)
92
+ repr += ';x-ro-domain-type="' + domain_type + '"'
93
+ elsif element_type && [:object_collection, :action].include?(type)
94
+ repr +=';x-ro-element-type="' + element_type + '"'
95
+ end
96
+
97
+ repr
98
+ end
99
+
100
+ def underscore_to_hyphen_string(symbol)
101
+ symbol.to_s.sub('_', '-')
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,79 @@
1
+ module RestfulObjects
2
+ class DomainModel
3
+ include LinkGenerator
4
+
5
+ attr_accessor :base_url, :compatible_mode
6
+ attr_reader :metadata_schema, :version, :user, :types, :services, :objects
7
+
8
+ def self.current
9
+ @current ||= DomainModel.new
10
+ end
11
+
12
+ def self.current=(value)
13
+ @current = value
14
+ end
15
+
16
+ def initialize
17
+ @base_url = 'http://localhost'
18
+ @metadata_schema = :selectable
19
+ @compatible_mode = false
20
+ @user = User.new(@base_url, 'anonymous')
21
+ @types = TypeList.new
22
+ @services = ServiceList.new(@base_url)
23
+ @objects = ObjectList.new(@base_url)
24
+ end
25
+
26
+ def get_homepage
27
+ { 'links' => [
28
+ link_to(:self, '/', :homepage),
29
+ link_to(:user, '/user', :user),
30
+ link_to(:services, '/services', :list),
31
+ link_to(:version, '/version', :version),
32
+ link_to(:domain_types, '/domain-types', :type_list)
33
+ ],
34
+ 'extensions' => {}
35
+ }.to_json
36
+ end
37
+
38
+ def get_version
39
+ { 'links' => [
40
+ link_to(:self, '/version', :version),
41
+ link_to(:up, '/', :homepage),
42
+ ],
43
+ 'specVersion' => '1.0',
44
+ 'optionalCapabilities' => {
45
+ 'blobsClobs' => true,
46
+ 'deleteObjects' => true,
47
+ 'domainModel' => metadata_schema.to_s,
48
+ 'protoPersistentObjects' => true,
49
+ 'validateOnly' => false
50
+ },
51
+ 'extensions' => {}
52
+ }.to_json
53
+ end
54
+
55
+ def get_user
56
+ @user.get_as_json
57
+ end
58
+
59
+ def get_services
60
+ services.get_list
61
+ end
62
+
63
+ def metadata_schema=(value)
64
+ if not [:simple, :formal, :selectable].include?(value)
65
+ raise "invalid metadata schema, choose :simple, :formal or :selectable"
66
+ end
67
+ @metadata_schema = value
68
+ end
69
+
70
+ def reset
71
+ @base_url = 'http://localhost'
72
+ @user = nil
73
+ @types.clear
74
+ @services.clear
75
+ @objects.clear
76
+ end
77
+ end
78
+ end
79
+
@@ -0,0 +1,132 @@
1
+ require 'bigdecimal'
2
+ require 'base64'
3
+
4
+ module RestfulObjects
5
+ module Object
6
+ include LinkGenerator
7
+
8
+ def self.included(base)
9
+ RestfulObjects::DomainModel.current.types.add(base.name)
10
+
11
+ base.class_eval do
12
+ extend ObjectMacros
13
+ include ObjectBase
14
+ include ObjectProperties
15
+ include ObjectCollections
16
+ include ObjectActions
17
+ end
18
+ end
19
+ end
20
+
21
+ module ObjectBase
22
+ attr_accessor :is_service, :title
23
+
24
+ def initialize
25
+ super
26
+ @deleted = false
27
+ @is_service = self.class.ancestors.include? RestfulObjects::Service
28
+ @title = "#{self.class.name} (#{object_id})"
29
+ rs_register_in_model
30
+ rs_type.collections.each_value { |collection| instance_variable_set "@#{collection.id}".to_sym, Array.new }
31
+ on_after_create if respond_to? :on_after_create
32
+ end
33
+
34
+ def rs_register_in_model
35
+ rs_model.objects.register(self) if not @is_service
36
+ end
37
+
38
+ def rs_model
39
+ RestfulObjects::DomainModel.current
40
+ end
41
+
42
+ def rs_type
43
+ rs_model.types[self.class.name]
44
+ end
45
+
46
+ def get_representation
47
+ HttpResponse.new(representation.to_json,
48
+ 'application/json;profile="urn:org.restfulobjects:repr-types/object";x-ro-domain-type="' + rs_type.id + '"')
49
+ end
50
+
51
+ def rs_instance_id
52
+ object_id
53
+ end
54
+
55
+ def representation
56
+ representation = {
57
+ 'title' => title,
58
+ 'members' => generate_members,
59
+ 'links' => [ link_to(:described_by, "/domain-types/#{self.class.name}", :domain_type) ],
60
+ 'extensions' => rs_type.metadata
61
+ }
62
+
63
+ if not is_service
64
+ representation['instanceId'] = object_id.to_s
65
+ representation['links'] << link_to(:self, "/objects/#{self.class.name}/#{object_id}", :object)
66
+ else
67
+ representation['serviceId'] = self.class.name
68
+ representation['links'] << link_to(:self, "/services/#{self.class.name}", :object)
69
+ end
70
+
71
+ representation
72
+ end
73
+
74
+ def generate_members
75
+ if is_service
76
+ actions_members
77
+ else
78
+ properties_members.merge(collections_members.merge(actions_members))
79
+ end
80
+ end
81
+
82
+ def rs_delete
83
+ on_before_delete if respond_to? :on_before_delete
84
+ @deleted = true
85
+ on_after_delete if respond_to? :on_after_delete
86
+ end
87
+
88
+ def deleted?
89
+ @deleted
90
+ end
91
+
92
+ def encode_value(value, type)
93
+ return nil if value.nil?
94
+ case type
95
+ when :string
96
+ value.to_s
97
+ when :int
98
+ value.to_i
99
+ when :decimal
100
+ value.to_f
101
+ when :date
102
+ value.strftime('%Y-%m-%d')
103
+ when :blob
104
+ Base64.encode64(value).strip
105
+ else
106
+ raise "encode_value unsupported property type: #{type}"
107
+ end
108
+ end
109
+
110
+ def decode_value(value, type)
111
+ return nil if value.nil?
112
+ case type
113
+ when :string
114
+ value.to_s
115
+ when :int
116
+ value.to_i
117
+ when :decimal
118
+ Float(value)
119
+ when :date
120
+ Date.parse(value)
121
+ when :blob
122
+ Base64.decode64(value)
123
+ else
124
+ raise "decode_value unsupported property type: #{type}"
125
+ end
126
+ end
127
+
128
+ def get_self_link
129
+ link_to(:self, "/objects/#{self.class.name}/#{self.object_id}", :object)
130
+ end
131
+ end
132
+ end