ilasno
ilasno

Reputation: 724

Splitting a class into multiple files in Ruby on Rails

I'm trying to split a large model into multiple files for logical organization. So i have two files:

model1.rb

class Model1 < ActiveRecord::Base
  before_destroy :destroying
  has_many :things, :dependent=>:destroy

  def method1
    ...
  end
  def method2
    ...
  end

end
require 'model1_section1'

model1_section1.rb

class Model1
  def method3
    ...
  end
  def self.class_method4
    ...
  end
end

but when the app loads, and there is a call to Model1.class_method4, i get:

undefined method `class_method4' for #<Class:0x92534d0>

i've also tried this for the require:

require File.join(File.dirname(__FILE__), 'model1_section1')

What am i doing wrong here?

Upvotes: 14

Views: 11761

Answers (5)

Kevin Bedell
Kevin Bedell

Reputation: 13404

Here's an article that does a good job of proposing solutions to this problem:

http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/

Upvotes: 4

Phil
Phil

Reputation: 2807

I know that I'm late on this, but while trying to work out how to do this myself I stumbled on this question.

I think the answer to why the class re-opening is not working as expected in the example code is that the class is initially defined as:

(in model1.rb)

class Model1 < ActiveRecord::Base

and then a re-opened as: (in model1_section1.rb)

class Model1

i.e. the second definition is lacking the inherited class.

I've used separate .rb files to split up my enormous models, and they have worked nicely for me. Though I'll admit I used include and something more like this:

(in workcase.rb)

  class Workcase < ActiveRecord::Base
    include AuthorizationsWorkcase
    include WorkcaseMakePublic
    include WorkcasePostActions

    after_create :set_post_create_attributes 
    # associations, etc

    # rest of my core model definition
  end

(in workcase_make_public.rb)

  module WorkcaseMakePublic
    def alt_url_subject
      self.subject.gsub(/[^a-zA-Z0-9]/, '_').downcase
    end
    # more object definitions
  end

  class Workcase < ActiveRecord::Base
    def self.get_my_stuff
      do_something_non_instance_related
    end
    # more class definitions
  end  

This has allowed me to incorporate class and object methods into each included .rb file. The only caveat (since I didn't use the concerns extension) was that access to class constants from within module object methods required the constant to be qualified with the class name (such as Workcase::SOME_CONST) rather than directly as would be allowable if called in the primary file.

Overall, this approach seemed to require the least amount of rewrite of my code to make things into manageable blocks of code.

Maybe this is not the true 'Rails way' but it does seem to work nicely in my particular scenario.

Upvotes: 2

iHiD
iHiD

Reputation: 2438

I know I'm answering this a little late, but I've just done this in one of my apps so thought I'd post the solution I used.

Let's this was my model:

class Model1 < ActiveRecord::Base

  # Stuff you'd like to keep in here
  before_destroy :destroying
  has_many :things, :dependent => :destroy

  def method1
  end
  def method2
  end

  # Stuff you'd like to extract
  before_create :to_creation_stuff
  scope :really_great_ones, #...

  def method3
  end
  def method4
  end
end

You can refactor it to:

# app/models/model1.rb
require 'app/models/model1_mixins/extra_stuff'
class Model1 < ActiveRecord::Base

  include Model1Mixins::ExtraStuff

  # Stuff you'd like to keep in here
  before_destroy :destroying
  has_many :things, :dependent => :destroy

  def method1
  end
  def method2
  end
end

and:

# app/models/model1_mixins/extra_stuff.rb
module Model1Mixins::ExtraStuff

  extend ActiveSupport::Concern

  included do
    before_create :to_creation_stuff
    scope :really_great_ones, #...
  end

  def method3
  end
  def method4
  end
end

It works perfectly thanks to the extra cleanliness that ActiveSupport::Concern gives you. Hope this solves this old question.

Upvotes: 10

Tom Lianza
Tom Lianza

Reputation: 4072

If you're literally trying to split a class into two files (similar to partial classes in C#), I don't know of a ruby-friendly way to do that.

However, one common way to wind up with classes that have considerable functionality (including a large number of methods) is via Mixins. Modules can be mixed into a class and their methods are literally included in the class.

Upvotes: 1

Chuck Callebs
Chuck Callebs

Reputation: 16441

There's a neat gem called modularity that will do exactly what you're wanting.

A good guide on how to split them properly is on gem-session.

Upvotes: 1

Related Questions