7

Case:

My station forms contain a slug field, if a value is entered it should be used as the slug.

EDIT: some clarification:

What I want is much like how slugs work in wordpress:

  • If no slug is provided -> slug the name
  • If slug is provided -> use the user entered slug
  • If slug is updated -> push old slug to history

My problem:

Can´t figure out how to get Friendly Id to use the user provided slug.

class Station < ActiveRecord::Base
  extend FriendlyId
  belongs_to :user
  has_many  :measures
  validates_uniqueness_of :hw_id
  validates_presence_of :hw_id
  class_attribute :zone_class
  self.zone_class ||= Timezone::Zone
  friendly_id :name, :use => [:slugged, :history]

  before_save :set_timezone!

  ....

  def should_generate_new_friendly_id?
    name_changed? or slug_changed?
  end
end

edit:

<%= form_for(@station) do |f| %>

<%=
    f.div_field_with_label(:name) do |key|
      f.text_field(key)
    end
%>
<%=
    f.div_field_with_label(:slug) do |key|
      f.text_field(key)
    end
%>
<%=
    f.div_field_with_label(:hw_id, 'Hardware ID') do |key|
      f.text_field(key)
    end
%>
<%=
    f.div_field_with_label(:latitude) do |key|
      f.text_field(key)
    end
%>
<%=
    f.div_field_with_label(:longitude) do |key|
      f.text_field(key)
    end
%>
<%= f.div_field_with_label(:user_id, "Owner") do |key|
        f.select(:user_id, options_from_collection_for_select(User.all, :id, :email), { include_blank: true  })
    end
%>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %><%= form_for(@station) do |f| %>

<%=
    f.div_field_with_label(:name) do |key|
      f.text_field(key)
    end
%>
<%=
    f.div_field_with_label(:slug) do |key|
      f.text_field(key)
    end
%>
<%=
    f.div_field_with_label(:hw_id, 'Hardware ID') do |key|
      f.text_field(key)
    end
%>
<%=
    f.div_field_with_label(:latitude) do |key|
      f.text_field(key)
    end
%>
<%=
    f.div_field_with_label(:longitude) do |key|
      f.text_field(key)
    end
%>
<%= f.div_field_with_label(:user_id, "Owner") do |key|
        f.select(:user_id, options_from_collection_for_select(User.all, :id, :email), { include_blank: true  })
    end
%>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>
max
  • 96,212
  • 14
  • 104
  • 165
  • Can you show the form? Right now you are using the name for the friendly_id. All you would need to do is allow the user to change the name parameter. – ChrisBarthol Nov 14 '13 at 23:27
  • The user can change already change the name parameter - what I want is for the user to be able to customize the slug - but the app should generate a slug based on name if no slug is provided – max Nov 15 '13 at 03:07
  • I guess I'm confused as to what you are asking. The user can change the name, hence they can customize the slug. If you want to the slug generated from a different parameter you would just have `friendly_id :parameter, :use => [:slugged, :history]` – ChrisBarthol Nov 15 '13 at 16:36
  • I want to be able to edit the slug indepently of the name, consider this example: "Fried Green Tomatoes at the Whistle Stop Cafe" -> /green_tomatoes – max Nov 15 '13 at 18:56

2 Answers2

5

This is how i solved it:

class Station < ActiveRecord::Base
  extend FriendlyId
  belongs_to :user
  has_many  :measures
  validates_uniqueness_of :hw_id
  validates_presence_of :hw_id
  class_attribute :zone_class
  self.zone_class ||= Timezone::Zone
  friendly_id :name, :use => [:slugged, :history]
  before_save :evaluate_slug
  before_save :set_timezone!


  def should_generate_new_friendly_id?
    if !slug?
      name_changed?
    else
      false
    end
  end

end

And the tests:

/spec/models/station_spec.rb

describe Station do
  ...   
  let(:station) { create(:station) }

  describe "slugging" do
    it "should slug name in absence of a slug" do
      station = create(:station, name: 'foo')
      expect(station.slug).to eq 'foo'
    end

    it "should use slug if provided" do
      station = create(:station, name: 'foo', slug: 'bar')
      expect(station.slug).to eq 'bar'
    end
  end
  ...
end

/spec/controllers/stations_controller.rb

describe StationsController do

    ...

    describe "POST create" do
        it "creates a station with a custom slug" do
          valid_attributes[:slug] = 'custom_slug'
          post :create, {:station => valid_attributes}
          get :show, id: 'custom_slug'
          expect(response).to be_success
        end

        ...
    end

    describe "PUT update" do
        it "updates the slug" do
          put :update, {:id => station.to_param, :station => { slug: 'custom_slug' }}
          get :show, id: 'custom_slug'
          expect(response).to be_success
        end

        ...
    end 

    ...
end
max
  • 96,212
  • 14
  • 104
  • 165
  • What if there is a conflict? Like if the user provides a slug that is already in use? – mscriven May 09 '14 at 15:59
  • @mscriven, by default friendly-id will resolve it by making the slug unique by appending a hash such as 'foo-cdeea311-1957-4fab-b649-dd2a2b9ad90d'. I think you could use `validates_uniqueness_of :slug` if you want to enforce it with Active Record validations. – max May 09 '14 at 16:23
1

To change what parameter is used as the slug you just need to change the friendly_id parameter:

class Station < ActiveRecord::Base
  extend FriendlyId
  belongs_to :user
  has_many  :measures
  validates_uniqueness_of :hw_id
  validates_presence_of :hw_id
  class_attribute :zone_class
  self.zone_class ||= Timezone::Zone
  friendly_id :SLUGNAME, :use => [:slugged, :history]

  before_save :set_timezone!

  ....

  def should_generate_new_friendly_id?
   name_changed? or SLUGNAME_changed?
  end
end

Then in your view just have a way for the user to alter the slugname, following your view:

<%=
   f.div_field_with_label(:SLUGNAME) do |key|
     f.text_field(key)
   end
%>
ChrisBarthol
  • 4,871
  • 2
  • 21
  • 24
  • Sorry if I failed at explaining but this missed the mark. Big thanks for your effort though! – max Nov 16 '13 at 15:09