2
votes

I have a subclassed ActiveRecord model which uses a separate table to store records and friendly_id (4.1.0.beta.1) to generate slugs. Problem is friendly_id is using the parent class's table to check for existing slugs, instead of using the child table. Basically I'd like friendly_id to scope its checks to the right table.

Example:

class Parent
  friendly_id :name, :use => :slugged
end

class Child < Parent
  self.table_name = 'children'
end

Parent.create(name: 'hello').slug
> 'hello'

Child.create(name: 'hello').slug
> 'hello--2'

I want friendly_id to generate the 'hello' slug for the second create, because there are no records in the children table with that slug. Is there a way to configure or monkey patch the class friendly id uses for its queries?

EDIT: added friendly_id version for future reference

1

1 Answers

1
votes

I'm posting my own solution to this problem, just in case someone is having the same problem. I should reiterate that this problem was found on version 4.1.0.beta.1 of the friendly_id gem (which at the time was the most recent version), so this issue may not occur any more.

To solve this problem, I basically configured slug_generator_class to use my own class, so I could monkey patch the culprit method.

In my model:

friendly_id do |config|
  config.slug_generator_class = SubclassScopableSlugGenerator
end

In an initializer, I overrode the FriendlyId::SlugGenerator.conflicts method so I could access the sluggable_class var:

# Lets a non-STI subclass of a FriendlyId parent (i.e. a subclass with its
# own dedicated table) have independent slug uniqueness.
class SubclassScopableSlugGenerator < FriendlyId::SlugGenerator
  private
  def conflicts
    # this is the only line we're actually changing
    sluggable_class = friendly_id_config.model_class

    pkey  = sluggable_class.primary_key
    value = sluggable.send pkey
    base = "#{column} = ? OR #{column} LIKE ?"
    # Awful hack for SQLite3, which does not pick up '\' as the escape character without this.
    base << "ESCAPE '\\'" if sluggable.connection.adapter_name =~ /sqlite/i
    scope = sluggable_class.unscoped.where(base, normalized, wildcard)
    scope = scope.where("#{pkey} <> ?", value) unless sluggable.new_record?

    length_command = "LENGTH"
    length_command = "LEN" if sluggable.connection.adapter_name =~ /sqlserver/i
    scope = scope.order("#{length_command}(#{column}) DESC, #{column} DESC")
  end
end