curation_concerns 1.0.0.beta6 → 1.0.0.beta7

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/app/actors/curation_concerns/actors/add_to_work_actor.rb +35 -0
  3. data/app/controllers/concerns/curation_concerns/curation_concern_controller.rb +21 -4
  4. data/app/forms/curation_concerns/forms/work_form.rb +10 -4
  5. data/app/models/concerns/curation_concerns/nested_works.rb +14 -0
  6. data/app/models/concerns/curation_concerns/work_behavior.rb +1 -0
  7. data/app/presenters/curation_concerns/composite_presenter_factory.rb +22 -0
  8. data/app/presenters/curation_concerns/model_proxy.rb +1 -1
  9. data/app/presenters/curation_concerns/work_show_presenter.rb +21 -2
  10. data/app/search_builders/curation_concerns/work_relation.rb +40 -0
  11. data/app/services/curation_concerns/actors/actor_factory.rb +1 -0
  12. data/app/services/curation_concerns/contextual_path.rb +19 -0
  13. data/app/services/curation_concerns/thumbnail_path_service.rb +2 -1
  14. data/app/views/curation_concerns/base/_actions.html.erb +3 -0
  15. data/app/views/curation_concerns/base/_file_manager_member.html.erb +1 -1
  16. data/app/views/curation_concerns/base/_file_manager_members.html.erb +1 -1
  17. data/app/views/curation_concerns/base/_form_in_works.html.erb +6 -0
  18. data/app/views/curation_concerns/base/_form_supplementary_fields.html.erb +1 -0
  19. data/app/views/curation_concerns/base/_member.html.erb +3 -3
  20. data/app/views/curation_concerns/base/_related_files.html.erb +5 -5
  21. data/app/views/curation_concerns/base/_representative_media.html.erb +1 -1
  22. data/app/views/curation_concerns/base/_show_actions.html.erb +14 -0
  23. data/app/views/curation_concerns/base/file_manager.html.erb +1 -1
  24. data/app/views/curation_concerns/base/show.html.erb +9 -1
  25. data/config/locales/curation_concerns.en.yml +3 -0
  26. data/lib/curation_concerns/rails/routes.rb +10 -0
  27. data/lib/curation_concerns/version.rb +1 -1
  28. data/lib/generators/curation_concerns/work/templates/model.rb.erb +2 -0
  29. data/spec/actors/curation_concerns/work_actor_spec.rb +33 -0
  30. data/spec/controllers/curation_concerns/generic_works_controller_spec.rb +9 -0
  31. data/spec/features/create_child_work_spec.rb +62 -0
  32. data/spec/models/generic_work_spec.rb +25 -0
  33. data/spec/presenters/curation_concerns/work_show_presenter_spec.rb +22 -2
  34. data/spec/search_builders/curation_concerns/work_relation_spec.rb +10 -0
  35. data/spec/services/thumbnail_path_service_spec.rb +1 -1
  36. data/spec/views/curation_concerns/base/_member.html.erb_spec.rb +3 -0
  37. data/spec/views/curation_concerns/base/_show_actions.html.erb_spec.rb +10 -1
  38. data/spec/views/curation_concerns/base/file_manager.html.erb_spec.rb +16 -2
  39. metadata +13 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 36e9a2f37af838e6dd164d0a8e6e8b77bf82f1aa
4
- data.tar.gz: e34ea9149f44ab96758c4a7d0bbef7e1ef6dbae2
3
+ metadata.gz: 22cca2bceccfc06d12a86825fe10d8f27ecfc0b7
4
+ data.tar.gz: 2cb151b95b0c2da91d3fcb2b0b54404e4d4ad1d1
5
5
  SHA512:
6
- metadata.gz: e085e78c5b3d079635159e39198be326573a045fb9609eddd9d2c4bd7fbfaf57cef8b8666686fda4ce08581f990166eb6d43cea9c416adf599c8550d725d1bb0
7
- data.tar.gz: ad13311d91b04790c04a3fe7b6db9f8383ace5228547170722f9bd9204b3297d9303b0dfc9ac3d894eed5ec8c97fd19715204106d9e95a2949556aa5b1688463
6
+ metadata.gz: 080f23f93d1c098d0f15e3c1c6c416de18cf2f40894d8fb2b700e8a4e571b632d8c0a680b50fc72a6547336ad3aef527196c76c9eb65f66e72de57ea5be19538
7
+ data.tar.gz: 99c4d8ee5bf8f9f6e7272cc45a54a114672e7371f64a3b108cfb1ad9bff90d3897a2619de52f58bb6274e03be48b22414d7678059f07aeceeea34d7d76d9d723
@@ -0,0 +1,35 @@
1
+ module CurationConcerns
2
+ module Actors
3
+ class AddToWorkActor < AbstractActor
4
+ def create(attributes)
5
+ work_ids = attributes.delete(:in_works_ids)
6
+ next_actor.create(attributes) && add_to_works(work_ids)
7
+ end
8
+
9
+ def update(attributes)
10
+ work_ids = attributes.delete(:in_works_ids)
11
+ add_to_works(work_ids) && next_actor.update(attributes)
12
+ end
13
+
14
+ private
15
+
16
+ def add_to_works(new_work_ids)
17
+ return true unless new_work_ids.present?
18
+ (curation_concern.in_works_ids - new_work_ids).each do |old_id|
19
+ work = ::ActiveFedora::Base.find(old_id)
20
+ work.ordered_members.delete(curation_concern)
21
+ work.members.delete(curation_concern)
22
+ work.save
23
+ end
24
+
25
+ # add to new
26
+ (new_work_ids - curation_concern.in_works_ids).each do |work_id|
27
+ work = ::ActiveFedora::Base.find(work_id)
28
+ work.ordered_members << curation_concern
29
+ work.save
30
+ end
31
+ true
32
+ end
33
+ end
34
+ end
35
+ end
@@ -12,7 +12,7 @@ module CurationConcerns::CurationConcernController
12
12
  class_attribute :_curation_concern_type, :show_presenter
13
13
  self.show_presenter = CurationConcerns::WorkShowPresenter
14
14
  attr_accessor :curation_concern
15
- helper_method :curation_concern
15
+ helper_method :curation_concern, :contextual_path
16
16
  end
17
17
 
18
18
  module ClassMethods
@@ -53,7 +53,7 @@ module CurationConcerns::CurationConcernController
53
53
  # or the user doesn't have access to it.
54
54
  def show
55
55
  respond_to do |wants|
56
- wants.html { presenter }
56
+ wants.html { presenter && parent_presenter }
57
57
  wants.json do
58
58
  # load and authorize @curation_concern manually because it's skipped for html
59
59
  # This has to use #find instead of #load_instance_from_solr because
@@ -117,13 +117,22 @@ module CurationConcerns::CurationConcernController
117
117
  @presenter ||= show_presenter.new(curation_concern_from_search_results, current_ability, request)
118
118
  end
119
119
 
120
+ def parent_presenter
121
+ @parent_presenter ||=
122
+ begin
123
+ if params[:parent_id]
124
+ @parent_presenter ||= show_presenter.new(search_result_document(id: params[:parent_id]), current_ability, request)
125
+ end
126
+ end
127
+ end
128
+
120
129
  def _prefixes
121
130
  @_prefixes ||= super + ['curation_concerns/base']
122
131
  end
123
132
 
124
133
  def after_create_response
125
134
  respond_to do |wants|
126
- wants.html { redirect_to [main_app, curation_concern] }
135
+ wants.html { redirect_to contextual_path(curation_concern, parent_presenter) }
127
136
  wants.json { render :show, status: :created, location: polymorphic_path([main_app, curation_concern]) }
128
137
  end
129
138
  end
@@ -166,10 +175,18 @@ module CurationConcerns::CurationConcernController
166
175
  CurationConcerns::WorkSearchBuilder
167
176
  end
168
177
 
178
+ def contextual_path(presenter, parent_presenter)
179
+ ::CurationConcerns::ContextualPath.new(presenter, parent_presenter).show
180
+ end
181
+
169
182
  private
170
183
 
171
184
  def curation_concern_from_search_results
172
- _, document_list = search_results(params)
185
+ search_result_document(params)
186
+ end
187
+
188
+ def search_result_document(search_params)
189
+ _, document_list = search_results(search_params)
173
190
  raise CanCan::AccessDenied.new(nil, :show) if document_list.empty?
174
191
  document_list.first
175
192
  end
@@ -7,7 +7,7 @@ module CurationConcerns
7
7
  delegate :human_readable_type, :open_access?, :authenticated_only_access?,
8
8
  :open_access_with_embargo_release_date?, :private_access?,
9
9
  :embargo_release_date, :lease_expiration_date, :member_ids,
10
- :visibility, to: :model
10
+ :visibility, :in_works_ids, to: :model
11
11
 
12
12
  self.terms = [:title, :creator, :contributor, :description,
13
13
  :keyword, :rights, :publisher, :date_created, :subject, :language,
@@ -15,7 +15,7 @@ module CurationConcerns
15
15
  :representative_id, :thumbnail_id, :files,
16
16
  :visibility_during_embargo, :embargo_release_date, :visibility_after_embargo,
17
17
  :visibility_during_lease, :lease_expiration_date, :visibility_after_lease,
18
- :visibility, :ordered_member_ids, :source]
18
+ :visibility, :ordered_member_ids, :source, :in_works_ids]
19
19
 
20
20
  self.required_fields = [:title]
21
21
 
@@ -42,8 +42,14 @@ module CurationConcerns
42
42
  # This determines whether the allowed parameters are single or multiple.
43
43
  # By default it delegates to the model.
44
44
  def multiple?(term)
45
- return true if term.to_s == 'ordered_member_ids'
46
- super
45
+ case term.to_s
46
+ when 'ordered_member_ids'
47
+ true
48
+ when 'in_works_ids'
49
+ true
50
+ else
51
+ super
52
+ end
47
53
  end
48
54
 
49
55
  # Overriden to cast 'rights' to an array
@@ -0,0 +1,14 @@
1
+ module CurationConcerns
2
+ module NestedWorks
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :valid_child_concerns
7
+ self.valid_child_concerns = CurationConcerns::ClassifyConcern.new.all_curation_concern_classes
8
+ end
9
+
10
+ def in_works_ids
11
+ in_works.map(&:id)
12
+ end
13
+ end
14
+ end
@@ -14,6 +14,7 @@ module CurationConcerns::WorkBehavior
14
14
  include CurationConcerns::RequiredMetadata
15
15
  include Hydra::AccessControls::Embargoable
16
16
  include GlobalID::Identification
17
+ include CurationConcerns::NestedWorks
17
18
 
18
19
  included do
19
20
  property :owner, predicate: RDF::URI.new('http://opaquenamespace.org/ns/hydra/owner'), multiple: false
@@ -0,0 +1,22 @@
1
+ module CurationConcerns
2
+ ##
3
+ # Dynamic presenter which instantiates a file set presenter if given an object
4
+ # with a given ID, but otherwise instantiates a work presenter.
5
+ class CompositePresenterFactory
6
+ attr_reader :file_set_presenter_class, :work_presenter_class, :file_set_ids
7
+ def initialize(file_set_presenter_class, work_presenter_class, file_set_ids)
8
+ @file_set_presenter_class = file_set_presenter_class
9
+ @work_presenter_class = work_presenter_class
10
+ @file_set_ids = file_set_ids
11
+ end
12
+
13
+ def new(*args)
14
+ obj = args.first
15
+ if file_set_ids.include?(obj.id)
16
+ file_set_presenter_class.new(*args)
17
+ else
18
+ work_presenter_class.new(*args)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -4,7 +4,7 @@ module CurationConcerns
4
4
  module ModelProxy
5
5
  delegate :to_param, :to_key, :id, to: :solr_document
6
6
 
7
- delegate :model_name, to: :_delegated_to
7
+ delegate :model_name, :valid_child_concerns, to: :_delegated_to
8
8
 
9
9
  def to_partial_path
10
10
  _delegated_to._to_partial_path
@@ -15,6 +15,9 @@ module CurationConcerns
15
15
  # modify this attribute to use an alternate presenter class for the child works
16
16
  self.work_presenter_class = self
17
17
 
18
+ # Methods used by blacklight helpers
19
+ delegate :has?, :first, :fetch, to: :solr_document
20
+
18
21
  # @param [SolrDocument] solr_document
19
22
  # @param [Ability] current_ability
20
23
  # @param [ActionDispatch::Request] request the http request context
@@ -45,7 +48,15 @@ module CurationConcerns
45
48
  # @return FileSetPresenter presenter for the representative FileSets
46
49
  def representative_presenter
47
50
  return nil if representative_id.blank?
48
- @representative_presenter ||= member_presenters([representative_id]).first
51
+ @representative_presenter ||=
52
+ begin
53
+ result = member_presenters([representative_id]).first
54
+ if result.respond_to?(:representative_presenter)
55
+ result.representative_presenter
56
+ else
57
+ result
58
+ end
59
+ end
49
60
  end
50
61
 
51
62
  # @return [Array<WorkShowPresenter>] presenters for the ordered_members that are not FileSets
@@ -56,12 +67,16 @@ module CurationConcerns
56
67
  # @param [Array<String>] ids a list of ids to build presenters for
57
68
  # @param [Class] presenter_class the type of presenter to build
58
69
  # @return [Array<presenter_class>] presenters for the ordered_members (not filtered by class)
59
- def member_presenters(ids = ordered_ids, presenter_class = file_presenter_class)
70
+ def member_presenters(ids = ordered_ids, presenter_class = composite_presenter_class)
60
71
  PresenterFactory.build_presenters(ids,
61
72
  presenter_class,
62
73
  *presenter_factory_arguments)
63
74
  end
64
75
 
76
+ def composite_presenter_class
77
+ CompositePresenterFactory.new(file_presenter_class, work_presenter_class, ordered_ids & file_set_ids)
78
+ end
79
+
65
80
  # @return [Array<CollectionPresenter>] presenters for the collections that this work is a member of
66
81
  def collection_presenters
67
82
  PresenterFactory.build_presenters(in_collection_ids,
@@ -69,6 +84,10 @@ module CurationConcerns
69
84
  *presenter_factory_arguments)
70
85
  end
71
86
 
87
+ def link_name
88
+ current_ability.can?(:read, id) ? to_s : 'File'
89
+ end
90
+
72
91
  private
73
92
 
74
93
  def presenter_factory_arguments
@@ -0,0 +1,40 @@
1
+ module CurationConcerns
2
+ class WorkRelation < ActiveFedora::Relation
3
+ def initialize(opts = {})
4
+ super(DummyModel, opts)
5
+ end
6
+
7
+ def equivalent_class?(klass)
8
+ CurationConcerns.config.curation_concerns.include?(klass)
9
+ end
10
+
11
+ def search_model_clause
12
+ clauses = CurationConcerns.config.curation_concerns.map do |k|
13
+ ActiveFedora::SolrQueryBuilder.construct_query_for_rel(has_model: k.to_s)
14
+ end
15
+ clauses.size == 1 ? clauses.first : "(#{clauses.join(' OR ')})"
16
+ end
17
+
18
+ class DummyModel
19
+ def self.primary_concern
20
+ CurationConcerns.config.curation_concerns.first
21
+ end
22
+
23
+ def self.delegated_attributes
24
+ primary_concern.delegated_attributes
25
+ end
26
+
27
+ def self.solr_query_handler
28
+ primary_concern.solr_query_handler
29
+ end
30
+
31
+ def self.default_sort_params
32
+ primary_concern.default_sort_params
33
+ end
34
+
35
+ def self.id_to_uri(*args)
36
+ primary_concern.id_to_uri(*args)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -9,6 +9,7 @@ module CurationConcerns
9
9
 
10
10
  def self.stack_actors(curation_concern)
11
11
  [AddToCollectionActor,
12
+ AddToWorkActor,
12
13
  AssignRepresentativeActor,
13
14
  AttachFilesActor,
14
15
  ApplyOrderActor,
@@ -0,0 +1,19 @@
1
+ module CurationConcerns
2
+ class ContextualPath
3
+ include Rails.application.routes.url_helpers
4
+ include ActionDispatch::Routing::PolymorphicRoutes
5
+ attr_reader :presenter, :parent_presenter
6
+ def initialize(presenter, parent_presenter)
7
+ @presenter = presenter
8
+ @parent_presenter = parent_presenter
9
+ end
10
+
11
+ def show
12
+ if parent_presenter
13
+ polymorphic_path([:curation_concerns, :parent, presenter.model_name.singular], parent_id: parent_presenter.id, id: presenter.id)
14
+ else
15
+ polymorphic_path([presenter])
16
+ end
17
+ end
18
+ end
19
+ end
@@ -8,6 +8,7 @@ module CurationConcerns
8
8
 
9
9
  thumb = fetch_thumbnail(object)
10
10
  return unless thumb
11
+ return call(thumb) unless thumb.is_a?(::FileSet)
11
12
  if thumb.audio?
12
13
  audio_image
13
14
  elsif thumbnail?(thumb)
@@ -19,7 +20,7 @@ module CurationConcerns
19
20
 
20
21
  def fetch_thumbnail(object)
21
22
  return object if object.thumbnail_id == object.id
22
- ::FileSet.find(object.thumbnail_id)
23
+ ::ActiveFedora::Base.find(object.thumbnail_id)
23
24
  rescue ActiveFedora::ObjectNotFoundError
24
25
  Rails.logger.error("Couldn't find thumbnail #{object.thumbnail_id} for #{object.id}")
25
26
  nil
@@ -0,0 +1,3 @@
1
+ <% if member.model_name.singular.to_sym == :file_set %>
2
+ <%= render "curation_concerns/file_sets/actions", file_set: member %>
3
+ <% end %>
@@ -6,7 +6,7 @@
6
6
  <%= f.input :title, as: :string, input_html: { name: "#{f.object.model_name.singular}[title][]", class: "title" }, value: node.to_s, label: false %>
7
7
  </div>
8
8
  <div class="file-set-link pull-right">
9
- <%= link_to polymorphic_path([main_app, node]), title: "Edit file" do %>
9
+ <%= link_to contextual_path(node, @presenter), title: "Edit file" do %>
10
10
  <span class="glyphicon glyphicon-edit" aria-hidden="true"></span>
11
11
  <% end %>
12
12
  </div>
@@ -1,5 +1,5 @@
1
1
  <ul id="sortable" data-id="<%= @presenter.id %>" data-class-name="<%= @presenter.model_name.plural %>" data-singular-class-name="<%= @presenter.model_name.singular %>" class="list-unstyled grid clearfix">
2
- <% @presenter.file_presenters.each do |member| %>
2
+ <% @presenter.member_presenters.each do |member| %>
3
3
  <%= render "file_manager_member", node: member %>
4
4
  <% end %>
5
5
  </ul>
@@ -0,0 +1,6 @@
1
+ <% if params[:parent_id] %>
2
+ <% (f.object.in_works_ids + [params[:parent_id]]).uniq.each do |work_id| %>
3
+ <%= f.input :in_works_ids, as: :hidden, input_html: { value: work_id, multiple: true } %>
4
+ <% end %>
5
+ <%= hidden_field_tag 'parent_id', params[:parent_id] %>
6
+ <% end %>
@@ -8,3 +8,4 @@
8
8
  </div>
9
9
 
10
10
  <%= render "form_rights", f: f %>
11
+ <%= render "form_in_works", f: f %>
@@ -2,11 +2,11 @@
2
2
  <td class="thumbnail">
3
3
  <%= render_thumbnail_tag member %>
4
4
  </td>
5
- <td class="attribute filename"><%= link_to(member.link_name, main_app.curation_concerns_file_set_path(member)) %></td>
6
- <td class="attribute date_uploaded"><%= member.date_uploaded %></td>
5
+ <td class="attribute filename"><%= link_to(member.link_name, contextual_path(member, @presenter)) %></td>
6
+ <td class="attribute date_uploaded"><%= member.try(:date_uploaded) %></td>
7
7
  <td class="attribute permission"><%= member.permission_badge %></td>
8
8
  <td>
9
- <%= render 'curation_concerns/file_sets/actions', file_set: member %>
9
+ <%= render 'actions', member: member %>
10
10
  </td>
11
11
  </tr>
12
12
 
@@ -1,7 +1,7 @@
1
- <% if presenter.file_set_presenters.present? %>
1
+ <% if presenter.member_presenters.present? %>
2
2
  <div class="panel panel-default related_files">
3
3
  <div class="panel-heading">
4
- <h2>Files</h2>
4
+ <h2><%= t('curation_concerns.show.related_files.heading') %></h2>
5
5
  </div>
6
6
  <table class="table table-striped">
7
7
  <thead>
@@ -14,11 +14,11 @@
14
14
  </tr>
15
15
  </thead>
16
16
  <tbody>
17
- <%= render partial: 'member', collection: presenter.file_set_presenters %>
17
+ <%= render partial: 'member', collection: presenter.member_presenters %>
18
18
  </tbody>
19
19
  </table>
20
20
  </div>
21
21
  <% elsif can? :edit, presenter.id %>
22
- <h2>Files</h2>
23
- <p class="text-center"><em>This <%= presenter.human_readable_type %> has no files associated with it. You can add one using the "Attach a File" button below.</em></p>
22
+ <h2><%= t('curation_concerns.show.related_files.heading') %></h2>
23
+ <p class="text-center"><em>This <%= presenter.human_readable_type %> has no members associated with it. You can add one using the "Attach a File" button below.</em></p>
24
24
  <% end %>
@@ -1,4 +1,4 @@
1
- <% if presenter.representative_id.present? %>
1
+ <% if presenter.representative_id.present? && presenter.representative_presenter.present? %>
2
2
  <%= media_display presenter.representative_presenter %>
3
3
  <% else %>
4
4
  <%= image_tag 'nope.png', class: "canonical-image" %>
@@ -4,6 +4,20 @@
4
4
  <%= link_to "Edit This #{@presenter.human_readable_type}", edit_polymorphic_path([main_app, @presenter]), class: 'btn btn-default' %>
5
5
  <%= link_to "Attach a File", main_app.new_curation_concerns_file_set_path(@presenter), class: 'btn btn-default' %>
6
6
  <%= link_to t("file_manager.link_text"), polymorphic_path([main_app, :file_manager, @presenter]), class: 'btn btn-default' %>
7
+ <% if @presenter.valid_child_concerns.length > 0 %>
8
+ <div class="btn-group">
9
+ <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
10
+ Attach Child <span class="caret"></span>
11
+ </button>
12
+ <ul class="dropdown-menu">
13
+ <% @presenter.valid_child_concerns.each do |concern| %>
14
+ <li>
15
+ <%= link_to "Attach #{concern.human_readable_type}", polymorphic_path([main_app, :new, :curation_concerns, :parent, concern.model_name.singular], parent_id: @presenter.id) %>
16
+ </li>
17
+ <% end %>
18
+ </ul>
19
+ </div>
20
+ <% end %>
7
21
  <%= link_to "Delete This #{@presenter.human_readable_type}", [main_app, @presenter], class: 'btn btn-danger pull-right', data: { confirm: "Delete this #{@presenter.human_readable_type}?" }, method: :delete %>
8
22
  <% end %>
9
23
  <% if collector %>
@@ -5,7 +5,7 @@
5
5
  </li>
6
6
  </ul>
7
7
 
8
- <% if !@presenter.file_presenters.empty? %>
8
+ <% if !@presenter.member_presenters.empty? %>
9
9
  <div data-action="file-manager">
10
10
  <div class="col-md-3" id="file-manager-tools">
11
11
  <h2>Toolbar</h2>
@@ -1,6 +1,14 @@
1
1
  <% provide :page_title, @presenter.page_title %>
2
2
  <% provide :page_header do %>
3
- <h1><%= @presenter %> <span class="human_readable_type">(<%= @presenter.human_readable_type %>)</span></h1>
3
+ <h1><%= @presenter %> </h1>
4
+ <% if @parent_presenter %>
5
+ <ul class="breadcrumb">
6
+ <li><%= link_to @parent_presenter, polymorphic_path([main_app, @parent_presenter]) %></li>
7
+ <li class="active"><%= @presenter.human_readable_type %></li>
8
+ </ul>
9
+ <% else %>
10
+ <span class="human_readable_type">(<%= @presenter.human_readable_type %>)</span>
11
+ <% end %>
4
12
  <% end %>
5
13
 
6
14
  <% collector = can?(:collect, @presenter.id) %>
@@ -14,6 +14,9 @@ en:
14
14
  institution:
15
15
  name: "Your Institution"
16
16
  homepage_url: "#"
17
+ show:
18
+ related_files:
19
+ heading: 'Members'
17
20
  search:
18
21
  form:
19
22
  q:
@@ -19,6 +19,16 @@ module ActionDispatch::Routing
19
19
  end
20
20
  end
21
21
 
22
+ resources :parent, only: [] do
23
+ concerns_to_route.each do |curation_concern_name|
24
+ namespaced_resources curation_concern_name, except: [:index], &block
25
+ end
26
+ end
27
+
28
+ resources :parent, only: [] do
29
+ resources :file_sets, only: [:show]
30
+ end
31
+
22
32
  resources :permissions, only: [] do
23
33
  member do
24
34
  get :confirm
@@ -1,3 +1,3 @@
1
1
  module CurationConcerns
2
- VERSION = "1.0.0.beta6".freeze
2
+ VERSION = "1.0.0.beta7".freeze
3
3
  end
@@ -3,5 +3,7 @@
3
3
  class <%= class_name %> < ActiveFedora::Base
4
4
  include ::CurationConcerns::WorkBehavior
5
5
  include ::CurationConcerns::BasicMetadata
6
+ # Change this to restrict which works can be added as a child.
7
+ # self.valid_child_concerns = []
6
8
  validates :title, presence: { message: 'Your work must have a title.' }
7
9
  end
@@ -74,6 +74,18 @@ describe CurationConcerns::Actors::GenericWorkActor do
74
74
  end
75
75
  end
76
76
  end
77
+ context 'with in_work_ids' do
78
+ let(:parent) { FactoryGirl.create(:generic_work) }
79
+ let(:attributes) do
80
+ FactoryGirl.attributes_for(:generic_work, visibility: visibility).merge(
81
+ in_works_ids: [parent.id]
82
+ )
83
+ end
84
+ it "attaches the parent" do
85
+ expect(subject.create(attributes)).to be true
86
+ expect(curation_concern.in_works).to eq [parent]
87
+ end
88
+ end
77
89
  context 'with a file' do
78
90
  let(:attributes) do
79
91
  FactoryGirl.attributes_for(:generic_work, visibility: visibility).tap do |a|
@@ -171,6 +183,27 @@ describe CurationConcerns::Actors::GenericWorkActor do
171
183
  end
172
184
  end
173
185
 
186
+ context 'with in_works_ids' do
187
+ let(:parent) { FactoryGirl.create(:generic_work) }
188
+ let(:old_parent) { FactoryGirl.create(:generic_work) }
189
+ let(:attributes) do
190
+ FactoryGirl.attributes_for(:generic_work).merge(
191
+ in_works_ids: [parent.id]
192
+ )
193
+ end
194
+ before do
195
+ curation_concern.apply_depositor_metadata(user.user_key)
196
+ curation_concern.save!
197
+ old_parent.ordered_members << curation_concern
198
+ old_parent.save!
199
+ end
200
+ it "attaches the parent" do
201
+ expect(subject.update(attributes)).to be true
202
+ expect(curation_concern.in_works).to eq [parent]
203
+
204
+ expect(old_parent.reload.members).to eq []
205
+ end
206
+ end
174
207
  context 'adding to collections' do
175
208
  let!(:collection1) { create(:collection, user: user) }
176
209
  let!(:collection2) { create(:collection, user: user) }
@@ -13,6 +13,15 @@ describe CurationConcerns::GenericWorksController do
13
13
  get :show, id: a_work
14
14
  expect(response).to be_success
15
15
  end
16
+ context "with a parent work" do
17
+ render_views
18
+ it "renders a breadcrumb" do
19
+ parent = create(:generic_work, title: ['Parent Work'], user: user, ordered_members: [a_work])
20
+ get :show, id: a_work, parent_id: parent
21
+
22
+ expect(response.body).to have_content "Parent Work"
23
+ end
24
+ end
16
25
  end
17
26
 
18
27
  context 'someone elses private work' do
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+ require 'redlock'
3
+
4
+ feature 'Creating a new child Work' do
5
+ let(:user) { FactoryGirl.create(:user) }
6
+
7
+ let(:redlock_client_stub) { # stub out redis connection
8
+ client = double('redlock client')
9
+ allow(client).to receive(:lock).and_yield(true)
10
+ allow(Redlock::Client).to receive(:new).and_return(client)
11
+ client
12
+ }
13
+ let(:parent) { FactoryGirl.create(:generic_work, user: user, title: ["Parent First"]) }
14
+
15
+ before do
16
+ sign_in user
17
+
18
+ # stub out characterization. Travis doesn't have fits installed, and it's not relevant to the test.
19
+ allow(CharacterizeJob).to receive(:perform_later)
20
+ redlock_client_stub
21
+ parent
22
+ end
23
+
24
+ it 'creates the child work' do
25
+ visit "/concern/parent/#{parent.id}/generic_works/new"
26
+ work_title = 'My Test Work'
27
+ within('form.new_generic_work') do
28
+ fill_in('Title', with: work_title)
29
+ click_on('Create Generic work')
30
+ end
31
+ expect(page).to have_content parent.title.first
32
+ visit "/concern/generic_works/#{parent.id}"
33
+ expect(page).to have_content work_title
34
+ end
35
+
36
+ context "when it's being updated" do
37
+ let(:curation_concern) { FactoryGirl.create(:generic_work, user: user) }
38
+ before do
39
+ parent.ordered_members << curation_concern
40
+ parent.save!
41
+ end
42
+ it 'can be updated' do
43
+ visit "/concern/parent/#{parent.id}/generic_works/#{curation_concern.id}/edit"
44
+ click_on "Update Generic work"
45
+
46
+ expect(parent.reload.ordered_members.to_a.length).to eq 1
47
+ end
48
+ it "doesn't lose other memberships" do
49
+ new_parent = FactoryGirl.create(:generic_work, user: user)
50
+ new_parent.ordered_members << curation_concern
51
+ new_parent.save!
52
+
53
+ visit "/concern/parent/#{parent.id}/generic_works/#{curation_concern.id}/edit"
54
+ click_on "Update Generic work"
55
+
56
+ expect(parent.reload.ordered_members.to_a.length).to eq 1
57
+ expect(new_parent.reload.ordered_members.to_a.length).to eq 1
58
+
59
+ expect(curation_concern.reload.in_works_ids.length).to eq 2
60
+ end
61
+ end
62
+ end
@@ -14,6 +14,11 @@ describe GenericWork do
14
14
  it { is_expected.to eq 'curation_concerns_generic_work' }
15
15
  end
16
16
 
17
+ describe '.valid_child_concerns' do
18
+ it "is all registered curation concerns by default" do
19
+ expect(described_class.valid_child_concerns).to eq [described_class]
20
+ end
21
+ end
17
22
  context 'with attached files' do
18
23
  subject { FactoryGirl.create(:work_with_files) }
19
24
 
@@ -27,6 +32,13 @@ describe GenericWork do
27
32
  subject { described_class.indexer }
28
33
  it { is_expected.to eq CurationConcerns::WorkIndexer }
29
34
  end
35
+ context "with children" do
36
+ subject { FactoryGirl.create(:work_with_file_and_work) }
37
+ it "can have the thumbnail set to the work" do
38
+ subject.thumbnail = subject.ordered_members.to_a.last
39
+ expect(subject.save).to eq true
40
+ end
41
+ end
30
42
 
31
43
  describe 'to_solr' do
32
44
  subject { build(:work, date_uploaded: Date.today).to_solr }
@@ -62,4 +74,17 @@ describe GenericWork do
62
74
  subject { work.to_global_id }
63
75
  it { is_expected.to be_kind_of GlobalID }
64
76
  end
77
+
78
+ describe "#in_works_ids" do
79
+ let(:parent) { FactoryGirl.create(:generic_work) }
80
+ subject { FactoryGirl.create(:generic_work) }
81
+ before do
82
+ parent.ordered_members << subject
83
+ parent.save!
84
+ end
85
+
86
+ it "returns ids" do
87
+ expect(subject.in_works_ids).to eq [parent.id]
88
+ end
89
+ end
65
90
  end
@@ -61,6 +61,17 @@ describe CurationConcerns::WorkShowPresenter do
61
61
  end
62
62
  end
63
63
 
64
+ describe "#member_presenters" do
65
+ let(:obj) { create(:work_with_file_and_work) }
66
+ let(:attributes) { obj.to_solr }
67
+
68
+ it "returns appropriate classes for each" do
69
+ expect(presenter.member_presenters.size).to eq 2
70
+ expect(presenter.member_presenters.first).to be_instance_of(::CurationConcerns::FileSetPresenter)
71
+ expect(presenter.member_presenters.last).to be_instance_of(described_class)
72
+ end
73
+ end
74
+
64
75
  describe "#file_set_presenters" do
65
76
  let(:obj) { create(:work_with_ordered_files) }
66
77
  let(:attributes) { obj.to_solr }
@@ -86,7 +97,7 @@ describe CurationConcerns::WorkShowPresenter do
86
97
  let(:attributes) { {} }
87
98
  let(:presenter_class) { double }
88
99
  before do
89
- allow(presenter).to receive(:file_presenter_class).and_return(presenter_class)
100
+ allow(presenter).to receive(:composite_presenter_class).and_return(presenter_class)
90
101
  allow(presenter).to receive(:ordered_ids).and_return(['12', '33'])
91
102
  allow(presenter).to receive(:file_set_ids).and_return(['33', '12'])
92
103
  end
@@ -104,7 +115,7 @@ describe CurationConcerns::WorkShowPresenter do
104
115
  let(:attributes) { obj.to_solr }
105
116
  let(:presenter_class) { double }
106
117
  before do
107
- allow(presenter).to receive(:file_presenter_class).and_return(presenter_class)
118
+ allow(presenter).to receive(:composite_presenter_class).and_return(presenter_class)
108
119
  end
109
120
  it "has a representative" do
110
121
  expect(CurationConcerns::PresenterFactory).to receive(:build_presenters)
@@ -134,6 +145,15 @@ describe CurationConcerns::WorkShowPresenter do
134
145
  it { is_expected.to eq 'foo' }
135
146
  end
136
147
 
148
+ describe "#valid_child_concerns" do
149
+ subject { presenter }
150
+ it "delegates to the class attribute of the model" do
151
+ allow(GenericWork).to receive(:valid_child_concerns).and_return([GenericWork])
152
+
153
+ expect(subject.valid_child_concerns).to eq [GenericWork]
154
+ end
155
+ end
156
+
137
157
  describe "#attribute_to_html" do
138
158
  let(:renderer) { double('renderer') }
139
159
 
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe CurationConcerns::WorkRelation do
4
+ let!(:work) { create(:generic_work) }
5
+ let!(:file_set) { create(:file_set) }
6
+ let!(:collection) { create(:collection) }
7
+ it 'has works and not collections or file sets' do
8
+ expect(subject).to eq [work]
9
+ end
10
+ end
@@ -32,7 +32,7 @@ describe CurationConcerns::ThumbnailPathService do
32
32
  let(:original_file) { mock_file_factory(mime_type: 'image/jpeg') }
33
33
  before do
34
34
  allow(File).to receive(:exist?).and_return(true)
35
- allow(FileSet).to receive(:find).with('999').and_return(representative)
35
+ allow(ActiveFedora::Base).to receive(:find).with('999').and_return(representative)
36
36
  allow(representative).to receive(:original_file).and_return(original_file)
37
37
  end
38
38
 
@@ -25,6 +25,9 @@ describe 'curation_concerns/base/_member.html.erb' do
25
25
  allow(view).to receive(:can?).with(:read, kind_of(String)).and_return(true)
26
26
  allow(view).to receive(:can?).with(:edit, kind_of(String)).and_return(true)
27
27
  allow(view).to receive(:can?).with(:destroy, String).and_return(true)
28
+ allow(view).to receive(:contextual_path).with(anything, anything) do |x, y|
29
+ CurationConcerns::ContextualPath.new(x, y).show
30
+ end
28
31
  render 'curation_concerns/base/member.html.erb', member: presenter
29
32
  end
30
33
 
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe 'curation_concerns/base/show_actions' do
4
4
  let(:model) { double('model', persisted?: true, to_param: '123', model_name: GenericWork.model_name) }
5
- let(:presenter) { double("presenter", human_readable_type: 'Image', id: '123', to_model: model) }
5
+ let(:presenter) { double("presenter", human_readable_type: 'Image', id: '123', to_model: model, valid_child_concerns: [GenericWork]) }
6
6
  before do
7
7
  assign(:presenter, presenter)
8
8
  render 'curation_concerns/base/show_actions.html.erb', collector: collector, editor: editor
@@ -15,4 +15,13 @@ describe 'curation_concerns/base/show_actions' do
15
15
  expect(rendered).to have_link 'Add to a Collection'
16
16
  end
17
17
  end
18
+ context "as an editor" do
19
+ let(:editor) { true }
20
+ let(:collector) { true }
21
+ context "when there are valid_child_concerns" do
22
+ it "creates a link" do
23
+ expect(rendered).to have_link 'Attach Generic Work', href: "/concern/parent/#{presenter.id}/generic_works/new"
24
+ end
25
+ end
26
+ end
18
27
  end
@@ -1,8 +1,9 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe "curation_concerns/base/file_manager.html.erb" do
4
- let(:members) { [file_set] }
4
+ let(:members) { [file_set, member] }
5
5
  let(:file_set) { CurationConcerns::FileSetPresenter.new(solr_doc, nil) }
6
+ let(:member) { CurationConcerns::WorkShowPresenter.new(solr_doc_work, nil) }
6
7
  let(:solr_doc) do
7
8
  SolrDocument.new(
8
9
  resource.to_solr.merge(
@@ -13,6 +14,16 @@ RSpec.describe "curation_concerns/base/file_manager.html.erb" do
13
14
  )
14
15
  )
15
16
  end
17
+ let(:solr_doc_work) do
18
+ SolrDocument.new(
19
+ resource.to_solr.merge(
20
+ id: "work",
21
+ title_tesim: "Work",
22
+ thumbnail_path_ss: "/test/image/path.jpg",
23
+ label_tesim: ["work"]
24
+ )
25
+ )
26
+ end
16
27
  let(:resource) { FactoryGirl.build(:file_set) }
17
28
 
18
29
  let(:parent) { FactoryGirl.build(:generic_work) }
@@ -26,7 +37,7 @@ RSpec.describe "curation_concerns/base/file_manager.html.erb" do
26
37
  let(:blacklight_config) { CatalogController.new.blacklight_config }
27
38
 
28
39
  before do
29
- allow(parent_presenter).to receive(:file_presenters).and_return([file_set])
40
+ allow(parent_presenter).to receive(:member_presenters).and_return([file_set, member])
30
41
  assign(:presenter, parent_presenter)
31
42
  # Blacklight nonsense
32
43
  allow(view).to receive(:dom_class) { '' }
@@ -35,6 +46,9 @@ RSpec.describe "curation_concerns/base/file_manager.html.erb" do
35
46
  allow(view).to receive(:search_session).and_return({})
36
47
  allow(view).to receive(:current_search_session).and_return(nil)
37
48
  allow(view).to receive(:curation_concern).and_return(parent)
49
+ allow(view).to receive(:contextual_path).with(anything, anything) do |x, y|
50
+ CurationConcerns::ContextualPath.new(x, y).show
51
+ end
38
52
  render
39
53
  end
40
54
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: curation_concerns
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta6
4
+ version: 1.0.0.beta7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Zumwalt
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-06-01 00:00:00.000000000 Z
13
+ date: 2016-06-02 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: hydra-head
@@ -617,6 +617,7 @@ files:
617
617
  - app/actors/curation_concerns/actors/abstract_actor.rb
618
618
  - app/actors/curation_concerns/actors/actor_stack.rb
619
619
  - app/actors/curation_concerns/actors/add_to_collection_actor.rb
620
+ - app/actors/curation_concerns/actors/add_to_work_actor.rb
620
621
  - app/actors/curation_concerns/actors/apply_order_actor.rb
621
622
  - app/actors/curation_concerns/actors/assign_representative_actor.rb
622
623
  - app/actors/curation_concerns/actors/attach_files_actor.rb
@@ -745,6 +746,7 @@ files:
745
746
  - app/models/concerns/curation_concerns/has_representative.rb
746
747
  - app/models/concerns/curation_concerns/human_readable_type.rb
747
748
  - app/models/concerns/curation_concerns/naming.rb
749
+ - app/models/concerns/curation_concerns/nested_works.rb
748
750
  - app/models/concerns/curation_concerns/permissions.rb
749
751
  - app/models/concerns/curation_concerns/permissions/readable.rb
750
752
  - app/models/concerns/curation_concerns/permissions/writable.rb
@@ -760,6 +762,7 @@ files:
760
762
  - app/models/single_use_link.rb
761
763
  - app/models/version_committer.rb
762
764
  - app/presenters/curation_concerns/collection_presenter.rb
765
+ - app/presenters/curation_concerns/composite_presenter_factory.rb
763
766
  - app/presenters/curation_concerns/embargo_presenter.rb
764
767
  - app/presenters/curation_concerns/file_set_presenter.rb
765
768
  - app/presenters/curation_concerns/lease_presenter.rb
@@ -790,8 +793,10 @@ files:
790
793
  - app/search_builders/curation_concerns/search_filters.rb
791
794
  - app/search_builders/curation_concerns/single_result.rb
792
795
  - app/search_builders/curation_concerns/single_use_link_search_builder.rb
796
+ - app/search_builders/curation_concerns/work_relation.rb
793
797
  - app/search_builders/curation_concerns/work_search_builder.rb
794
798
  - app/services/curation_concerns/actors/actor_factory.rb
799
+ - app/services/curation_concerns/contextual_path.rb
795
800
  - app/services/curation_concerns/curation_concern.rb
796
801
  - app/services/curation_concerns/derivative_path.rb
797
802
  - app/services/curation_concerns/embargo_service.rb
@@ -867,6 +872,7 @@ files:
867
872
  - app/views/collections/index.html.erb
868
873
  - app/views/collections/new.html.erb
869
874
  - app/views/collections/show.html.erb
875
+ - app/views/curation_concerns/base/_actions.html.erb
870
876
  - app/views/curation_concerns/base/_attribute_rows.html.erb
871
877
  - app/views/curation_concerns/base/_attributes.html.erb
872
878
  - app/views/curation_concerns/base/_collections.html.erb
@@ -880,6 +886,7 @@ files:
880
886
  - app/views/curation_concerns/base/_form_additional_information.html.erb
881
887
  - app/views/curation_concerns/base/_form_descriptive_fields.html.erb
882
888
  - app/views/curation_concerns/base/_form_files_and_links.html.erb
889
+ - app/views/curation_concerns/base/_form_in_works.html.erb
883
890
  - app/views/curation_concerns/base/_form_media.html.erb
884
891
  - app/views/curation_concerns/base/_form_permission.html.erb
885
892
  - app/views/curation_concerns/base/_form_permission_embargo.html.erb
@@ -1076,6 +1083,7 @@ files:
1076
1083
  - spec/features/add_file_spec.rb
1077
1084
  - spec/features/catalog_search_spec.rb
1078
1085
  - spec/features/collection_spec.rb
1086
+ - spec/features/create_child_work_spec.rb
1079
1087
  - spec/features/create_work_spec.rb
1080
1088
  - spec/features/embargo_spec.rb
1081
1089
  - spec/features/lease_spec.rb
@@ -1156,6 +1164,7 @@ files:
1156
1164
  - spec/search_builders/curation_concerns/embargo_search_builder_spec.rb
1157
1165
  - spec/search_builders/curation_concerns/file_set_search_builder_spec.rb
1158
1166
  - spec/search_builders/curation_concerns/lease_search_builder_spec.rb
1167
+ - spec/search_builders/curation_concerns/work_relation_spec.rb
1159
1168
  - spec/search_builders/resource_types_service_spec.rb
1160
1169
  - spec/services/curation_concern_spec.rb
1161
1170
  - spec/services/derivative_path_spec.rb
@@ -1278,6 +1287,7 @@ test_files:
1278
1287
  - spec/features/add_file_spec.rb
1279
1288
  - spec/features/catalog_search_spec.rb
1280
1289
  - spec/features/collection_spec.rb
1290
+ - spec/features/create_child_work_spec.rb
1281
1291
  - spec/features/create_work_spec.rb
1282
1292
  - spec/features/embargo_spec.rb
1283
1293
  - spec/features/lease_spec.rb
@@ -1358,6 +1368,7 @@ test_files:
1358
1368
  - spec/search_builders/curation_concerns/embargo_search_builder_spec.rb
1359
1369
  - spec/search_builders/curation_concerns/file_set_search_builder_spec.rb
1360
1370
  - spec/search_builders/curation_concerns/lease_search_builder_spec.rb
1371
+ - spec/search_builders/curation_concerns/work_relation_spec.rb
1361
1372
  - spec/search_builders/resource_types_service_spec.rb
1362
1373
  - spec/services/curation_concern_spec.rb
1363
1374
  - spec/services/derivative_path_spec.rb