orcid 0.0.4 → 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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +10 -0
  3. data/.hound.yml +793 -0
  4. data/.travis.yml +14 -0
  5. data/Gemfile +14 -0
  6. data/README.md +107 -6
  7. data/Rakefile +17 -9
  8. data/app/assets/images/orcid/.keep +0 -0
  9. data/app/controllers/orcid/application_controller.rb +13 -4
  10. data/app/controllers/orcid/profile_connections_controller.rb +34 -6
  11. data/app/controllers/orcid/profile_requests_controller.rb +18 -15
  12. data/app/models/orcid/profile.rb +15 -5
  13. data/app/models/orcid/profile_connection.rb +20 -20
  14. data/app/models/orcid/profile_request.rb +39 -20
  15. data/app/models/orcid/work.rb +45 -55
  16. data/app/models/orcid/work/xml_parser.rb +45 -0
  17. data/app/models/orcid/work/xml_renderer.rb +29 -0
  18. data/app/services/orcid/remote/profile_creation_service.rb +40 -32
  19. data/app/services/orcid/remote/profile_query_service.rb +82 -0
  20. data/app/services/orcid/remote/profile_query_service/query_parameter_builder.rb +43 -0
  21. data/app/services/orcid/remote/profile_query_service/search_response.rb +31 -0
  22. data/app/services/orcid/remote/service.rb +16 -13
  23. data/app/services/orcid/remote/work_service.rb +58 -43
  24. data/app/templates/orcid/work.template.v1.1.xml.erb +32 -1
  25. data/app/views/orcid/profile_connections/_orcid_connector.html.erb +14 -0
  26. data/app/views/orcid/profile_connections/new.html.erb +4 -4
  27. data/bin/rails +8 -0
  28. data/config/{application.yml → application.yml.example} +3 -13
  29. data/config/locales/orcid.en.yml +5 -1
  30. data/config/routes.rb +4 -2
  31. data/lib/generators/orcid/install/install_generator.rb +46 -7
  32. data/lib/orcid.rb +23 -11
  33. data/lib/orcid/configuration.rb +32 -13
  34. data/lib/orcid/configuration/provider.rb +27 -13
  35. data/lib/orcid/engine.rb +20 -4
  36. data/lib/orcid/exceptions.rb +33 -4
  37. data/lib/orcid/named_callbacks.rb +3 -1
  38. data/lib/orcid/spec_support.rb +19 -9
  39. data/lib/orcid/version.rb +1 -1
  40. data/lib/tasks/orcid_tasks.rake +3 -3
  41. data/orcid.gemspec +51 -0
  42. data/rubocop.txt +1164 -0
  43. data/script/fast_specs +22 -0
  44. data/spec/controllers/orcid/profile_connections_controller_spec.rb +101 -0
  45. data/spec/controllers/orcid/profile_requests_controller_spec.rb +116 -0
  46. data/spec/factories/orcid_profile_requests.rb +11 -0
  47. data/spec/factories/users.rb +9 -0
  48. data/spec/fast_helper.rb +12 -0
  49. data/spec/features/batch_profile_spec.rb +31 -0
  50. data/spec/features/non_ui_based_interactions_spec.rb +117 -0
  51. data/spec/features/profile_connection_feature_spec.rb +19 -0
  52. data/spec/features/public_api_query_spec.rb +36 -0
  53. data/spec/fixtures/orcid_works.xml +55 -0
  54. data/spec/lib/orcid/configuration/provider_spec.rb +40 -0
  55. data/spec/lib/orcid/configuration_spec.rb +38 -0
  56. data/spec/lib/orcid/named_callbacks_spec.rb +28 -0
  57. data/spec/lib/orcid_spec.rb +97 -0
  58. data/spec/models/orcid/profile_connection_spec.rb +81 -0
  59. data/spec/models/orcid/profile_request_spec.rb +131 -0
  60. data/spec/models/orcid/profile_spec.rb +76 -0
  61. data/spec/models/orcid/work/xml_parser_spec.rb +40 -0
  62. data/spec/models/orcid/work/xml_renderer_spec.rb +18 -0
  63. data/spec/models/orcid/work_spec.rb +53 -0
  64. data/spec/services/orcid/remote/profile_creation_service_spec.rb +40 -0
  65. data/spec/services/orcid/remote/profile_query_service/query_parameter_builder_spec.rb +44 -0
  66. data/spec/services/orcid/remote/profile_query_service/search_response_spec.rb +14 -0
  67. data/spec/services/orcid/remote/profile_query_service_spec.rb +118 -0
  68. data/spec/services/orcid/remote/service_spec.rb +26 -0
  69. data/spec/services/orcid/remote/work_service_spec.rb +44 -0
  70. data/spec/spec_helper.rb +99 -0
  71. data/spec/support/non_orcid_models.rb +11 -0
  72. data/spec/support/stub_callback.rb +25 -0
  73. data/spec/test_app_templates/Gemfile.extra +3 -0
  74. data/spec/test_app_templates/lib/generators/test_app_generator.rb +36 -0
  75. data/spec/views/orcid/profile_connections/new.html.erb_spec.rb +25 -0
  76. data/spec/views/orcid/profile_requests/new.html.erb_spec.rb +23 -0
  77. metadata +119 -29
  78. data/app/runners/orcid/profile_lookup_runner.rb +0 -33
  79. data/app/runners/orcid/runner.rb +0 -15
  80. data/app/services/orcid/remote/profile_lookup_service.rb +0 -56
  81. data/app/services/orcid/remote/profile_lookup_service/search_response.rb +0 -23
  82. data/lib/orcid/query_parameter_builder.rb +0 -38
@@ -0,0 +1,43 @@
1
+ require_dependency 'orcid/remote/profile_query_service'
2
+ module Orcid
3
+ module Remote
4
+ class ProfileQueryService
5
+ # http://support.orcid.org/knowledgebase/articles/132354-searching-with-the-public-api
6
+ module QueryParameterBuilder
7
+
8
+ module_function
9
+ # Responsible for converting an arbitrary query string to the acceptable
10
+ # Orcid query format.
11
+ #
12
+ # @TODO - Note this is likely not correct, but is providing the singular
13
+ # point of entry
14
+ def call(input = {})
15
+ params = {}
16
+ q_params = []
17
+ text_params = []
18
+ input.each do |key, value|
19
+ next if value.nil? || value.to_s.strip == ''
20
+ case key.to_s
21
+ when 'start', 'row'
22
+ params[key] = value
23
+ when 'q', 'text'
24
+ text_params << "#{value}"
25
+ else
26
+ q_params << "#{key.to_s.gsub('_', '-')}:#{value}"
27
+ end
28
+ end
29
+
30
+ case text_params.size
31
+ when 0; then nil
32
+ when 1
33
+ q_params << "text:#{text_params.first}"
34
+ else
35
+ q_params << "text:((#{text_params.join(') AND (')}))"
36
+ end
37
+ params[:q] = q_params.join(' AND ')
38
+ params
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,31 @@
1
+ require_dependency 'orcid/remote/profile_query_service'
2
+ module Orcid
3
+ module Remote
4
+ class ProfileQueryService
5
+ # A search response against Orcid. This should implement the Questioning
6
+ # Authority query response object interface.
7
+ class SearchResponse
8
+ delegate :[], :has_key?, :key?, :fetch, to: :@records
9
+ def initialize(attributes = {})
10
+ @attributes = attributes.with_indifferent_access
11
+ end
12
+
13
+ def id
14
+ @attributes.fetch(:id)
15
+ end
16
+
17
+ def orcid_profile_id
18
+ @attributes.fetch(:id)
19
+ end
20
+
21
+ def label
22
+ @attributes.fetch(:label)
23
+ end
24
+
25
+ def biography
26
+ @attributes.fetch(:biography)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,19 +1,22 @@
1
1
  require 'orcid/named_callbacks'
2
- module Orcid::Remote
3
- class Service
4
- def initialize
5
- @callbacks = Orcid::NamedCallbacks.new
6
- yield(@callbacks) if block_given?
7
- end
2
+ module Orcid
3
+ module Remote
4
+ # An abstract service class, responsible for making remote calls and
5
+ # issuing a callback.
6
+ class Service
7
+ def initialize
8
+ @callbacks = Orcid::NamedCallbacks.new
9
+ yield(@callbacks) if block_given?
10
+ end
8
11
 
9
- def call
10
- raise NotImplementedError.new("Define #{self.class}#call")
11
- end
12
+ def call
13
+ fail NotImplementedError, ("Define #{self.class}#call")
14
+ end
12
15
 
13
- def callback(name, *args)
14
- @callbacks.call(name, *args)
15
- args
16
+ def callback(name, *args)
17
+ @callbacks.call(name, *args)
18
+ args
19
+ end
16
20
  end
17
-
18
21
  end
19
22
  end
@@ -1,52 +1,67 @@
1
1
  require 'orcid/exceptions'
2
- module Orcid::Remote
3
- class WorkService
4
- def self.call(orcid_profile_id, options = {})
5
- new(orcid_profile_id, options).call
6
- end
2
+ module Orcid
3
+ module Remote
4
+ # Responsible for interacting with the Orcid works endpoint
5
+ class WorkService
6
+ def self.call(orcid_profile_id, options = {})
7
+ new(orcid_profile_id, options).call
8
+ end
7
9
 
8
- attr_reader :headers, :token, :orcid_profile_id, :body, :request_method, :path
9
- def initialize(orcid_profile_id, options = {})
10
- @orcid_profile_id = orcid_profile_id
11
- @request_method = options.fetch(:request_method) { :get }
12
- @body = options.fetch(:body) { "" }
13
- @token = options.fetch(:token) { Orcid.access_token_for(orcid_profile_id) }
14
- @headers = options.fetch(:headers) { default_headers }
15
- @path = options.fetch(:path) { default_path }
16
- end
10
+ attr_reader(
11
+ :headers, :token, :orcid_profile_id, :body, :request_method, :path
12
+ )
13
+ def initialize(orcid_profile_id, options = {})
14
+ @orcid_profile_id = orcid_profile_id
15
+ @request_method = options.fetch(:request_method) { :get }
16
+ @body = options.fetch(:body) { '' }
17
+ @token = options.fetch(:token) { default_token }
18
+ @headers = options.fetch(:headers) { default_headers }
19
+ @path = options.fetch(:path) { default_path }
20
+ end
17
21
 
18
- # :post will append works to the Orcid Profile
19
- # :put will replace the existing Orcid Profile works with the payload
20
- # :get will retrieve the Orcid Profile
21
- # http://support.orcid.org/knowledgebase/articles/177528-add-works-technical-developer
22
- def call
23
- response = deliver
24
- response.body
25
- end
22
+ # :post will append works to the Orcid Profile
23
+ # :put will replace the existing Orcid Profile works with the payload
24
+ # :get will retrieve the Orcid Profile
25
+ # http://support.orcid.org/knowledgebase/articles/177528-add-works-technical-developer
26
+ def call
27
+ response = deliver
28
+ response.body
29
+ end
26
30
 
27
- protected
28
- def deliver
29
- token.request(request_method, path, body: body, headers: headers)
30
- rescue OAuth2::Error => e
31
- raise Orcid::RemoteServiceError.new(
32
- response_body: e.response.body,
33
- response_status: e.response.status,
34
- client: token.client,
35
- token: token,
36
- request_method: request_method,
37
- request_path: path,
38
- request_body: body,
39
- request_headers: headers
40
- )
41
- end
31
+ protected
42
32
 
43
- def default_headers
44
- { 'Accept' => 'application/xml', 'Content-Type'=>'application/orcid+xml' }
45
- end
33
+ def deliver
34
+ token.request(request_method, path, body: body, headers: headers)
35
+ rescue OAuth2::Error => e
36
+ handle_oauth_error(e)
37
+ end
46
38
 
47
- def default_path
48
- "v1.1/#{orcid_profile_id}/orcid-works/"
49
- end
39
+ def handle_oauth_error(e)
40
+ fail Orcid::RemoteServiceError,
41
+ response_body: e.response.body,
42
+ response_status: e.response.status,
43
+ client: token.client,
44
+ token: token,
45
+ request_method: request_method,
46
+ request_path: path,
47
+ request_body: body,
48
+ request_headers: headers
49
+ end
50
+
51
+ def default_token
52
+ Orcid.access_token_for(orcid_profile_id)
53
+ end
50
54
 
55
+ def default_headers
56
+ {
57
+ 'Accept' => 'application/xml',
58
+ 'Content-Type' => 'application/orcid+xml'
59
+ }
60
+ end
61
+
62
+ def default_path
63
+ "v1.1/#{orcid_profile_id}/orcid-works/"
64
+ end
65
+ end
51
66
  end
52
67
  end
@@ -9,10 +9,41 @@
9
9
  <orcid-work>
10
10
  <work-title>
11
11
  <title><%= work.title %></title>
12
+ <% if work.subtitle.present? %><subtitle><%= work.subtitle %></subtitle><% end %>
12
13
  </work-title>
14
+ <% if work.journal_title.present? %><journal-title><%= work.journal_title %></journal-title><% end %>
15
+ <% if work.short_description.present? %><short-description><%= work.short_description %></short-description><% end %>
16
+ <% if work.work_citation? %><work-citation>
17
+ <work-citation-type><%= work.citation_type %></work-citation-type>
18
+ <citation><%= work.citation %></citation>
19
+ </work-citation><% end %>
13
20
  <work-type><%= work.work_type %></work-type>
21
+ <% if work.publication_date? %><publication-date>
22
+ <year><%= work.publication_year %></year>
23
+ <% if work.publication_month.present? %><month><%= work.publication_month %></month><% end %>
24
+ <publication-date><% end %>
25
+ <% if work.external_identifiers.present? %><work-external-identifiers>
26
+ <% work.external_identifiers.each do |external_identifier| %><work-external-identifier>
27
+ <work-external-identifier-type><%= external_identifier.type %></work-external-identifier-type>
28
+ <work-external-identifier-id><%= external_identifier.identifier %></work-external-identifier-id>
29
+ </work-external-identifier><% end %>
30
+ </work-external-identifiers><% end %>
31
+ <% if work.url.present? %><url><%= work.url %></url><% end %>
32
+ <!--
33
+ <work_contributors>
34
+ <contributor>
35
+ <credit_name/>
36
+ <contributor_attributes>
37
+ <contributor_sequence/>
38
+ <contributor_role/>
39
+ </contributor_attributes>
40
+ </contributor>
41
+ </work_contributors>
42
+ -->
43
+ <% if work.language_code.present? %><language-code><%= work.language_code %></language-code><% end %>
44
+ <% if work.country.present? %><country><%= work.country %></country><% end %>
14
45
  </orcid-work>
15
46
  <% end %></orcid-works>
16
47
  </orcid-activities>
17
48
  </orcid-profile>
18
- </orcid-message>
49
+ </orcid-message>
@@ -0,0 +1,14 @@
1
+ <% profile_connection = Orcid::ProfileConnection.new %>
2
+ <p><br />
3
+ <%= link_to "Look up your existing ORCID", orcid.new_profile_connection_path(profile_connection:{text: default_search_text}) %>
4
+ <span>-OR-</span>
5
+ <%= link_to "Create an ORCID", orcid.new_profile_request_path %>
6
+ </p>
7
+ <p>-OR-</p>
8
+ <%= simple_form_for(profile_connection, as: :profile_connection, url: orcid.profile_connections_path, method: :post) do |f| %>
9
+ <%= f.input :orcid_profile_id, label: "Enter your existing ORCID (####-####-####-####)", class:"orcid-input" %>
10
+ <button type="submit" class="search-submit btn btn-primary" id="keyword-search-submit" tabindex="2">
11
+ <span class="orcid-connect">Connect</span>
12
+ </button>
13
+ <% end %>
14
+
@@ -1,19 +1,19 @@
1
1
  <%= simple_form_for(profile_connection, as: :profile_connection, url: orcid.new_profile_connection_path, method: :get, html: {class: 'search-form'}) do |f| %>
2
- <%= field_set_tag("Search ORCID Profiles", class: 'accessible-hidden') do %>
2
+ <%= field_set_tag("Search ORCID Profiles") do %>
3
3
  <% profile_connection.available_query_attribute_names.each do |field_name| %>
4
4
  <%= f.input field_name, as: :search %>
5
5
  <% end %>
6
6
  <% end %>
7
7
  <button type="submit" class="search-submit btn btn-primary" id="keyword-search-submit" tabindex="2">
8
- <i class="icon-search icon-white"></i><span class="accessible-hidden">Search</span>
8
+ <i class="icon-search icon-white"></i><span>Search</span>
9
9
  </button>
10
10
  <% end %>
11
11
 
12
12
  <% profile_connection.with_orcid_profile_candidates do |candidates| %>
13
13
  <%= simple_form_for(profile_connection, as: :profile_connection, url: orcid.profile_connections_path,) do |f| %>
14
- <%= field_set_tag("Select an ORCID Profile", class: 'accessible-hidden') do %>
14
+ <%= field_set_tag("Select an ORCID Profile") do %>
15
15
  <%= f.collection_radio_buttons :orcid_profile_id, candidates, :id, :label %>
16
16
  <% end %>
17
17
  <%= f.submit %>
18
18
  <% end %>
19
- <% end %>
19
+ <% end %>
data/bin/rails ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
3
+
4
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
5
+ ENGINE_PATH = File.expand_path('../../lib/orcid/engine', __FILE__)
6
+
7
+ require 'rails/all'
8
+ require 'rails/engine/commands'
@@ -6,20 +6,10 @@
6
6
  # GMAIL_USERNAME: Your_Gmail_Username
7
7
  # makes 'Your_Gmail_Username' available as ENV["GMAIL_USERNAME"]
8
8
 
9
- # Add application configuration variables here, as shown below.
10
- #
11
- ADMIN_NAME: First User
12
- ADMIN_EMAIL: user@example.com
13
- ADMIN_PASSWORD: changeme
14
-
15
9
  # From http://support.orcid.org/knowledgebase/articles/162412-tutorial-create-a-new-profile-using-curl
16
- ORCID_APP_ID: 0000-0002-6683-6607
17
- ORCID_APP_SECRET: e1ff26a2-d889-40be-bd41-acac09214cae
18
-
19
- ORCID_EXISTING_PUB_EMAIL: jeremy.n.friesen@gmail.com
20
- ORCID_EXISTING_PUB_PROFILE_ID: 0000-0002-3636-1153
21
- ORCID_EXISTING_PUB_NAME: Jeremy Friesen
10
+ ORCID_APP_ID: 0000-0000-0000-0000
11
+ ORCID_APP_SECRET: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
22
12
 
23
- ORCID_CLAIMED_PROFILE_ID: 0000-0002-1117-8571
13
+ ORCID_CLAIMED_PROFILE_ID: 0000-0000-0000-0000
24
14
  ORCID_CLAIMED_PROFILE_PASSWORD: password1A
25
15
  ORCID_CLAIMED_PROFILE_EMAIL: cwksfxyqkmrudrfrdxvlzjxl@mailinator.com
@@ -25,4 +25,8 @@ en:
25
25
  messages:
26
26
  existing_request_not_found: "Unable to find an existing Orcid Profile request. Go ahead and create one."
27
27
  existing_request: "You have already submitted an Orcid Profile request."
28
- previously_connected_profile: "You have already connected to an Orcid Profile (%{orcid_profile_id})."
28
+ previously_connected_profile: "You have already connected to an Orcid Profile (%{orcid_profile_id})."
29
+ connections:
30
+ messages:
31
+ profile_connection_not_found: "Unable to find an existing Orcid Profile connection."
32
+ verified_profile_connection_exists: "You have already connected and verified your Orcid Profile (%{orcid_profile_id})."
data/config/routes.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  Orcid::Engine.routes.draw do
2
- resource :profile_request, only: [:show, :new, :create]
3
- resources :profile_connections, only: [:new, :create, :index]
2
+ scope module: 'orcid' do
3
+ resource :profile_request, only: [:show, :new, :create]
4
+ resources :profile_connections, only: [:new, :create, :index]
5
+ end
4
6
  end
@@ -1,10 +1,28 @@
1
1
  require 'rails/generators'
2
2
 
3
3
  module Orcid
4
+ # If you want to quickly add Orcid integration into your application.
5
+ # This assumes the use of the ubiqutous Devise gem.
4
6
  class InstallGenerator < Rails::Generators::Base
5
7
  source_root File.expand_path('../templates', __FILE__)
6
8
 
7
9
  class_option :devise, default: false, type: :boolean
10
+ class_option :skip_application_yml, default: false, type: :boolean
11
+
12
+ def create_application_yml
13
+ unless options[:skip_application_yml]
14
+ create_file 'config/application.yml' do
15
+ orcid_app_id = ask('What is your Orcid Client ID?')
16
+ orcid_app_secret = ask('What is your Orcid Client Secret?')
17
+ [
18
+ '',
19
+ "ORCID_APP_ID: #{orcid_app_id}",
20
+ "ORCID_APP_SECRET: #{orcid_app_secret}",
21
+ ''
22
+ ].join("\n")
23
+ end
24
+ end
25
+ end
8
26
 
9
27
  def install_devise_multi_auth
10
28
  if options[:devise]
@@ -14,14 +32,26 @@ module Orcid
14
32
  end
15
33
  end
16
34
 
35
+ def copy_locale
36
+ copy_file(
37
+ '../../../../../config/locales/orcid.en.yml',
38
+ 'config/locales/orcid.en.yml'
39
+ )
40
+ end
41
+
17
42
  def install_migrations
18
- rake "orcid:install:migrations"
43
+ rake 'orcid:install:migrations'
19
44
  end
20
45
 
21
- def install_omniauth_strategies
22
- config_code = ", :omniauthable, :omniauth_providers => [:orcid]"
23
- insert_into_file 'app/models/user.rb', config_code, { :after => /:validatable/, :verbose => false }
46
+ def update_devise_user
47
+ config_code = ', :omniauthable, :omniauth_providers => [:orcid]'
48
+ insert_into_file(
49
+ 'app/models/user.rb',
50
+ config_code, after: /:validatable/, verbose: false
51
+ )
52
+ end
24
53
 
54
+ def update_devise_omniauth_provider
25
55
  init_code = %(
26
56
  config.omniauth(:orcid, Orcid.provider.id, Orcid.provider.secret,
27
57
  scope: Orcid.provider.authentication_scope,
@@ -32,16 +62,25 @@ module Orcid
32
62
  }
33
63
  )
34
64
  )
35
- insert_into_file 'config/initializers/devise.rb', init_code, {after: /Devise\.setup.*$/, verbose: true}
65
+ insert_into_file(
66
+ 'config/initializers/devise.rb',
67
+ init_code, after: /Devise\.setup.*$/, verbose: true
68
+ )
36
69
  end
37
70
 
38
71
  def mount_orcid_engine
39
72
  route 'mount Orcid::Engine => "/orcid"'
40
73
  end
41
74
 
42
- def install_initializer
43
- template 'orcid_initializer.rb.erb', 'config/orcid_initializer.rb'
75
+ def migrate_the_database
76
+ rake 'db:migrate'
44
77
  end
45
78
 
79
+ def install_initializer
80
+ template(
81
+ 'orcid_initializer.rb.erb',
82
+ 'config/initializers/orcid_initializer.rb'
83
+ )
84
+ end
46
85
  end
47
86
  end