Search code examples
rubyruby-on-rails-5friendly-id

Conditionally reset slug using friendly_id gem


My app assigns slugs in the usual way using the friendly_id gem:

class Organization < ApplicationRecord
  extend FriendlyId
  friendly_id :name, use: :slugged
  strip_attributes
end

When a user changes the organization.name, by default the slug does not change. But I want to give the user the option to reset the slug to match the name.

From the console, this is simple:

>> organization.update(slug: nil)

Friendly_id jumps in with a before_validate hook and generates a new slug. But if I try setting slug to nil using the OrganizationsController#update method, it doesn't work:

Started PATCH "/organizations/slo-mo-100?organization%5Bslug%5D=" for 127.0.0.1 at 2017-10-10 16:37:30 -0600
Processing by OrganizationsController#update as HTML
  Parameters: {"organization"=>{"slug"=>""}, "id"=>"slo-mo-100"}
  Organization Load (0.5ms)  SELECT  "organizations".* FROM "organizations" WHERE "organizations"."slug" = $1 ORDER BY "organizations"."id" ASC LIMIT $2  [["slug", "slo-mo-100"], ["LIMIT", 1]]
   (0.3ms)  BEGIN
  SQL (4.1ms)  UPDATE "organizations" SET "slug" = $1, "updated_at" = $2 WHERE "organizations"."id" = $3  [["slug", nil], ["updated_at", "2017-10-10 22:37:30.077956"], ["id", 1]]
   (0.2ms)  ROLLBACK
Completed 500 Internal Server Error in 24ms (ActiveRecord: 6.2ms)

PG::NotNullViolation - ERROR:  null value in column "slug" violates not-null constraint

I'd like the #update action to act just like the console does, that is, to assign a new slug when the incoming value is set to nil.


Solution

  • It turns out the solution is quite simple. The problem is that the update parameters come through with slug set to an empty string ({"slug"=>""}) instead of nil. I am using the strip_attributes gem to convert empty strings to nil, but strip_attributes wasn't getting called until after friendly_id had already checked to see if the slug field was nil.

    I solved the problem by switching the order of the callbacks to fire strip_attributes before friendly_id:

    class Organization < ApplicationRecord
      extend FriendlyId
      strip_attributes
      friendly_id :name, use: :slugged
    end