pdc 0.1.0

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.
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