ForbiddenAttributesError в Rails 4

Я работаю над обновлением устаревшего приложения до рельсов 4, и я получаю необъяснимое (по крайней мере, мне) ForbiddenAttributesError.

У меня есть белый список параметров, прежде чем использовать их для создания нового экземпляра Station, но по какой-то причине я все еще получаю ForbiddenAttributesError. Есть ли способ получить дополнительную информацию о том, какой атрибут вызывает ошибку?


** ИЗМЕНИТЬ. Я использую Devise и CanCan. Если я удалю load_and_authorize_resource, ошибки исчезнут (и авторизация тоже!). Я пробовал решения на https://github.com/ryanb/cancan/issues/835 безрезультатно.


rspec spec/контроллеры/station_controller_spec.rb

  1) StationsController POST 'create' invalid should not create a new record
     Failure/Error: post :create, :station => { :name => 'foo' }
     ActiveModel::ForbiddenAttributesError:
       ActiveModel::ForbiddenAttributesError
     # ./spec/controllers/stations_controller_spec.rb:67:in `block (5 levels) in <top (required)>'
     # ./spec/controllers/stations_controller_spec.rb:66:in `block (4 levels) in <top (required)>'

  2) StationsController POST 'create' valid should create a new record
     Failure/Error: before { post :create, :station => FactoryGirl.attributes_for(:station, :hw_id => rand(1000)) }
     ActiveModel::ForbiddenAttributesError:
       ActiveModel::ForbiddenAttributesError
     # ./spec/controllers/stations_controller_spec.rb:73:in `block (4 levels) in <top (required)>'

  3) StationsController POST 'create' valid
     Failure/Error: before { post :create, :station => FactoryGirl.attributes_for(:station, :hw_id => rand(1000)) }
     ActiveModel::ForbiddenAttributesError:
       ActiveModel::ForbiddenAttributesError
     # ./spec/controllers/stations_controller_spec.rb:73:in `block (4 levels) in <top (required)>'

schema.rb

  create_table "stations", :force => true do |t|
    t.string   "name"
    t.string   "hw_id"
    t.float    "lat"
    t.float    "lon"
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
    t.string   "timezone"
    t.float    "balance"
    t.integer  "user_id"
    t.boolean  "down"
  end

station_controller.rb

      ...
      # POST /stations
      # POST /stations.xml
      def create
        @station = Station.new(station_params)
        @station.set_timezone!
        logger.debug("@station " + @station.inspect)
        respond_to do |format|
          if @station.save
            format.html { redirect_to(@station, :notice => 'Station was successfully created.') }
            format.xml  { render :xml => @station, :status => :created, :location => @station }
            format.yaml { render :status => :ok, :nothing => true }
          else
            # create users list if the station cannot be created
            @users = User.all_users_select(current_user)
            format.html { render :action => "new" }
            format.xml  { render :xml => @station.errors, :status => :unprocessable_entity }
            format.yaml { render :status => :unprocessable_entity, :nothing => true }
          end
        end
      end

      ...

      # whitelists params for mass assignment
      def station_params
        params.require(:station).permit(
            :user_id, :name, :hw_id, :measures, :lat, :lon, :authenticity_token, :commit
        )
      end

station_controller_spec.rb

      describe "POST 'create'" do
        describe "invalid" do
          it "should not create a new record" do
            expect do
              post :create, :station => { :name => 'foo' }
            end.to_not change(Station, :count)
          end
        end

        describe "valid" do
          before { post :create, :station => FactoryGirl.attributes_for(:station, :hw_id => rand(1000)) }
          it "should create a new record" do
            expect do
              post :create, :station => FactoryGirl.attributes_for(:station, :hw_id => rand(1000))
            end.to change(Station, :count)
          end
          subject { response }
          it { should redirect_to station_path(assigns(:station)) }
        end
      end

заводы/station.rb

    FactoryGirl.define do
      factory :station do
        name "Test Staton"
        hw_id Time.now.to_s
        lat 63.401839
        lon 13.072183
      end
      factory :invalid_station, class: Station do
        name nil
      end
    end

модели/station.rb

#include Geokit::Geocoders
class Station < ActiveRecord::Base
  #acts_as_mappable :default_units => :kms,
   #                  :lat_column_name => :lat,
   #                  :lng_column_name => :lon
  belongs_to :user
  has_many :measures, :dependent => :destroy
  has_one :current_measure, :class_name => "Measure"

  # arduino client has not memory enough to post the station name so it cannot be required!
  validates :hw_id, :presence => true # must have a hw_id
  validates :hw_id, :uniqueness => true # and the hw_id must be unique

  # Geocoder gem attribute mapping
  geocoded_by :name, :latitude  => :lat, :longitude => :lon

  def owned_by?(owner)
    user==owner
  end

  def calibrate_speed(speed)
    speed/250
  end

  def self.send_low_balance_alerts
    stations = Station.find(:all)
    stations.each do |station|
      logger.info "Checking station #{station.name}"
      logger.info "Last measure at #{station.current_measure.created_at}"
      if station.balance/100 < 15 && !station.down
        if !station.user.nil?
          logger.info "Send reminder to owner #{station.user}"
          UserMailer.notify_about_low_balance(station.user, station).deliver
        end
      end
    end
  end

  def self.send_down_alerts
    stations = Station.find(:all)
    stations.each do |station|
      logger.info "Checking station #{station.name}"
      logger.info "Last measure at #{station.current_measure.created_at}"
      if station.current_measure.created_at < 15.minutes.ago && !station.down
        station.down = true
        station.save
        if !station.user.nil?
          logger.info "Send reminder to owner #{station.user}"
          UserMailer.notify_about_station_down(station.user, station).deliver
        end
      end
    end
  end

  def self.get_timezone(lat, lon)
    timezone = Timezone::Zone.new :latlon => [lat, lon]
    timezone.active_support_time_zone
  end



  def set_timezone!
    if (!self.lat.nil? && !self.lon.nil?)
      self.timezone  = Station.get_timezone(self.lat, self.lon)
    end
  end

end

Ответ 1

Это была полностью проблема, когда CanCan несовместим с Rails 4 и не имеет ничего общего с моей моделью или контроллером Station.
Мое обходное решение было skip_load_resource для действия create.

before_filter :authenticate_user!, :except => [:show, :index]
load_and_authorize_resource :except => [:show, :index]
skip_load_resource :only => [:create]

Взрыв! Все стало зеленым.

ОБНОВЛЕНИЕ. Отъезд CanCanCan - это продолжение уже мертвого cancan и функции поддержки сильных параметров Rails 4 среди других вещи.

Ответ 2

Используется ли станция для пользователя? Я не думаю, что у вас должны быть сильные параметры для user_id. Пользователь user_id должен быть создан автоматически, как ваши модели настроены. Поэтому ваш сильный код params должен быть:

  def station_params
    params.require(:station).permit(
         :name, :hw_id, :measures, :lat, :lon, :authenticity_token, :commit #user_id taken out
    )
  end

Кроме того, вы хотели сделать hw_id строку? Обычно, когда что-то говорит id, это целое число. Кроме того, если hw - это модель, которая имеет некоторый тип отношения к станции, она также не должна быть в сильных параметрах.