RAILS How can I build a nested form to change a has_many through associated object boolean attribute using checkboxes?

495 views Asked by At

I have 3 Models: User, Profile and Photo.

Profile < ApplicationRecord
 belongs_to :user
 has_many :photos, through: :user

Photo < ApplicationRecord
 belongs to :user

User < ApplicationRecord
 has_one :profile
 has_many :photos

I'd like to build a form for @profile that also displays checkboxes for all of its associated photos.

When a photo is checked, I'd like the that photo's #featured_status to be turned to be TRUE. (it has a default value of 1 in my database).

Photo class has these methods

class Photo < ApplicationRecord
 belongs_to :user


 has_attached_file :image, styles: { medium: "300x300>", thumb: "100x100>" }, default_url: "/images/:style/missing.png"
 validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/
 validates :image, :title, :description, presence: true

 FEATURED_STATUS = { not_featured: 0, featured: 1 }

 def featured?
  self.featured_status == FEATURED_STATUS[:featured]
 end

 def not_featured?
  self.featured_status == FEATURED_STATUS[:not_featured]
 end

 def myPhoto
  ActionController::Base.helpers.image_tag("#{image.url(:thumb)}")
 end

end

How can I build this form?

I've tried different variations of using fields_for, collection_check_boxes, check_boxes and I can't seem to capture the information correctly.

At the moment this is my form.

<%= simple_form_for @profile do |f| %>

<%= f.input :bio, input_html: { class: 'form-control'} %>

    <section class="main">
        <label>Featured Profile Photos</label>
        <% @profile.photos.each do |photo| %>
            <%= form_for ([@profile, photo]) do |f| %>
                <%= f.check_box :featured_status, :checked => (true if photo.featured?) %>
                <%= f.submit %>
                <% end %>
            <label><%= photo.myPhoto %></label>
        <% end %>
    </section>

<%= f.button :submit %>

When the form renders, there are multiple "update" buttons - one for each photo. I'm also not able to submit the @profile.bio changes at the same time as updating a photo's featured_status.

Ideally, I'd like to have each of those photo update buttons to be hidden and just have one submit button that updates the Profile bio:text and renders the @profile.

At the same time, I'd like the photo.featured_status to be turned to true/false as soon as the checkbox is marked. (Maybe using Javascript?)

Any suggestions are really appreciated.

2

There are 2 answers

1
Maxence On

as per your last comments I would design my relationship like this :

User
has_one :profile
has_many :photos

Profile
belongs_to :user

Photo
belongs to :user 

Now you can decide where you want to put the photo display on/off editing. Either in Users Edit action, alongside other User data, or even Profile data. Or maybe by creating a specific action in the Photo controller (the edit action is usually for editing a single instance, not all instances for a specific user, which should amount to a few)

Then if you have actual code and views, you can go further.

8
Jay-Ar Polidario On

app/models/profile.rb:

class Profile < ApplicationRecord
  has_one :user
  has_many :photos, through: :user
  accepts_nested_attributes_for :photos
end

app/controllers/profiles_controller.rb:

class ProfilesController < ApplicationController
  before_action :set_profile, only: [:update] # etc...

  def update
    if @profile.update(profile_params)
      # success, do something; i.e:
      # redirect_to @profile, notice: 'Profile has been updated'
    else
      # validation errors, do something; i.e:
      # render :edit
    end
  end

  private

  def set_profile
    @profile = Profile.find(params[:id])
  end

  def profile_params
    params.require(:profile).permit(:bio, photos_attributes: [:id, :featured_status]) # etc...
  end
end

app/views/profiles/_form.html.erb

<%= simple_form_for @profile do |f| %>
  <%= f.input :bio, input_html: { class: 'form-control'} %>

  <section class="main">
    <label>Featured Profile Photos</label>

    <%= f.simple_fields_for :photos do |ff| %>
      <%= ff.check_box :featured_status %>
      <label><%= ff.object.myPhoto %></label>
    <% end %>
  </section>

  <%= f.button :submit %>
<% end %>

Recommendations:

  • Similar with the other comments from Maxence and iGian, I would advise something like below:

    • User has_many / has_one Profiles
    • Profile has_many Photos

    ... instead of:

    • Profile has_one User
    • Profile has_many Photos

    ... for the sole reason being that intuitively speaking, I think a User (someone who logs in) can have one or many Profiles, of which then a Profile should belong to a User. Currently you have a Profile has_one User, which is always gonna work either way where the foreign_key is gonna be (either User or Profile is ok), but what I usually do to know where the foreign_key is placed is is to take into account a possible future change: (i.e. has_one becomes has_many), and therefore now that I established this, it makes more sense for a User to have many Profiles, instead of a Profile to have many Users. Of course, if you intentionally want it this way and aware of your schema implementations, then it's always also a correct way to do it (depending on your prerequisites) :)

References: