-1

I have a model association between two models Listing & Pricing. These are associated through Listing_Pricings model on a many to many basis.

If a listing is created the user can select through a check box field one of three pricing options which I intend to have saved in a seperate model Listing_Pricings. When a user selects the pricing option I can see it in the params hash but it fails to save in my database model.

Help please

My controller

class ListingsController < ApplicationController

    protect_from_forgery except: [:upload_photo]
    before_action :authenticate_user!, except: [:show]
    before_action :set_listing, except: [:new, :create]
    before_action :set_step, only: [:update, :edit]
    before_action :is_authorised, only: [:edit, :update, :upload_photo, :delete_photo]
    before_action :set_category, only: [:new, :edit, :show]

    def new
        @listing = current_user.listings.new
        @listing.listing_pricings
        @urgencies = Urgency.all
    end

    def create
      @listing = current_user.listings.new listing_params
      @listing.listing_pricings.first.listing_id = current_listing.id

      if @listing.save
        redirect_to edit_listing_path(@listing), notice: "Save..."
      else
        redirect_to request.referrer, flash: { error: @listing.errors.full_messages}
      end
  end

    def edit
      @urgencies = Urgency.all

      @listing = Listing.find(params[:id])

      @listing_category = @listing.category
      @category_pricings = @listing_category.pricings.all

      @listing_price = @listing.listing_pricings
    end

    def update

      @listing = Listing.find(params[:id])
      @listing_category = @listing.category

      #@category_pricings = @listing_category.pricings.all
      #@category = Category.find(params[:id])

      @category_pricings = @listing_category.pricings.paginate(page: params[:page], per_page: 5)



      @listing_pricing  = @listing.listing_pricings
      #@listing_price = @listing_pricing.first.pricing
      @listing_price = @listing.listing_pricings.build

      @urgencies = Urgency.all



      if @step == 2 && @listing.listing_pricings.each do |pricing|
        if @listing.has_single_pricing && !pricing.bronze?
          next;
        else
          if pricing[:listing_id].blank?
            #|| pricing[:description].blank? || pricing[:complete_by_date].blank? || pricing[:price].blank?
            return redirect_to request.referrer, flash: {error: "Invalid Pricing"}
            end
          end
        end
      end

      if @step == 3 && listing_params[:description].blank?
        return redirect_to request.referrer, flash: {error: "Description cannot be blank"}
      end

      if @step == 4 && @listing.photos.blank?
        return redirect_to request.referrer, flash: {error: "You don't have any photos"}
      end

      if @step == 5
        @listing_category_pricings.each do |pricing|
          if @listing.has_single_pricing || !pricing.bronze? || !pricing.silver? || !pricing.gold? || !pricing.platinum?
            next;
          else
            if pricing[:overview].blank? || pricing[:description].blank? || pricing[:complete_by_date].blank? || pricing[:price].blank?
              return redirect_to edit_listing_path(@listing, step: 2), flash: {error: "Invalid pricing"}
            end
          end
        end

        if @listing.description.blank?
          return redirect_to edit_listing_path(@listing, step: 3), flash: {error: "Description cannot be blank"}
        elsif @listing.photos.blank?
          return redirect_to edit_listing_path(@listing, step: 4), flash: {error: "You don't have any photos"}
        end
      end

      if @listing.update(listing_params)
        flash[:notice] = "Saved..."
      else
        return redirect_to request.referrer, flash: {error: @listing.errors.full_messages}
      end

      if @step < 5
        redirect_to edit_listing_path(@listing, step: @step + 1)
      else
        redirect_to users_dashboard_path
      end
    end

    def show
      @listing = Listing.find(params[:id])
      @listing_category = @listing.category
      @listing_category_pricings = @listing_category.pricings.all
      @urgencies = Urgency.all
    end

    def upload_photo
      @listing.photos.attach(params[:file])
      render json: { success: true}
    end


    def delete_photo
      @image = ActiveStorage::Attachment.find(params[:photo_id])
      @image.purge
      redirect_to edit_listing_path(@listing, step: 4)
    end

    def set_pricing_id
      Listing.update_all({pricing_id: true}, {id: params[:listing_id]} )

    end


    private

    def set_step
      @step = params[:step].to_i > 0 ? params[:step].to_i : 1
      if @step > 5
        @step = 5
      end
    end

    def set_category
      @categories = Category.all
    end

    def set_listing
      @listing = Listing.find(params[:id])
    end

    def is_authorised
      redirect_to root_path, alert: "You do not have permission" unless current_user.id == @listing.user_id
    end

    def listing_params
      params.require(:listing).permit(:title, :video, :description, :active, :category_id, :budget, :urgency_id, :has_single_pricing,:pricing_id)
    end

    def category_params
      params.require(:category).permit(:name)
    end

  end

Listing.rb

class Listing < ApplicationRecord
  belongs_to :user
  belongs_to :category
  belongs_to :urgency




  has_many :listing_pricings, dependent: :destroy
  has_many :pricings, through: :listing_pricings


  has_many_attached :photos
  has_rich_text :description

  validates :title, presence: { message: 'cannot be blank' }



  #has_many :pricings
  #accepts_nested_attributes_for :pricings
  #has_many :listing_categories
end

Pricings.rb

class Pricing < ApplicationRecord

  belongs_to :category, optional: false

  has_many :listing_pricings
  has_many :listings, through: :listing_pricings

  enum pricing_type: [:bronze, :silver, :gold, :platinum]
end

ListingPricing.rb

class ListingPricing < ApplicationRecord

  belongs_to :listing, optional: true, dependent: :destroy

  belongs_to :pricing, optional: true, dependent: :destroy
end

My View

<div class="step-content <%= 'is-active' if @step == 2 %>">
    <div class="field">
        <div class="control">

              <div class="tile is-ancestor">
                            <% @category_pricings.each do |cp| %>

                              <div class="tile is-parent">
                                <article class="tile is-child box">
                                        <div class="subtitle"><%= "#{cp.overview}" %></div>
                                        <div class="content"><%= "Deposit payable: £#{cp.price}" %></div>
                                        <div class="content"><%= "Time to complete: #{pluralize(cp.complete_by_date, 'Day')}" %></div>
                                        <tr valign="bottom"><div class="content"><%= "#{cp.description}" %></div></tr>
                                        <tr valign="bottom"><div class="content"><%= "#{cp.id}" %></div></tr>

                                    <%= f.fields_for :listing_pricings do |lp| %>
                                      <%= hidden_field_tag "pricing_id[]", cp.id %>
                                      <div class="form-group">
                                        <%= lp.check_box_tag :pricing_id, cp.id %>
                                      </div>
                                    <% end %>

                                </article>
                              </div>
                            <% end %>
              </div>
      </div>
  </div>
</div>

My error trace says PG::NotNullViolation: ERROR: null value in column "pricing_id" violates not-null constraint DETAIL: Failing row contains (16, 2, null, 2019-12-13 17:51:50.722906, 2019-12-13 17:51:50.722906).

Any thoughts guys ?

Parameters:

{"utf8"=>"✓",
 "_method"=>"patch",
 "authenticity_token"=>"tOnX6Q5YjHgXn3Xk5Wh2NioPfLrziiPVwyHkLF8BBFOjuWHdM1w8A7AdGpHdFGR3n+zlFsN2B/3IOMenXU1daA==",
 "step"=>"2",
 "listing"=>{"title"=>"Please clean my home", "category_id"=>"3", "urgency_id"=>"10", "has_single_pricing"=>"0", "description"=>"<div>This is my second listing&nbsp;</div>", "video"=>""},
 "pricing_id"=>"1",
 "commit"=>"Save & Continue",
 "id"=>"2"}

enter image description here

project_kingz
  • 229
  • 3
  • 14
  • Yeah, just use `pricing_ids=` instead of `accepts_nested_attributes`. I can't find a good dupe target but the same question is asked pretty much daily. https://stackoverflow.com/questions/59270108/option-from-collection-select-creates-a-new-one-on-submit-rails-5/59271269#59271269 – max Dec 13 '19 at 18:26
  • That question is a about a HABTM association but the same applies to `has_many through:`. To just assign an associations (create rows in the join table) just pass an array of ids. `Listing.new(pricing_ids: [1,2,3])`. Thats what the form collection helpers do. You don't need accepts nested attributes or fields_for. All you need is the select/checkboxes on the form and `permit(:foo, :bar, pricing_ids: [])` in your params whitelist. – max Dec 13 '19 at 18:31
  • Will not solve the problem, but you do this `accepts_nested_attributes_for :listing_pricings, allow_destroy: true, reject_if: proc { |att| att['name'].blank? }` and you didn't white list `name` in `params.require(:listing).permit(:title, :video, :description, :active, :category_id, :budget, :urgency_id, :has_single_pricing, listing_pricings_attributes: [:id, :listing_id, :pricing_id])` – Tun Dec 13 '19 at 18:41
  • @Tun thanks for your response but this solution doesnt create a successful update to my ListingPricing model – project_kingz Dec 15 '19 at 19:58
  • Also thanks @max , I have also tried your suggestion but no luck . I have added the error I am still getting – project_kingz Dec 15 '19 at 20:05

1 Answers1

0

I do this for one of my project. I replace my models with Pricing, Listing and ListPricing for you.

In your model, you have to have dependent: :destroy on has_many ... through relationship (otherwise, unchecking checkbox will not work).

# app/model/listing.rb
class Listing < ApplicationRecord
  has_many :listing_pricings
  has_many :pricings, through: :listing_pricings, dependent: :destroy

  # your code
end

In your view,

- Pricing.all.each do |p|
  = f.check_box :pricing_ids, { multiple: true }, p.id, false
  = f.label :pricing_id, p.something
end

In listing controller, make sure you permit :pricing_ids in listing controller

# app/controllers/listings_controller.rb

def listing_param
  params.require(:listing).permit(
    # other params you permit,
    pricing_ids: []
  )
end
Tun
  • 1,345
  • 13
  • 34
  • hello @Tun thanks so much for your input on this . Unfortunately your solution doesnt seem to work for me . I still get a violation were my secondary model data just isnt being saved . I have added the error debug – project_kingz Dec 15 '19 at 19:54
  • I don't see `pricing_ids` in parameter of your (updated?) question. – Tun Dec 16 '19 at 04:16
  • I have tried added :pricing_ids in my params whitelist. I strangely get Unpermitted parameter: :pricing_ids and dont understand why – project_kingz Dec 17 '19 at 19:45