Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add slugify field option to slug field #155

Open
treagod opened this issue Feb 5, 2024 · 4 comments
Open

Add slugify field option to slug field #155

treagod opened this issue Feb 5, 2024 · 4 comments

Comments

@treagod
Copy link
Contributor

treagod commented Feb 5, 2024

Description

Let's enhance the functionality of the slug field in Marten by introducing a new optional slugify option. This option will allow developers to specify another field whose value should be slugified to generate the slug automatically.

Background

Currently, when defining a slug field in Marten, developers manually set the value of the slug field based on the value of another field, typically by applying a slugification function to that field's value. This process can be repetitive and error-prone, especially when dealing with multiple entities with similar slug generation requirements.

So let's add a slugify option to the slug field configuration, which accepts a string or symbol representing the name of the field whose value should be used to generate the slug. When this option is specified, Marten will automatically generate the slug based on the value of the specified field.

Functionality

The slugify option will be added to the configuration of the slug field in Marten entity definitions.
It will accept a string or symbol representing the name of the field whose value should be slugified.
Upon saving a record, Marten will automatically generate the slug by applying slugification to the value of the specified field.
If the value of the specified field changes, Marten will update the corresponding slug accordingly.

Example Usage:

class Article < Marten::Model
  field :id, :big_int, primary_key: true, auto: true
  field :title, String
  field :slug, :slug, slugify: :title
end

In this example, the value of the title field will be used to generate the slug for the slug field automatically.

Considerations

A way to let the developer define his own slugify function

@ellmetha
Copy link
Member

ellmetha commented Feb 6, 2024

That would be a neat functionality! 👍

A few things that come to mind and that we should probably consider:

  1. Did you have something in mind regarding how to implement this? I guess generating a before_validation callback when the option is used could work.
  2. In terms of slug generation, I think we should try to avoid adding a dependency just for that and implement a Slugifiable concern (or similar) in the framework directly if possible.

@treagod
Copy link
Contributor Author

treagod commented Feb 7, 2024

I think I was a little too quick in writing the issue. I'm no longer quite sure to what extent we can generate the slug automatically. I'll give it some thought, but I'd leave the issue here with the "Discussion" label for now so that we don't lose sight of it. A standard way to generate the slug would be neat in any case

@treagod
Copy link
Contributor Author

treagod commented Mar 12, 2024

So my solution was to create a concern Sluggable:

module Sluggable
  macro included
    macro slugify(from_field, slug_field, *callbacks)
      \{% if callbacks.size == 0 %}
      before_validation :generate_slug
      \{% else %}
      \{% for callback in callbacks %}
      \{{callback.id}} :generate_slug
      \{% end %}
      \{% end %}

      private def generate_slug
          # generate slug logic
      end
    end
  end
end

which is used as following:

require "./concerns/*"

class Article < Marten::Model
  include Sluggable

  slugify :title, :url_slug
  # Or define which callbacks shall generate the slug
  # slugify :title, :url_slug, :before_create

  field :id, :big_int, primary_key: true, auto: true
  field :title, :string, max_size: 255
  field :url_slug, :slug 

  with_timestamp_fields
end

Is that a solution you could see inside the Marten Project, @ellmetha?

@ellmetha
Copy link
Member

@treagod I think I tend to prefer the approach in your first message where the field was responsible for defining from which other field the slug should be generated:

class Article < Marten::Model
  field :id, :big_int, primary_key: true, auto: true
  field :title, :string, max_size: 255
  field :slug, :slug, slugify: :title
end

The reason I prefer this approach is that it allows us to avoid introducing a new method to the model DSL and that way we keep things a bit simpler. When it comes to configuring how fields should behave, the idea is really to leverage the #field macro as much as possible. I believe your initial proposition aligns well with this design and offers a clear path forward for this particular use case.

In terms of implementation: the slug field could generate a before_validation callback as I was suggesting in my previous message, but I am realizing that we could even implement this without having to rely on macros at all (probably simpler). Indeed, the slug field abstraction could execute some logic in its #validate method in order to generate the slug value from the configured model field and assign the generated value to this field. Eg. something like:

class Marten::DB::Field::Slug
  # ...

  def validate(record, value)
    # Generate the slug value if applicable
    if !(source_field = slugify).nil? && value.nil?
      source_field_value = record.get_field_value(source_field)
      value = generate_slug(source_field_value)
      record.set_field_value(id, value)
    end

    return if !value.is_a?(::String)

    # Leverage string's built-in validations (max size).
    super

    if !value.empty? && !Core::Validator::Slug.valid?(value)
      record.errors.add(id, I18n.t("marten.db.field.slug.errors.invalid"))
    end
  end

  private def generate_slug(value)
    # Generate slug...
  end
end

Generating the slug is also something that could be done in the #prepare_save field method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

2 participants