2

I understand that application_controller.rb is the place to put all the methods, etc that you would like made available in all your controllers since they all inherit from this class. Great.

But what is the equivalent for Models? In other words, I want a place where I can create a couple of super classes that my models will inherit from.

For example, I have a method that searches different tables for entries in all CAPS via REGEXP in Mysql. I'd like to be able to create the method only once and call it for different tables/models.

What is the Rails way of doing this?

I thought I could create a class that would inherit from ActiveRecord::Base (as all models do) , put the methods in there and then inherit all my models from that class. But thought there would surely be a better way to do it.

Thanks.

Edit

Per Semyon's answer I'm editing the post to show the routes I am using. It works now:

# models/dvd.rb
require 'ModelFunctions'
class Dvd < ActiveRecord::Base
    extend ModelFunctions
    ...
end

# lib/ModelFunctions.rb
module ModelFunctions
    def detect_uppercase(object)
        case object
        ...
        where("(#{field} COLLATE utf8_bin) REGEXP '^[\w[:upper:]]{5,}' ").not_locked.reorder("LENGTH(#{field}), #{table}.#{field} ASC")
    end
end

In config/application.rb

config.autoload_paths += %W(#{config.root}/lib)
kakubei
  • 5,321
  • 4
  • 44
  • 66
  • Don't use `require 'model_common'` - use `include ModelCommon`; see http://stackoverflow.com/questions/318144/what-is-the-difference-between-include-and-require-in-ruby – Thilo Feb 13 '12 at 15:43
  • Thanks Thilo, the link in your answer said to use require, that's why I tried that. But trying include ModelCommon, gives me this error: `Expected .../app/models/model_common.rb to define ModelCommon`. I thought you `required` the file, then `included` the methods. Is that wrong? – kakubei Feb 13 '12 at 16:21

2 Answers2

2

Take a look at mixins, for example here:

http://ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html

In a Rails app you could create a module in the lib directory that defines your methods and then include it in your models.

EDIT: To be specific for your example, you're trying to define a class method. You can do this in a mixin like this:

module Common
  module ClassMethods
    def detect_uppercase(object)
      case object
      when 'dvd'
          field = 'title'
      ... 
      end    
      where("(#{field} COLLATE utf8_bin) REGEXP '^[\w[:upper:]]  {5,}').not_locked.reorder('LENGTH(title), title ASC')"
    end
  end

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

Now when you include Common in your model, that model's class will be extended to include the new class methods, and you should be able to call Dvd.detect_uppercase.

Thilo
  • 17,565
  • 5
  • 68
  • 84
  • yes, mix-ins are fit into ActiveRecord ideology to share some kind of functionality. Inheritance from super-class is make sense for Singe Table Inheritance pattern usage – Anatoly Feb 13 '12 at 14:19
  • great, this sounds promising. So using mixins I could create a method that would change certain parameters based on the model that called it? I'll give it a try. Thanks. – kakubei Feb 13 '12 at 15:04
  • This looked promising, but when calling `Dvd.detect_uppercase('dvd')` after making the changes in your edit, the latest error is this: `NameError: uninitialized constant Common::ClassMethods` – kakubei Feb 14 '12 at 09:01
  • Thanks Thilo. I'm trying both your answer and Semyon's. Wouldn't Semyon's way accomplish the same thing in a less roudabout way? I ask because I see that both use the extend functionality (haven't read up on that). But I find the last bit in your code extremely confusing, the one with `self.incude(base) base.extend(ClassMethods)`. It's like it is extending itself??? – kakubei Feb 14 '12 at 09:36
  • The `included` method is called at the point in time when the module is included in another class. `base` refers to that including class. So this methods extends the base class with the methods defined in the `ClassMethods` module. I know this can be confusing at first, but it's a very common ruby pattern. Seymon's way will work too - there's usually more than one way to do this stuff. – Thilo Feb 14 '12 at 09:43
1

Put the reusable method in some module next to your Dvd class. You can move it in a separate file later.

# app/models/dvd.rb
module CaseInsensitiveSearch
  def case_insensitive_search(field, value)
    # searching field for value goes here
  end
end

class Dvd
end

After extending a class with the module you can use case_insensitive_search on the class. Including the module will make case_insensitive_search an instance method which is not what you want.

class Dvd
  extend CaseInsensitiveSearch
end

Dvd.case_insensitive_search("title", "foo")

And of course you can use it inside Dvd class.

class Dvd
  def self.search(query)
    case_insensitive_search("title", query)
  end
end

Dvd.search("foo")

Now when you made sure it works, you will probably want to move it in a separate file and use it across multiple classes. Place it in lib/case_insensitive_search.rb and make sure you have this line in config/application.rb:

config.autoload_paths += %W(#{config.root}/lib)

Now you can require it anywhere you want to use it:

require 'case_insensitive_search'

class Dvd
  extend CaseInsensitiveSearch
end

The last thing I'd like to suggest. Create multiple modules with meaningful names. So instead of CommonModel have CaseInsensitiveSearch and so on.

Simon Perepelitsa
  • 20,350
  • 8
  • 55
  • 74
  • So this goes back to the first thought I had: create a file with these methods and then all my models would inherit from that file/class. Is this correct? – kakubei Feb 13 '12 at 17:09
  • Nope, you can put any code you want in a file, not just classes. To make the methods available outside a file you define a module and place methods inside it. – Simon Perepelitsa Feb 13 '12 at 17:22
  • So you cannot just put methods there, you need to wrap them into the same `module CaseInsensitiveSearch; end` thing. And you do the same `extend CaseInsensitiveSearch` in the Dvd class. No new classes and no inheritance. – Simon Perepelitsa Feb 13 '12 at 17:25
  • Great Semyon, thanks. This worked until I moved it to /lib. Now I get `uninitialized constant ModelFunctions`. Please see my edit above for the routes to make sure I didn't do it wrong :) – kakubei Feb 14 '12 at 09:22
  • Sorry, I can be an idiot at times, I was missing the quotes around `ModelFunction` in the require statement. Now it works! So My question now is, which way is better. This way or the above (Thilo's answer). It seems that if I do it this way, all methods will be class methods because of the `extend` functionality is this correct? (Please excuse my ignorance, I'm still farily new to Rails). – kakubei Feb 14 '12 at 09:41
  • One *issue* I seem to have now is that if I make any changes to the module, they will not be updated in my app until I quit the server (in Development mode using WEBrick) and start it again. Is this normal? Is there a way to reload that module on the fly? – kakubei Feb 14 '12 at 09:56
  • Try to put it inside app subdirectory like app/models or app/concerns and name the file as underscored version of your module name (e.g. for module ModelFunctions name the file model_functions.rb) and then remove require statements, Rails will handle requiring and reloading the module based on it's name. – Simon Perepelitsa Feb 25 '12 at 13:21
  • Great, this last trick worked! Now I don't have to restart the webrick client each time. Thanks. – kakubei Feb 27 '12 at 11:34