Greg
Greg

Reputation: 2609

undefined method `before_save' for YearsController:Class Did you mean? before_action

Error: undefined method 'before_save' for YearsController:Class Did you mean? before_action

https://guides.rubyonrails.org/active_record_callbacks.html talks about "objects may be created, updated, and destroyed" and I want to interrupt the process if the image being added via ActiveStorage already exists to avoid duplicates. before_save is one of the available callbacks.

rails (6.0.0). I'm not experienced with ActiveRecord callbacks but the https://api.rubyonrails.org Active Record callbacks lists before_save, but not before_action. But no error with before_action. I'm not even sure when I need to activate the callback, but am working my way through what works. What callbacks are allowed? And not sure what before_action would be.

years_controller.rb

class YearsController < ApplicationController
  helper_method :sort_column, :sort_direction
  before_action :set_year, only: [:show, :edit, :update, :destroy] # 2019.06.20 Can't see that this is being used anywhere
  before_action :set_s3_direct_post, only: [:new, :edit, :create, :update]
# before_action :dup_check, only: [:new, :edit, :update] # runs, but not what I think I want
before_save :dup_check, only: [:new, :edit, :update] # undefined method `before_save' for YearsController:Class Did you mean?  before_action  
  PAGE_SIZE = 10

  def index
    @years = Year.order(sort_column + " " + sort_direction)

     respond_to do |format|
      format.html {}
      format.json { render json: @years }
    end
  end

  def documents
    @years = Year.all
  end

  def map_one  # mapping one connection/year
    # need to use the year passed in and then make it for a year
    years = Year.where( year_date: '1865-01-01' .. '1995-12-31' )
  end

  def search
    # Copeland except within the else
    @page = (params[:page] || 0).to_i
    if params[:keywords].present?
      @keywords = params[:keywords]
      year_search_term = YearSearchTerm.new(@keywords)
      @years = Year.where(
          year_search_term.where_clause,
          year_search_term.where_args).
        order(year_search_term.order).
        offset(PAGE_SIZE * @page).limit(PAGE_SIZE)
    else
      @years = Year.order(sort_column + " " + sort_direction)
    end
    respond_to do |format|
      format.html {}
      format.json { render json: @years }
    end
  end


  def summary
    @years = Year.order(:year_date)
    respond_to do |format|
      format.html {}
      format.json { render json: @years }
    end
  end

  def show
   end

  # GET /years/new
  def new
    # All from Tutorial Points Tutorial
    @year = Year.new({:year_date => "1900-09-01"}) # This is default. Helps to change when going through a group.
    @locations = Location.all
    @people    = Person.all
  end

  # GET /years/1/edit
  def edit
     @positions = Position.all # Needed for Positions/ Titles list to work, However not added to database
  end

  # POST /years
  # POST /years.json
  def create
    @year = Year.new(year_params)
    respond_to do |format|
      if @year.save
        format.html { redirect_to @year, notice: 'Connection was successfully created.' }
        format.json { render :show, status: :created, location: @year }
      else
        format.html { render :new }
        format.json { render json: @year.errors, status: :unprocessable_entity }
      end
    end
    repopulateResidResto()
  end

  # PATCH/PUT /years/1
  # PATCH/PUT /years/1.json
  def update
    respond_to do |format|
      if @year.update(year_params)
        format.html { redirect_to @year, notice: 'Connection was successfully updated.' }
        format.json { render :show, status: :ok, location: @year }
      else
        format.html { render :edit }
        format.json { render json: @year.errors, status: :unprocessable_entity }
      end
    end
    repopulateResidResto() # nice to have some error handling here
  end

  # DELETE /years/1
  # DELETE /years/1.json
  def destroy
    @year.destroy
    respond_to do |format|
      format.html { redirect_to years_url, notice: 'Connection was successfully destroyed.' }
      format.json { head :no_content }
    end
    repopulateResidResto()
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_year
      @year = Year.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def year_params
      params.require(:year).permit(:year_date, :person_id, :location_id, :resto, :resto_name, :resid, :title, :source, :source_url, :ref_image, :doc_image , :ref_url, :notes, :ref_url, :caption, documents: []) # document replaces _images, brackets for has many
     end

    def set_s3_direct_post
        @s3_direct_post = S3_BUCKET.presigned_post(key: "uploads/#{SecureRandom.uuid}/${filename}", success_action_status: '201', acl: 'public-read')
    end

    def dup_check
     # if active_storage_blobs(:checksum ) == an existing blob
     # let's first run a test seeing if a know checksum exists. That works. Now get the existing checksums     
     test_checksum = '0M4nc4nuUaVuqo3+sJw+Lg==' # will get for selected image at some point

     test_checksum == '0M4nc4nuUaVuqo3+sJw+Lg==' ? (puts "years_controller:158. test_checksum #{test_checksum} does exist") : (puts "years_controller:158. test_checksum #{test_checksum} does not exist") # Need the ()
      puts "years_controller:160. dup_check entered via before_action. before_save results in an errro"
    end

    # NOT CLEAR ANY OF THIS IS NEEDED. BUT PUT IT IN AGAIN WHEN CREATE DIDN'T WORK
    def person_params
      params.require(:person).permit(:last_name, :given_name, :full_name, :full_name_id)
    end

    def sort_column
      Year.column_names.include?(params[:sort]) ? params[:sort] : "year_date" # it does matter what this last field is.
    end

    def sort_direction
      %w[asc desc].include?(params[:direction]) ? params[:direction] : "asc"
    end

end

year.rb

class Year < ApplicationRecord
  belongs_to :location
  belongs_to :person
  has_many   :resto_resid_lines
  has_many_attached :documents # ActiveStorage
  has_rich_text :caption # ActionText 2019.12.05 not implemented because form needs major changes to work
  scope :with_eager_loaded_documents, -> { eager_load(documents_attachments: :blob) } 

  default_scope -> { order(:year_date) } 
  validates :person_id,   presence: true
  validates :location_id, presence: true 

  validates_presence_of :resto, {:if => :resto_name?, message: 'Click on Restaurant (above) since a restaurant name has been selected' } 
  validates :resto, :presence => { :if => :resto_name?, message: 'Select Restaurant since a restaurant name has been specified' } # is this the same as above, but not working?


  def self.search(search)
    where("year_date ILIKE ? OR resto ILIKE ? OR resto_name ILIKE ? OR resid ILIKE ? OR title ILIKE ? OR source ILIKE ? OR source_url ILIKE ? OR ref_url ILIKE ? OR notes ?", "%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%") 
  end

  def map_popup
    "#{person.given_name} #{person.last_name} was a #{title} at #{location.address} in #{year_date.to_formatted_s(:month_year)}"
  end

  def next
    Year.where("id > ? AND year_date = ?", id, year_date).first
  end

  def previous
    Year.where("id < ? AND year_date = ?", id, year_date).last
  end

  private

  def resid_xor_resid
    if [resto, resid].compact.count != 1
      errors.add(:base, "Select Residence or Restaurant, but  not both")
    end
  end

 end

Lots of cruft because this is my first app and I've been working at it for years.

Upvotes: 0

Views: 1025

Answers (1)

ARK
ARK

Reputation: 802

Good thing you said you will try to research yourself before coming back.

What you are missing is that some callbacks are specific to the type of class you are calling them into. I hope it is obvious to you by now that save is something particular to model and controller has nothing to with it (@record.save, remember?). On the other hand, before_action and after_action are the ones used in controllers as controllers have actions. Reference: Ruby-on-Rails guides

Solution:

  1. As duplicity is related to model, you should add before_save or before_create (deprecated, I guess) to model instead of second solution:
class Year < ApplicationRecord
  before_save :is_duplicate?

  private

  def is_duplicate
    return Year.where([the_attributes_that_make_it_duplicate]).present? # or something like that :P
    # throw :abort or return false (I guess you can't through in this case)
  end
end
  1. You can check for duplicity before action and redirect back if duplicate in controller like:
class YearsController < ApplicationController
  before_action :dup_check

  private

  def dup_check
    set_year
    redirect_to your_desired_path if Year.where([the_attributes_that_make_it_duplicate]).present?
  end
end

Upvotes: 0

Related Questions