pdc 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +27 -0
  3. data/.gitignore +10 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +21 -0
  7. data/CODE_OF_CONDUCT.md +49 -0
  8. data/Gemfile +32 -0
  9. data/Guardfile +36 -0
  10. data/LICENSE +21 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +41 -0
  13. data/Rakefile +11 -0
  14. data/bin/console +11 -0
  15. data/bin/setup +11 -0
  16. data/docs/.gitignore +3 -0
  17. data/docs/LICENSE_sphinx_deployment +27 -0
  18. data/docs/Makefile +179 -0
  19. data/docs/source/conf.py +257 -0
  20. data/docs/source/example.rst +10 -0
  21. data/docs/source/index.rst +23 -0
  22. data/docs/sphinx_deployment.mk +117 -0
  23. data/examples/active_attr.rb +33 -0
  24. data/examples/http_failures.rb +18 -0
  25. data/examples/local_pdc_dev.rb +15 -0
  26. data/examples/logger.rb +50 -0
  27. data/examples/pdc_curb_access_token.rb +36 -0
  28. data/examples/pdc_resource_tests.rb +173 -0
  29. data/examples/pdc_test_cache.rb +48 -0
  30. data/examples/prod_failures.rb +26 -0
  31. data/examples/prod_pdc.rb +14 -0
  32. data/lib/pdc/base.rb +14 -0
  33. data/lib/pdc/config.rb +157 -0
  34. data/lib/pdc/errors.rb +8 -0
  35. data/lib/pdc/http/errors.rb +43 -0
  36. data/lib/pdc/http/request/append_slash.rb +19 -0
  37. data/lib/pdc/http/request.rb +12 -0
  38. data/lib/pdc/http/response/pagination.rb +43 -0
  39. data/lib/pdc/http/response/parser.rb +62 -0
  40. data/lib/pdc/http/response/raise_error.rb +13 -0
  41. data/lib/pdc/http/response.rb +3 -0
  42. data/lib/pdc/http/result.rb +28 -0
  43. data/lib/pdc/http.rb +12 -0
  44. data/lib/pdc/logger.rb +19 -0
  45. data/lib/pdc/resource/attribute_modifier.rb +43 -0
  46. data/lib/pdc/resource/attribute_store.rb +22 -0
  47. data/lib/pdc/resource/attributes.rb +144 -0
  48. data/lib/pdc/resource/errors.rb +3 -0
  49. data/lib/pdc/resource/identity.rb +75 -0
  50. data/lib/pdc/resource/path.rb +63 -0
  51. data/lib/pdc/resource/per_thread_registry.rb +54 -0
  52. data/lib/pdc/resource/relation/finder.rb +24 -0
  53. data/lib/pdc/resource/relation/pagination.rb +33 -0
  54. data/lib/pdc/resource/relation/query.rb +14 -0
  55. data/lib/pdc/resource/relation.rb +81 -0
  56. data/lib/pdc/resource/rest_api.rb +34 -0
  57. data/lib/pdc/resource/scope_registry.rb +19 -0
  58. data/lib/pdc/resource/scopes.rb +29 -0
  59. data/lib/pdc/resource/value_parser.rb +31 -0
  60. data/lib/pdc/resource/wip.rb +0 -0
  61. data/lib/pdc/resource.rb +12 -0
  62. data/lib/pdc/v1/arch.rb +6 -0
  63. data/lib/pdc/v1/product.rb +5 -0
  64. data/lib/pdc/v1/release.rb +18 -0
  65. data/lib/pdc/v1/release_variant.rb +17 -0
  66. data/lib/pdc/v1.rb +8 -0
  67. data/lib/pdc/version.rb +3 -0
  68. data/lib/pdc.rb +25 -0
  69. data/pdc.gemspec +38 -0
  70. data/spec/fixtures/vcr/_page_count_returns_total_count.yml +141 -0
  71. data/spec/fixtures/vcr/brew_can_be_nil.yml +61 -0
  72. data/spec/fixtures/vcr/brew_may_be_present.yml +50 -0
  73. data/spec/fixtures/vcr/caches_multiple_response.yml +173 -0
  74. data/spec/fixtures/vcr/caches_response_with_a_query.yml +64 -0
  75. data/spec/fixtures/vcr/caches_response_with_multiple_query.yml +64 -0
  76. data/spec/fixtures/vcr/caches_response_without_query.yml +61 -0
  77. data/spec/fixtures/vcr/can_iterate_using_each.yml +187 -0
  78. data/spec/fixtures/vcr/fetches_variants_of_a_release.yml +663 -0
  79. data/spec/fixtures/vcr/must_return_number_of_resources.yml +61 -0
  80. data/spec/fixtures/vcr/preserves_the_filters.yml +49 -0
  81. data/spec/fixtures/vcr/returns_resources_on_that_page.yml +49 -0
  82. data/spec/fixtures/vcr/returns_the_total_count_and_not_items_in_page.yml +135 -0
  83. data/spec/fixtures/vcr/should_not_be_in_the_list_of_attributes.yml +95 -0
  84. data/spec/fixtures/vcr/works_with_where.yml +49 -0
  85. data/spec/pdc/config_spec.rb +115 -0
  86. data/spec/pdc/http/errors_spec.rb +58 -0
  87. data/spec/pdc/resource/attributes_spec.rb +231 -0
  88. data/spec/pdc/resource/cache_spec.rb +88 -0
  89. data/spec/pdc/resource/count_spec.rb +45 -0
  90. data/spec/pdc/resource/identity_spec.rb +96 -0
  91. data/spec/pdc/resource/pagination_spec.rb +51 -0
  92. data/spec/pdc/resource/path_spec.rb +30 -0
  93. data/spec/pdc/resource/relation_spec.rb +94 -0
  94. data/spec/pdc/resource/rest_api_spec.rb +23 -0
  95. data/spec/pdc/resource/value_parser_spec.rb +15 -0
  96. data/spec/pdc/resource/wip_spec.rb +0 -0
  97. data/spec/pdc/v1/arch_spec.rb +34 -0
  98. data/spec/pdc/v1/release_spec.rb +54 -0
  99. data/spec/pdc/version_spec.rb +5 -0
  100. data/spec/spec_helper.rb +39 -0
  101. data/spec/support/fixtures.rb +116 -0
  102. data/spec/support/vcr.rb +10 -0
  103. data/spec/support/webmock.rb +34 -0
  104. metadata +295 -0
data/lib/pdc/logger.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'logger'
2
+
3
+ module PDC
4
+ class << self
5
+ attr_writer :logger
6
+
7
+ def logger
8
+ @logger ||= ::Logger.new($stdout).tap do |log|
9
+ log.progname = name
10
+ end
11
+ end
12
+ end
13
+
14
+ module Logging
15
+ def logger
16
+ PDC.logger
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,43 @@
1
+ require 'active_support'
2
+
3
+ module PDC::Resource
4
+ module AttributeModifier
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ # rename an existing attribute `from` to `to`
9
+ # NOTE from must exist and to must not
10
+ def attribute_rename(from, to)
11
+ attribute_modifications << [:rename, from, to]
12
+ end
13
+
14
+ def attribute_modifications
15
+ @attribute_modifications ||= []
16
+ end
17
+ end
18
+
19
+ def initialize(attrs = {})
20
+ super
21
+ self.class.attribute_modifications.each do |what, *args|
22
+ case what
23
+ when :rename
24
+ apply_attr_rename(*args)
25
+ else
26
+ PDC.logger.warn "Invalid attribute transformation #{what}: #{from} #{to}"
27
+ end
28
+ end
29
+
30
+ yield self if block_given?
31
+ end
32
+
33
+ private
34
+
35
+ def apply_attr_rename(from, to)
36
+ if attributes.key?(from) && !attributes.key?(to)
37
+ attributes[to] = attributes.delete(from)
38
+ else
39
+ PDC.logger.info "rename: not applied for from: #{from}, to: #{to} | #{attributes}"
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,22 @@
1
+ module PDC::Resource
2
+ class AttributeStore < HashWithIndifferentAccess
3
+ def to_params
4
+ each_with_object({}) do |(key, value), parameters|
5
+ parameters[key] = parse_value(value)
6
+ end.with_indifferent_access
7
+ end
8
+
9
+ private
10
+
11
+ def parse_value(value)
12
+
13
+ case
14
+ when value.is_a?(PDC::Base) then value.attributes.to_params
15
+ when value.is_a?(Array) then value.map { |v| parse_value(v) }
16
+ else value
17
+ end
18
+ end
19
+
20
+ end
21
+ end
22
+
@@ -0,0 +1,144 @@
1
+ module PDC::Resource
2
+ module Attributes
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ attr_reader :attributes
7
+ delegate :[], to: :attributes
8
+ end
9
+
10
+ # :nodoc:
11
+ module ClassMethods
12
+ # define attributes on Model using
13
+ # attributes :attribute_name, attribute_name_2 ...
14
+ #
15
+ def attributes(*names)
16
+ return attributes_metadata.keys if names.empty?
17
+ names.each { |n| attributes_metadata[n] ||= default_metadata }
18
+ define_methods_in_container(names)
19
+ end
20
+
21
+ def attribute(name, metadata)
22
+ attr_exists = attributes_metadata.key?(name)
23
+ attributes_metadata[name] ||= default_metadata
24
+ attributes_metadata[name].merge!(metadata)
25
+
26
+ define_methods_in_container(name) unless attr_exists
27
+ end
28
+
29
+ def attributes_metadata
30
+ @attributes_metadata ||= HashWithIndifferentAccess.new(primary_key => default_metadata )
31
+ end
32
+
33
+ def attribute_parser(name)
34
+ # do not add to attributes of the class if not already present
35
+ metadata = attributes_metadata.fetch(name, default_metadata)
36
+ metadata[:parser]
37
+ end
38
+
39
+ private
40
+
41
+ def default_metadata
42
+ { parser: ValueParser }
43
+ end
44
+
45
+ def define_methods_in_container(names)
46
+ instance_method_container.module_eval do
47
+ Array.wrap(names).each do |name|
48
+ define_method(name) do
49
+ attribute(name)
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ # By adding instance methods via an included module, they become
56
+ # overridable with "super".
57
+ # see: http://thepugautomatic.com/2013/07/dsom/
58
+ def instance_method_container
59
+ unless @instance_method_container
60
+ @instance_method_container = Module.new
61
+ include @instance_method_container
62
+ end
63
+ @instance_method_container
64
+ end
65
+ end # class methods
66
+
67
+ def initialize(attributes = {})
68
+ self.attributes = attributes
69
+ @uri_template = scoped.uri
70
+ yield(self) if block_given?
71
+ end
72
+
73
+ def attributes=(new_attributes)
74
+ @attributes ||= AttributeStore.new
75
+ use_setters(new_attributes) if new_attributes
76
+ end
77
+
78
+ def []=(name, value)
79
+ set_attribute(name, value)
80
+ end
81
+
82
+ def inspect
83
+ "#<#{self.class}(#{@uri_template}) id: #{id.inspect} #{inspect_attributes}>"
84
+ end
85
+
86
+ private
87
+
88
+ def use_setters(attributes)
89
+ attributes.each { |key, value| public_send "#{key}=", value }
90
+ end
91
+
92
+ def method_missing(name, *args, &block)
93
+ case
94
+ when attribute?(name) then attribute(name)
95
+ when predicate?(name) then predicate(name)
96
+ when setter?(name) then set_attribute(name, args.first)
97
+ else super
98
+ end
99
+ end
100
+
101
+ def respond_to_missing?(name, include_private = false)
102
+ attribute?(name) || predicate?(name) || setter?(name) || super
103
+ end
104
+
105
+ def attribute?(name)
106
+ attributes.key?(name) || self.class.attributes_metadata.key?(name)
107
+ end
108
+
109
+ def attribute(name)
110
+ attributes[name]
111
+ end
112
+
113
+ def predicate?(name)
114
+ name.to_s.end_with?('?')
115
+ end
116
+
117
+ def predicate(name)
118
+ attribute(depredicate(name)).present?
119
+ end
120
+
121
+ def depredicate(name)
122
+ name.to_s.chomp('?').to_sym
123
+ end
124
+
125
+ def setter?(name)
126
+ name.to_s.end_with?('=')
127
+ end
128
+
129
+ def set_attribute(name, value)
130
+ attr_name = name.to_s.chomp('=')
131
+ unless attribute?(attr_name)
132
+ logger.warn "Setting unknown attribute: #{attr_name} #{self.class.name}"
133
+ end
134
+ parser = self.class.attribute_parser(attr_name)
135
+ attributes[attr_name] = parser.parse(value)
136
+ end
137
+
138
+ def inspect_attributes
139
+ attributes.map { |k, v| "#{k}: #{v.inspect}" }.join(' ')
140
+ end
141
+
142
+ end
143
+ end
144
+
@@ -0,0 +1,3 @@
1
+ module PDC
2
+ class MultipleResultsError < Error; end
3
+ end
@@ -0,0 +1,75 @@
1
+ require 'pdc'
2
+ require_relative 'path'
3
+
4
+ module PDC::Resource
5
+ # handles id, primary_key and uri of a Resource
6
+ module Identity
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ def primary_key
11
+ @primary_key ||= default_primary_key
12
+ end
13
+
14
+ def primary_key=(value)
15
+ @primary_key = value
16
+ end
17
+
18
+ def uri(value = nil)
19
+ @uri ||= Path.new(value || default_uri).to_s
20
+ end
21
+
22
+ # returns <version>/<resource-name> from the class.name
23
+ def resource_path
24
+ @resource_path ||= model_name.collection.sub(%r{^pdc\/}, '').tr('_', '-')
25
+ end
26
+
27
+ private
28
+
29
+ def default_uri
30
+ resource_path + "/(:#{primary_key})"
31
+ end
32
+
33
+ # default pkey for FooBar is foo_bar_id
34
+ def default_primary_key
35
+ model_name.foreign_key.to_s
36
+ end
37
+ end # ClassMethods
38
+
39
+ def id?
40
+ attributes[primary_key].present?
41
+ end
42
+
43
+ def id
44
+ attributes[primary_key]
45
+ end
46
+
47
+ def id=(value)
48
+ attributes[primary_key] = value if value.present?
49
+ end
50
+
51
+ def hash
52
+ id.hash
53
+ end
54
+
55
+ def ==(other)
56
+ other.instance_of?(self.class) && id? && id == other.id
57
+ end
58
+ alias :eql? :==
59
+
60
+ def as_json(options = nil)
61
+ attributes.as_json(options)
62
+ end
63
+
64
+ def uri
65
+ Path.new(self.class.uri.to_s, attributes).expanded
66
+ end
67
+
68
+ private
69
+ # helper method so that primary_key can be called directly
70
+ # from an instance
71
+ def primary_key
72
+ self.class.primary_key
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,63 @@
1
+ require 'uri_template'
2
+
3
+ module PDC::Resource
4
+ class Path
5
+
6
+ def initialize(pattern, params = {})
7
+ @pattern = pattern
8
+ @params = params.symbolize_keys
9
+ end
10
+
11
+ def join(other_path)
12
+ self.class.new File.join(path, other_path.to_s), @params
13
+ end
14
+
15
+ def to_s
16
+ @pattern
17
+ end
18
+
19
+ def expanded
20
+ path.to_s
21
+ end
22
+
23
+ def variables
24
+ @variables ||= uri_template.variables.map(&:to_sym)
25
+ end
26
+
27
+ private
28
+
29
+ def path
30
+ validate_required_params!
31
+ uri_template.expand(@params).chomp('/')
32
+ end
33
+
34
+ def uri_template
35
+ @uri_template ||= URITemplate.new(:colon, pattern_with_rfc_style_parens)
36
+ end
37
+
38
+ def pattern_with_rfc_style_parens
39
+ @pattern.gsub('(', '{').gsub(')', '}')
40
+ end
41
+
42
+ def validate_required_params!
43
+ return unless missing_required_params.any?
44
+ missing_params = missing_required_params.join(', ')
45
+ raise PDC::InvalidPathError, "Missing required params: #{missing_params} in #{@pattern}"
46
+ end
47
+
48
+ def missing_required_params
49
+ required_params - params_with_values
50
+ end
51
+
52
+ def params_with_values
53
+ @params.map do |key, value|
54
+ key if value.present?
55
+ end.compact
56
+ end
57
+
58
+ def required_params
59
+ @pattern.scan(/\/:(\w+)/).flatten.map(&:to_sym)
60
+ end
61
+ end
62
+ end
63
+
@@ -0,0 +1,54 @@
1
+ # PerThreadRegistry from ActiveSupport 4.0
2
+
3
+ # This module is used to encapsulate access to thread local variables.
4
+ #
5
+ # Instead of polluting the thread locals namespace:
6
+ #
7
+ # Thread.current[:connection_handler]
8
+ #
9
+ # you define a class that extends this module:
10
+ #
11
+ # module ActiveRecord
12
+ # class RuntimeRegistry
13
+ # extend ActiveSupport::PerThreadRegistry
14
+ #
15
+ # attr_accessor :connection_handler
16
+ # end
17
+ # end
18
+ #
19
+ # and invoke the declared instance accessors as class methods. So
20
+ #
21
+ # ActiveRecord::RuntimeRegistry.connection_handler = connection_handler
22
+ #
23
+ # sets a connection handler local to the current thread, and
24
+ #
25
+ # ActiveRecord::RuntimeRegistry.connection_handler
26
+ #
27
+ # returns a connection handler local to the current thread.
28
+ #
29
+ # This feature is accomplished by instantiating the class and storing the
30
+ # instance as a thread local keyed by the class name. In the example above
31
+ # a key "ActiveRecord::RuntimeRegistry" is stored in <tt>Thread.current</tt>.
32
+ # The class methods proxy to said thread local instance.
33
+ #
34
+ # If the class has an initializer, it must accept no arguments.
35
+
36
+ module PDC::Resource
37
+ module PerThreadRegistry
38
+ def self.extended(object)
39
+ object.instance_variable_set '@per_thread_registry_key', object.name.freeze
40
+ end
41
+ def instance
42
+ Thread.current[@per_thread_registry_key] ||= new
43
+ end
44
+ protected
45
+ def method_missing(name, *args, &block) # :nodoc:
46
+ # Caches the method definition as a singleton method of the receiver.
47
+ #
48
+ # By letting #delegate handle it, we avoid an enclosure that'll capture args.
49
+ singleton_class.delegate name, to: :instance
50
+ send(name, *args, &block)
51
+ end
52
+ end
53
+ end
54
+
@@ -0,0 +1,24 @@
1
+ module PDC::Resource
2
+ module Finder
3
+ extend ActiveSupport::Concern
4
+
5
+ # returns the contents for the current scope without any pagination as
6
+ # an +Array+ of +Resources+
7
+ def contents!
8
+ return @contents if @contents
9
+ @contents = result.data.map { |result| new(result) }
10
+ end
11
+
12
+ def find_one!
13
+ raise(PDC::ResourceNotFound, params) if result.data.empty?
14
+ raise(PDC::MultipleResultsError, params) if result.data.length > 1
15
+ @find_one ||= new(result.data.first)
16
+ end
17
+
18
+ private
19
+ def result
20
+ @result ||= fetch(clone)
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,33 @@
1
+ module PDC::Resource
2
+ PAGINATION = :pagination
3
+ PAGINATION_KEYS = [
4
+ :resource_count,
5
+ :previous_page,
6
+ :next_page,
7
+ ].freeze
8
+
9
+ module Pagination
10
+ extend ActiveSupport::Concern
11
+
12
+ PDC::Resource::PAGINATION_KEYS.each do |symbol|
13
+ define_method(symbol) do
14
+ result.pagination[symbol]
15
+ end
16
+ end
17
+
18
+ def each_page
19
+ return to_enum(:each_page) unless block_given?
20
+
21
+ # results are not fetched yet so use the clone for next pages
22
+ # and create new relation based on the next_page metadata
23
+
24
+ relation = clone
25
+ yield relation
26
+
27
+ until (next_page = relation.next_page).nil?
28
+ relation = self.class.new(klass, uri: next_page)
29
+ yield relation
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,14 @@
1
+ module PDC::Resource
2
+ module Query
3
+ extend ActiveSupport::Concern
4
+
5
+ def where(conditions)
6
+ return self if conditions.empty?
7
+
8
+ relation = clone
9
+ relation.params = params.merge(conditions)
10
+ relation
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,81 @@
1
+ require 'pdc/resource/relation/query'
2
+ require 'pdc/resource/relation/pagination'
3
+ require 'pdc/resource/relation/finder'
4
+
5
+ module PDC::Resource
6
+ class Relation
7
+ include Enumerable
8
+ include PDC::Logging
9
+ include Query
10
+ include Finder
11
+ include Pagination
12
+
13
+ attr_reader :klass
14
+ attr_writer :params
15
+
16
+ alias :all :to_a
17
+
18
+ def initialize(klass, options = {})
19
+ @klass = klass
20
+ @options = options
21
+ @params = {}
22
+ end
23
+
24
+ def params
25
+ @params.symbolize_keys
26
+ end
27
+
28
+ def uri
29
+ @options[:uri] || klass.uri
30
+ end
31
+
32
+ # TODO: need to scale this so that mulitle variables in a uri can
33
+ # be passed - e.g.
34
+ # ReleaseVarant.uri is 'v1/release-variant/(:release)/(:id)'
35
+ #
36
+ # so find(id, release: 'rel') need to work
37
+ def find(id, vars = {})
38
+ raise PDC::ResourceNotFound if id.blank?
39
+ where(primary_key => id)
40
+ .where(vars)
41
+ .find_one!
42
+ end
43
+
44
+ # TODO: handle pagination here
45
+ def each(&block)
46
+ return to_enum(:each) unless block_given?
47
+
48
+ each_page do |relation|
49
+ resources = relation.contents!
50
+ resources.each(&block)
51
+ end
52
+ end
53
+
54
+ # all! returns all records of the +Resource+
55
+ # NOTE: use this method for resources with small footprint
56
+ def all!
57
+ where(page_size: -1).contents!
58
+ end
59
+
60
+ def count
61
+ result.pagination[:resource_count]
62
+ end
63
+
64
+ private
65
+
66
+ def method_missing(name, *args, &block)
67
+ # pass anything that relation doesn't know to the klass
68
+ super unless klass.respond_to? name
69
+
70
+ with_scope { klass.send(name, *args, &block) }
71
+ end
72
+
73
+ # Keep hold of current scope while running a method on the class
74
+ def with_scope
75
+ previous, klass.current_scope = klass.current_scope, self
76
+ yield
77
+ ensure
78
+ klass.current_scope = previous
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,34 @@
1
+ module PDC::Resource
2
+ module RestApi
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :connection, instance_accessor: false
7
+ end
8
+
9
+ module ClassMethods
10
+ def request(method, path, query = {})
11
+ PDC.logger.debug ' >>>'.yellow + " : #{path.ai} #{query.ai}"
12
+ ActiveSupport::Notifications.instrument('request.pdc', method: method) do |payload|
13
+ response = connection.send(method) do |request|
14
+ request.url path, query
15
+ end
16
+ payload[:url], payload[:status] = response.env.url, response.status
17
+ PDC::Http::Result.new(response)
18
+ end
19
+ end
20
+
21
+ def fetch(relation)
22
+ # TODO: handle v1/release-variant/(:release)/(:variant)
23
+ # attributes won't contain (:release) and (:variant_id)
24
+ params = relation.params
25
+ resource_path = Path.new(relation.uri, params)
26
+
27
+ uri = resource_path.expanded
28
+ query = params.except(*resource_path.variables)
29
+ request(:get, uri, query)
30
+ end
31
+ end # classmethod
32
+ end
33
+ end
34
+
@@ -0,0 +1,19 @@
1
+ require_relative 'per_thread_registry'
2
+
3
+ module PDC::Resource
4
+ class ScopeRegistry
5
+ extend PerThreadRegistry
6
+
7
+ def initialize
8
+ @store = Hash.new { |hash, key| hash[key] = {} }
9
+ end
10
+
11
+ def value_for(scope_type, variable_name)
12
+ @store[scope_type][variable_name]
13
+ end
14
+
15
+ def set_value_for(scope_type, variable_name, value)
16
+ @store[scope_type][variable_name] = value
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,29 @@
1
+ module PDC::Resource
2
+ module Scopes
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ delegate :find, :count, :where, :first, :all!, :all, to: :scoped
7
+
8
+ def scoped
9
+ current_scope || Relation.new(self, uri: uri)
10
+ end
11
+
12
+ def scope(name, code)
13
+ define_singleton_method name, code
14
+ end
15
+
16
+ def current_scope=(value)
17
+ ScopeRegistry.set_value_for(:current_scope, name, value)
18
+ end
19
+
20
+ def current_scope
21
+ ScopeRegistry.value_for(:current_scope, name)
22
+ end
23
+ end # class methods
24
+
25
+ def scoped
26
+ self.class.scoped
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ module PDC::Resource
2
+ # Internal
3
+ # ValueParser takes in a value and returns the parsed form of the input
4
+ # e.g. Hash gets converted to OpenStruct so that
5
+ # foo: {
6
+ # bar: {
7
+ # too: 'too moo',
8
+ # baz: {
9
+ # value: 'foobarbaz'
10
+ # }
11
+ # }
12
+ # }
13
+ # can be accessed as foo.bar.too and foo.bar.baz.value
14
+ class ValueParser
15
+ class << self
16
+ def parse(value)
17
+ case
18
+ when value.is_a?(Array) then value.map { |v| parse(v) }
19
+ when value.is_a?(Hash) then OpenStruct.new(parse_hash(value))
20
+ else value
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def parse_hash(hash)
27
+ hash.map { |k, v| [k.to_sym, parse(v)] }.to_h
28
+ end
29
+ end
30
+ end
31
+ end
File without changes