Wolfmans Howlings

A programmers Blog about Ruby, Rails and a few other issues

Capistrano deploy from local repository

Posted by Jim Morris Sat, 27 May 2006 00:08:00 GMT

UPDATE 2006-12-06 I have replaced this with a full blown SCM module that works much better, see this posting

It seems that many people are in the same position as I was, they want to deploy to a remote server farm that does not have access to the SCCM (subversion,CVS,perforce etc), which is usually behind a corporate firewall.

The following recipe overcomes this by checking out the source code from the local repository (using svn in this case) and goes into your deploy.rb.

What it does is checkout the latest copy of the application from your local subversion repository, tars it up, and copies the tar file to the remote server, then detars it and does the normal deploy tasks from then on.

desc <<DESC
Update all servers with the latest release of the source code.
This is a modified version that copies a local copy to the remote site
DESC

task :update_code, :roles => [:app, :db, :web] do
    on_rollback { delete release_path, :recursive => true }

    #puts "doing my update_code"
    temp_dest= "tmp_code"

    #puts "...get a local copy of the code into #{temp_dest} from local svn"
    # but this could also just be your local development folder
    system("svn export -q #{configuration.repository} #{temp_dest}")

    #puts "...tar the folder"
    # you could exclude files here that you don't want on your production server
    system("tar -C #{temp_dest} -c -z -f code_update.tar.gz .")

    #puts "...Sending tar file to remote server"
    put(File.read("code_update.tar.gz"), "code_update.tar.gz")

    #puts "...detar code on server"
    run <<-CMD
        mkdir -p #{release_path} &&
        tar -C #{release_path} -x -z -f code_update.tar.gz &&
        rm -rf code_update.tar.gz &&
        rm -rf #{release_path}/log #{release_path}/public/system &&
        ln -nfs #{shared_path}/log #{release_path}/log &&
        ln -nfs #{shared_path}/system #{release_path}/public/system
    CMD

    #puts "...cleanup"
    system("rm -rf #{temp_dest} code_update.tar.gz")
end

This recipe does what many people need todo which is replace the database.yml with the production version, and also repalces the .htaccess with the production version (YMMV)

desc "fix up database and .htaccess"
task :after_update_code do
  run "cp #{release_path}/config/database.yml.templ #{release_path}/config/database.yml"
  run "cp #{release_path}/public/dot.htaccess.deploy #{release_path}/public/.htaccess"
end

Posted in ,  | Tags , , ,  | no comments | no trackbacks

Howto format ruby code for blogs

Posted by Jim Morris Fri, 26 May 2006 07:14:00 GMT

How do I get that nice formatted ruby code inline?

Well if you are on typo trunk use this…

<div class="typocode"><pre><code class="typocode_ruby ">       <span class="punct">...</span><span class="ident">ruby</span> <span class="ident">code</span><span class="punct">...</span>
</code></pre></div>

If you are not on typo trunk (which I am not yet) you can do the following…

I had to search around for this so I thought I’d put the recipe here.

I got most of my information from this site, you can also use this site to convert the code for you, but I found that more cumbersome than what I do below.

Basically all the hard work is done by the syntax gem install it as…

> gem install syntax

Then I use this little script called code2html.rb which converts the code in the clipboard and puts it back in the clipbboard…

require 'rio'
require 'rubygems'
require 'syntax/convertors/html'

if ARGV.size > 0
    code= File.read(ARGV[0])
else
    code= `dcop klipper klipper getClipboardContents`
end

convertor = Syntax::Convertors::HTML.for_syntax "ruby"
@code_html = convertor.convert( code )

puts @code_html

if ARGV.size > 0
    fn= "#{File.basename(ARGV[0], File.extname(ARGV[0]))}.html"
    rio(fn) << @code_html
else
    # put the results back on the clipboard, NB this may fail if there are shell specific characters
    system("dcop klipper klipper setClipboardContents \"#{@code_html}\"")
end

The clipboard stuff is kind of kde specific.

Alternatively you can specify a filename on the command line, and it will convert that file and put the results in a file with .html as the extension. (Note this requires the rio gem).

You will need this CSS available to your web page to render it nicely.

pre {
    background-color: #f1f1f3;
    color: #112;
    padding: 10px;
    font-size: 1.1em;
    overflow: auto;
    margin: 4px 0px;
          width: 95%;
}



/* Syntax highlighting */
pre .normal {}
pre .comment { color: #005; font-style: italic; }
pre .keyword { color: #A00; font-weight: bold; }
pre .method { color: #077; }
pre .class { color: #074; }
pre .module { color: #050; }
pre .punct { color: #447; font-weight: bold; }
pre .symbol { color: #099; }
pre .string { color: #944; background: #FFE; }
pre .char { color: #F07; }
pre .ident { color: #004; }
pre .constant { color: #07F; }
pre .regex { color: #B66; background: #FEF; }
pre .number { color: #F99; }
pre .attribute { color: #5bb; }
pre .global { color: #7FB; }
pre .expr { color: #227; }
pre .escape { color: #277; }

Posted in  | Tags , ,  | 8 comments | no trackbacks

Create Rails ActiveRecord Models from DDL

Posted by Jim Morris Thu, 25 May 2006 06:20:00 GMT

I had a database with about 60 tables in it, most where simple lookup tables with simple has_many and belongs_to relationships, and I didn't want to manually create all the models, with the associations by hand. I googled around and came across Bill Katz's dbmodel, which takes the output of dbdesigner and creates the models with the relationships. However I already had the Databases and schema setup, and I didn't have (and couldn't find) a copy of dbdesigner to use. So I hacked Bills dbmodel to read a DDL file that was created from the command rake db:structure:dump, as I was using Postgresql this file had all the relevant relationship info in it plus a bit extra.

I added the ability to create the relationships that had non standard table names and foreign keys, and also added some validations to the created models.

By default all relationships are created as has_many (and belongs_to), and tables can have multiple belongs_to referencing the same table so long as the foreign keys are different. Also models are created for every table found in the DDL.

I also added an yaml file as an override so you can specify habtm and has_one relationships too.

There are two files to this modification, a hacked version of the original dbmodel.rb and a new file which encapsulates the parsing of the DDL file. The main changes to the original dbmodel.rb consists of removing the parsing of the original xml file and reading the changes from a modified hash of the tables and associations.

The new ddl.rb file handles the parsing of the DDL file, and building a hash of data for the tables, with their associations and validations. It also reads the YAML file that overides the association types.

You can get a zip of the two files from this link ddl2model.zip.

Both files need to be in the same directory and it is run from the command line, the yaml override being in the same directory as the dbmodel.rb script. The ddl file to be processed is given on the command line.

The YAML file assocs.yml is used to tell the ddl.rb script about relations that are anything other than has_many, so:

extable1:
   - {:assoc: has_one, :ref: extable2, :column: extable1_id}
   - {:assoc: has_one, :ref: extable3, :column: extable1_id}
   - {:assoc: habtm, :ref: extable1_extable2, :column: extable1_id}
extable2:
   - {:assoc: habtm, :ref: extable1_extable2, :column: extable2_id}

Tells the parser that the model for a table named extable1 should create a has_one relation for extable2 using the foreign key extable1_id. Ditto for extable3.

It also specifies that the model for extable2 has a habtm relationship using the join table extable1_extable2 using foreign key extable1_id. Note that the habtm override needs to be specified for both tables, in this case extable2 also has a habtm override.

You can also ignore a table with this:

table3: []

will ignore the table called table3.

Lastly the generator will add validate_presence_of based on any NOT NULL constraints found on the column DDL.

Update

A better solution to this problem has been provided here http://db-discovery.rubyforge.org/

Posted in ,  | Tags , ,  | 5 comments | no trackbacks

An Admin page for Rails Role Based Authentication

Posted by Jim Morris Sat, 20 May 2006 20:19:00 GMT

This is an example of an admin page for the Rails Recipes book Role Based Authentication, using a tree control and checkboxes for HABTM.

I used the Silverstripe tree control to render two trees, on the left a list of all Roles and what Rights they have. On the right a tree with all controllers and a checkbox for each action for each controller, under that a set of checkboxes that allow you to apply the selected rights to the selected Roles.

This technique seems a good match where the lists are hierarchical so it can cut down on the screen real-estate for very large lists.

Example code...

In the model:

  • The Rights class has_and_belongs_to_many :roles
  • The Roles class has_and_belongs_to_many :rights

I setup the data by creating hashes in the controller which are available to the view...

In roles_controller.rb...

def edit
    # roll everything up into a hierarchical tree structure
    # top level is hash of roles
    #   next level is hash of permissions with controller as key and array of actions as value
    @role_tree= {}
    roles= Role.find_all
    roles.each do |r|
        ph= {}
        rights= r.rights
        rights.each do |p|
            ph[p.controller] ||= []
            ph[p.controller] << p.action
        end
        @role_tree[r.name] = ph
    end

    # creates a tree of controllers with the actions for each controller
    # top level is hash of controllers
    #  next level is an array of actions
    @rights_tree= {}
    rights= Right.find_all
    rights.each do |r|
        name= r.controller
        @rights_tree[name] ||= []
        @rights_tree[name] << r.action
    end
end

The view (edit.rhtml) is simple...

<h1>Role and Rights Editor</h1>

<table cellspacing="10" cellpadding="20%">
<tr>
<th align="left">Roles</th>
<th align="left">Rights</th>
<tr>
<td valign="top"><%= render_role_tree(@role_tree) %></td>
<td><%= start_form_tag :action => 'edit' %>
   <%= render_rights_tree(@rights_tree) %>
<p>
The checked items above will replace the Rights of the following
Roles:<br>
   <% @role_tree.each_key do |role| %>
    <%= check_box_tag "role", role %> <%= role %>
   <% end %>
<p>
   <%= submit_tag "Allocate to selected Roles" %>
<%= end_form_tag %>
</td>
</table>

This part goes in helpers, and creates the html for the two trees this is specific for the silverstripe tree control...

module RoleHelper
    def render_role_tree(tree)
        ret = ''
        ret += "<ul class='tree'>"

        tree.each_key do |r|
            ret += '<li><a href="#">' + r + '</a>' # list roles
            h= tree[r] # get hash of controllers/[actions]
            unless h.empty?
                ret += '<ul>'
                h.each_key do |c|
                    ret += '<li><a href="#">' + c + '</a>' # list controllers
                    a= h[c]
                    unless a.empty?
                        ret += '<ul>'
                        a.each do |an|
                            ret += '<li><a href="#">' + an + '</a></li>' # list actions
                        end
                        ret += '</ul>'
                    end
                    ret += '</li>'
                end
                ret += '</ul>'
            end
            ret += '</li>'
        end
        ret += '</ul>'
        ret
    end

    def render_rights_tree(tree)
        ret = ''
        ret += "<ul class='tree'>"

        tree.each_key do |r|
            ret += "<li><a href=\"#\">#{r}</a>"# list controllers
            aa= tree[r] # get array of actions
            unless aa.empty?
                ret += '<ul>'
                ret += "<li><a href=\"#\" onclick=\"checkAll('right[#{r}][]'); return false;\">Check All</a>"
                aa.each do |a|
                    ret += '<li>' + "<input type=\"checkbox\" name=\"right[#{r}][]\" value=\"#{a}\" />" + a + '</li>' # list actions
                end
                ret += '</ul>'
            end
            ret += '</li>'
        end
        ret += '</ul>'
        ret
    end
end

the action handler in the controller for the post basically goes through all the selected checkboxes and sets the relevant habtms.

def edit
    if request.post?
        #p params
        unless params['role'].nil?
            a_roles= params['role']
            a_rights= params['right']
            unless a_rights.nil?
                a_roles.each { |ar|
                    role= Role.find_by_name(ar)
                    unless role.nil?
                        role.rights.clear
                        a_rights.each { |controller, aa|
                            aa.each { |action|
                                right= Right.find_by_controller_and_action(controller, action)
                                unless right.nil?
                                    role.rights << right
                                else
                                    puts "Right #{controller}/#{action} not found"
                                end
                            }
                        }
                    else
                        puts "Role #{ar} not found"
                    end
                }
                flash[:notice]= 'rights updated'
            else
                flash[:warning]= 'no rights were selected'
            end
        else
            flash[:warning]= 'You need to select the role to allocate to'
        end
    end
end

Checking and unchecking all the boxes

This is done with a little bit of javascript.

The view code looks like...

 &lt;a href="#" onclick="checkAll('permissions_<%= controller_id %>[]'); return false;"\>all&lt;/a>
 &lt;a href="#" onclick="uncheckAll('permissions_<%= controller_id %>[]'); return false;"\>none&lt;/a>

The java script is...

function checkAll(name)
{
    boxes = document.getElementsByName(name)
    for (i = 0; i < boxes.length; i++)
        boxes[i].checked = true ;
}

function uncheckAll(name)
{
    boxes = document.getElementsByName(name)
    for (i = 0; i < boxes.length; i++)
        boxes[i].checked = false ;
}

Automatically adding Rights.

Lastly the question of how to get all those Rights into the table? I liked the approach of the UserEngine so I borrowed it from them (thanks:), Add this to the app/models/right.rb file, then whenever you need to get all the new actions and controllers populated call Right.synchronize_with_controllers from the console, (or create a button on your admin page to do it). Here is the code...


class Right < ActiveRecord::Base
  has_and_belongs_to_many :roles
  validates_presence_of :controller, :action, :name
  validates_uniqueness_of :name

  # Ensure that the table has one entry for each controller/action pair
  def self.synchronize_with_controllers
    # weird hack. otherwise ActiveRecord has no idea about the superclass of any
    # ActionController stuff...
    require RAILS_ROOT + "/app/controllers/application"

    # Load all the controller files
    controller_files = Dir[RAILS_ROOT + "/app/controllers/**/*_controller.rb"]

    # we need to load all the controllers...
    controller_files.each do |file_name|
      require file_name #if /_controller.rb$/ =~ file_name
    end


    # Find the actions in each of the controllers, and add them to the database
    subclasses_of(ApplicationController).each do |controller|
      controller.public_instance_methods(false).each do |action|
        next if action =~ /return_to_main|component_update|component/
        if find_all_by_controller_and_action(controller.controller_path, action).empty?
          self.new(:name => "#{controller}.#{action}", :controller => controller.controller_path, :action => action).save!
          logger.info "added: #{controller} - #{controller.controller_path}, #{action}"
        end
      end
      # The following thanks to Tom Styles 
      # Then check to make sure that all the rights for that controller in the database
      # still exist in the controller itself
      self.find(:all, :conditions => ['controller = ?', controller.controller_path]).each do |right_to_go|
        unless controller.public_instance_methods(false).include?(right_to_go.action)
          right_to_go.destroy
        end
      end
    end
  end
end

Posted in  | Tags , ,  | 10 comments | no trackbacks

Older posts: 1 ... 8 9 10