orcid 0.8.0 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
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