Friday, March 29, 2013

Migrating Instiki to Rails 3.2

Objective: Migration of Instiki to Rails 3.2 

Instiki is a great wiki system, which I am using to drive the content of http://www.netxforge.com.  Instiki however is behind in respect of the Rails version it was created with. This is a problem for me, as I want to integrate instiki, into a broader Ruby On Rails application. 

This blog describes the steps I took to do it. The conversion is unfortunately not available as a download or on a repo somewhere, but I hope that the list of issues (And how I solved them) can help others to repeat it and make the upgrade available. 

The creator of Instiki, alias Distler has endorsed this initiative in the instiki forum.

So here are the steps: 

Warning: some of the stuff is perhaps a bit specific to my setup so be carefull. Also I haven't done everything yet, and some things don't work correctly. One example is the use of JavaScript. In rails 3, JQuery is favoured over Prototype.js etc... , the use of assets and separation of Javascript from erb templates is good practise. Some of this is still to be done. 


----- (I Recommend to start the server after each step, to get a feel of the progress)


Step 1. Use a rails 3.2 generated app to compare the current instiki and the rails 3.2 structure. We refer to this as the Template Rails App (TRA)

Step 1.1 Update the Gemfile (It now looks like this, see explanation in separate steps)

source "http://rubygems.org"

gem "rails", "=3.2.12"

# Gems used only for assets and not required
# in production environments by default.
group :assets do
  gem 'sass-rails',   '~> 3.2.3'
  gem 'coffee-rails', '~> 3.2.1'

  # See https://github.com/sstephenson/execjs#readme for more supported runtimes
  # gem 'therubyracer', :platforms => :ruby

  gem 'uglifier', '>= 1.0.3'
end

gem 'jquery-rails'

gem "sqlite3", :require => "sqlite3"
gem "itextomml", ">=1.4.10"
gem "rack", ">=1.4.5"
gem "mongrel", ">=1.2.0.pre2"
gem "rubyzip"
gem "RedCloth", ">=4.0.0"
gem "erubis"
gem "nokogiri"
gem "rake"
gem "rdoc"
gem "json"
gem "file_signature", :git => 'http://github.com/distler/file_signature.git'
gem "maruku", :git => 'http://github.com/distler/maruku.git', :branch => 'nokogiri'
# gem "mysql2"

Step 2. Replace the /script's content with the TRA's /scripts content, delete the old files from /script

Step 3. Migration of the /config folder

Step 3.1 Create application.rb (Rails 3.  has an application.rb file) and configure it in the steps below.
   
    Load the /lib folder  as this is turned off, edit/add these params.

config.autoload_paths << "#{Rails.root}/lib"
config.autoload_paths << "#{Rails.root}/lib/chunks"
    Do some java script setup, for the new rails 3 assets concept. Instiki should actually migrate away from scriptaculous.js and use JQuery
        
config.action_view.javascript_expansions[:legacy] = %w(prototype.js scriptaculous.js)

Step 3.3 copy in the environments/ and initializers/ from TRA     

The TRA application name will be different then what Instiki should be. The following file should be edited and the     first line should be renamed from 

"TemplateApp::Application.configure do" to "InstikiApp::Application.configure do"
 
config/environments/development.rb     
config/environments/test.rb     
config/environments/production.rb 


Step 3.4 move the original environment.rb out of the way (renamed it to environment.rb.backup), and copy the environment.rb from the template rails app   

Some migrations from the contents of this file.         

Rename the last line to InstikiApp::Application.initialize!     

require_dependency 'instiki_errors' => Moved this to a custom initializer named config/initializers/instiki_init.rb It looks like this: 
 
require 'instiki_errors'
require 'wiki_content'
        

require 'instiki_errors' # migrated from instiki environment.rb     
require 'wiki_content' # Needed to load properly         [TODO]     

rexml_versions => Not sure what to do with this. It scans various directories to get an REXML version.     
# Miscellaneous monkey patches (here be dragons ...)     

require 'caching_stuff'     
require 'logging_stuff'     require 'rack_stuff'                Note: Not using require_dependency, as this is undocumented (Rails internal) and for development only. Not really a requirement here     I believe. 

Step 3.4 Copy in boot.rb from TRA, remove preinitializer.rb which is a pre Rails 3 hack to get bundler working. 

See: http://gembundler.com/v1.3/rails23.html 

Step 3.5 root.rb     [Carefull] This route.rb is slightly specific to my application, but it includes the wiki routes, so you can extract the relevant ones:
 
[TODO] some routes don't work in the rails 3. instill, need to be fixed.
 
def connect_to_web(generic_path, generic_routing_options, *options)
  if defined? DEFAULT_WEB
    explicit_path = generic_path.gsub(/:web\/?/, '') # Strip the /:web
    explicit_routing_options = generic_routing_options.merge(:web => DEFAULT_WEB)
    match explicit_path, explicit_routing_options
  end

  match generic_path, generic_routing_options
# map.connect(generic_path, generic_routing_options)
end

# :id's can be arbitrary junk
id_regexp = /.+/

InstikiApp::Application.routes.draw do

# SEE:  http://yehudakatz.com/2009/12/26/the-rails-3-router-rack-it-up/

  root :to => 'public#page', :id => 'HomePage'

  # Wiki Admin:

  match 'create_system', :to => 'admin#create_system'
  match 'create_web', :to => 'admin#create_web'
  match 'delete_web', :to => 'admin#delete_web'
  match 'delete_files', :to => 'admin#delete_files'
  match 'web_list', :to => 'wiki#web_list'

  # Application
  match ':controller/:action(/:id)'

  # Wiki webs routing
  connect_to_web ':web/edit_web',  :to => 'admin#edit_web' #Edit an arbitrary web.
  connect_to_web ':web/remove_orphaned_pages',  :to => 'admin#remove_orphaned_pages' #Remove pages which are not referenced by any other page
  connect_to_web ':web/remove_orphaned_pages_in_category',  :to => 'admin#remove_orphaned_pages_in_category'
  connect_to_web ':web/file/delete/:id',  :to => 'file#delete', :constraints => {:id => /[-._\w]+/}, :id => nil
  connect_to_web ':web/files/pngs/:id',  :to => 'file#blahtex_png', :constraints => {:id => /[-._\w]+/}, :id => nil
  connect_to_web ':web/files/:id',  :to => 'file#file', :constraints => {:id => /[-._\w]+/}, :id => nil
  connect_to_web ':web/file_list/:sort_order',  :to => 'wiki#file_list', :sort_order => nil
  connect_to_web ':web/import/:id',  :to => 'file#import'
  connect_to_web ':web/login',  :to => 'wiki#login'
  connect_to_web ':web/web_list',  :to => 'wiki#web_list'
  connect_to_web ':web/show/diff/:id', :to => 'wiki#show', :mode => 'diff', :requirements => {:id => id_regexp}
  connect_to_web ':web/revision/diff/:id/:rev',  :to => 'wiki#revision', :mode => 'diff', :constraints => { :rev => /\d+/, :id => id_regexp}
  connect_to_web ':web/revision/:id/:rev',  :to => 'wiki#revision', :constraints => { :rev => /\d+/, :id => id_regexp}
  connect_to_web ':web/source/:id/:rev', :to => 'wiki#source', :constraints => { :rev => /\d+/, :id => id_regexp}
  connect_to_web ':web/list/:category',  :to => 'wiki#list', :constraints => { :category => /.*/}, :category => nil
  connect_to_web ':web/recently_revised/:category',  :to => 'wiki#recently_revised', :requirements => { :category => /.*/}, :category => nil
  connect_to_web ':web/:action/:id',  :to => 'wiki', :constraints => {:id => id_regexp}
  connect_to_web ':web/:action', :to =>  'wiki'
  connect_to_web ':web',  :to => 'wiki#index' 

Step 4 problem with plugin: protect_forms_from_spam, comment it out. Find an alternative

Step 5. Deal with assets (Stylesheets, Javascript, Images)

    Read this: http://guides.rubyonrails.org/asset_pipeline.html
   
    Assets should be pre-compiled with:
        bundle exec rake assets:precompile
    these will end up in the /public/assets/ folder

Step 5.1 Update the Gemfile to include:

    # Gems used only for assets and not required
    # in production environments by default.
    group :assets do
      gem 'sass-rails',   '~> 3.2.3'
      gem 'coffee-rails', '~> 3.2.1'

      # See https://github.com/sstephenson/execjs#readme for more supported runtimes
      # gem 'therubyracer', :platforms => :ruby

      gem 'uglifier', '>= 1.0.3'
    end

Step 5.2 Create asset folders
   
    /app/assets/stylsheet

        - copy in application.css from TRA
        - [OPTIONAL] rename .css to .css.erb to use assets in CSS for example: <%= asset_path 'someimage.png' %>        

    /app/assets/javascript

        - application.js is auto created, but we already have an application.js file with a bit of scripts in it. so copy the following lines
        from TRA and add them to the instiki application.js
       
        // This is a manifest file that'll be compiled into application.js, which will include all the files
        // listed below.
        //
        // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
        // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
        //
        // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
        // the compiled file.
        //
        // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
        // GO AFTER THE REQUIRES BELOW.
        //
        //= require jquery
        //= require jquery_ujs
        //= require_tree .

    /app/assets/images
        - use image_tag helper methods.

Step 5.3
   
    Copy in the assets from /public in the respective locations under /aspp/assets from the 2.x instiki

Step 6 Replace @controller with controller wherever this occurs in the controllers.

Step 7. Fix issues with rendering and default.layout/
    [DEBUG]: Render an action without the layout for troubleshooting: render :layout => false
    fix the layout content insertion point, was @content_for_layout versus modern yield. so => <%= yield %>

    Use: <%= debug params %>

Step 8. problem with sublayout: (Wiki source)
   
    Showing /Users/Christophe/Documents/Spaces/netxforge_aptana/com.netxforge.store/app/views/layouts/application.html.erb where line #46 raised:

    undefined method `sub_layout' for #<WikiController:0x007ffb07b5bf58>

Step 9. link_to_remote issues: , now workaround, not using the :update tag. [TODO]

    Currently URL's like this are generated, which embeds a js into the URL with onclick statement).

    <a onclick="new Ajax.Updater('intropage', '/public/page/features?menu=true&partial=true',
    {asynchronous:true, evalScripts:true}); return false;" href="#"></a>

    :update is not supported anymore in Rails 3. (AJAX call back to update a DOM id), see the following articles:

    http://www.simonecarletti.com/blog/2010/06/unobtrusive-javascript-technique/

    New approach is based on separation of the HTML and JS, so the JS should do the update.

Step 10. Issues with form_tag

    in template edit.html.erb, the form tag starts with '<%' erb shabang, but should be '<%='
    replacing this fixed the problem.

    See 3.0 Release notes, section 7.4.2.

    Note: The included javascript should be moved to the application.js or another .js under /app/assets/javascript

Step 11. ActiveModel::MassAssignmentSecurity erros on various model objects[SOLVED]
    because of this:
   
    https://gist.github.com/peternixey/1978249

    in various model objects, to fix these errors.

    Page =>  attr_accessible :locked_at, :locked_by, :name
    Revision=> attr_accessible :revised_at, :page, :content, :author
    .....

Step 12. Undefined method error in WikiContent[SOLVED]
    It turned out, that
    include ChunkManager (in wiki_content.rb) didbn't load properly, as
    ChunkManager has dependencies on various other classes in /chunks.
    Made sure these are loaded in application.rb (See Step 3.1)

    however this causes another issue:

Step 12.1 Error, when include /lib/chunks
    Expected .../lib/chunks/wiki.rb to define Wiki
    Actually the whole app now fails with different errors:

    This could be a conflict in naming of classes, as /chunks defines a wiki.rb
    See this post:
    http://stackoverflow.com/questions/10948779/expected-to-define-when-calling-class-inside-a-module

    fixed by:

    - renamed wiki to wiki_c and references to it.
    - removed call to html_safe in WikiContent.render! as this points to a method which
    will produce a ActiveSupport::SafeBuffer instance from the WikiContent, so not adhering to the instance type,
    this gived method_errors

Step 13. URL generator in wiki gives wrong urls for: [TODO]
    all Pages => /:web/list/HomePage (So the :page is appended and shou'd not).
    edit Web => /:web/edit_web/HomePage (:page appended, should not).

Step 14. Dealing with XML templates.
    renamed atom.rxml to atom.builder simply solved the problem.

Step 15. Grab a coffee, and reflect on your great achievements sofar :-)

Step 16. Warning: You don't need to install rails_xss as a plugin for Rails 3 and after. [TODO]
   
    http://simianarmy.com/post/11117853564/upgrading-to-rails-3-rails-xss
    - What to do with this? It's not clear to me what the new situation is.

Step 17. [2013-03-13 12:26:00] WARN  Could not determine content-length of response body. Set content-length of the response or set Response#chunked = true

    Caused by webrick server, don't worry about it.


1 comment:

  1. In the spirit of open source, have you posted your fork of Instiki anywhere?

    ReplyDelete