Skip to content

Commit

Permalink
Merge pull request #268 from openstax/delegates_api
Browse files Browse the repository at this point in the history
Show delegates in the exercises APIs
  • Loading branch information
reedstrm authored Sep 13, 2019
2 parents f67fe59 + 957fd6a commit 30331f8
Show file tree
Hide file tree
Showing 23 changed files with 200 additions and 110 deletions.
2 changes: 1 addition & 1 deletion app/models/author.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ class Author < ApplicationRecord

validates :user, uniqueness: { scope: :publication_id }

delegate :name, to: :user
delegate :name, :delegations_as_delegator, :delegations_as_delegate, to: :user

end
2 changes: 1 addition & 1 deletion app/models/copyright_holder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ class CopyrightHolder < ApplicationRecord

validates :user, uniqueness: { scope: :publication_id }

delegate :name, to: :user
delegate :name, :delegations_as_delegator, :delegations_as_delegate, to: :user

end
11 changes: 9 additions & 2 deletions app/models/exercise.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,11 @@ class Exercise < ApplicationRecord
}
]

PRELOAD_ASSOCIATIONS = [
CACHEABLE_ASSOCIATIONS = [
:attachments,
:logic,
:tags,
publication: [
:publication_group,
:derivations,
authors: { user: :account },
copyright_holders: { user: :account }
Expand All @@ -78,6 +77,14 @@ class Exercise < ApplicationRecord
]
]

UNCACHEABLE_ASSOCIATIONS = [
publication: [
publication_group: :publications,
authors: { user: :delegations_as_delegator },
copyright_holders: { user: :delegations_as_delegator }
]
]

acts_as_votable
user_html :stimulus
publishable
Expand Down
6 changes: 4 additions & 2 deletions app/models/publication.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@ class Publication < ApplicationRecord
end

scope :visible_for, ->(options) do
next all if options[:can_view_solutions]

user = options[:user]
user = user.human_user if user.is_a?(OpenStax::Api::ApiUser)
next published if !user.is_a?(User) || user.is_anonymous?
Expand Down Expand Up @@ -108,6 +106,10 @@ class Publication < ApplicationRecord
)
end

def delegations
(authors + copyright_holders).map(&:user).uniq.flat_map(&:delegations_as_delegator)
end

def uid
"#{number}@#{version}"
end
Expand Down
2 changes: 1 addition & 1 deletion app/models/vocab_distractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class VocabDistractor < ApplicationRecord

delegate :group_uuid, :number, to: :distractor_publication_group, allow_nil: true
delegate :tags, :uuid, :version, :uid, :published_at, :license, :authors, :copyright_holders,
:derivations, :nickname, :name, :definition, to: :distractor_term
:delegations, :derivations, :nickname, :name, :definition, to: :distractor_term

def reload
@distractor_term = nil
Expand Down
11 changes: 9 additions & 2 deletions app/models/vocab_term.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,23 @@ class VocabTerm < ApplicationRecord
]
]

PRELOAD_ASSOCIATIONS = [
CACHEABLE_ASSOCIATIONS = [
:tags,
:vocab_distractors,
publication: [
:publication_group,
authors: { user: :account },
copyright_holders: { user: :account }
]
]

UNCACHEABLE_ASSOCIATIONS = [
publication: [
publication_group: :publications,
authors: { user: :delegations_as_delegate },
copyright_holders: { user: :delegations_as_delegate }
]
]

EXERCISE_PUBLICATION_FIELDS = [
:license, :yanked_at, :embargoed_until, :embargo_children_only, :major_change
]
Expand Down
36 changes: 33 additions & 3 deletions app/representers/api/v1/delegation_representer.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
module Api::V1
class DelegationRepresenter < Roar::Decorator

include Roar::JSON

property :delegator_id,
Expand All @@ -13,19 +12,50 @@ class DelegationRepresenter < Roar::Decorator

property :delegate_id,
type: Integer,
writeable: true,
writeable: false,
readable: true,
schema_info: {
required: true
}

property :delegate_type,
type: String,
writeable: true,
writeable: false,
readable: true,
schema_info: {
required: true
}

property :can_assign_authorship,
type: :boolean,
writeable: false,
readable: true,
schema_info: {
required: true
}

property :can_assign_copyright,
type: :boolean,
writeable: false,
readable: true,
schema_info: {
required: true
}

property :can_read,
type: :boolean,
writeable: false,
readable: true,
schema_info: {
required: true
}

property :can_update,
type: :boolean,
writeable: false,
readable: true,
schema_info: {
required: true
}
end
end
11 changes: 9 additions & 2 deletions app/representers/api/v1/exercises/base_representer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@ class BaseRepresenter < Roar::Decorator

include Roar::JSON

SOLUTIONS = ->(user_options:, **) { !user_options[:no_solutions] }
NOT_SOLUTIONS_ONLY = ->(user_options:, **) { !user_options[:solutions_only] }
CACHED_PUBLIC_FIELDS = ->(user_options:, **) do
user_options[:render].blank? || user_options[:render] == :cached_public
end
CACHED_PRIVATE_FIELDS = ->(user_options:, **) do
user_options[:render].blank? || user_options[:render] == :cached_private
end
UNCACHED_FIELDS = ->(user_options:, **) do
user_options[:render].blank? || user_options[:render] == :uncached
end

end
end
4 changes: 2 additions & 2 deletions app/representers/api/v1/exercises/combo_choice_representer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class ComboChoiceRepresenter < BaseRepresenter
schema_info: {
required: true
},
if: NOT_SOLUTIONS_ONLY
if: CACHED_PUBLIC_FIELDS

property :correctness,
type: Float,
Expand All @@ -19,7 +19,7 @@ class ComboChoiceRepresenter < BaseRepresenter
schema_info: {
type: 'number'
},
if: SOLUTIONS
if: CACHED_PRIVATE_FIELDS

end
end
16 changes: 8 additions & 8 deletions app/representers/api/v1/exercises/question_representer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class QuestionRepresenter < BaseRepresenter
writeable: true,
readable: true,
setter: ->(input:, **) { self.temp_id = input },
if: NOT_SOLUTIONS_ONLY
if: CACHED_PUBLIC_FIELDS

property :answer_order_matters,
as: :is_answer_order_important,
Expand All @@ -15,14 +15,14 @@ class QuestionRepresenter < BaseRepresenter
schema_info: {
type: 'boolean'
},
if: NOT_SOLUTIONS_ONLY
if: CACHED_PUBLIC_FIELDS

property :stimulus,
as: :stimulus_html,
type: String,
writeable: true,
readable: true,
if: NOT_SOLUTIONS_ONLY
if: CACHED_PUBLIC_FIELDS

collection :stems,
class: Stem,
Expand All @@ -43,22 +43,22 @@ class QuestionRepresenter < BaseRepresenter
schema_info: {
required: true
},
if: NOT_SOLUTIONS_ONLY
if: CACHED_PUBLIC_FIELDS

collection :collaborator_solutions,
class: CollaboratorSolution,
extend: CollaboratorSolutionRepresenter,
writeable: true,
readable: true,
setter: AR_COLLECTION_SETTER,
if: SOLUTIONS
if: CACHED_PRIVATE_FIELDS

collection :community_solutions,
class: CommunitySolution,
extend: CommunitySolutionRepresenter,
writeable: false,
readable: true,
if: SOLUTIONS
if: CACHED_PRIVATE_FIELDS

collection :hints,
type: String,
Expand All @@ -74,7 +74,7 @@ class QuestionRepresenter < BaseRepresenter
required: true,
description: 'Author-supplied hints for the question'
},
if: NOT_SOLUTIONS_ONLY
if: CACHED_PUBLIC_FIELDS

collection :parent_dependencies,
as: :dependencies,
Expand All @@ -86,7 +86,7 @@ class QuestionRepresenter < BaseRepresenter
schema_info: {
required: true
},
if: NOT_SOLUTIONS_ONLY
if: CACHED_PUBLIC_FIELDS

end
end
53 changes: 32 additions & 21 deletions app/representers/api/v1/exercises/representer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,45 @@ class Representer < BaseRepresenter

# Attachments may (for a while) contain collaborator solution attachments, so
# only show them to those who can see solutions
has_attachments(if: SOLUTIONS)
has_attachments(if: CACHED_PRIVATE_FIELDS)

has_logic(if: NOT_SOLUTIONS_ONLY)
has_tags(if: NOT_SOLUTIONS_ONLY)
has_logic(if: CACHED_PUBLIC_FIELDS)
has_tags(if: CACHED_PUBLIC_FIELDS)

publishable(if: NOT_SOLUTIONS_ONLY)
publishable(if: CACHED_PUBLIC_FIELDS)

collection :delegations, inherit: true, if: UNCACHED_FIELDS

property :is_vocab?,
as: :is_vocab,
writeable: false,
readable: true,
if: NOT_SOLUTIONS_ONLY
if: CACHED_PUBLIC_FIELDS

property :vocab_term_uid,
type: Integer,
writeable: false,
readable: true,
getter: ->(*) { vocab_term.try!(:uid) },
if: SOLUTIONS
if: CACHED_PRIVATE_FIELDS

property :title,
type: String,
writeable: true,
readable: true,
if: NOT_SOLUTIONS_ONLY
if: CACHED_PUBLIC_FIELDS

property :stimulus,
as: :stimulus_html,
type: String,
writeable: true,
readable: true,
if: NOT_SOLUTIONS_ONLY
if: CACHED_PUBLIC_FIELDS

collection :questions,
instance: ->(*) { Question.new(exercise: self) },
extend: ->(input:, **) {
input.nil? || input.stems.length > 1 ? \
input.nil? || input.stems.length > 1 ?
QuestionRepresenter : SimpleQuestionRepresenter
},
writeable: true,
Expand All @@ -53,16 +55,22 @@ class Representer < BaseRepresenter
type: Array,
writeable: false,
readable: true,
getter: ->(user_options:, **) do
visible_versions(can_view_solutions: SOLUTIONS.call(user_options: user_options))
end
getter: ->(decorator:, represented:, user_options:, **) do
represented.visible_versions(
can_view_solutions: decorator.class.can_view_solutions?(represented, user_options)
)
end,
if: UNCACHED_FIELDS

def self.can_view_solutions?(represented, user_options)
user_options[:can_view_solutions] || represented.can_view_solutions?(user_options[:user])
end

def self.cache_key_types_for(represented, options = {})
user_options = options.fetch(:user_options, {})

[ 'no_solutions' ].tap do |types|
types << 'solutions_only' if user_options[:can_view_solutions] ||
represented.can_view_solutions?(user_options[:user])
types << 'solutions_only' if can_view_solutions?(represented, user_options)
end
end

Expand Down Expand Up @@ -99,18 +107,21 @@ def recursive_merge(enum1, enum2)
def to_hash(options = {})
user_options = options.fetch(:user_options, {})

no_solutions = Rails.cache.fetch(
public_hash = Rails.cache.fetch(
self.class.cache_key_for(represented, 'no_solutions'), expires_in: NEVER_EXPIRES
) { super(options.merge(user_options: user_options.merge(no_solutions: true))) }
) { super(options.merge(user_options: user_options.merge(render: :cached_public))) }

uncached_hash = super(options.merge(user_options: user_options.merge(render: :uncached)))

return no_solutions unless user_options[:can_view_solutions] ||
represented.can_view_solutions?(user_options[:user])
return recursive_merge(
public_hash, uncached_hash
) unless self.class.can_view_solutions?(represented, user_options)

solutions_only = Rails.cache.fetch(
private_hash = Rails.cache.fetch(
self.class.cache_key_for(represented, 'solutions_only'), expires_in: NEVER_EXPIRES
) { super(options.merge(user_options: user_options.merge(solutions_only: true))) }
) { super(options.merge(user_options: user_options.merge(render: :cached_private))) }

recursive_merge(no_solutions.except(:versions), solutions_only)
recursive_merge(recursive_merge(public_hash, private_hash), uncached_hash)
end

end
Expand Down
Loading

0 comments on commit 30331f8

Please sign in to comment.