orcid 0.0.4 → 0.1.0

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