Search code examples
rubyactiverecordormdocumentationsequel

Where to store column descriptions with an ORM (especially Sequel)?


When I use an ORM like Sequel, the columns of a Model are defined by the connected database. How can I get a documentation for the columns of the table?

Example (Sequel, but I think the principle is the same for AR):

I create a table with this migration:

Sequel.migration do
  up do
    create_table(:person) do
      primary_key :id
      String :name, :null=>false
    end
  end
  down do
    drop_table(:person)
  end
end

There is no way to add a column description to the database (or is there one? Please no DB-specific solution).

The corresponding Model is defined as

class Person < Sequel::Model
end

When I generate my documentation with rdoc, the documentation for Person is "empty", I get no description of the columns.

I can add a getter-method for a column like here:

class Person < Sequel::Model
  #Name of the person
  attr_reader :name
end

I get a description with rdoc, but there are two problems:

  • Person#name is always nil, the connection to the DB is lost
  • And if it would work, it is a violation of the DRY-principle.

I could add a list of all columns:

#Columns:
# * name: Name of the person
# ...
class Person < Sequel::Model
end

But again:

  • This breaks the DRY-principle.
  • I want to see all available columns like a getter-methods.

I founds the annotate-gem. But this gem deletes previous comments and it adds only technical informations. I can not add comments.

Some other model frameworks such as datamapper, mongomaper and mongoid directly define the attributes in the models. (source) But I don't want to change my ORM.

So my question: What is the best-practice to store descriptions of the columns?


Solution

  • Up to now no answer - perhaps there is no best-practice.

    In meantime I had an idea with a modifies the idea of the annotate-gem.

    The attached script creates a new ruby file with the documentation for the models. This ruby file may not be added to your application (it will break the models), but you can add it to your rdoc-call.

    Add the following code att the end of the following script:

    require 'your_application' #Get your model definitions
    
    #Define ModelDocumentation with comments for the different fields
    doc = ModelDoc.new(
      #reference with table.field
      'tables.field1' => 'field one of table tables',
      #reference with model.field
      'Table#field2' => "field two of table, referenced by model Table",
    )
    doc.save('my_doc.rb')
    

    And now the script:

    #encoding: utf-8
    =begin rdoc
    Generate documentation file for Model, 
    enriched with data from docdata
    =end
    
    =begin rdoc
    Get all subclasses from a class.
    
    Code from http://stackoverflow.com/a/436724/676874
    =end
    class Class
      def subclasses
        ObjectSpace.each_object(Class).select { |klass| klass < self }
      end
    end  
    
    
    =begin rdoc
    Class to generate a Model-documentation
    
    ==Usage:
    
    Write a script like this:
    
      require 'my_application' 
      require 'model_doc' #this script
    
      doc = ModelDoc.new(
        #reference with table.field
        'tables.field1' => 'field one of table tables',
        #reference with model.field
        'Table#field2' => "field two of table, referenced by model Table",
      )
      doc.save('my_doc.rb')
    
    my_doc.rb can be included to your library. 
    Don't load it to your application!
    Only add it to your documentation with rdoc.
    =end
    class ModelDoc
    =begin rdoc
    Class to generate a Model-documentation.
    
    Comments are a hash with.
    
    Keys may be:
    * Modelname#field
    * tablename.field
    =end
      def initialize( comments = {} )
        @comments = comments
      end
    
      def save(filename)
        docfile = File.new(filename, 'w')
    
        puts "(Re-)Generate #{docfile.path}"
        docfile << <<doc
    =begin
    This file is generated by #{__FILE__},
    based on data from #{Sequel::Model.db.inspect}
    
    Don't use this code in your implementation!
    This code will overwrite your real model.
    
    
    This is only for usage with rdoc.
    
    =end
    doc
        Sequel::Model.subclasses.each{|model|
          docfile << "\n\n#\n"
          docfile << "#Model for table '#{model.table_name}'\n"
          docfile << "#\n"
          docfile << "class #{model} < Sequel::Model\n"
          model.columns.each{|column|
            comment = @comments[[model, column].join('#')]
            comment = @comments[[model.table_name, column].join('.')] unless comment
    
            docfile << "  #\n  #table field #{field_description(model.table_name, column, model.db_schema[column]).join("\n  #* ")}\n"
            if comment
              docfile << comment.gsub(/^/, "  #")
              docfile << "\n"
            end
            #~ docfile << "  #\n#accessor generated by Model\n"
            docfile << "  attr_reader #{column.inspect}\n"
          }
          docfile << "end\n\n"
        }
    
        docfile.close
      end
    
      def field_description(table_name, column, db_schema)
        [
          "#{table_name}.#{column} (#{db_schema[:type]}/#{db_schema[:db_type]})",
          db_schema[:primary_key] ? "This is a key field" : nil,
          "Default is #{db_schema[:default] || 'undefined'}",
          #fixme: ruby_default
          "Null-values are #{'not ' unless db_schema[:allow_null]}allowed",
        ].compact
      end
    end #ModelDoc
    

    This script works only for Sequel, but I think it can be adapted for AR.