Class: Philiprehberger::Multipart::Builder

Inherits:
Object
  • Object
show all
Defined in:
lib/philiprehberger/multipart/builder.rb

Overview

DSL builder for constructing multipart/form-data bodies

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(boundary: nil) ⇒ Builder

Returns a new instance of Builder.

Parameters:

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

    optional custom boundary string



17
18
19
20
# File 'lib/philiprehberger/multipart/builder.rb', line 17

def initialize(boundary: nil)
  @boundary = boundary || generate_boundary
  @parts = []
end

Instance Attribute Details

#boundaryString (readonly)

Returns the multipart boundary string.

Returns:

  • (String)

    the multipart boundary string



11
12
13
# File 'lib/philiprehberger/multipart/builder.rb', line 11

def boundary
  @boundary
end

#partsArray<Part> (readonly)

Returns the parts added to the builder.

Returns:

  • (Array<Part>)

    the parts added to the builder



14
15
16
# File 'lib/philiprehberger/multipart/builder.rb', line 14

def parts
  @parts
end

Instance Method Details

#content_lengthInteger

Calculate the byte size of the multipart body

Returns:

  • (Integer)

    the body size in bytes



97
98
99
100
# File 'lib/philiprehberger/multipart/builder.rb', line 97

def content_length
  size = @parts.sum { |part| part.to_s(@boundary).bytesize }
  size + "--#{@boundary}--\r\n".bytesize
end

#content_typeString

Return the Content-Type header value with boundary

Returns:

  • (String)


77
78
79
# File 'lib/philiprehberger/multipart/builder.rb', line 77

def content_type
  "multipart/form-data; boundary=#{@boundary}"
end

#field(name, value) ⇒ self

Add a text field

Parameters:

  • name (Symbol, String)

    the field name

  • value (String)

    the field value

Returns:

  • (self)


27
28
29
30
# File 'lib/philiprehberger/multipart/builder.rb', line 27

def field(name, value)
  @parts << Part.new(name, value.to_s)
  self
end

#file(name, path_or_io, filename: nil, content_type: nil) ⇒ self

Add a file field

Accepts either a file path (String) or an IO object (responds to :read). When passing an IO object, the ‘filename:` keyword is required. Content type is auto-detected from the filename when not provided.

Parameters:

  • name (Symbol, String)

    the field name

  • path_or_io (String, #read)

    the file path or IO object

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

    override filename (required for IO objects)

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

    the MIME content type (auto-detected if nil)

Returns:

  • (self)

Raises:

  • (Error)

    if the file does not exist (path mode) or filename is missing (IO mode)



44
45
46
47
48
49
50
51
# File 'lib/philiprehberger/multipart/builder.rb', line 44

def file(name, path_or_io, filename: nil, content_type: nil)
  if path_or_io.respond_to?(:read)
    add_io_file(name, path_or_io, filename, content_type)
  else
    add_path_file(name, path_or_io, filename, content_type)
  end
  self
end

#headersHash

Return the headers hash for the request

Returns:

  • (Hash)


105
106
107
108
109
110
# File 'lib/philiprehberger/multipart/builder.rb', line 105

def headers
  {
    'Content-Type' => content_type,
    'Content-Length' => content_length.to_s
  }
end

#part(name) ⇒ Part?

Look up the first part previously added with the given name.

Allows post-construction tweaks, e.g. ‘builder.part(’avatar’).content_type = ‘image/webp’‘. String and Symbol lookups are equivalent.

Parameters:

  • name (Symbol, String)

    the field name to look up

Returns:

  • (Part, nil)

    the first matching Part, or nil if absent



61
62
63
64
# File 'lib/philiprehberger/multipart/builder.rb', line 61

def part(name)
  needle = name.to_s
  @parts.find { |p| p.name.to_s == needle }
end

#to_sString

Render the complete multipart body as a string

Returns:

  • (String)


69
70
71
72
# File 'lib/philiprehberger/multipart/builder.rb', line 69

def to_s
  body = @parts.map { |part| part.to_s(@boundary) }.join
  "#{body}--#{@boundary}--\r\n"
end

#write_to(io) ⇒ #write

Stream the multipart body to an IO object

Writes each part directly to the IO without building the full body string in memory. Useful for large file uploads.

Parameters:

  • io (#write)

    the IO object to write to

Returns:

  • (#write)

    the IO object



88
89
90
91
92
# File 'lib/philiprehberger/multipart/builder.rb', line 88

def write_to(io)
  @parts.each { |part| io.write(part.to_s(@boundary)) }
  io.write("--#{@boundary}--\r\n")
  io
end