17

On a Content model have an attribute named slug. When creating a new record, I want to use a helper to populate this field, but on an existing record I want to use the value from the database.

Currently I have:

<% if @content.new_record? %>
  <%= f.text_field :slug, :value => "#{generate_slug(6)}" %>
<% else %>
  <%= f.text_field :slug %>
<% end %>

But that seems a bit verbose. Is this the best way, or is there no other way? (Rails newb just trying to find the "Rails way" on issues I'm unsure of)


Edit

I should note that the helper is currently in /app/helpers/application_helper.rb Moved to be a private action in the Contents controller. David's answer worked great.

Nowaker
  • 12,154
  • 4
  • 56
  • 62
jyoseph
  • 5,435
  • 9
  • 45
  • 64

3 Answers3

24

In your controller

@content.slug ||= generate_slug(6)

This will assign a value to the slug attribute if none is present

Then, in your view you can simply use

<%= f.text_field :slug %>
David Sulc
  • 25,946
  • 3
  • 52
  • 54
  • This looks awesome. How can I make my helper accessible within the controller? (generate_slug(6) is a helper). I get `undefined method `generate_slug' for #` – jyoseph Jan 09 '11 at 04:39
  • Scratch that, I added it as a private action in the Contents controller. Nevermind me! Thanks for the answer! – jyoseph Jan 09 '11 at 04:54
  • As noted by carpeliam, depending on how, and how often, you're going to use this functionality, you might want to put in the model as a method (`def slug; slug ||= generate_slug(6); end). – David Sulc Jan 09 '11 at 05:49
0

Options

  1. Try after_initialize callback in your model.
  2. Try creating a method in your model where you set defaults and call it in your new action in the controller. Also call this method if your create fails and you render new. Remember to set default only when no value exists by using the ||= operator.

Example to follow. I'm typing on phone!

Aditya Sanghi
  • 13,370
  • 2
  • 44
  • 50
  • 1
    `after_initialize` probably isn't your best bet, since it will also be called every time you retrieve an object from the db. Unless that's what you want? – David Sulc Jan 09 '11 at 04:39
0

I happen to use jQuery in my projects, so when I want some functionality like this, I usually use something like labelify. Then, I'd use something like <%= f.text_field :slug, :title => generate_slug(6) %>. (Hot tip, you don't need to put the #generate_slug call inside of a string if it returns something that will resolve to a string by itself, in fact it's more performant if you don't.)

If you don't want to go with jQuery approach, you might want to wrap this piece of logic in your model.

def Content < ActiveRecord::Base
  def slug
    self.new_record? ? self.slug_for_new_record : attributes[:slug]
  end

  private
  def slug_for_new_record
    # I don't know what you're doing in generate_slug, but it sounds model-
    # related, so if so, put it here and not in a helper
  end
end

If it really belongs in the view, still another option is to just make your Ruby a little bit more concise (you'll have to judge if this is more readable):

<%= f.text_field :slug, :value => (generate_slug(6) if @content.new_record?) %>

Don't forget the parens surrounding (generate_slug(6) if @content.new_record?). If you do, the if will be applied to the text_field, which is not what you want.

But there are still more ways to do it. The above line of code isn't great if your logic might change and you're pasting this code all over your rails project. When I wanted to add a 'required' class to my text fields but only if they were a new record (we had some legacy data that we didn't want to make people clean up), I created my own form builder with a required_field method that just called text_field and added a 'required' class if the item was a new record. This might seem like a work, but we have around 20 different forms, each with potentially multiple required fields, and it's a lot easier to change the business logic in one place. So if you really think this logic belongs in the view but you've got a ton of these lines of code and you don't want to have to change it in a million places, then FormBuilder is the way to go. I think this is in most cases prettier and more appropriate than a helper, but again, beauty is in the eye of the beholder. Here's my code somewhat adapted for your case:

# config/environment.rb
ActionView::Base.default_form_builder = NamespacesAreFun::FormBuilder

# lib/namespaces_are_fun/form_builder.rb
module NamespacesAreFun
  class FormBuilder < ActionView::Helpers::FormBuilder
    def slug_field(method, options = {})
      opts = options.to_options
      opts.merge!(:value => generate_slug) if self.object.new_record?
      text_field(method, opts)
    end
  end
end

# views/.../your_view.html.erb
<%= f.slug_field :slug %>

Hopefully in all of these different approaches is one that fits your project.

carpeliam
  • 6,691
  • 2
  • 38
  • 42
  • I don't have privileges to leave comments on David's answer, but this seems like a place where you might want to apply "Fat Model Skinny Controller". http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model – carpeliam Jan 09 '11 at 05:39
  • I commented my answer to that effect. Although this should be put in the model only if used frequently. If (e.g.) it's only used as a default attribute in a single place (e.g. user picks a slug on sign up, with a default provided for him) it should go in the controller in (only) that specific action. – David Sulc Jan 09 '11 at 05:52