orcid 0.8.0 → 0.8.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +0 -0
  3. data/.hound.yml +10 -0
  4. data/.mailmap +3 -0
  5. data/.travis.yml +1 -1
  6. data/Gemfile +1 -0
  7. data/README.md +39 -2
  8. data/app/controllers/orcid/profile_connections_controller.rb +4 -0
  9. data/app/controllers/orcid/profile_requests_controller.rb +5 -1
  10. data/app/models/orcid/profile.rb +26 -11
  11. data/app/models/orcid/profile_connection.rb +14 -4
  12. data/app/models/orcid/profile_request.rb +6 -80
  13. data/app/models/orcid/profile_status.rb +7 -1
  14. data/app/models/orcid/work/xml_renderer.rb +6 -4
  15. data/app/services/orcid/profile_request_coordinator.rb +112 -0
  16. data/app/services/orcid/remote/profile_creation_service.rb +6 -7
  17. data/app/services/orcid/remote/profile_query_service.rb +2 -5
  18. data/app/services/orcid/remote/profile_query_service/response_parser.rb +13 -7
  19. data/db/migrate/20140205185339_update_orcid_profile_requests.rb +6 -0
  20. data/lib/orcid.rb +15 -1
  21. data/lib/orcid/engine.rb +0 -15
  22. data/lib/orcid/exceptions.rb +24 -2
  23. data/lib/orcid/version.rb +1 -1
  24. data/orcid.gemspec +5 -1
  25. data/spec/controllers/orcid/profile_requests_controller_spec.rb +2 -2
  26. data/spec/features/non_ui_based_interactions_spec.rb +3 -1
  27. data/spec/features/orcid_work_query_spec.rb +17 -0
  28. data/spec/lib/orcid/exceptions_spec.rb +33 -0
  29. data/spec/lib/orcid_spec.rb +1 -0
  30. data/spec/models/orcid/profile_connection_spec.rb +8 -6
  31. data/spec/models/orcid/profile_request_spec.rb +9 -91
  32. data/spec/models/orcid/work_spec.rb +21 -0
  33. data/spec/services/orcid/profile_request_coordinator_spec.rb +111 -0
  34. data/spec/services/orcid/remote/profile_creation_service_spec.rb +17 -0
  35. data/spec/services/orcid/remote/profile_query_service/query_parameter_builder_spec.rb +11 -16
  36. data/spec/services/orcid/remote/profile_query_service_spec.rb +21 -9
  37. data/spec/services/orcid/remote/service_spec.rb +9 -4
  38. data/spec/spec_helper.rb +4 -0
  39. metadata +16 -5
  40. data/rubocop.txt +0 -1164
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cdbf8eaf74801c8b3c9d2325d362d14e7f7e3c8b
4
- data.tar.gz: dc2b890ba3958416489f364983885bf6a08a0f85
3
+ metadata.gz: e325b389824d646a737db51751aea1ffe7bc2869
4
+ data.tar.gz: 336b4bd518961b2f784f948f47bf70afcd252baa
5
5
  SHA512:
6
- metadata.gz: 18825277ac23c713d06fad1ead3df6565bf1273fcb77cb53e01839cd7a5e443c7fad3f17cc4eefa1c70a73d4a69d7a03e5c1b5999d187a421a40c2ed0f8fd7ed
7
- data.tar.gz: 3737282f7a406c4ca10cae3f2d51b315d512278c834680c20c57821f140864d2e0188a888a6a89eb0ff1a6f3f5df9ec0cf93cb9354d493121f19683062a6aa3e
6
+ metadata.gz: f7f07ca24430ed0063f5cfb9bb4dc615263f5c401fc2a406f246f996444f737e31a9ac17960ec11a603be0a9322cb8ec33b873f67f9d1215d3461d123eebffc1
7
+ data.tar.gz: e22ac42cc8be96e67a0e720b6388c54ceb9d79c8e9dbe4ac24b1dac17773383f7d057a681ff598475179654694e3169a0a1119ddbebd9aaf119177d6c879c6a0
data/.coveralls.yml ADDED
File without changes
data/.hound.yml CHANGED
@@ -396,6 +396,16 @@ ParenthesesAroundCondition:
396
396
 
397
397
  PercentLiteralDelimiters:
398
398
  Description: 'Use `%`-literal delimiters consistently'
399
+ PreferredDelimiters:
400
+ '%': ()
401
+ '%i': ()
402
+ '%q': ()
403
+ '%Q': ()
404
+ '%r': '{}'
405
+ '%s': ()
406
+ '%w': ()
407
+ '%W': ()
408
+ '%x': ()
399
409
  Enabled: true
400
410
 
401
411
  PerlBackrefs:
data/.mailmap ADDED
@@ -0,0 +1,3 @@
1
+ Dan Brubaker Horst <dan@brubakerhorst.com> <dan.brubaker.horst@gmail.com>
2
+ Glen Horton <hortongn@ucmail.uc.edu> <glen.horton@gmail.com>
3
+ Carolyn Cole <cam156@psu.edu> cam156
data/.travis.yml CHANGED
@@ -6,7 +6,7 @@ env:
6
6
  global:
7
7
  - NOKOGIRI_USE_SYSTEM_LIBRARIES=true
8
8
 
9
- script: 'rake spec:travis'
9
+ script: 'COVERAGE=true rake spec:travis'
10
10
 
11
11
  bundler_args: --without headless debug
12
12
 
data/Gemfile CHANGED
@@ -12,3 +12,4 @@ gemspec
12
12
 
13
13
  # To use debugger
14
14
  # gem 'debugger'
15
+ gem 'coveralls', require: false
data/README.md CHANGED
@@ -1,6 +1,38 @@
1
- # Orcid [![Version](https://badge.fury.io/rb/orcid.png)](http://badge.fury.io/rb/orcid) [![Build Status](https://travis-ci.org/projecthydra-labs/orcid.png?branch=master)](https://travis-ci.org/projecthydra-labs/orcid)
1
+ # Orcid
2
2
 
3
- A [Rails Engine](https://guides.rubyonrails.org/engines.html) for integrating with [Orcid](https://orcid.org).
3
+ [![Version](https://badge.fury.io/rb/orcid.png)](http://badge.fury.io/rb/orcid)
4
+ [![Build Status](https://travis-ci.org/projecthydra-labs/orcid.png?branch=master)](https://travis-ci.org/projecthydra-labs/orcid)
5
+ [![Coverage Status](https://img.shields.io/coveralls/projecthydra-labs/orcid.svg)](https://coveralls.io/r/projecthydra-labs/orcid)
6
+ [![API Docs](http://img.shields.io/badge/API-docs-blue.svg)](http://rubydoc.info/gems/orcid/0.8.0/frames)
7
+ [![APACHE 2 License](http://img.shields.io/badge/APACHE2-license-blue.svg)](./LICENSE)
8
+ [![Contributing Guidelines](http://img.shields.io/badge/CONTRIBUTING-Guidelines-blue.svg)](./CONTRIBUTING.md)
9
+
10
+ A [Rails Engine](https://guides.rubyonrails.org/engines.html) for integrating with [Orcid](https://orcid.org). It leverages the [Devise MultiAuth plugin](https://rubygems.org/gems/devise-multi_auth) for negotiating the interaction with [orcid.org](https://orcid.org).
11
+
12
+ **Note: While this is part of ProjectHydra, this gem is not dependent on any of the Hydra components.**
13
+
14
+ ## Features
15
+
16
+ Associate ORCID with your user account for the application by:
17
+
18
+ * Creating an ORCID
19
+ * Looking up and associating with an existing ORCID
20
+ * Providing an ORCID to directly associate with your account
21
+
22
+ Authentication
23
+
24
+ * Using OAuth2, you can use orcid.org as one of your authentication mechanisms
25
+
26
+ Interacting with ORCID Profile Works:
27
+
28
+ **The functionality exists, but it will be a bit bumpy to implement.**
29
+ **Plans are to improve the integration with Version 1.0.0 of the Orcid gem.**
30
+
31
+ * Query for your Orcid Profile's works
32
+ * Append one or more works to your Orcid Profile
33
+ * Replace your Orcid Profile works with one or more works
34
+
35
+ ## Getting Started with the Orcid gem
4
36
 
5
37
  To fully interact with the Orcid remote services, you will need to [register your ORCID application profile](#registering-for-an-orcid-application-profile).
6
38
 
@@ -9,6 +41,7 @@ To fully interact with the Orcid remote services, you will need to [register you
9
41
  * [Registering for an ORCID application profile](#registering-for-an-orcid-application-profile)
10
42
  * [Setting up your own ORCIDs in the ORCID Development Sandbox](#setting-up-your-own-orcids-in-the-orcid-development-sandbox)
11
43
  * [Running the tests](#running-the-tests)
44
+ * [Versioning](#versioning)
12
45
  * [Contributing to this gem](./CONTRIBUTING.md)
13
46
 
14
47
  ## Installation
@@ -121,3 +154,7 @@ Run the online tests with
121
154
  ```console
122
155
  $ rake spec:online
123
156
  ```
157
+
158
+ ## Versioning
159
+
160
+ **Orcid** uses [Semantic Versioning 2.0.0](http://semver.org/)
@@ -1,5 +1,9 @@
1
1
  module Orcid
2
2
  # Responsible for negotiating a user request Profile Creation.
3
+ #
4
+ # @TODO - Leverage the Orcid::ProfileStatus object instead of the really
5
+ # chatty method names. The method names are fine, but the knowledge of
6
+ # Orcid::ProfileStatus is encapsulated in that class.
3
7
  class ProfileConnectionsController < Orcid::ApplicationController
4
8
  respond_to :html
5
9
  before_filter :authenticate_user!
@@ -1,5 +1,9 @@
1
1
  module Orcid
2
2
  # Responsible for helping a user request a new Orcid Profile.
3
+ #
4
+ # @TODO - Leverage the Orcid::ProfileStatus object instead of the really
5
+ # chatty method names. The method names are fine, but the knowledge of
6
+ # Orcid::ProfileStatus is encapsulated in that class.
3
7
  class ProfileRequestsController < Orcid::ApplicationController
4
8
  respond_to :html
5
9
  before_filter :authenticate_user!
@@ -67,7 +71,7 @@ module Orcid
67
71
  end
68
72
 
69
73
  def create_profile_request(profile_request)
70
- profile_request.save && Orcid.enqueue(profile_request)
74
+ profile_request.save && Orcid::ProfileRequestCoordinator.call(profile_request)
71
75
  end
72
76
 
73
77
  def profile_request_params
@@ -2,22 +2,21 @@ module Orcid
2
2
  # Provides a container around an Orcid Profile and its relation to the Orcid
3
3
  # Works.
4
4
  class Profile
5
- attr_reader(
6
- :orcid_profile_id, :mapper, :remote_service, :xml_renderer, :xml_parser
7
- )
8
- private :mapper
9
- def initialize(orcid_profile_id, config = {})
5
+ attr_reader :orcid_profile_id, :mapper, :remote_service, :xml_renderer, :xml_parser
6
+ private :mapper, :remote_service, :xml_renderer, :xml_parser
7
+ def initialize(orcid_profile_id, collaborators = {})
10
8
  @orcid_profile_id = orcid_profile_id
11
- @mapper = config.fetch(:mapper) { Orcid.mapper }
12
- @remote_service = config.fetch(:remote_service) do
13
- Orcid::Remote::WorkService
14
- end
15
- @xml_renderer = config.fetch(:xml_renderer) { Orcid::Work::XmlRenderer }
16
- @xml_parser = config.fetch(:xml_parser) { Orcid::Work::XmlParser }
9
+ @mapper = collaborators.fetch(:mapper) { default_mapper }
10
+ @remote_service = collaborators.fetch(:remote_service) { default_remote_service }
11
+ @xml_renderer = collaborators.fetch(:xml_renderer) { default_xml_renderer }
12
+ @xml_parser = collaborators.fetch(:xml_parser) { default_xml_parser }
17
13
  end
18
14
 
19
15
  # Answers the question: Has the user been authenticated via the ORCID
20
16
  # system.
17
+ #
18
+ # @TODO - Extract this to the Orcid::ProfileStatus object. As the method
19
+ # is referenced via a controller, this can easily be moved.
21
20
  def verified_authentication?
22
21
  Orcid.authenticated_orcid?(orcid_profile_id)
23
22
  end
@@ -44,6 +43,22 @@ module Orcid
44
43
 
45
44
  protected
46
45
 
46
+ def default_mapper
47
+ Orcid.mapper
48
+ end
49
+
50
+ def default_remote_service
51
+ Orcid::Remote::WorkService
52
+ end
53
+
54
+ def default_xml_renderer
55
+ Orcid::Work::XmlRenderer
56
+ end
57
+
58
+ def default_xml_parser
59
+ Orcid::Work::XmlParser
60
+ end
61
+
47
62
  # Note: We can handle
48
63
  def normalize_work(*works)
49
64
  Array.wrap(works).map do |work|
@@ -1,3 +1,5 @@
1
+ require 'virtus'
2
+ require 'active_model'
1
3
  module Orcid
2
4
  # Responsible for connecting an authenticated user to the ORCID profile that
3
5
  # the user searched for and selected.
@@ -21,10 +23,7 @@ module Orcid
21
23
  validates :user, presence: true
22
24
  validates :orcid_profile_id, presence: true
23
25
 
24
- def save(collaborators = {})
25
- persister = collaborators.fetch(:persister) do
26
- Orcid.method(:connect_user_and_orcid_profile)
27
- end
26
+ def save
28
27
  valid? ? persister.call(user, orcid_profile_id) : false
29
28
  end
30
29
 
@@ -32,6 +31,17 @@ module Orcid
32
31
  false
33
32
  end
34
33
 
34
+ attr_writer :persister
35
+ def persister
36
+ @persister ||= default_persister
37
+ end
38
+ private :persister
39
+
40
+ def default_persister
41
+ require 'orcid'
42
+ Orcid.method(:connect_user_and_orcid_profile)
43
+ end
44
+
35
45
  attr_writer :profile_query_service
36
46
  def profile_query_service
37
47
  @profile_query_service ||= default_profile_query_service
@@ -18,90 +18,16 @@ module Orcid
18
18
 
19
19
  belongs_to :user
20
20
 
21
- def run(options = {})
22
- # Why dependency injection? Because this is going to be a plugin, and
23
- # things can't possibly be simple. I also found it easier to test the
24
- # #run method with these injected dependencies
25
- validator = options.fetch(:validator) { method(:validate_before_run) }
26
- return false unless validator.call(self)
27
-
28
- payload_xml_builder = options.fetch(:payload_xml_builder) do
29
- method(:xml_payload)
30
- end
31
- profile_creation_service = options.fetch(:profile_creation_service) do
32
- default_profile_creation_service
33
- end
34
- profile_creation_service.call(payload_xml_builder.call(attributes))
35
- end
36
-
37
- def default_profile_creation_service
38
- @default_profile_creation_service ||= begin
39
- Orcid::Remote::ProfileCreationService.new do |on|
40
- on.success do |orcid_profile_id|
41
- handle_profile_creation_response(orcid_profile_id)
42
- end
43
- end
44
- end
45
- end
46
-
47
- def validate_before_run(context = self)
48
- validate_profile_id_is_unassigned(context) &&
49
- validate_user_does_not_have_profile(context)
50
- end
51
-
52
- def validate_user_does_not_have_profile(context)
53
- user_orcid_profile = Orcid.profile_for(context.user)
54
- return true unless user_orcid_profile
55
- message = "#{context.class} ID=#{context.to_param}'s associated user" \
56
- " #{context.user.to_param} already has an assigned :orcid_profile_id" \
57
- " #{user_orcid_profile.to_param}"
58
- context.errors.add(:base, message)
59
- false
60
- end
61
- private :validate_user_does_not_have_profile
62
-
63
- def validate_profile_id_is_unassigned(context)
64
- return true unless context.orcid_profile_id?
65
- message = "#{context.class} ID=#{context.to_param} already has an" \
66
- " assigned :orcid_profile_id #{context.orcid_profile_id.inspect}"
67
- context.errors.add(:base, message)
68
- false
69
- end
70
- private :validate_profile_id_is_unassigned
71
-
72
- # NOTE: This one lies ->
73
- # http://support.orcid.org/knowledgebase/articles/177522-create-an-id-technical-developer
74
- # NOTE: This one was true at 2014-02-06:14:55 ->
75
- # http://support.orcid.org/knowledgebase/articles/162412-tutorial-create-a-new-record-using-curl
76
- def xml_payload(input = attributes)
77
- attrs = input.with_indifferent_access
78
- returning_value = <<-XML_TEMPLATE
79
- <?xml version="1.0" encoding="UTF-8"?>
80
- <orcid-message
81
- xmlns:xsi="http://www.orcid.org/ns/orcid https://raw.github.com/ORCID/ORCID-Source/master/orcid-model/src/main/resources/orcid-message-1.1.xsd"
82
- xmlns="http://www.orcid.org/ns/orcid">
83
- <message-version>1.1</message-version>
84
- <orcid-profile>
85
- <orcid-bio>
86
- <personal-details>
87
- <given-names>#{attrs.fetch('given_names')}</given-names>
88
- <family-name>#{attrs.fetch('family_name')}</family-name>
89
- </personal-details>
90
- <contact-details>
91
- <email primary="true">#{attrs.fetch('primary_email')}</email>
92
- </contact-details>
93
- </orcid-bio>
94
- </orcid-profile>
95
- </orcid-message>
96
- XML_TEMPLATE
97
- returning_value.strip
98
- end
99
-
100
- def handle_profile_creation_response(orcid_profile_id)
21
+ def successful_profile_creation(orcid_profile_id)
101
22
  self.class.transaction do
102
23
  update_column(:orcid_profile_id, orcid_profile_id)
103
24
  Orcid.connect_user_and_orcid_profile(user, orcid_profile_id)
104
25
  end
105
26
  end
27
+
28
+ def validation_error_on_profile_creation(error_message)
29
+ update_column(:response_text, error_message)
30
+ end
31
+
106
32
  end
107
33
  end
@@ -1,4 +1,11 @@
1
1
  module Orcid
2
+ # Responsible for determining a given user's orcid profile state as it
3
+ # pertains to the parent application.
4
+ #
5
+ # @TODO - There are quite a few locations where the state related behavior
6
+ # has leaked out (i.e. the Orcid::ProfileConnectionsController and Orcid::
7
+ # ProfileRequestsController)
8
+ #
2
9
  # ProfileStatus.status
3
10
  # **:authenticated_connection** - User has authenticated against the Orcid
4
11
  # remote system
@@ -39,7 +46,6 @@ module Orcid
39
46
  return callback(:unknown)
40
47
  end
41
48
  end
42
- return callback(:unknown)
43
49
  end
44
50
 
45
51
  private
@@ -9,10 +9,7 @@ module Orcid
9
9
  attr_reader :works, :template
10
10
  def initialize(works, options = {})
11
11
  self.works = works
12
- @template = options.fetch(:template_path) do
13
- template_name = 'app/templates/orcid/work.template.v1.1.xml.erb'
14
- Orcid::Engine.root.join(template_name).read
15
- end
12
+ @template = options.fetch(:template) { default_template }
16
13
  end
17
14
 
18
15
  def call
@@ -24,6 +21,11 @@ module Orcid
24
21
  def works=(thing)
25
22
  @works = Array.wrap(thing)
26
23
  end
24
+
25
+ def default_template
26
+ template_name = 'app/templates/orcid/work.template.v1.1.xml.erb'
27
+ Orcid::Engine.root.join(template_name).read
28
+ end
27
29
  end
28
30
  end
29
31
  end
@@ -0,0 +1,112 @@
1
+ require 'orcid/exceptions'
2
+
3
+ module Orcid
4
+ # Responsible for converting an Orcid::ProfileRequest into an Orcid::Profile.
5
+ class ProfileRequestCoordinator
6
+
7
+ def self.call(profile_request, collaborators = {})
8
+ new(profile_request, collaborators).call
9
+ end
10
+
11
+ def initialize(profile_request, collaborators = {})
12
+ self.profile_request = profile_request
13
+ @remote_service = collaborators.fetch(:remote_service) { default_remote_service }
14
+ @profile_state_finder = collaborators.fetch(:profile_state_finder) { default_profile_state_finder }
15
+ @logger = collaborators.fetch(:logger) { default_logger }
16
+ end
17
+
18
+ attr_reader :profile_request, :remote_service, :profile_state_finder, :logger
19
+ private :remote_service, :profile_state_finder, :logger
20
+
21
+ def call
22
+ profile_state_finder.call(user) do |on|
23
+ on.unknown { handle_unknown_profile_state }
24
+ on.profile_request_pending { |request| handle_profile_request }
25
+ on.pending_connection { |profile| handle_pending_connection_for(profile) }
26
+ on.authenticated_connection { |profile| handle_authenticated_connection_for(profile) }
27
+ end
28
+ true
29
+ end
30
+
31
+ private
32
+
33
+ def handle_profile_request
34
+ payload = xml_payload(profile_request)
35
+ remote_service.call(payload) do |on|
36
+ on.success { |orcid_profile_id| profile_request.successful_profile_creation(orcid_profile_id) }
37
+ on.failure { }
38
+ on.orcid_validation_error { |error_message| profile_request.validation_error_on_profile_creation(error_message) }
39
+ end
40
+ end
41
+
42
+ protected
43
+
44
+ def profile_request=(request)
45
+ if !request.respond_to?(:user) || !request.user.present?
46
+ fail MissingUserForProfileRequest, request
47
+ end
48
+ if !request.respond_to?(:validation_error_on_profile_creation)
49
+ raise ProfileRequestMethodExpectedError.new(request, :validation_error_on_profile_creation)
50
+ end
51
+ if !request.respond_to?(:successful_profile_creation)
52
+ raise ProfileRequestMethodExpectedError.new(request, :successful_profile_creation)
53
+ end
54
+ @profile_request = request
55
+ end
56
+
57
+ def user
58
+ profile_request.user
59
+ end
60
+
61
+ private
62
+
63
+ def default_logger
64
+ Rails.logger
65
+ end
66
+
67
+ def default_remote_service
68
+ require 'orcid/remote/profile_creation_service'
69
+ Orcid::Remote::ProfileCreationService
70
+ end
71
+
72
+ def default_profile_state_finder
73
+ require 'orcid/profile_status'
74
+ Orcid::ProfileStatus.method(:for)
75
+ end
76
+
77
+ def handle_unknown_profile_state
78
+ require 'orcid/exceptions'
79
+ fail Orcid::ProfileRequestStateError, user
80
+ end
81
+
82
+ def handle_pending_connection_for(profile)
83
+ message = "Attempted to request ORCID for #{profile_request.class} ID=#{profile_request.to_param}."
84
+ message << " There is a pending connection for #{user.class} ID=#{user.to_param}."
85
+ logger.notice(message)
86
+ end
87
+
88
+ def handle_authenticated_connection_for(profile)
89
+ message = "Attempted to request ORCID for #{profile_request.class} ID=#{profile_request.to_param}."
90
+ message << " There is an authenticated connection for #{user.class} ID=#{user.to_param}."
91
+ logger.notice(message)
92
+ end
93
+
94
+ def xml_payload(request)
95
+ attrs = request.attributes.with_indifferent_access
96
+ returning_value = <<-XML_TEMPLATE
97
+ <?xml version="1.0" encoding="UTF-8"?>
98
+ <orcid-message
99
+ xmlns:xsi="http://www.orcid.org/ns/orcid https://raw.github.com/ORCID/ORCID-Source/master/orcid-model/src/main/resources/orcid-message-1.1.xsd"
100
+ xmlns="http://www.orcid.org/ns/orcid">
101
+ <message-version>1.1</message-version>
102
+ <orcid-profile>\n<orcid-bio>\n<personal-details>
103
+ <given-names>#{attrs.fetch('given_names')}</given-names>
104
+ <family-name>#{attrs.fetch('family_name')}</family-name>
105
+ </personal-details>\n<contact-details>
106
+ <email primary="true">#{attrs.fetch('primary_email')}</email>
107
+ </contact-details>\n</orcid-bio>\n</orcid-profile>\n</orcid-message>
108
+ XML_TEMPLATE
109
+ returning_value.strip
110
+ end
111
+ end
112
+ end