shaf 1.5.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/iana_link_relations.csv.gz +0 -0
- data/lib/shaf.rb +6 -0
- data/lib/shaf/alps/attribute_serializer.rb +41 -0
- data/lib/shaf/alps/json_serializer.rb +50 -0
- data/lib/shaf/alps/relation_serializer.rb +70 -0
- data/lib/shaf/api_doc/link_relations.rb +77 -0
- data/lib/shaf/app.rb +12 -5
- data/lib/shaf/authenticator.rb +56 -0
- data/lib/shaf/authenticator/base.rb +161 -0
- data/lib/shaf/authenticator/basic_auth.rb +25 -0
- data/lib/shaf/authenticator/challenge.rb +32 -0
- data/lib/shaf/authenticator/parameter.rb +31 -0
- data/lib/shaf/authenticator/request.rb +17 -0
- data/lib/shaf/command/console.rb +1 -1
- data/lib/shaf/command/generate.rb +5 -2
- data/lib/shaf/command/new.rb +20 -7
- data/lib/shaf/command/templates/Gemfile.erb +1 -0
- data/{templates/config/settings.yml → lib/shaf/command/templates/config/settings.yml.erb} +1 -5
- data/lib/shaf/errors.rb +11 -0
- data/lib/shaf/extensions.rb +3 -3
- data/lib/shaf/extensions/api_routes.rb +60 -0
- data/lib/shaf/extensions/authorize.rb +11 -9
- data/lib/shaf/extensions/log.rb +1 -1
- data/lib/shaf/extensions/resource_uris.rb +139 -63
- data/lib/shaf/extensions/symbolic_routes.rb +22 -19
- data/lib/shaf/formable.rb +1 -2
- data/lib/shaf/formable/form.rb +1 -1
- data/lib/shaf/generator.rb +2 -0
- data/lib/shaf/generator/base.rb +2 -3
- data/lib/shaf/generator/controller.rb +11 -7
- data/lib/shaf/generator/doc.rb +17 -0
- data/lib/shaf/generator/forms.rb +1 -0
- data/lib/shaf/generator/helper.rb +2 -1
- data/lib/shaf/generator/migration/base.rb +7 -3
- data/lib/shaf/generator/migration/type.rb +4 -26
- data/lib/shaf/generator/migration/types.rb +45 -16
- data/lib/shaf/generator/model.rb +1 -2
- data/lib/shaf/generator/profile.rb +52 -0
- data/lib/shaf/generator/serializer.rb +38 -73
- data/lib/shaf/generator/templates/api/policy.rb.erb +2 -2
- data/lib/shaf/generator/templates/api/profile.rb.erb +16 -0
- data/lib/shaf/generator/templates/api/serializer.rb.erb +2 -2
- data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +1 -2
- data/lib/shaf/generator/templates/spec/serializer_spec.rb.erb +5 -5
- data/lib/shaf/helpers.rb +4 -0
- data/lib/shaf/helpers/authentication.rb +79 -0
- data/lib/shaf/helpers/cache_control.rb +1 -2
- data/lib/shaf/helpers/json_html.rb +58 -18
- data/lib/shaf/helpers/payload.rb +27 -41
- data/lib/shaf/helpers/vary.rb +8 -0
- data/lib/shaf/logger.rb +12 -0
- data/lib/shaf/parser.rb +65 -0
- data/lib/shaf/parser/base.rb +44 -0
- data/lib/shaf/parser/form_data.rb +15 -0
- data/lib/shaf/parser/json.rb +26 -0
- data/lib/shaf/profile.rb +110 -0
- data/lib/shaf/profile/attribute.rb +29 -0
- data/lib/shaf/profile/evaluator.rb +46 -0
- data/lib/shaf/profile/relation.rb +29 -0
- data/lib/shaf/profile/unique_id.rb +58 -0
- data/lib/shaf/profiles.rb +42 -0
- data/lib/shaf/profiles/shaf_basic.rb +20 -0
- data/lib/shaf/profiles/shaf_error.rb +48 -0
- data/lib/shaf/profiles/shaf_form.rb +109 -0
- data/lib/shaf/responder.rb +41 -2
- data/lib/shaf/responder/alps_json.rb +25 -0
- data/lib/shaf/responder/base.rb +20 -17
- data/lib/shaf/responder/hal.rb +62 -7
- data/lib/shaf/responder/html.rb +65 -9
- data/lib/shaf/responder/problem_json.rb +1 -1
- data/lib/shaf/serializer.rb +31 -0
- data/lib/shaf/settings.rb +25 -12
- data/lib/shaf/spec.rb +1 -0
- data/lib/shaf/spec/authenticator.rb +13 -0
- data/lib/shaf/spec/base.rb +1 -1
- data/lib/shaf/spec/http_method_utils.rb +1 -1
- data/lib/shaf/spec/integration_spec.rb +25 -13
- data/lib/shaf/spec/payload_utils.rb +2 -2
- data/lib/shaf/supported_http_methods.rb +15 -0
- data/lib/shaf/tasks/api_doc_task.rb +24 -3
- data/lib/shaf/tasks/routes_task.rb +14 -17
- data/lib/shaf/upgrade/manifest.rb +11 -2
- data/lib/shaf/upgrade/package.rb +78 -49
- data/lib/shaf/upgrade/version.rb +11 -10
- data/lib/shaf/utils.rb +19 -5
- data/lib/shaf/version.rb +3 -1
- data/lib/shaf/yard.rb +34 -0
- data/lib/shaf/yard/attribute_method_handler.rb +19 -0
- data/lib/shaf/yard/attribute_object.rb +30 -0
- data/lib/shaf/yard/base_method_handler.rb +30 -0
- data/lib/shaf/yard/link_method_handler.rb +39 -0
- data/lib/shaf/yard/link_object.rb +60 -0
- data/lib/shaf/yard/nested_attributes.rb +37 -0
- data/lib/shaf/yard/parser.rb +64 -0
- data/lib/shaf/yard/profile_method_handler.rb +51 -0
- data/lib/shaf/yard/profile_object.rb +21 -0
- data/lib/shaf/yard/resource_object.rb +55 -0
- data/lib/shaf/yard/serializer_handler.rb +27 -0
- data/templates/api/controllers/base_controller.rb +0 -10
- data/templates/api/controllers/docs_controller.rb +5 -3
- data/templates/api/controllers/root_controller.rb +7 -1
- data/templates/api/policies/base_policy.rb +2 -0
- data/templates/api/serializers/base_serializer.rb +1 -3
- data/templates/api/serializers/error_serializer.rb +1 -5
- data/templates/api/serializers/form_serializer.rb +1 -5
- data/templates/api/serializers/validation_error_serializer.rb +1 -5
- data/templates/config.ru +1 -1
- data/templates/config/bootstrap.rb +1 -2
- data/templates/config/directories.rb +52 -44
- data/templates/config/helpers.rb +1 -1
- data/templates/config/initializers.rb +52 -8
- data/templates/config/initializers/authentication.rb +18 -0
- data/templates/config/initializers/db_migrations.rb +2 -2
- data/templates/config/initializers/logging.rb +2 -2
- data/templates/frontend/assets/css/main.css +33 -1
- data/templates/frontend/views/headers.erb +20 -0
- data/templates/frontend/views/layout.erb +7 -1
- data/templates/frontend/views/payload.erb +1 -0
- data/templates/spec/spec_helper.rb +2 -0
- data/upgrades/0.5.0.tar.gz +0 -0
- data/upgrades/1.0.4.tar.gz +0 -0
- data/upgrades/1.1.0.tar.gz +0 -0
- data/upgrades/1.6.0.tar.gz +0 -0
- data/upgrades/1.6.1.tar.gz +0 -0
- data/upgrades/2.0.0.tar.gz +0 -0
- data/yard_templates/api_doc/doc_index/html/body.erb +3 -0
- data/yard_templates/api_doc/doc_index/setup.rb +8 -0
- data/yard_templates/api_doc/html/css/api-doc.css +222 -0
- data/yard_templates/api_doc/html/favicon.ico +0 -0
- data/yard_templates/api_doc/html/js/switch_tab.js +17 -0
- data/yard_templates/api_doc/html/setup.rb +59 -0
- data/yard_templates/api_doc/layout/html/footer.erb +3 -0
- data/yard_templates/api_doc/layout/html/header.erb +7 -0
- data/yard_templates/api_doc/layout/html/layout.erb +13 -0
- data/yard_templates/api_doc/layout/setup.rb +24 -0
- data/yard_templates/api_doc/profile/html/attributes.erb +10 -0
- data/yard_templates/api_doc/profile/html/profile.erb +6 -0
- data/yard_templates/api_doc/profile/html/relations.erb +10 -0
- data/yard_templates/api_doc/profile/setup.rb +38 -0
- data/yard_templates/api_doc/profile_attribute/html/attribute.erb +23 -0
- data/yard_templates/api_doc/profile_attribute/setup.rb +21 -0
- data/yard_templates/api_doc/profile_relation/html/relation.erb +37 -0
- data/yard_templates/api_doc/profile_relation/setup.rb +41 -0
- data/yard_templates/api_doc/resource/html/attributes.erb +10 -0
- data/yard_templates/api_doc/resource/html/profile.erb +14 -0
- data/yard_templates/api_doc/resource/html/relations.erb +10 -0
- data/yard_templates/api_doc/resource/html/resource.erb +5 -0
- data/yard_templates/api_doc/resource/setup.rb +56 -0
- data/yard_templates/api_doc/resource_attribute/html/attribute.erb +23 -0
- data/yard_templates/api_doc/resource_attribute/setup.rb +20 -0
- data/yard_templates/api_doc/resource_relation/html/relation.erb +47 -0
- data/yard_templates/api_doc/resource_relation/setup.rb +80 -0
- data/yard_templates/api_doc/setup.rb +31 -0
- data/yard_templates/api_doc/sidebar/html/profile_list.erb +8 -0
- data/yard_templates/api_doc/sidebar/html/search.erb +7 -0
- data/yard_templates/api_doc/sidebar/html/serializer_list.erb +8 -0
- data/yard_templates/api_doc/sidebar/html/sidebar.erb +13 -0
- data/yard_templates/api_doc/sidebar/setup.rb +56 -0
- metadata +140 -30
- metadata.gz.sig +1 -2
- data/lib/shaf/extensions/current_user.rb +0 -48
- data/lib/shaf/responder/hal_serializable.rb +0 -54
data/lib/shaf/logger.rb
ADDED
data/lib/shaf/parser.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module Shaf
|
6
|
+
module Parser
|
7
|
+
class Error < StandardError; end
|
8
|
+
|
9
|
+
INPUT_BODY = 'shaf.input_body'
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def register(parser)
|
13
|
+
parsers << parser
|
14
|
+
end
|
15
|
+
|
16
|
+
def unregister(parser)
|
17
|
+
parsers.delete(parser)
|
18
|
+
end
|
19
|
+
|
20
|
+
def input?(request)
|
21
|
+
!!input(request)
|
22
|
+
end
|
23
|
+
|
24
|
+
def for(request)
|
25
|
+
clazz = parser_for(request)
|
26
|
+
return unless clazz
|
27
|
+
|
28
|
+
body = input(request)
|
29
|
+
clazz.new(request: request, body: body)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def parser_for(request)
|
35
|
+
parsers.find do |parser|
|
36
|
+
parser.can_handle? request
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def parsers
|
41
|
+
@parsers ||= Set.new
|
42
|
+
end
|
43
|
+
|
44
|
+
def input(request)
|
45
|
+
body = request.get_header(INPUT_BODY)
|
46
|
+
body ||= read_input(request).tap do |b|
|
47
|
+
request.set_header(INPUT_BODY, b)
|
48
|
+
end
|
49
|
+
|
50
|
+
body unless String(body).strip.empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
def read_input(request)
|
54
|
+
request.body.rewind
|
55
|
+
request.body.read
|
56
|
+
ensure
|
57
|
+
request.body.rewind
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
require 'shaf/parser/base'
|
64
|
+
require 'shaf/parser/json'
|
65
|
+
require 'shaf/parser/form_data'
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Shaf
|
2
|
+
module Parser
|
3
|
+
class Base
|
4
|
+
class << self
|
5
|
+
def inherited(child)
|
6
|
+
Parser.register(child)
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def mime_type(type = nil, value = nil)
|
11
|
+
if type
|
12
|
+
@mime_type = type
|
13
|
+
@mime_type = Sinatra::Base.mime_type(type, value) if type.is_a? Symbol
|
14
|
+
end
|
15
|
+
|
16
|
+
@mime_type if defined? @mime_type
|
17
|
+
end
|
18
|
+
|
19
|
+
def can_handle?(request)
|
20
|
+
mime_type == request.content_type
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :request, :body
|
26
|
+
|
27
|
+
def initialize(request:, body:)
|
28
|
+
@request = request
|
29
|
+
@body = body
|
30
|
+
end
|
31
|
+
|
32
|
+
def call
|
33
|
+
raise NotImplementedError, "#{self.class} must implement #call"
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def mime_type
|
39
|
+
self.class.mime_type
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Shaf
|
2
|
+
module Parser
|
3
|
+
class FormData < Base
|
4
|
+
def self.can_handle?(request)
|
5
|
+
request.form_data? || request.parseable_data?
|
6
|
+
end
|
7
|
+
|
8
|
+
def call
|
9
|
+
request.POST.tap do |data| # Returns form params from Rack::Request
|
10
|
+
data.delete '_method' # If the method override hack is used remove the _method key
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Shaf
|
2
|
+
module Parser
|
3
|
+
class Json < Base
|
4
|
+
|
5
|
+
mime_type :json, 'application/json'
|
6
|
+
|
7
|
+
def self.can_handle?(request)
|
8
|
+
request.content_type&.match? %r{application/(.*\+)?json}
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
@payload ||= parse_json
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def parse_json
|
18
|
+
return {} if body.empty?
|
19
|
+
|
20
|
+
JSON.parse(body, symbolize_names: true)
|
21
|
+
rescue JSON::ParserError => e
|
22
|
+
raise Error, e.message
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/shaf/profile.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'shaf/profile/evaluator'
|
4
|
+
require 'shaf/extensions/resource_uris'
|
5
|
+
|
6
|
+
module Shaf
|
7
|
+
class Profile
|
8
|
+
include Shaf::UriHelper
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def inherited(child)
|
12
|
+
Profiles.register child
|
13
|
+
end
|
14
|
+
|
15
|
+
def name(str = nil)
|
16
|
+
@name = str if str
|
17
|
+
@name if defined? @name
|
18
|
+
end
|
19
|
+
|
20
|
+
def doc(str = nil)
|
21
|
+
@doc = str if str
|
22
|
+
@doc if defined? @doc
|
23
|
+
end
|
24
|
+
|
25
|
+
def example(str)
|
26
|
+
examples << str
|
27
|
+
end
|
28
|
+
|
29
|
+
def match?(str)
|
30
|
+
normalize(name) == normalize(str)
|
31
|
+
end
|
32
|
+
|
33
|
+
def attributes
|
34
|
+
@attributes ||= []
|
35
|
+
end
|
36
|
+
|
37
|
+
def relations
|
38
|
+
@relations ||= []
|
39
|
+
end
|
40
|
+
|
41
|
+
def examples
|
42
|
+
@examples ||= []
|
43
|
+
end
|
44
|
+
|
45
|
+
def attribute(*args, **kwargs, &block)
|
46
|
+
evaluator.attribute(*args, **kwargs, &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
def relation(*args, **kwargs, &block)
|
50
|
+
evaluator.rel(*args, **kwargs, &block)
|
51
|
+
end
|
52
|
+
alias rel relation
|
53
|
+
|
54
|
+
def descriptor(id)
|
55
|
+
find_attribute(id) || find_relation(id)
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_attribute(id)
|
59
|
+
attributes.find { |attr| attr.id.to_sym == id.to_sym }
|
60
|
+
end
|
61
|
+
|
62
|
+
def find_relation(id)
|
63
|
+
relations.find { |rel| rel.id.to_sym == id.to_sym }
|
64
|
+
end
|
65
|
+
|
66
|
+
def use(*descriptors, from:, doc: nil)
|
67
|
+
descriptors.each do |id|
|
68
|
+
desc = from.descriptor(id)
|
69
|
+
href = profile_path(from.name, fragment_id: id)
|
70
|
+
|
71
|
+
case desc
|
72
|
+
when Relation
|
73
|
+
kwargs = {
|
74
|
+
doc: doc || desc&.doc,
|
75
|
+
href: href,
|
76
|
+
http_methods: desc.http_methods,
|
77
|
+
payload_type: desc.payload_type,
|
78
|
+
content_type: desc.content_type,
|
79
|
+
}
|
80
|
+
relation(id, **kwargs)
|
81
|
+
when Attribute
|
82
|
+
attribute(id, href: href, doc: doc)
|
83
|
+
when NilClass
|
84
|
+
raise "#{from.name} does not have a descriptor with id #{id}"
|
85
|
+
else
|
86
|
+
raise Errors::ServerError, "Unsupported descriptor: #{desc}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def evaluator
|
94
|
+
Evaluator.new(parent: self)
|
95
|
+
end
|
96
|
+
|
97
|
+
def normalize(name)
|
98
|
+
name.to_s.downcase.tr('-', '_')
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def name
|
103
|
+
normalize(self.class.name)
|
104
|
+
end
|
105
|
+
|
106
|
+
def normalize(str)
|
107
|
+
self.class.normalize(str)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'shaf/profile/unique_id'
|
4
|
+
|
5
|
+
module Shaf
|
6
|
+
class Profile
|
7
|
+
class Attribute
|
8
|
+
include UniqueId
|
9
|
+
|
10
|
+
attr_reader :name, :doc, :href, :type, :parent
|
11
|
+
|
12
|
+
def initialize(name, **opts)
|
13
|
+
@name = name.to_sym
|
14
|
+
@doc = opts[:doc].freeze
|
15
|
+
@href = opts[:href].freeze
|
16
|
+
@type = opts[:type]&.to_s
|
17
|
+
@parent = opts[:parent]
|
18
|
+
end
|
19
|
+
|
20
|
+
def attributes
|
21
|
+
@attributes ||= []
|
22
|
+
end
|
23
|
+
|
24
|
+
def relations
|
25
|
+
@relations ||= []
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'shaf/profile/attribute'
|
4
|
+
require 'shaf/profile/relation'
|
5
|
+
|
6
|
+
module Shaf
|
7
|
+
class Profile
|
8
|
+
class Evaluator
|
9
|
+
attr_reader :parent, :allowed
|
10
|
+
|
11
|
+
def initialize(parent:, allowed: nil)
|
12
|
+
@parent = parent
|
13
|
+
@allowed = allowed && Array(allowed).map(&:to_sym)
|
14
|
+
end
|
15
|
+
|
16
|
+
def attribute(name, doc:, type: :string, &block)
|
17
|
+
return unless allow? :attribute
|
18
|
+
|
19
|
+
attr = Attribute.new(name, doc: doc, type: type, parent: parent)
|
20
|
+
self.class.new(parent: attr, allowed: allowed).instance_exec(&block) if block
|
21
|
+
parent.attributes << attr
|
22
|
+
end
|
23
|
+
|
24
|
+
def relation(name, **kwargs, &block)
|
25
|
+
return unless allow? :rel
|
26
|
+
|
27
|
+
rel = Relation.new(name, parent: parent, **kwargs)
|
28
|
+
self.class.new(parent: rel, allowed: [:attribute]).instance_exec(&block) if block
|
29
|
+
parent.relations << rel
|
30
|
+
end
|
31
|
+
alias rel relation
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def allow?(name)
|
36
|
+
return true unless allowed
|
37
|
+
return true if allowed.include? name
|
38
|
+
|
39
|
+
Shaf.log.warn "#{name} is not allowed to be nested inside #{parent.class} " \
|
40
|
+
"(or parent object containing #{parent.class})"
|
41
|
+
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'shaf/profile/unique_id'
|
4
|
+
|
5
|
+
module Shaf
|
6
|
+
class Profile
|
7
|
+
class Relation
|
8
|
+
include UniqueId
|
9
|
+
|
10
|
+
attr_reader :name, :doc, :href, :http_methods, :payload_type, :content_type, :parent
|
11
|
+
|
12
|
+
def initialize(name, **opts)
|
13
|
+
@name = name.to_sym
|
14
|
+
@doc = opts[:doc].freeze
|
15
|
+
@href = opts[:href].freeze
|
16
|
+
http_methods = Array(opts[:http_method]) + Array(opts[:http_methods])
|
17
|
+
http_methods << 'GET' if http_methods.empty?
|
18
|
+
@http_methods = http_methods.map { |m| m.to_s.upcase }.uniq.freeze
|
19
|
+
@payload_type = opts[:payload_type].freeze
|
20
|
+
@content_type = opts[:content_type].freeze
|
21
|
+
@parent = opts[:parent]
|
22
|
+
end
|
23
|
+
|
24
|
+
def attributes
|
25
|
+
@attributes ||= []
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shaf
|
4
|
+
class Profile
|
5
|
+
module UniqueId
|
6
|
+
def id
|
7
|
+
return @id if defined? @id
|
8
|
+
@id = __find_unique_id
|
9
|
+
end
|
10
|
+
|
11
|
+
def __pending_id?
|
12
|
+
@__pending_id ||= false
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def __find_unique_id
|
18
|
+
@__pending_id = true
|
19
|
+
|
20
|
+
return name.to_s unless __id_collision? name.to_s
|
21
|
+
|
22
|
+
id = [parent.name, name].join('_')
|
23
|
+
return id unless __id_collision? id
|
24
|
+
|
25
|
+
id = "#{id}0"
|
26
|
+
|
27
|
+
loop do
|
28
|
+
id = id.next
|
29
|
+
break id unless __id_collision? id
|
30
|
+
end
|
31
|
+
ensure
|
32
|
+
@__pending_id = false
|
33
|
+
end
|
34
|
+
|
35
|
+
def __id_collision? id
|
36
|
+
descriptor = self
|
37
|
+
|
38
|
+
loop do
|
39
|
+
break false unless descriptor.respond_to?(:parent) && descriptor.parent
|
40
|
+
descriptor = descriptor.parent
|
41
|
+
|
42
|
+
__parent_descriptors(descriptor).each do |desc|
|
43
|
+
next if desc == self
|
44
|
+
next if desc.__pending_id?
|
45
|
+
return true if desc.id == id
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def __parent_descriptors(parent)
|
51
|
+
descriptors = []
|
52
|
+
descriptors += parent.attributes if parent.respond_to? :attributes
|
53
|
+
descriptors += parent.relations if parent.respond_to? :relations
|
54
|
+
descriptors
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|