user1185081
user1185081

Reputation: 2118

How to pass variables from a controller to a model in Rails 5.2?

I created a controller an model to import an Excel sheet, and insert or update records in the database.

It works fine, and I now want to add a counter of inserts / updates to notify the user. I was expecting to do it using instance variables, but this does not work: the variables are not known inside the model.

Can you help me understand why and find a solution?

Here is the controller:

class ClassificationValuesImportsController < ApplicationController
  # Check for active session
  before_action :authenticate_user!

  def new
    @classification = Classification.find(params[:classification_id])
    @classification_values_import = ClassificationValuesImport.new
  end

  def create
    @update_counter = 0
    @insert_counter = 0
    @classification = Classification.find(params[:classification_id])
    @classification_values_import = ClassificationValuesImport.new(params[:classification_values_import])
    if @classification_values_import.file.nil?
      render :new, notice: t('FileNameCannotBeEmpty')
    end

    if @classification_values_import.save
      redirect_to @classification, notice: "#{t('ImportedObjects')}: #{@insert_counter} inserted, #{@update_counter} updated"
    else
      @classification_values_import.errors.full_messages.each do |msg|
        puts msg
        @classification.errors[:base] << msg
      end
      render :new
    end
  end


end

Here is the model:

class ClassificationValuesImport
  include ActiveModel::Model
  extend ActiveModel::Naming
  include ActiveModel::Conversion
  include ActiveModel::Validations

  attr_accessor :file, :parent_id

  def initialize(attributes = {})
    attributes.each { |name, value| send("#{name}=", value) }
  end

  def persisted?
    false
  end

  def save
    if imported_values_lists.map(&:valid?).all?
      imported_values_lists.each(&:save!)
      puts "///////////////////// Count of linked values_lists /////////////////////"
      print "Updates : "
      puts @update_counter
      print "Insert : "
      puts @insert_counter
      true
    else
      imported_values_lists.each_with_index do |column, index|
        column.errors.full_messages.each do |message|
          errors.add :base, "Row #{index+2}: #{message}"
        end
      end
      false
    end
  end

  def imported_values_lists
    @imported_values_lists ||= load_imported_values_lists
  end

  def load_imported_values_lists

    # Read input file
    spreadsheet = self.open_spreadsheet(file)
    puts spreadsheet.sheets
    puts spreadsheet.sheets[1]
    spreadsheet.default_sheet = spreadsheet.sheets.last
    header = spreadsheet.row(1)

    # Get the list of values lists to upload
    @classification = Classification.find(parent_id)
    playground_id = @classification.playground_id
    values_lists = Array.new
    @classification.values_lists_classifications.order(:sort_order).each do |link|
      values_lists[link.sort_order] = link.values_list_id
    end
    header = spreadsheet.row(1)

    # Upload or update values for each values list
    (2..spreadsheet.last_row).map do |i|
      # Read column indexes
      code = header.index("Code") +1
      parent = header.index("Parent") +1
      level = header.index("Level") +1
      valid_from = header.index("Valid from") +1
      valid_to = header.index("Valid to") +1
      name = header.index("Name_en") +1

      if record = Value.find_by(values_list_id: values_lists[spreadsheet.cell(i,level).to_i], code: spreadsheet.cell(i,code))
        @update_counter += 1
      else record = Value.new(playground_id: playground_id, values_list_id: values_lists[spreadsheet.cell(i,level).to_i], code: spreadsheet.cell(i,code))
        @insert_counter += 1
      end
      #record.active_from = spreadsheet.cell(i,'Valid from')
      #record.active_to = spreadsheet.cell(i,'Valid to')
      record.name = spreadsheet.cell(i,name)
      record.description = 'HCL upload test'
      record.created_at = record.created_at || Time.now
      record.updated_at = record.updated_at || Time.now
      #record.anything = { 'Comment' => 'This is a test', 'Author' => 'Fred'}.to_json
      ### create first translation for current_user if data is available
      #record.name_translations.build(field_name: 'name', language: 'en', translation: record.name) unless record.name.blank?
      #record.description_translations.build(field_name: 'description', language: language, translation: record.description) unless record.description.blank?
      puts "test"
      puts record.attributes
      record
    end
  end

  def open_spreadsheet(file)
    case File.extname(file.original_filename)
    when ".csv" then Roo::CSV.new(file.path, csv_options: {col_sep: ";"})
#    when ".xls" then Roo::Excel.new(file.path, nil, :ignore)
    when ".xlsx" then Roo::Excelx.new(file.path)
    else raise "Unknown file type: #{file.original_filename}"
    end
  end
end

Upvotes: 0

Views: 49

Answers (1)

felipeecst
felipeecst

Reputation: 1415

You can define transient attributes in your model:

attr_accessor :update_counter, :insert_counter

... and them set and access the values both from the controller and the model, as a regular attribute:

@classification.update_counter = 0
@classification.insert_counter = 0

You should be careful, however, as this approach can make it harder to debug where the values are coming from.

I believe the best thing to do would be to move the entire logic to the model, handling update_counter and insert_counter as instance variables, and then expose a public getter for them. You could use attr_reader :update_counter, :insert_counter instead of attr_accessor in this case. Then, you can use @classification.update_counter to obtain the value in the controller.

If the model needs any information from the controller, you could pass them as arguments to the save method, and pass them forward as needed.

Upvotes: 1

Related Questions