Can I build several objects in different parts of a page? - ruby-on-rails

I have a Page model that has many TextBlock:
class Page < ApplicationRecord
has_many :text_blocks, as: :textable, dependent: :destroy
accepts_nested_attributes_for :text_blocks
end
In the form where a page can be edited, I have to display 3 text blocks in the upper part of the form, and 3 text blocks in the lower part of the form. Here is the code in my view...
Upper text blocks:
<%= render 'shared/admin/form-fields/text-blocks-form', f: f, number: 3, value: true %>
Lower text blocks:
<%= render 'shared/admin/form-fields/text-blocks-form', f: f, number: 2, value: false %>
And here is the text-blocks-form partial:
<div class="form-group">
<div class="row">
<div class="col-lg-12 text-blocks">
<div class="row">
<% number.times { f.object.text_blocks.where(upper_position: value).build } unless f.object.text_blocks.where(upper_position: value).any? %>
<%= f.fields_for :text_blocks do |input| %>
<div class="<%= "col-lg-#{cells(number)}" %>">
<%= render 'shared/admin/form-fields/text-blocks-fields', f: input, value: value %>
</div>
<% end %>
</div>
</div>
</div>
</div>
Upper text blocks are built as expected, there are 3 blocks but in the lower part of the form, there are 5 blocks instead of 2. It seems, that it just add 3 + 2. Is there any way to build text blocks as it was described above? Thanks ahead.

I can easily explain why you have this problem, maybe a little less easy to fix this in good way.
The reason why have this that in the first run you create three nested items (on the association), and then iterate over them using f.fields_for :text_blocks, and in the second run you add two more, and then iterating over f.fields_for :text_blocks will iterate over all five created blocks.
An easy fix would be to change your view as follows:
<div class="form-group">
<div class="row">
<div class="col-lg-12 text-blocks">
<div class="row">
<% number.times { f.object.text_blocks.where(upper_position: value).build } unless f.object.text_blocks.where(upper_position: value).any? %>
<%= f.fields_for :text_blocks do |input| %>
<%- if input.object.upper_position == value %>
<div class="<%= "col-lg-#{cells(number)}" %>">
<%= render 'shared/admin/form-fields/text-blocks-fields', f: input, value: value %>
</div>
<% end %>
<% end %>
</div>
</div>
</div>
</div>
If you always have 5 blocks (3 on top and 2 below), I would also consider adding a position or order column, allowing the textblocks to be re-rendered in the correct position after save as well.

Related

Conditional classNames & collection_check_boxes

In this example I am displaying all possible Tags on a Post when creating or editing a Post. I want to change the background-color to indicate if an item is checked or not, so I can remove the checkbox. You can see my failed attempt where I conditionally try to change the background-color in the class attribute of tag_item.label.
How can I check if tag_item is checked?
<div class="flex flex-wrap gap-3">
<%= form.collection_check_boxes :tag_ids, Tag.all, :id, :name do |tag_item| %>
<%= tag_item.label class: "#{tag_item.check_box ? "bg-red-500" : "bg-gray-100"} select-none border2 border-gray-100 flex w-auto p-2 text-sm cursor-pointer text-center" %>
<%= tag_item.check_box do |tag_item_checkbox| %>
<p> <%= tag_item_checkbox.inspect %> </p>
<% end %>
<% end %>
</div>
So, where you're doing the tag_item.check_box ? "bg-red-500" : "bg-gray-100", that tag_item.check_box needs to really be checking whether the tag_item of this iteration is set on the post model.
You might already have a way of referring to the given post (an #post or post local), or you can refer to it in a form context with form.object, so then you need to check whether the current tag_item is in the post's tags, eg:
<%= tag_item.label class: "#{ post.tags.include?(tag_item) ? "bg-red-500" : "bg-gray-100" } other classes" ...etc
Make sense?
So Im a new Rails developer but solved the problem my way, forgive me that I could not find the "rails way" to this problem. But anyways - it works like a charm.
tag_item.check_box.inspect["checked"] is returning if the tag is applied to the post (in edit view). The toggle(tag_item_id) function is therefore only for setting the background-color client-side as clollection_check_boxes and tag_item.check_box are handling the checked-status and the data when the form get submitted.
...
<div class="flex flex-wrap gap-3">
<%= form.collection_check_boxes :tag_ids, Tag.all, :id, :name do |tag_item| %>
<div id="post_tag_id_<%= tag_item.object.id %>">
<%= tag_item.label class: "#{tag_item.check_box.inspect["checked"] ? "bg-red-500" : "bg-gray-100"} select-none border2 border-gray-100 flex w-auto p-2 text-sm cursor-pointer text-center" %>
<%= tag_item.check_box class: "hidden", onclick: "toggle(#{tag_item.object.id})" %>
</div>
<% end %>
</div>
...
<script>
function toggle(tag_item_id) {
const label = document.querySelector("#post_tag_id_" + tag_item_id + " > label");
const checkbox = document.getElementById("post_tag_ids_" + tag_item_id);
if (!checkbox.checked) {
label.classList.remove("bg-red-500");
label.classList.add("bg-gray-100");
} else {
label.classList.remove("bg-gray-100");
label.classList.add("bg-red-500");
}
}
</script>

editing multiple child records while accessing values of other attributes of said record

For class
class Product
has_many :productunits
accepts_nested_attributes_for :productunits
class Productunit
belongs_to :product
belongs_to :unit
validates :product_id, presence: true
validates :unit_id, presence: true
the following form is meant only to update existing records (of what is effectively a join table), while formatting the fields in columns (one column per child) and effecting some view logic on whether the field should be shown or not.
<div class='grid-x grid-margin-x'>
<%= f.fields_for :productunits do |price_fields| %>
<div class='cell small-2 text-right'>
<h4><%# productunit.unit.name %> </h4>
<%# if productunit.unit.capacity == 2 %
2 <%= label %> <%= price_fields.number_field :price2 %>
<%# end %>
</div>
<% end %>
</div>
However a number problems are arising:
I cannot invoke the value of an attribute of record being edited (say productunit.unit.capacity)
The natural break in child records is not accessible to html tags for formatting (<div class='cell [...]). Worse, rails is throwing the child record id outside the div definition </div>
<input type="hidden" value="3" name="product[productunits_attributes][1][id]" id="product_productunits_attributes_1_id" />
<div class='cell small-2 text-right'>
submitting the form returns an error Productunits unit can't be blankwhich would be fair for a new record, but is definitely not expected when editing an existing one.
Unfortunately, the rails guide is thin in this regard.
I cannot invoke the value of an attribute of record being edited
You can get the object wrapped by a form builder or input builder though the object method:
<div class='grid-x grid-margin-x'>
<%= f.fields_for :productunits do |price_fields| %>
<div class='cell small-2 text-right'>
<h4><%= price_fields.object.unit.name %> </h4>
<% if price_fields.object.unit.capacity == 2 %
2 <%= price_fields.label :price2 %> <%= price_fields.number_field :price2 %>
<% end %>
</div>
<% end %>
</div>
2 The natural break in child records is not accessible to html tags
for formatting...
fields_for just iterates through the child records. Its just a loop. I'm guessing you just have broken html like a stray </div> tag or whatever you're doing with <%= label %>.
submitting the form returns an error Productunits unit can't be blankwhich would be fair for a new record, but is definitely not expected when editing an existing one.
You're not passing a id for the unit. Rails does not do this automatically. Either use a hidden input or the collection helpers.
<div class='grid-x grid-margin-x'>
<%= f.fields_for :productunits do |price_fields| %>
<div class='cell small-2 text-right'>
<h4><%= price_fields.object.unit.name %> </h4>
<% if price_fields.object.unit.capacity == 2 %
2 <%= price_fields.label :price2 %> <%= price_fields.number_field :price2 %>
<% end %>
<%= price_fields.collection_select(:unit_id, Unit.all, :id, :name) %>
# or
<%= price_fields.hidden_field(:unit_id) %>
</div>
<% end %>
</div>
On a side note you should name your model ProductUnit, your table product_units and use product_unit everywhere else. See the Ruby Style Guide.

Trying to collect a list of ids from checkboxes to delete from my database

I'm creating an app (personal project) that'll help us schedule and record Nerf tournaments for a group I hang out with. I have tournament and player models. A tournament can have multiple players.
I want to be able to check multiple players and choose to delete them from the tournament. The thing is though is that all of my players are rendered in a nested partial. And my tournament partial has a form already inside of it.
Here is _tournament.html.erb:
<section class="panel" id="<%= dom_id(tournament) %>">
<h1 class="heading">
<%= tournament.name %>
</h1>
<ul>
<%= render tournament.players %>
</ul>
<section class="add_players">
<%= form_for [tournament, Player.new] do |f| %>
<div class="form-group">
<%= f.text_field :name %>
</div>
<div class="form-group">
<%= f.text_field :team %>
</div>
<%= f.submit "Add Player", class="button blue" %>
<% end %>
</section>
<section class="delete_all">
<button class="button red" type="button">Delete Players</button>
</section>
</section>
and here is _player.html.erb:
<li id="<%= dom_id(player) %>">
<div class="player">
<%= check_box_tag "player_ids[]", player.id %>
<span class="name"><%= player.name %></span> |
<span class="team"><%= player.team %></span>
</div>
</li>
I'm familiar with Railscast #52 where he does a simpler example of this, but I can't figure out how to get all of those checked checkboxes in the tournament partial and use those ID's to delete those players from the tournament.
I've tried wrapping the _tournament.html.erb partial inside of a form_tag but it breaks some styling (and HTML doesn't really allow that as a standard.)
Any guidance or help would be great!
What you are doing is creating a form that posts to /tournaments/:tournament_id/players which makes sense if you are assigning the players one by one or creating a single player.
It does not make sense at all if you are assigning multiple players to a tournament. Because you are actually changing the parent resource.
In that case you should be sending a POST request to /tournaments (to create a tournament) or a PATCH request to /tournaments/:id (to modify an existing record).
<%= form_for tournament do |f| %>
<div class="form-group">
<%= f.label :player_ids, "Assign players" %>
<%= f.collection_check_boxes :player_ids, Player.all, :id, :name %>
</div>
<%= f.submit %>
<% end %>
class TournamentsController < ApplicationController
# ...
private
def tournament_params
params.require(:tournament)
.permit(:foo, :bar, player_ids: [])
end
end
If you want to be able to create multiple player records in a single request you should use nested attributes and fields_for.
To unassign all the players you can do a PATCH request to /tournaments/:id
with an empty array as the value for tournaments[:players_ids]. You can do this with javascript by creating a handler that unchecks all the checkboxes and submits the form when the user clicks the button (or confirms it).
Or you could create a custom DELETE /tournaments/:tournament_id/players route.
resources :tournaments do
resources :players
delete :players, on: :collection, action: :delete_all_players
end

Rails - conditional display of html block

On my views I use 1 form that includes a block that renders comments. I do not want to run it when creating a new record. So, I tried conditions like so...
<% unless #annotation_id.nil? %>
<hr>
<div class="row">
<div class="col-md-8">
<h4>Comments</h4>
<%= render #annotation.comments %>
</div>
<div class="col-md-4">
<%= render 'comments/form' %>
</div>
</div>
<% end %>
This however results in never displaying the block - also when the annotation record exists. What am I doing wrong?
You don't show that you have actually set #annotation_id to something.
A simpler way might be to use the .new_record? method instead, like:
<% unless #annotation.new_record? %>
...
<% end %>
use if #annotation.persisted? or unless #annotation.new_record?

HTML Snippets that repeat in the Rails views

I am using a bootstrapping framework, that has the following snippet, which I use a lot:
<div class="panel panel-default">
<div class="panel-heading">...</div>
<div class="panel-body">
...
</div>
</div>
(The snippet will be actually bigger, but I want to start small)
So I thought I would build a partial and use it, something like this:
Partial
<div class="panel panel-default">
<div class="panel-heading"><%= title %></div>
<div class="panel-body">
<%= yield %>
</div>
</div>
Use example
<%= render partial: "panel", locals: { title: "Hello" } do %>
Testing
<% end %>
But this apparently is not working. I get the following error:
'nil' is not an ActiveModel-compatible object. It must implement :to_partial_path.
Am I doing something wrong here? Did I understood partials wrong?
In order for yield to work I think you need to use render :layout instead of/in addition to :partial:
<%= render layout: "panel", locals: { title: "Hello" } do %>
Testing
<% end %>
Have a read of the PartialRenderer examples for more information

Resources