curation_concerns 1.0.0.beta6 → 1.0.0.beta7

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