views:

242

answers:

2

I'd like to define a method that is available in both my views and my models

Say I have a view helper:

def foo(s)
  "hello #{s}"
end

A view might use the helper like this:

<div class="data"><%= foo(@user.name) %></div>

However, this <div> will be updated with a repeating ajax call. I'm using a to_json call in a controller returns data like so:

render :text => @item.to_json(:only => [...], :methods => [:foo])

This means, that I have to have foo defined in my Item model as well:

class Item
  def foo
    "hello #{name}"
  end
end

It'd be nice if I could have a DRY method that could be shared in both my views and my models.

Usage might look like this:

Helper

def say_hello(s)
  "hello #{s}"
end

User.rb model

def foo
  say_hello(name)
end

Item.rb model

def foo
  say_hello(label)
end

View

<div class="data"><%= item.foo %></div>

Controller

def observe
  @items = item.find(...)
  render :text => @items.to_json(:only=>[...], :methods=>[:foo])
end

IF I'M DUMB,

please let me know.

I don't know the best way to handle this, but I don't want to completely go against best-practices here.

If you can think of a better way, I'm eager to learn!

A: 

I would simply put this method in my model, which is available to both view and controller.

Model:

def say_hello
  "hello #{self.name}"
end

To add this to all of your models:

class AbstractModel < ActiveRecord::Base  
  self.abstract_class = true

   def say_hello
      self.respond_to?(:name) ? "hello #{self.name}" : "hello" 
   end

end

class MyModel < AbstractModel
end

The ternary operator on respond_to? handles the case where a model has no name column.

Toby Hede
How would I implement it once so **all** models had access to it?
macek
Amended to show adding to all models.
Toby Hede
Alternatively, you could put the method in a mixin and mix it into any models that need it. I fail to see what kind of method would be needed generically across all models, though.
Karl
+3  A: 

You could add some modules, and then have in the models that you want the method to work (on a specific attribute) something like this:

The modules (add this to the lib folder)

module hasFoo
  def self.included(base)
    base.extend ClassMethods
  end
end

module ClassMethods
  def has_foo(method)
     define_method foo do
       field = self.send(method.to_sym)
       field
     end
  end
end

Then in your model, just add

has_foo :name

Then you can just call model.foo

And I think that would do it...

Hock
This looks elegant, but can you elaborate on this a little bit? Based on Rails conventions, what would this/these files be called? `lib.has_foo.rb`? Is `ClassMethods` an *existing* Rails module we are just extending here?
macek
If you place the modules inside the lib folder, you can just include it from your application.For example, if you called it hasFoo, then you just need to "include hasFoo". The ClassMethods is already defined in rails, so you'll be adding things to it. When you include hasFoo it will extend the ClassMethods modules with your new methods.To call the foo method, just call @my_model.foo.I hope it's more clear to you now :-)
Hock