0

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.

Kim
  • 21
  • 5
  • Can you give more details about the association between models? One is missing. Are you referring to something similar to http://edgeguides.rubyonrails.org/association_basics.html#the-has-many-through-association? – iGian Jun 30 '18 at 21:59
  • Hi yes! Thats the same association. The User Model has_many photos and has_one profile. – Kim Jun 30 '18 at 22:30
  • Why have you declared that `Photo has_one profile` ? Photo is already the child of profile.. It can't really be both the parent and the child. (well it can but you will see that later, especially for Comment models where comment can be the parent of another comment but also a child of a comment, but it needs specific wording) – Maxence Jun 30 '18 at 22:34
  • It seems something is wrong with your code if `User` is the join table and you are using the `has_many :through` Association. – iGian Jun 30 '18 at 22:35
  • 1
    @iGian is correct: in the edgeguide `Appointments` are the children of both `Patients` and `Physicians`. In your app design. one `Photo` can probably belong to a `Profile`and a `User`, though `User` already belong to `Profile`. `Users` and `profiles` are not 2 distinct entities like in the guide but are already related in your design. Please explain what are Users and Profiles, their difference and we will try to tell you the right relationships – Maxence Jun 30 '18 at 22:45
  • I think I understand what you mean, would it be correct if I changed my Photo association to `class Photo has_one :profile, inverse_of :photo`? I tried removing the has_one and received an error when calling `photo.profile`. Adding the inverse_of option seemed to relieve that. – Kim Jun 30 '18 at 22:55
  • Just think about associations as an ancestry tree. I am not sure `Photo` should own anything. The Photo is something that belongs to a profile, a user, a product, a company... I am not sure it should own any of them. – Maxence Jun 30 '18 at 22:58
  • I wanted to exclusively have a Profile Model. Unlike the User Model - which is soley used to login and post Photos, the Profile model has a bio:string attribute and photos(by User inheritance). Photo Model has attributes of title:string, description:text, image(uploaded file) and a featured_status:integer (default of 1). I wanted the Profile Edit Form to have inputs to change the profile.bio AND have checkboxes next to each associated Photo of that profile. When someone checks/unchecks the photo, I'd like the Photo's featured_status to be toggled to 0/1. – Kim Jun 30 '18 at 23:02
  • Also, thank you Maxence, I took out the has_one in Photo and simply called photo.user.profile instead – Kim Jun 30 '18 at 23:10

2 Answers2

0

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.

Maxence
  • 2,029
  • 4
  • 18
  • 37
  • Thanks Maxence, I've added some additional code in my original question above. Please let me know if you have any suggestions. I appreciate you taking the time! The profile.photos relationship is to fulfill a project requirement for school. I needed a nested form – Kim Jul 01 '18 at 00:20
0

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:

Jay-Ar Polidario
  • 6,463
  • 14
  • 28
  • I just noticed that you are using Profile has_one User, but at the same time User has_one Profile. Only one of them should be has_one, while the other should be belongs_to, and not both has_one each other. I do not know where you put the foreign_key, i.e. do you have a `user_id` column in profiles table? (then it should be Profile belongs_to :user), or... do yo have a `profile_id` column in users table? (then it should be User belongs_to :profile. Now, my solution above assumes that you have a `profile_id`column in users table. – Jay-Ar Polidario Jul 01 '18 at 01:34
  • My mistake! Yes I've implemented Profile belongs to one user. I've been staring at my code a while so my brain is melting. My foreign key is on the Profile model. – Kim Jul 01 '18 at 02:54
  • I see, since it's Profile belongs_to user, I think my solution above would still work because I just looked at this [SO post](https://stackoverflow.com/questions/7365895/does-accepts-nested-attributes-for-work-with-belongs-to), because seems like nested attributes also work for belongs_to at Rails >= 4. If not let me know, I'll try to test this out myself when I get the time. – Jay-Ar Polidario Jul 01 '18 at 03:34
  • Hi Jay! Thank you a million times for these recommendations, I've been trying to work on this one feature for a few days now. I've implemented your recommendations for the form along with the profile controller to a T. The form seems perfect for what I wanted to be rendered. However, on submit, I am getting an error and the photo changes are not being saved. – Kim Jul 01 '18 at 04:24
  • The full message errors are: `photos.user must exist, photos.image can't be blank, photos.title can't be blank` . These are my singular photo validations. I'm not positive, but it seems that the form is passing a whole collection of photos to the method (photos.user instead of photo.user) rather than an individual one...? Do you know how I can fix this? Does it have something to do with my Profile Photo relationship? – Kim Jul 01 '18 at 04:24
  • the source code is here on github if it helps. thank you so much Jay-Ar https://github.com/KimGonzales/inscape/tree/featured-photos – Kim Jul 01 '18 at 04:28
  • @Kim I just sent you a [Git Pull Request on your project](https://github.com/KimGonzales/inscape/pull/1). Click the [Files Changed] tab there, and you'll see the changes. Mainly only that I forgot to add `:id` into the `photos_attributes` above. I tested this working but you'll need to add / merge with your current local code, because I assumed your local code already have the featured_status column in photos table, because your remote master branch currently doesnt still have it :) Let me know if you still encounter problems. – Jay-Ar Polidario Jul 01 '18 at 05:01
  • Jay-Ar you are the bomb. Thank you so much, this worked beautifully. – Kim Jul 02 '18 at 16:26