posts by date

11/2007
Section content with Control.Tabs

Sometimes views can get unavoidably large (think eBay registration form). And sometimes it’s nice to group related content within a view. Well, in either case, here’s an option to do just that.

I found a prototype extension, Control.Tabs which basically gives you a menu to show certain pieces of markup. It suited my needs and here’s how I tied it into Rails.

I didn’t want to fiddle with javascript each time I needed this in a view, so I created a block helper to do the work for me1:

def tabbed_content(*links, &block)
  dom_id = links.last.is_a?(Hash) ? links.pop.values.last : 'tabbed-menu'

  list_items = links.inject([]) { |items, link| items << content_tag(:li, link_to(link.to_s.capitalize, "##{link.to_s.downcase}")) }
  tabs = content_tag(:ul, list_items, :id => dom_id)

  concat tabs, block.binding
  yield
  concat javascript_tag("new Control.Tabs('#{dom_id}');"), block.binding
end

Now when a view gets inherently too large (or needs grouped), I can make it scroll-friendly and simplify the code. As an example, here’s how I’d use it to section off someone’s online resume.

<% tabbed_content :education, :work, :references do -%>

  <div id='education'>
    <h2>Education Background</h2>
    <p>whatever you want</p>
  </div>

  <div id='work'>
    <h2>Work Experience</h2>
    <p>whatever you want</p>
  </div>

  <div id='references'>
    <h2>References</h2>
    <p>whatever you want</p>
  </div>

<% end -%>

This will place a set of links (capitalized versions of what was passed in) at the top of the content, defaulting to the first section. As expected, clicking a link will show only that section. And the javascript writes an “active” class to the appropriate link, which is nice for the CSS.

As a side note, there is a default DOM id being written to the <ul>, but it can be overridden if desired (maybe there’s more than one on a page?).

<% tabbed_content :education, :work, :references, :dom_id => 'tabbed-menu-2' do -%>

I know this is incredibly simple and basic, but someone might find it useful. And of course, if you do happen to use this, make sure you have the Control.Tabs javascript library included in your layout as needed (Chris has a tip for that).

1 Note: in Rails 2.0 extract_options! could be used instead of manually checking if the last item is a Hash, which is something I currently do a lot.

Flickr Uploadr 3.0 beta

Here’s the official thread and here’s a direct download. Editing tags/titles/descriptions/sets is much easier now, and it supports drag and drop reordering for those suffering from obsessedwithuploadorderitis like myself. It automatically saves your progress, too, which is awfully thoughtful. If you don’t have it yet, you should get it.

Being deceitful doesn't pay

I came across a new hosting site (can’t remember the name) that supported Ruby on Rails. I wasn’t on a hunt for new hosting, but noticed the design in a CSS gallery. It went from very attractive to repulsive in the blink of an eye (or as fast as you can say “false advertisement”).

The site design looked good. I thought the information was well presented and it just felt clean. As with most clean sites, it implied quality service. +1 for the site design.

This company promoted their Rails hosting pretty hard, which I liked. That’s pretty much the reason I have personal hosting, so seeing that was a good thing. +1 for the Rails support.

Their prices were strategically placed to get my attention, and it worked. The price that struck me was $5/month. From my experience, that’s pretty cheap. +1 for the price.

However, when I checked out the plans section the cheapest one was $5.99/month (a.k.a. $6, not $5). I realize it’s only an extra $0.99/month, but that’s not the point. I’m planning on seeing $5/month somewhere (anywhere) on the “plans” chart, because that’s what I was told earlier. But it wasn’t there. They led me to believe it was, though, because $5/month is much better than $5.99/month.

If they’re not truth-telling about that petty $0.99, how would I know they’re good with Rails? Or general support? Or anything else they claimed to be? And that’s the point. The only thing they got out of me was a guarantee that I’ll never sign up with them.

Maybe I’m the only one who thinks that way, but I doubt it.

MooMonth calendar

Here’s an interesting approach for displaying calendar events: MooMonth calendar. And here’s a demo to see it in action (make sure you click on a date twice).

It’d have to be on an app that had intense event-scheduling to alleviate the fact that a full-screen calendar might be an overkill solution. Still pretty cool, though. It makes me want to build something with a calendar.

Updating multi-model forms in Rails

The other day I received an email about the post concerning multi-model forms in Rails. This person had a similar situation, but was having difficulty saving the records on an update. I didn’t make that part very clear, so I figured I’d complete the puzzle.

If you recall, there was a check in the set_model_attributes method that looks for an id (attributes[:id].blank?) so it’d know whether it was supposed to update or create. If there’s an id present, it must be an update.

Based on these two lines,

_model = send(model).detect {|m| m.id == attributes[:id].to_i}
_model.attributes = attributes

there’s a misconception that the model’s attributes are getting saved. Setting the attributes like this does not save them, but it’s easy to do. I’m using an after_update callback which looks like this:

class Course < ActiveRecord::Base

  has_many :holes, :dependent => :destroy
  has_many :tees,  :dependent => :destroy  

  after_update { |c| c.save_associated :holes, :tees }

  # ...

end

And the save_associated method looks like this:

def save_associated(*models)
  models.each do |model|
    send(model).each do |attributes|
      # handle validations before this point
      # and pass false to the save method to
      # ensure that the attributes get saved
      attributes.save(false)
    end
  end
end

If you’re only dealing with one association you can eliminate the parameters and the nested loop, as well as provide a more meaningful method name for the callback (such as save_holes). All in all, this seems to work pretty good.

Chris mentioned conductors in an earlier comment, which also seems like a viable option.

Textilizing multiple fields at once

Textile is awesome. So awesome, in fact, that I use it on just about every text area of every Rails application I build. In the database I typically have a field for the raw text and a field for the marked-up, textilized text (one for editing, one for displaying). And up until recently I’ve only had at most one field per model that needed to be “textilized.” Well the helper I was using will no longer suffice, so I’ve implemented a new version. It’s now a class method and can accept multiple columns at once, as well as a column suffix (defaults to _html). Here’s how it works:

# Implied DB columns:
# - notes,       notes_html
# - description, description_html
# - summary,     summary_html

class FilledWithText < ActiveRecord::Base
  textilize :notes, :description, :summary
end

However, if there is some sort of conflict or need to have the columns be something other than [column]_html, you can just pass the suffix as a key/value pair at the end of the parameter list. Also, you can give the key any name you’d like for readability sake—it only really cares about the value…

# Implied DB columns:
# - about,   about_source
# - article, article_source

class MoreText < ActiveRecord::Base
  textilize :about, :article, :suffix => 'source'
  # or
  textilize :about, :article, :ending_of_textilized_columns => 'source'
end

It’s been quite handy for me so far. I’m sure the implementation isn’t perfect, but here’s what I came up with:

def textilize(*columns)
  methods = []
  suffix  = columns.last.is_a?(Hash) ? columns.pop.values.last : 'html'

  columns.each do |column|
    define_method "#{column}_to_html" do
      self["#{column}_#{suffix}"] = RedCloth.new(self[column] || '').to_html
    end
    methods << "#{column}_to_html".to_sym
  end

  before_save *methods
end

If you use textile, it’s worth doing something like this, as it makes it so much easier to convert and self-document textilized columns. Just stick it in a module to use across all of your models.

Of course, after the fact I realized there was a much better solution. I should have known to check first. Oh well, it’s still good for me to cook my own soup, so to speak.

Handling multi-model forms in Rails

Sometimes (a lot of times, actually) I need to post a form that deals with several different models. For instance, in Golf Trac, fields_for "course[course_holes][]" passes an array of hole attributes that relate back to a course, where a course has_many holes. A course also has_many tees, so there’s a fields_for "course[course_tees][]", as well. This post isn’t about how to setup the forms, but more of what to do in the model.

Of course, I’m sure there are a lot of different ways to do this, but here’s my approach:

class Course < ActiveRecord::Base

  has_many :holes, :dependent => :destroy
  has_many :tees,  :dependent => :destroy

  def course_holes=(course_holes)
    set_model_attributes(:holes, course_holes)     
  end

  def course_tees=(course_tees)
    set_model_attributes(:tees, course_tees)
  end

  private

  def set_model_attributes(model, model_attributes)
    model_attributes.each_with_index do |attributes, index|
      if attributes[:id].blank?
        send(model).build(attributes)
      else
        _model = send(model).detect {|m| m.id == attributes[:id].to_i}
        _model.attributes = attributes
      end
    end
  end

end

I’m going to need the set_model_attributes method in a few other places, so it’ll eventually end up in a module.

2008 by Ryan Heath | Get In Touch

flickr

DesolateInfinityLooking upDazedBlurred