3

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?

knut
  • 27,320
  • 6
  • 84
  • 112

1 Answers1

0

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.

knut
  • 27,320
  • 6
  • 84
  • 112