Hi,
I’m implementing a basic personal finance application in Rails.
I’m struggling to implement a form array as part of a form. I want to use a form object, as the model does not match the form 1-to-1.
Effectively, a Transaction
can have multiple Payment
s. A Payment
must have a Category
associated with it.
The new view I am adding allows for multiple Payment
s to be added to a Transaction
at the same time.
The new view would look like this table, with the last row being a new row that can be added:
Date | Description | Payment Type | Actions | Gas | Electricity | Car Maintenance |
---|---|---|---|---|---|---|
2020-01-01 | British Gas | Bank Giro Credit (BGC) | View Button |
10.00 | ||
2020-01-02 | Southern Electric | Cheque (CHQ) | View Button |
20.00 | ||
2020-01-02 | Suzuki | Faster Payment Out (FPO) | View Button |
100.00 | ||
(date input) | (input) | (<select> list) |
Create Button |
(input field) |
(input field) |
(input field) |
Then, for the form’s POST
structure, I would like it to look like this:
{
"date": "2020-01-02",
"payment_type": "BGC",
"description": "PAYMENT DESCRIPTION",
"payments": [
{
"category_id": 2, // Electricity Payment
"amount": "20.00"
},
{
"category_id": 3, // Car Maintenance Payment
"amount": "100.00"
}
]
}
In order to facilitate this, I’m attempting to use form objects, as the forms themselves don’t map directly to the Transaction
, Payment
and Category
models.
For my Rails objects, I’m attempting to use YAAF
(though, if it can be attempted in “vanilla” Rails, I would appreciate any help also).
Here is the ERB making up the form:
<tr>
<!-- Editable section for each of the Categories. Creates 1 Transaction, and N Payments based on each category. -->
<%= form_with model: @transaction_form, scope: :transaction, url: create_transaction_and_payments_bank_account_closures_path(@bank_account), method: 'post', local: true do |form| %>
<td>
<%= form.label :date %>
<%= form.date_field :date, value: DateTime.now %>
</td>
<td>
<%= form.label :description %>
<%= form.text_field :description %>
</td>
<td>
<%= form.label :payment_type %>
<%= form.select :payment_type, Transaction.payment_types.keys.map { |w| ["#{w.titleize} (#{Transaction.payment_type_to_initial(w.to_sym)})", w]}, include_blank: true %>
</td>
<td>
<%= form.hidden_field :direction, value: direction %>
</td>
<td>
<%= form.submit 'Create' %>
</td>
<%= form.fields_for :payments do |payment_form| %>
<td>
<%= payment_form.hidden_field :category_id %>
<%= payment_form.text_field :amount %>
</td>
<% end %>
<% end %>
</tr>
When this is submitted, however, it ends up creating a structure of:
{
"direction" => "in",
"description" => "DESCRIPTION",
"date" => "2020-01-10",
"payment_type" => "bank_giro_credit",
"payments_attributes" => {
"0" => { "amount" => "10.00" },
"1" => { "amount" => "20.00" },
"2" => { "amount" => "" },
"3" => { "amount" => "" },
"4" => { "amount" => "" },
"5" => { "amount" => "" },
"6" => { "amount" => "" },
"7" => { "amount" => "" },
"8" => { "amount" => "" },
"9" => { "amount" => "" },
"10" => { "amount" => "" }
}
}
And here are the Ruby form objects:
class Closures::SimpleTransactionCreationForm < YAAF::Form
attr_accessor :direction,
:description,
:date,
:payment_type,
:payments_attributes,
:payments_params,
:categories
def initialize(args = {}, categories = [])
super(args)
@categories = categories
end
def payments
if @payments
return @payments
end
@payments = []
@categories.each.with_index do |c, i|
@payments.push(Closures::SimplePaymentCreationForm.new(
payments_params&.dig(:payments_attributes, i.to_s),
c
))
end
return @payments
end
end
class Closures::SimplePaymentCreationForm < YAAF::Form
attr_accessor :amount,
:category_id,
:category,
:_category
def initialize(attrs = {}, category)
super(attrs)
@_category = category
end
end
I have a couple of questions:
- With the built-in Rails form helpers (with form objects), is it possible to create the JSON structure as the POST request?
- Am I instantiating each column correctly?
- Do I need
YAAF
? Can this be achieved without libraries, or do I need to use a specific library to achieve this?
Any help would be greatly appreciated! I apologise if this post rambles for too long, but I’ve been struggling to find an answer online, and I’m lost as to what to do next. I’m relatively new to Rails, but I have lots of experience with other frameworks and how they achieve this.