Module: Basecamp::Services::CardsExtensions

Defined in:
lib/basecamp/services/cards_extensions.rb

Overview

Tri-state due_on for card updates, prepended onto the generated CardsService (see the on_load hook in basecamp.rb).

BC3's card controller is presence-aware on the JSON representation (+kanban/cards_controller.rb+, basecamp/bc3#12521): card_update_params is plain card_params, so an update writes exactly the keys the body carries. An omitted due_on leaves the card's due date UNCHANGED; an explicit "" (or null) clears it. The { due_on: nil }.merge(card_params) default survives only for the HTML/turbo_stream web forms, which post every field on every submit.

A clear therefore has to be stated, never encoded as an omission — omitting due_on to clear it is a silent no-op. "" is the spelling that travels: JSON null cannot reach the wire from here at all, because compact_params is kwargs.compact and drops nils (SPEC section 18 body compaction). Rails casts the blank string to nil on the date column, so "" is what a clear looks like end to end.

There is no read-before-write. An earlier version GET the card and resent its due date, because the server then nil'd an unmentioned one and a sparse PUT was destructive. Presence-awareness removed the hazard the extra round-trip covered, and with it the race the round-trip opened between the read and the write. Every case is a single PUT.

Instance Method Summary collapse

Instance Method Details

#update(card_id:, title: nil, content: nil, due_on: nil, assignee_ids: nil) ⇒ Hash

Updates a card, addressing only what the caller named.

due_on is tri-state:

  • nil (unaddressed) — no due_on key is sent; BC3 leaves the current due date alone
  • "" — a stated clear, sent as ""
  • a date — the due date is set

Every other argument is plain send-when-set: nil leaves the field off the body, and BC3 leaves the stored value untouched.

This is the same single PUT as #update_verbatim, which stays as the unnormalised path; update differs only in mapping an empty due_on to the "" the server reads as a clear.

Parameters:

  • card_id (Integer)

    card id

  • title (String, nil) (defaults to: nil)

    new title (nil = keep current)

  • content (String, nil) (defaults to: nil)

    new content (nil = keep current, "" clears)

  • due_on (String, nil) (defaults to: nil)

    new due date (nil = keep current, "" clears)

  • assignee_ids (Array, nil) (defaults to: nil)

    new assignees (nil = keep current, [] clears)

Returns:

  • (Hash)

    the updated card



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/basecamp/services/cards_extensions.rb', line 51

def update(card_id:, title: nil, content: nil, due_on: nil, assignee_ids: nil)
  resolved_due_on =
    if due_on.nil?
      # Unaddressed. +compact_params+ drops the nil, so no key is sent and
      # the presence-aware update never touches the stored date.
      nil
    elsif due_on.to_s.empty?
      # A stated clear. "" survives +compact_params+ (it removes only
      # nils) and reaches the wire as {"due_on": ""}.
      ""
    else
      due_on
    end

  update_verbatim(
    card_id: card_id,
    title: title,
    content: content,
    due_on: resolved_due_on,
    assignee_ids: assignee_ids
  )
end