ruby-on-railsruby-on-rails-4redmineredmine-plugins

Redmine Plugin Development - How to Avoid Duplicate Table Name and Model Name Between Plugins


I'm creating a plugin for Redmine. But it seems that, at migration, CREATE TABLE uses the model name as table name. What if I already have another plugin with the same table name?

Can I do something to automagically adds a prefix to the table name without change de model name?

The problem I have is with the model Carro .

Is it possible to use the rake scripts to instruct it to create the table like: CREATE_TABLE pluginName_modelName ?

How I created the plugin skeleton:

bundle exec ruby bin/rails generate redmine_plugin carros
      create  plugins/carros/app
      create  plugins/carros/app/controllers
      create  plugins/carros/app/helpers
      create  plugins/carros/app/models
      create  plugins/carros/app/views
      create  plugins/carros/db/migrate
      create  plugins/carros/lib/tasks
      create  plugins/carros/assets/images
      create  plugins/carros/assets/javascripts
      create  plugins/carros/assets/stylesheets
      create  plugins/carros/config/locales
      create  plugins/carros/test
      create  plugins/carros/test/fixtures
      create  plugins/carros/test/unit
      create  plugins/carros/test/functional
      create  plugins/carros/test/integration
      create  plugins/carros/README.rdoc
      create  plugins/carros/init.rb
      create  plugins/carros/config/routes.rb
      create  plugins/carros/config/locales/en.yml
      create  plugins/carros/test/test_helper.rb

The Model I'm having trouble with duplicate table name:

bundle exec ruby bin/rails generate redmine_plugin_model carros Carro modelo:string ano:integer cor:string km:integer
      create  plugins/carros/app/models/carro.rb
      create  plugins/carros/test/unit/carro_test.rb
      create  plugins/carros/db/migrate/001_create_carros.rb

And the migration is:

cat plugins/carros/db/migrate/001_create_carros.rb
class CreateCarros < ActiveRecord::Migration
  def change
    create_table :carros do |t|
      t.string :modelo
      t.integer :ano
      t.string :cor
      t.integer :km
    end
  end
end

And the error I would like to avoid, if possible without hard coding a prefix for the table:

bundle exec rake redmine:plugins:migrate
Migrating carros (Carros plugin)...
== 1 CreateCarros: migrating ==================================================
-- create_table(:carros)
rake aborted!
StandardError: An error has occurred, all later migrations canceled:

Mysql2::Error: Table 'carros' already exists: CREATE TABLE `carros` (`id` int(11) auto_increment PRIMARY KEY, `modelo` varchar(255), `ano` int(11), `cor` varchar(255), `km` int(11)) ENGINE=InnoDB                                                                            
/usr/local/www/redmine/plugins/carros/db/migrate/001_create_carros.rb:3:in `change'
/usr/local/www/redmine/lib/redmine/plugin.rb:481:in `migrate_plugin'
/usr/local/www/redmine/lib/redmine/plugin.rb:453:in `migrate'
/usr/local/www/redmine/lib/redmine/plugin.rb:467:in `block in migrate'
/usr/local/www/redmine/lib/redmine/plugin.rb:466:in `each'
/usr/local/www/redmine/lib/redmine/plugin.rb:466:in `migrate'
/usr/local/www/redmine/lib/tasks/redmine.rake:135:in `block (3 levels) in <top (required)>'
/usr/local/bin/bundle:23:in `load'
/usr/local/bin/bundle:23:in `<main>'

Caused by:
ActiveRecord::StatementInvalid: Mysql2::Error: Table 'carros' already exists: CREATE TABLE `carros` (`id` int(11) auto_increment PRIMARY KEY, `modelo` varchar(255), `ano` int(11), `cor` varchar(255), `km` int(11)) ENGINE=InnoDB                                            
/usr/local/www/redmine/plugins/carros/db/migrate/001_create_carros.rb:3:in `change'
/usr/local/www/redmine/lib/redmine/plugin.rb:481:in `migrate_plugin'
/usr/local/www/redmine/lib/redmine/plugin.rb:453:in `migrate'
/usr/local/www/redmine/lib/redmine/plugin.rb:467:in `block in migrate'
/usr/local/www/redmine/lib/redmine/plugin.rb:466:in `each'
/usr/local/www/redmine/lib/redmine/plugin.rb:466:in `migrate'
/usr/local/www/redmine/lib/tasks/redmine.rake:135:in `block (3 levels) in <top (required)>'
/usr/local/bin/bundle:23:in `load'
/usr/local/bin/bundle:23:in `<main>'

Caused by:
Mysql2::Error: Table 'carros' already exists
/usr/local/www/redmine/plugins/carros/db/migrate/001_create_carros.rb:3:in `change'
/usr/local/www/redmine/lib/redmine/plugin.rb:481:in `migrate_plugin'
/usr/local/www/redmine/lib/redmine/plugin.rb:453:in `migrate'
/usr/local/www/redmine/lib/redmine/plugin.rb:467:in `block in migrate'
/usr/local/www/redmine/lib/redmine/plugin.rb:466:in `each'
/usr/local/www/redmine/lib/redmine/plugin.rb:466:in `migrate'
/usr/local/www/redmine/lib/tasks/redmine.rake:135:in `block (3 levels) in <top (required)>'
/usr/local/bin/bundle:23:in `load'
/usr/local/bin/bundle:23:in `<main>'
Tasks: TOP => redmine:plugins:migrate
(See full trace by running task with --trace)

Things I tried without success:

Add a namespace (module) to the model:

If correctly undestood, ActiveModel::Name ActiveModel has a function that generates the the model's name, which is used by ActiveRecord::create_table .

#ActiveModel::Name
    def model_name
          @_model_name ||= begin
            namespace = module_parents.detect do |n|
              n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
            end
            ActiveModel::Name.new(self, namespace)
          end
    end

So I tried add a module to my model name:

module Tutorial
    class Carro < ActiveRecord::Base

    end
end

But it still complains about duplicate table name. So, add a module wrapping the class caused no effect:

bundle exec rake redmine:plugins:migrate NAME=carros
/usr/local/lib/ruby/gems/2.6/gems/activesupport-4.2.11.1/lib/active_support/core_ext/object/duplicable.rb:111: warning: BigDecimal.new is deprecated; use BigDecimal() method instead.
Migrating carros (Carros plugin)...
== 1 CreateCarros: migrating ==================================================
-- create_table(:carros)
rake aborted!
StandardError: An error has occurred, all later migrations canceled:

Mysql2::Error: Table 'carros' already exists: CREATE TABLE `carros` (`id` int(11) auto_increment PRIMARY KEY, `modelo` varchar(255), `ano` int(11), `cor` varchar(255), `km` int(11)) ENGINE=InnoDB

Solution

  • Basically I changed the model, wrapping it within a module:

    This is my new model. It overrides a property from ActiveRecord to set the table name:

    module Transporte
        class Carro < ActiveRecord::Base
            # https://github.com/rails/rails/blob/master/activerecord/test/models/developer.rb
            self.table_name = "trans_carros"
        end
    end
    

    The file system must reflect the change from models/carro.rb to models/transporte/carro.rb . The module is represented as a directory in file system to avoid files conflict.

    My migration file is now calling the model's table_name property when running CREATE TABLE.

    class CreateCarros < ActiveRecord::Migration
      def change
        say("Creating table " << Transporte::Carro.table_name)
        create_table Transporte::Carro.table_name do |t|
          t.string :modelo
          t.integer :ano
          t.string :cor
          t.integer :km
        end
      end
    end
    

    Suppose I changed the table name without wrapping the model in a module. When querying the database, I got wrong behavior :

    $ rails c
    irb> Carro.all
    

    The above SELECT the records from the table carros, from the first plugin that installed a model named Carro .

    But, with a module to avoid conflicts I have:

    $ rails c
    irb> Transporte::Carro.all
    

    And now it works as expected, using the correct model and querying the related table.