[Ovirt-devel] [PATCH] refactored popups and grids to pull more into the layouts,
Hugh O. Brock
hbrock at redhat.com
Thu May 29 21:05:42 UTC 2008
On Thu, May 29, 2008 at 03:58:56PM -0400, Scott Seago wrote:
> Scott Seago wrote:
>>
>> ------------------------------------------------------------------------
>>
>> _______________________________________________
>> Ovirt-devel mailing list
>> Ovirt-devel at redhat.com
>> https://www.redhat.com/mailman/listinfo/ovirt-devel
>
> >From 2351f8a85747668407463440c8c05c641788564b Mon Sep 17 00:00:00 2001
> From: Scott Seago <sseago at redhat.com>
> Date: Thu, 29 May 2008 15:54:19 -0400
> Subject: [PATCH] refactored popups and grids to pull more into the layouts, and static js functions into ovirt.js.
>
> Also, the children and all_children methods on Pool now accept find parameters.
>
> Signed-off-by: Scott Seago <sseago at redhat.com>
> ---
> wui/src/app/controllers/application.rb | 24 +-
> wui/src/app/controllers/hardware_controller.rb | 46 ++-
> wui/src/app/controllers/host_controller.rb | 3 -
> wui/src/app/controllers/storage_controller.rb | 2 +-
> wui/src/app/models/pool.rb | 11 +-
> wui/src/app/views/dashboard/index.html.erb | 50 --
> wui/src/app/views/hardware/move.rhtml | 13 +-
> wui/src/app/views/hardware/new.html.erb | 38 +-
> wui/src/app/views/hardware/show_hosts.rhtml | 1 +
> wui/src/app/views/hardware/show_storage.rhtml | 1 +
> wui/src/app/views/host/_grid.rhtml | 21 +-
> wui/src/app/views/host/addhost.html.erb | 71 +--
> wui/src/app/views/layouts/popup.rhtml | 7 +-
> wui/src/app/views/layouts/redux.rhtml | 64 +--
> wui/src/app/views/permission/new.rhtml | 26 +-
> wui/src/app/views/resources/new.rhtml | 27 +-
> wui/src/app/views/resources/show.rhtml | 78 ---
> wui/src/app/views/storage/_grid.rhtml | 11 +-
> wui/src/app/views/storage/addstorage.html.erb | 48 +-
> wui/src/app/views/storage/new.rhtml | 42 +-
> wui/src/app/views/vm/new.rhtml | 28 +-
> wui/src/public/javascripts/ovirt.js | 138 +++++
> .../betternestedset/lib/#better_nested_set.rb# | 583 --------------------
> .../betternestedset/lib/better_nested_set.rb | 33 +-
> 24 files changed, 350 insertions(+), 1016 deletions(-)
> create mode 100644 wui/src/public/javascripts/ovirt.js
> delete mode 100644 wui/src/vendor/plugins/betternestedset/lib/#better_nested_set.rb#
>
> diff --git a/wui/src/app/controllers/application.rb b/wui/src/app/controllers/application.rb
> index 5f20be3..3ca5125 100644
> --- a/wui/src/app/controllers/application.rb
> +++ b/wui/src/app/controllers/application.rb
> @@ -84,18 +84,30 @@ class ApplicationController < ActionController::Base
> end
> end
> end
> - def json_list(full_items, attributes)
> +
> + # don't define find_opts for array inputs
> + def json_list(full_items, attributes, arg_list=[], find_opts={})
> page = params[:page].to_i
> - item_list = full_items.paginate(:page => page,
> - :order => "#{params[:sortname]} #{params[:sortorder]}",
> - :per_page => params[:rp])
> + paginate_opts = {:page => page,
> + :order => "#{params[:sortname]} #{params[:sortorder]}",
> + :per_page => params[:rp]}
> + arg_list << find_opts.merge(paginate_opts)
> + item_list = full_items.paginate(*arg_list)
> json_hash = {}
> json_hash[:page] = page
> - json_hash[:total] = full_items.size
> + json_hash[:total] = item_list.total_entries
> json_hash[:rows] = item_list.collect do |item|
> item_hash = {}
> item_hash[:id] = item.id
> - item_hash[:cell] = attributes.collect {|attr| item.send(attr)}
> + item_hash[:cell] = attributes.collect do |attr|
> + if attr.is_a? Array
> + value = item
> + attr.each { |attr_item| value = value.send(attr_item)}
> + value
> + else
> + item.send(attr)
> + end
> + end
> item_hash
> end
> render :json => json_hash.to_json
> diff --git a/wui/src/app/controllers/hardware_controller.rb b/wui/src/app/controllers/hardware_controller.rb
> index ad857f1..0f0c904 100644
> --- a/wui/src/app/controllers/hardware_controller.rb
> +++ b/wui/src/app/controllers/hardware_controller.rb
> @@ -229,17 +229,27 @@ class HardwareController < ApplicationController
> if params[:id]
> pre_json
> hosts = @pool.hosts
> + find_opts = {}
> + include_pool = false
> else
> - # FIXME: no permissions checks here yet, no filtering of current pool yet
> - hosts = Host.find(:all)
> + # FIXME: no permissions or usage checks here yet
> + # filtering on which pool to exclude
> + id = params[:exclude_id]
> + hosts = Host
> + find_opts = {:include => :hardware_pool,
> + :conditions => ["pools.id != ?", id]}
> + include_pool = true
> end
> - json_list(hosts,
> - [:id, :hostname, :uuid, :hypervisor_type, :num_cpus, :cpu_speed, :arch, :memory_in_mb, :is_disabled_str])
> + attr_list = [:id, :hostname, :uuid, :hypervisor_type, :num_cpus, :cpu_speed, :arch, :memory_in_mb, :is_disabled_str]
> + attr_list.insert(2, [:hardware_pool, :name]) if include_pool
> + json_list(hosts, attr_list, [:all], find_opts)
> end
>
> def vm_pools_json
> - json_list(@pool.sub_vm_resource_pools,
> - [:id, :name])
> + json_list(Pool,
> + [:id, :name],
> + [@pool, :children],
> + {:finder => 'call_finder', :conditions => ["type = 'VmResourcePool'"]})
> end
>
> def users_json
> @@ -251,12 +261,20 @@ class HardwareController < ApplicationController
> if params[:id]
> pre_json
> storage_pools = @pool.storage_pools
> + find_opts = {}
> + include_pool = false
> else
> - # FIXME: no permissions checks here yet, no filtering of current pool yet
> - storage_pools = StoragePool.find(:all)
> + # FIXME: no permissions or usage checks here yet
> + # filtering on which pool to exclude
> + id = params[:exclude_id]
> + storage_pools = StoragePool
> + find_opts = {:include => :hardware_pool,
> + :conditions => ["pools.id != ?", id]}
> + include_pool = true
> end
> - json_list(storage_pools,
> - [:id, :display_name, :ip_addr, :get_type_label])
> + attr_list = [:id, :display_name, :ip_addr, :get_type_label]
> + attr_list.insert(2, [:hardware_pool, :name]) if include_pool
> + json_list(storage_pools, attr_list, [:all], find_opts)
> end
>
> def storage_volumes_json
> @@ -283,8 +301,10 @@ class HardwareController < ApplicationController
> resource_ids = resource_ids_str.split(",").collect {|x| x.to_i} if resource_ids_str
> begin
> @pool.create_with_resources(@parent, resource_type, resource_ids)
> - render :json => { :object => "pool", :success => true,
> + reply = { :object => "pool", :success => true,
> :alert => "Hardware Pool was successfully created." }
> + reply[:resource_type] = resource_type if resource_type
> + render :json => reply
> rescue
> render :json => { :object => "pool", :success => false,
> :errors => @pool.errors.localize_error_messages.to_a }
> @@ -308,7 +328,7 @@ class HardwareController < ApplicationController
> # in addition to the current pool (which is checked). We also need to fail
> # for hosts that aren't currently empty
> def add_hosts
> - host_ids_str = params[:host_ids]
> + host_ids_str = params[:resource_ids]
> host_ids = host_ids_str.split(",").collect {|x| x.to_i}
>
> begin
> @@ -347,7 +367,7 @@ class HardwareController < ApplicationController
> # in addition to the current pool (which is checked). We also need to fail
> # for storage that aren't currently empty
> def add_storage
> - storage_pool_ids_str = params[:storage_pool_ids]
> + storage_pool_ids_str = params[:resource_ids]
> storage_pool_ids = storage_pool_ids_str.split(",").collect {|x| x.to_i}
>
> begin
> diff --git a/wui/src/app/controllers/host_controller.rb b/wui/src/app/controllers/host_controller.rb
> index f89997f..623cc0c 100644
> --- a/wui/src/app/controllers/host_controller.rb
> +++ b/wui/src/app/controllers/host_controller.rb
> @@ -58,9 +58,6 @@ class HostController < ApplicationController
>
> def addhost
> @hardware_pool = Pool.find(params[:hardware_pool_id])
> - @unassigned = Pool.root.hosts.size
> - # FIXME: @assigned should match the updated assigned hosts query when that's done
> - @assigned = Host.find(:all).size
> render :layout => 'popup'
> end
>
> diff --git a/wui/src/app/controllers/storage_controller.rb b/wui/src/app/controllers/storage_controller.rb
> index 1c5b392..c8831e6 100644
> --- a/wui/src/app/controllers/storage_controller.rb
> +++ b/wui/src/app/controllers/storage_controller.rb
> @@ -88,7 +88,7 @@ class StorageController < ApplicationController
>
> def new2
> @storage_pools = @storage_pool.hardware_pool.storage_volumes
> - render :layout => 'popup'
> + render :layout => false
> end
>
> def insert_refresh_task
> diff --git a/wui/src/app/models/pool.rb b/wui/src/app/models/pool.rb
> index 490c5e6..5390beb 100644
> --- a/wui/src/app/models/pool.rb
> +++ b/wui/src/app/models/pool.rb
> @@ -69,10 +69,10 @@ class Pool < ActiveRecord::Base
> end
>
> def sub_hardware_pools
> - Pool.select_hardware_pools(children)
> + children({:conditions => "type='HardwarePool'"})
> end
> def sub_vm_resource_pools
> - Pool.select_vm_pools(children)
> + children({:conditions => "type='VmResourcePool'"})
> end
> def self_and_like_siblings
> self_and_siblings.select {|pool| pool[:type] == self.class.name}
> @@ -156,7 +156,12 @@ class Pool < ActiveRecord::Base
> hash
> end
> end
> -
> +
> + def self.call_finder(*args)
> + obj = args.shift
> + method = args.shift
> + obj.send(method, *args)
> + end
>
> protected
> def traverse_parents
> diff --git a/wui/src/app/views/dashboard/index.html.erb b/wui/src/app/views/dashboard/index.html.erb
> index 8715261..92cb4da 100644
> --- a/wui/src/app/views/dashboard/index.html.erb
> +++ b/wui/src/app/views/dashboard/index.html.erb
> @@ -1,57 +1,7 @@
> - <div id="data">
> - <div class="inside">
> -
> - <div id="dataTableWrapper">
> -
> - <%= render :partial => "/layouts/quick_stats", :locals => { :hpool => @default_pool, :vms => @vms } %>
> -
> - <div class="dataTable">
> -
> - <%= render :partial => "/layouts/hardware_pool_type_list", :locals => { :hardware_pools => @hardware_pools } %>
> -
> - </div> <!-- end #dataTable -->
> -
> - <div class="data-section">
> - <div class="data-section-header"><strong>Available Hosts (<%= @available_hosts.size %>)</strong></div>
> - <div class="data-section-stats">Statistics Data</div>
> - <div class="data-section-table">
> - <div class="inside">
> - <%= render :partial => "/host/list", :locals => { :hosts => @available_hosts } %>
> - </div>
> - </div>
> - </div>
> -
> - <div class="data-section">
> - <div class="data-section-header"><strong>Available Storage (<%= @available_storage_volumes.size %>)</strong></div>
> - <div class="data-section-header"><strong>Storage Pools (<%= @storage_pools.size %>)</strong></div>
> - <div class="data-section-stats">Statistics Data</div>
> - <div class="data-section-table">
> - <div class="inside">
> - <%= render :partial => "/storage/list_volumes", :locals => { :storage_volumes => @available_storage_volumes } %>
> - </div>
> - </div>
> - </div>
> -
> - </div> <!-- end #dataTableWrapper -->
> -
> - </div> <!-- end #data.inside -->
> - </div> <!-- end #data -->
> -
> - </td>
> <td id="right">
> - <div class="heading"> </div>
> <div class="tools">
>
> <h3>Actions</h3>
> - <%if @can_modify -%>
> - <div class="actions">
> -<!--
> - <%= link_to 'Add Storage', { :controller => 'storage', :action => 'new', :hardware_pool_id => @default_pool}, { :class => "create" } %>
> - <hr/>
> --->
> - <%= link_to 'Create New Pool', { :controller => "hardware", :action => 'new', :parent_id => @default_pool}, { :class => "create" } %>
> - </div>
> - <% end -%>
>
> <div class="actions">
> <div><%= link_to_if @can_set_perms, 'User Permissions', { :controller => 'permission', :action => 'new', :pool_id => @default_pool }, { :class => "edit" } %></div>
> diff --git a/wui/src/app/views/hardware/move.rhtml b/wui/src/app/views/hardware/move.rhtml
> index 190fc03..f5824c6 100644
> --- a/wui/src/app/views/hardware/move.rhtml
> +++ b/wui/src/app/views/hardware/move.rhtml
> @@ -1,3 +1,10 @@
> +<%- content_for :title do -%>
> + Move <%= @resource_type.capitalize %>
> +<%- end -%>
> +<%- content_for :description do -%>
> + Select an existing hardware pool or create a new pool for selected <%= @resource_type %>.
> +<%- end -%>
> +
> <script type="text/javascript">
> $(document).ready(function(){
> $("#move_tree").treeview({
> @@ -34,12 +41,7 @@ $('#move_to_new_pool').click(function(){
> });
> </script>
>
> -<div id="window">
>
> -<div class="dialog_title_small">
> - <div class="header">Move <%= @resource_type.capitalize %></div>
> - <div>Select an existing hardware pool or create a new pool for selected <%= @resource_type %>. </div>
> -</div>
>
> <div class="dialog_tree">
> <ul id="move_tree" class="filetree treeview-famfamfam treeview"></ul>
> @@ -63,4 +65,3 @@ $('#move_to_new_pool').click(function(){
> <div class="button_right_blue"></div>
> </div>
> </div>
> -</div>
> diff --git a/wui/src/app/views/hardware/new.html.erb b/wui/src/app/views/hardware/new.html.erb
> index a533871..f594009 100644
> --- a/wui/src/app/views/hardware/new.html.erb
> +++ b/wui/src/app/views/hardware/new.html.erb
> @@ -1,40 +1,30 @@
> -<div class="dialog_title_small">
> - <div class="header">Add New Hardware Pool</div>
> - <div>Add a new Hardware Pool to the <%= @parent.name %> pool.
> - <%= "Checked #{@resource_type} will be added to the new pool." if @resource_type %>
> - </div>
> -</div>
> +<%- content_for :title do -%>
> + <%= _("Add New Hardware Pool") %>
> +<%- end -%>
> +<%- content_for :description do -%>
> + Add a new Hardware Pool to the <%= @parent.name %> pool.
> + <%= "Checked #{@resource_type} will be added to the new pool." if @resource_type %>
> +<%- end -%>
>
> <form method="POST" action="<%= url_for :action => 'create' %>" id="pool_form" >
> -<div class="dialog_form">
> - <%= render :partial => 'form' %>
> -</div>
> -<%= popup_footer("$('#pool_form').submit()", "Create Hardware Pool") %>
> + <div class="dialog_form">
> + <%= render :partial => 'form' %>
> + </div>
> + <%= popup_footer("$('#pool_form').submit()", "Create Hardware Pool") %>
> </form>
> - <script type="text/javascript">
> +
> +<script type="text/javascript">
> $(function() {
> var hwpooloptions = {
> target: '<%= url_for :action => 'create' %>', // target element to update
> dataType: 'json',
> - success: afterNewHwPool // post-submit callback
> + success: afterHwPool // post-submit callback
> };
>
> // bind form using 'ajaxForm'
> $('#pool_form').ajaxForm(hwpooloptions);
> });
> -function afterNewHwPool(response, status){
> - ajax_validation(response, status)
> - if (response.success) {
> - jQuery(document).trigger('close.facebox');
> - // FIXME do we need to reload the tree here
> - <%= "$('##{@resource_type}_grid').flexReload()" if @resource_type %>
> - //$("#vmpools_grid").flexReload()
> - }
> -}
> </script>
>
> -<%- content_for :title do -%>
> -<%= _("New Hardware Pool") %>
> -<%- end -%>
>
>
> diff --git a/wui/src/app/views/hardware/show_hosts.rhtml b/wui/src/app/views/hardware/show_hosts.rhtml
> index ae0e8af..bcbc2b8 100644
> --- a/wui/src/app/views/hardware/show_hosts.rhtml
> +++ b/wui/src/app/views/hardware/show_hosts.rhtml
> @@ -51,6 +51,7 @@
> <div class="data_section">
> <%= render :partial => "/host/grid", :locals => { :table_id => "hosts_grid",
> :hwpool_id => @pool.id,
> + :exclude_id => nil,
> :on_select => "hosts_select" } %>
> </div>
> <div class="selection_detail" id="hosts_selection">
> diff --git a/wui/src/app/views/hardware/show_storage.rhtml b/wui/src/app/views/hardware/show_storage.rhtml
> index 3d547db..f0b14bc 100644
> --- a/wui/src/app/views/hardware/show_storage.rhtml
> +++ b/wui/src/app/views/hardware/show_storage.rhtml
> @@ -68,6 +68,7 @@
> <div class="data_section">
> <%= render :partial => "/storage/grid", :locals => { :table_id => "storage_grid",
> :hwpool_id => @pool.id,
> + :exclude_id => nil,
> :on_select => "storage_select" } %>
> </div>
>
> diff --git a/wui/src/app/views/host/_grid.rhtml b/wui/src/app/views/host/_grid.rhtml
> index 338bc78..25bdfd7 100644
> --- a/wui/src/app/views/host/_grid.rhtml
> +++ b/wui/src/app/views/host/_grid.rhtml
> @@ -7,18 +7,19 @@
> $("#<%= table_id %>").flexigrid
> (
> {
> - url: '<%= url_for :controller => "hardware", :action => "hosts_json", :id => hwpool_id %>',
> + url: '<%= url_for :controller => "hardware", :action => "hosts_json", :id => hwpool_id, :exclude_id => exclude_id %>',
> dataType: 'json',
> colModel : [
> - {display: '', name : 'id', width : 20, sortable : false, align: 'left', process: <%= table_id %>checkbox},
> - {display: 'Hostname', name : 'hostname', width : 60, sortable : true, align: 'left'},
> - {display: 'UUID', name : 'uuid', width : 180, sortable : true, align: 'left'},
> - {display: 'Hypervisor Type', name : 'hypervisor_type', width : 80, sortable : true, align: 'left'},
> - {display: 'CPUs', name : 'num_cpus', width : 40, sortable : true, align: 'left'},
> - {display: 'Speed (MHz)', name : 'cpu_speed', width : 60, sortable : true, align: 'right'},
> - {display: 'Arch', name : 'arch', width : 50, sortable : true, align: 'right'},
> - {display: 'RAM (MB)', name : 'memory', width : 60, sortable : true, align: 'right'},
> - {display: 'Disabled', name : 'is_disabled', width : 50, sortable : true, align: 'right'}
> + {display: '', width : 20, align: 'left', process: <%= table_id %>checkbox},
> + {display: 'Hostname', name : 'hostname', width : 60, align: 'left'},
> + <%= "{display: 'Hardware Pool', name : 'pools.name', width : 100, align: 'left'}," if exclude_id %>
> + {display: 'UUID', name : 'uuid', width : 180, align: 'left'},
> + {display: 'Hypervisor Type', name : 'hypervisor_type', width : 80, align: 'left'},
> + {display: 'CPUs', name : 'num_cpus', width : 40, align: 'left'},
> + {display: 'Speed (MHz)', name : 'cpu_speed', width : 60, align: 'right'},
> + {display: 'Arch', name : 'arch', width : 50, align: 'right'},
> + {display: 'RAM (MB)', name : 'memory', width : 60, align: 'right'},
> + {display: 'Disabled', name : 'is_disabled', width : 50, align: 'right'}
> ],
> sortname: "hostname",
> sortorder: "asc",
> diff --git a/wui/src/app/views/host/addhost.html.erb b/wui/src/app/views/host/addhost.html.erb
> index 69e0e66..bc3c797 100644
> --- a/wui/src/app/views/host/addhost.html.erb
> +++ b/wui/src/app/views/host/addhost.html.erb
> @@ -1,55 +1,18 @@
> -<div class="dialog_title">
> - <div class="header">Add Host</div>
> - <div>Select hosts from the list below to add to the <%= @hardware_pool.name %> hardware pool. <a href="#">Learn how to manage hosts</a></div>
> +<%- content_for :title do -%>
> + <%= _("Add Host") %>
> +<%- end -%>
> +<%- content_for :description do -%>
> + Select hosts from the list below to add to the <%= @hardware_pool.name %> hardware pool. <a href="#">Learn how to manage hosts</a>
> +<%- end -%>
> +
> +<div class="panel_header"></div>
> +<div class="dialog_body">
> + <%= render :partial => "/host/grid", :locals => { :table_id => "addhosts_grid",
> + :hwpool_id => nil, :exclude_id => @hardware_pool.id,
> + :on_select => "false" } %>
> </div>
> -<div id="toolbar_nav">
> -<ul>
> - <li class="current" id="unassigned_tab"><a href="#" onClick = "show_unassigned()"><img style="vertical-align:middle;" src="" /> Unassigned Hosts (<%= @unassigned %>)</a></li>
> - <li id="assigned_tab"><a href="#" onClick = "show_assigned()"><img style="vertical-align:middle;" src="" /> Assigned Hosts (<%= @assigned %>)</a></li>
> -</ul>
> -</div>
> -<script type="text/javascript">
> - function show_unassigned()
> - {
> - $("#assigned_tab").removeClass('current');
> - $("#unassigned_tab").addClass('current');
> - $("#addhosts_assigned_grid_div").hide()
> - $("#addhosts_unassigned_grid_div").show()
> - }
> - function show_assigned()
> - {
> - $("#assigned_tab").addClass('current');
> - $("#unassigned_tab").removeClass('current');
> - $("#addhosts_unassigned_grid_div").hide()
> - $("#addhosts_assigned_grid_div").show()
> - }
> - function add_hosts()
> - {
> - var assigned_selected= get_selected_checkboxes(document.addhosts_assigned_grid_form)
> - var unassigned_selected= get_selected_checkboxes(document.addhosts_unassigned_grid_form)
> - var hosts = Array.concat(assigned_selected,unassigned_selected)
> - if (validate_selected(hosts, "host")) {
> - $.post('<%= url_for :controller => "hardware", :action => "add_hosts", :id => @hardware_pool %>',
> - { host_ids: hosts.toString() },
> - function(data,status){
> - jQuery(document).trigger('close.facebox');
> - $("#hosts_grid").flexReload()
> - if (data.alert) {
> - alert(data.alert);
> - }
> - }, 'json');
> - }
> - }
> -</script>
> - <div class="panel_header"></div>
> - <div class="dialog_body">
> - <%= render :partial => "/host/grid", :locals => { :table_id => "addhosts_unassigned_grid",
> - :hwpool_id => Pool.root.id, :on_select => "false" } %>
> - <%= render :partial => "/host/grid", :locals => { :table_id => "addhosts_assigned_grid",
> - :hwpool_id => nil, :on_select => "false" } %>
> - </div>
> -</div>
> -<script type="text/javascript">
> - $("#addhosts_assigned_grid_div").hide()
> -</script>
> -<%= popup_footer("add_hosts()", "Add Hosts") %>
> +
> +<%= popup_footer("add_hosts('#{url_for :controller => "hardware",
> + :action => "add_hosts",
> + :id => @hardware_pool}')",
> + "Add Hosts") %>
> diff --git a/wui/src/app/views/layouts/popup.rhtml b/wui/src/app/views/layouts/popup.rhtml
> index 736517c..55dda58 100644
> --- a/wui/src/app/views/layouts/popup.rhtml
> +++ b/wui/src/app/views/layouts/popup.rhtml
> @@ -1,2 +1,7 @@
> -<%# currently nothing for popups here. %>
> +<div id="window">
> +<div class="dialog_title_small">
> + <div class="header"><%= yield :title -%></div>
> + <div><%= yield :description -%></div>
> +</div>
> <%= yield %>
> +</div>
> diff --git a/wui/src/app/views/layouts/redux.rhtml b/wui/src/app/views/layouts/redux.rhtml
> index 72a49f8..63a9a56 100644
> --- a/wui/src/app/views/layouts/redux.rhtml
> +++ b/wui/src/app/views/layouts/redux.rhtml
> @@ -27,6 +27,9 @@
> <%= javascript_include_tag "ui.slider.js" -%>
> <%= javascript_include_tag "jquery.cookie.js" -%>
> <%= javascript_include_tag "jquery.form.js" -%>
> +
> + <!-- ovirt-specific functions defined here
> + <%= javascript_include_tag "ovirt.js" -%>
> <script type="text/javascript">
> $(document).ready(function(){
> $("#tree").treeview({
> @@ -39,67 +42,10 @@
> action_type: "hyperlink"
> })
> });
> -
> - function get_selected_checkboxes(obj_form)
> - {
> - var selected_array = new Array()
> - var selected_index = 0
> - var checkboxes
> - if (obj_form.grid_checkbox) {
> - if (obj_form.grid_checkbox.length == undefined) {
> - checkboxes = [obj_form.grid_checkbox]
> - } else {
> - checkboxes = obj_form.grid_checkbox
> - }
> - for(var i=0; i < checkboxes.length; i++){
> - if(checkboxes[i].checked)
> - {
> - selected_array[selected_index]= checkboxes[i].value
> - selected_index++
> - }
> - }
> - }
> - return selected_array
> - }
> - function validate_selected(selected_array, name)
> - {
> - if (selected_array.length == 0) {
> - alert("Please select at least one " + name + " to continue")
> - return false
> - } else {
> - return true
> - }
> - }
> - function ajax_validation(response, status)
> - {
> - if (response.object) {
> - $(".fieldWithErrors").removeClass("fieldWithErrors");
> - $("div.errorExplanation").remove();
> - if (!response.success) {
> - for(i=0; i<response.errors.length; i++) {
> - var element = $("div.form_field:has(#"+response.object + "_" + response.errors[i][0]+")");
> - if (element) {
> - element.addClass("fieldWithErrors");
> - for(j=0; j<response.errors[i][1].length; j++) {
> - element.append('<div class="errorExplanation">'+response.errors[i][1][j]+'</div>');
> - }
> - }
> - }
> - }
> - if (response.alert) {
> - alert(response.alert)
> - }
> - }
> - }
> -
> - </script>
> - <script type="text/javascript">
> - jQuery(document).ready(function($) {
> + $(document).ready(function(){
> $('a[rel*=facebox]').facebox()
> })
> - </script>
> - <script type="text/javascript">
> - jQuery(document).ready(function($) {
> + $(document).ready(function(){
> $('a[rel*=close]').trigger('close.facebox')
> })
> </script>
> diff --git a/wui/src/app/views/permission/new.rhtml b/wui/src/app/views/permission/new.rhtml
> index 45dc239..70fb66b 100644
> --- a/wui/src/app/views/permission/new.rhtml
> +++ b/wui/src/app/views/permission/new.rhtml
> @@ -1,7 +1,9 @@
> -<div class="dialog_title_small">
> - <div class="header">Add New User</div>
> - <div>Add a new user to <%= @permission.pool.name %> pool.</div>
> -</div>
> +<%- content_for :title do -%>
> + Add New User
> +<%- end -%>
> +<%- content_for :description do -%>
> + Add a new user to <%= @permission.pool.name %> pool.
> +<%- end -%>
>
> <form method="POST" action="<%= url_for :action => 'create' %>" id="permission_form" >
> <div class="dialog_form">
> @@ -9,28 +11,16 @@
> </div>
> <%= popup_footer("$('#permission_form').submit()", "Create User Permission") %>
> </form>
> +
> <script type="text/javascript">
> $(function() {
> var permissionoptions = {
> target: '<%= url_for :action => 'create' %>', // target element to update
> dataType: 'json',
> - success: afterNewPermission // post-submit callback
> + success: afterPermission // post-submit callback
> };
>
> // bind form using 'ajaxForm'
> $('#permission_form').ajaxForm(permissionoptions);
> });
> -function afterNewPermission(response, status){
> - ajax_validation(response, status)
> - if (response.success) {
> - jQuery(document).trigger('close.facebox');
> - $("#users_grid").flexReload()
> - }
> -}
> </script>
> -
> -<%- content_for :title do -%>
> -<%= "Edit Permissions for #{@permission.pool.name}" %>
> -<%- end -%>
> -
> -
> diff --git a/wui/src/app/views/resources/new.rhtml b/wui/src/app/views/resources/new.rhtml
> index 5897a5e..6d856f7 100644
> --- a/wui/src/app/views/resources/new.rhtml
> +++ b/wui/src/app/views/resources/new.rhtml
> @@ -1,7 +1,9 @@
> -<div class="dialog_title_small">
> - <div class="header">Add New Virtual Machine Pool</div>
> - <div>Add a new Virtual Machine Pool to the <%= @parent.name %> pool.</div>
> -</div>
> +<%- content_for :title do -%>
> + Add New Virtual Machine Pool
> +<%- end -%>
> +<%- content_for :description do -%>
> + Add a new Virtual Machine Pool to the <%= @parent.name %> pool.
> +<%- end -%>
>
> <form method="POST" action="<%= url_for :action => 'create' %>" id="vm_pool_form" >
> <div class="dialog_form">
> @@ -9,27 +11,16 @@
> </div>
> <%= popup_footer("$('#vm_pool_form').submit()", "Create Virtual Machine Pool") %>
> </form>
> - <script type="text/javascript">
> +
> +<script type="text/javascript">
> $(function() {
> var vmpooloptions = {
> target: '<%= url_for :action => 'create' %>', // target element to update
> dataType: 'json',
> - success: afterNewVmPool // post-submit callback
> + success: afterVmPool // post-submit callback
> };
>
> // bind form using 'ajaxForm'
> $('#vm_pool_form').ajaxForm(vmpooloptions);
> });
> -function afterNewVmPool(response, status){
> - ajax_validation(response, status)
> - if (response.success) {
> - jQuery(document).trigger('close.facebox');
> - $("#vmpools_grid").flexReload()
> - }
> -}
> </script>
> -
> -<%- content_for :title do -%>
> -<%= _("New VM Resource Pool") %>
> -<%- end -%>
> -
> diff --git a/wui/src/app/views/resources/show.rhtml b/wui/src/app/views/resources/show.rhtml
> index c42ebeb..ecf8fc6 100644
> --- a/wui/src/app/views/resources/show.rhtml
> +++ b/wui/src/app/views/resources/show.rhtml
> @@ -1,87 +1,9 @@
>
> -<div class="panel_header"></div>
> - <div id="data">
> - <div class="inside">
> -
> - <%= error_messages_for 'vm_actions' %>
> -
> - <div id="dataTableWrapper">
> -
> - <div class="dataTable">
> - <div class="inside">
> -
> - <div class="data-table-column">
> -
> - <% form_tag( {:controller => 'resources', :action => 'vm_actions'}, {:name => "vm_actions", :method => "post"}) do -%>
> - <%= tag :input, { "type" => "hidden", "name" => "vm_actions[vm_resource_pool_id]", "value" => "@vm_resource_pool.id"} %>
> - <%= tag :input, { "type" => "submit", "name" => "vm_actions[#{VmTask::ACTION_START_VM}]", "value" => "Start"} %>
> - <%= tag :input, { "type" => "submit", "name" => "vm_actions[#{VmTask::ACTION_SHUTDOWN_VM}]", "value" => "Stop"} %>
> - <select onchange="return confirm_and_submit(this)" name="vm_actions[other_actions]">
> - <option class="other">Other Actions</option>
> - <option></option>
> - <% for action in @action_values %>
> - <option value="<%= action[1]%>"><%= action[0]%></option>
> - <% end %>
> - <option>-------------------</option>
> - <option class="remove" value="destroy" onSelect="confirm('Are you sure?'">Destroy</option>
> - </select>
> -
> - <br/>
> - <%= render :partial => '/layouts/alertbox' %>
> - <br/>
> -
> - <%= render :partial => "/vm/list", :locals => { :vms => @vm_resource_pool.vms } %>
> - <% end -%>
> -
> - </div>
> -
> - </div> <!-- end #data-table.inside -->
> - </div> <!-- end #dataTable -->
> -
> - <div class="data-section">
> - <div class="data-section-header"><strong>VM Resource Pool Quota</strong></div>
> - <div class="data-section-stats">Statistics Data</div>
> - <div class="data-section-table">
> - <div class="inside">
> -
> - <table>
> - <tr>
> - <th></th>
> - <td> in use / awaiting use / total allowed</td>
> - </tr>
> - <% resources = @vm_resource_pool.full_resources %>
> - <% for item in resources[:labels] %>
> - <% total_limit = resources[:total][item[1]]
> - total_limit = "unlimited" if total_limit.nil? %>
> - <tr>
> - <th><%= item[0]%>:</th>
> - <td><%= resources[:allocated][:current][item[1]] %> / <%= resources[:allocated][:pending][item[1]] %> / <%= total_limit %>
> - <%= item[2]%></td>
> - </tr>
> - <% end %>
> - </table>
> -
> - </div>
> - </div>
> - </div>
> -
> -
> - </div> <!-- end #dataTableWrapper -->
> -
> - </div> <!-- end #data.inside -->
> - </div> <!-- end #data -->
> -
> - </td>
> <td id="right">
> <div class="heading"> </div>
> <div class="tools">
> <h3>Actions</h3>
>
> - <%if @can_modify -%>
> - <div class="actions create">
> - <%= link_to_if @vm_resource_pool, 'Create New VM', {:controller => "vm", :action => 'new', :vm_resource_pool_id => @vm_resource_pool}, { :class => "create" } %>
> - </div>
> - <% end -%>
> <div class="actions">
> <%if @is_hwpool_admin -%>
> <%= link_to 'Edit VM Resource Pool', { :action => 'edit', :id => @vm_resource_pool }, { :class => "edit" } %>
> diff --git a/wui/src/app/views/storage/_grid.rhtml b/wui/src/app/views/storage/_grid.rhtml
> index 56fb621..feb855a 100644
> --- a/wui/src/app/views/storage/_grid.rhtml
> +++ b/wui/src/app/views/storage/_grid.rhtml
> @@ -7,13 +7,14 @@
> $("#<%= table_id %>").flexigrid
> (
> {
> - url: '<%= url_for :controller => "hardware", :action => "storage_pools_json", :id => hwpool_id %>',
> + url: '<%= url_for :controller => "hardware", :action => "storage_pools_json", :id => hwpool_id, :exclude_id => exclude_id %>',
> dataType: 'json',
> colModel : [
> - {display: '', name : 'id', width : 20, sortable : false, align: 'left', process: <%= table_id %>checkbox},
> - {display: 'Alias', name : 'display_name', width : 180, sortable : false, align: 'left'},
> - {display: 'IP', name : 'ip_addr', width : 80, sortable : true, align: 'left'},
> - {display: 'Type', name : 'type', width : 80, sortable : true, align: 'left'}
> + {display: '', width : 20, align: 'left', process: <%= table_id %>checkbox},
> + {display: 'Alias', width : 180, align: 'left'},
> + <%= "{display: 'Hardware Pool', name : 'pools.name', width : 100, align: 'left'}," if exclude_id %>
> + {display: 'IP', name : 'ip_addr', width : 80, align: 'left'},
> + {display: 'Type', name : 'storage_pools.type', width : 80, align: 'left'}
> ],
> sortname: "ip_addr",
> sortorder: "asc",
> diff --git a/wui/src/app/views/storage/addstorage.html.erb b/wui/src/app/views/storage/addstorage.html.erb
> index a747502..e465d12 100644
> --- a/wui/src/app/views/storage/addstorage.html.erb
> +++ b/wui/src/app/views/storage/addstorage.html.erb
> @@ -1,14 +1,17 @@
> -<div class="dialog_title">
> - <div class="header">Add Storage</div>
> - <div>Select storage pools from the list below to add to the <%= @hardware_pool.name %> hardware pool.</div>
> -</div>
> +<%- content_for :title do -%>
> + Add Storage
> +<%- end -%>
> +<%- content_for :description do -%>
> + Select storage pools from the list below to add to the <%= @hardware_pool.name %> hardware pool.
> +<%- end -%>
> +
> +<!-- we'll need this when we combine add/create for storage
> <div id="toolbar_nav">
> <ul>
> <li class="current" id="unassigned_tab"><a href="#" onClick = "show_unassigned()"><img style="vertical-align:middle;" src="" /> Unassigned Storage (<%= @unassigned %>)</a></li>
> <li id="assigned_tab"><a href="#" onClick = "show_assigned()"><img style="vertical-align:middle;" src="" /> Assigned Storage (<%= @assigned %>)</a></li>
> </ul>
> </div>
> -<script type="text/javascript">
> function show_unassigned()
> {
> $("#assigned_tab").removeClass('current');
> @@ -23,34 +26,17 @@
> $("#addstorage_unassigned_grid_div").hide()
> $("#addstorage_assigned_grid_div").show()
> }
> - function add_storage()
> - {
> - var assigned_selected= get_selected_checkboxes(document.addstorage_assigned_grid_form)
> - var unassigned_selected= get_selected_checkboxes(document.addstorage_unassigned_grid_form)
> - var storage = Array.concat(assigned_selected,unassigned_selected)
> - if (validate_selected(storage, "storage pool")) {
> - $.post('<%= url_for :controller => "hardware", :action => "add_storage", :id => @hardware_pool %>',
> - { storage_pool_ids: storage.toString() },
> - function(data,status){
> - jQuery(document).trigger('close.facebox');
> - $("#storage_grid").flexReload()
> - if (data.alert) {
> - alert(data.alert);
> - }
> - }, 'json');
> - }
> - }
> -</script>
> + $("#addstorage_assigned_grid_div").hide()
> +-->
> <div class="panel_header"></div>
> <div class="dialog_body">
> - <%= render :partial => "/storage/grid", :locals => { :table_id => "addstorage_unassigned_grid",
> - :hwpool_id => Pool.root.id, :on_select => "false" } %>
> - <%= render :partial => "/storage/grid", :locals => { :table_id => "addstorage_assigned_grid",
> - :hwpool_id => nil, :on_select => "false" } %>
> + <%= render :partial => "/storage/grid", :locals => { :table_id => "addstorage_grid",
> + :hwpool_id => nil, :exclude_id => @hardware_pool.id,
> + :on_select => "false" } %>
> </div>
> </div>
> -<script type="text/javascript">
> - $("#addstorage_assigned_grid_div").hide()
> -</script>
> -<%= popup_footer("add_storage()", "Add Selected Storage Pools") %>
> +<%= popup_footer("add_storage('#{url_for :controller => 'hardware',
> + :action => 'add_storage',
> + :id => @hardware_pool}')",
> + "Add Selected Storage Pools") %>
>
> diff --git a/wui/src/app/views/storage/new.rhtml b/wui/src/app/views/storage/new.rhtml
> index 6f88ce3..13dedb9 100644
> --- a/wui/src/app/views/storage/new.rhtml
> +++ b/wui/src/app/views/storage/new.rhtml
> @@ -1,51 +1,47 @@
> -<div class="dialog_title_small">
> - <div class="header">Add New Storage Pool</div>
> - <div>Add a new Storage Pool to this oVirt server.</div>
> -</div>
> +<%- content_for :title do -%>
> + Add New Storage Pool
> +<%- end -%>
> +<%- content_for :description do -%>
> + Add a new Storage Pool to this oVirt server.
> +<%- end -%>
>
> <div class="dialog_form">
> <%= error_messages_for 'storage_pool' %>
> - <% form_tag do %>
>
> + <% form_tag do %>
> <!--[form:storage_pool]-->
> <%= hidden_field_tag 'hardware_pool_id', @hardware_pool.id %>
> - <%= select_tag_with_label "Storage Type:", 'storage_type', @storage_types, :onChange => "load_details()" %>
> + <%= select_tag_with_label "Storage Type:", 'storage_type', @storage_types, :onChange => "load_storage_subform()" %>
> <!--[eoform:storage_pool]-->
> <% end %>
>
> <div class="clear_row"></div>
> <div class="clear_row"></div>
> </div>
> -<form method="POST" action="<%= url_for :action => 'create' %>" id="storage_pool_form" >
> -<div class="dialog_form">
> - <div id="new_storage_pool"/>
>
> -</div>
> -<%= popup_footer("$('#storage_pool_form').submit()", "New Storage Pool") %>
> +<form method="POST" action="<%= url_for :action => 'create' %>" id="storage_pool_form" >
> + <div class="dialog_form">
> + <div id="new_storage_pool"/>
> + </div>
> + <%= popup_footer("$('#storage_pool_form').submit()", "New Storage Pool") %>
> </form>
> - <script type="text/javascript">
> - function load_details()
> +
> +<script type="text/javascript">
> + function load_storage_subform()
> {
> $('#new_storage_pool').load('<%= url_for :controller => "storage", :action => "new2" %>',
> { hardware_pool_id: $('[name=hardware_pool_id]').val(), storage_type: $('[name=storage_type]').val()})
> }
> - load_details()
> + load_storage_subform()
> $(function() {
> var storageoptions = {
> target: '<%= url_for :action => 'create' %>', // target element to update
> //beforeSubmit: showStorageRequest, // pre-submit callback
> dataType: 'json',
> - success: afterNewStoragePool // post-submit callback
> + success: afterStoragePool // post-submit callback
> };
>
> // bind form using 'ajaxForm'
> $('#storage_pool_form').ajaxForm(storageoptions);
> });
> -function afterNewStoragePool(response, status){
> - ajax_validation(response, status)
> - if (response.success) {
> - jQuery(document).trigger('close.facebox');
> - $("#storage_grid").flexReload()
> - }
> -}
> - </script>
> +</script>
> diff --git a/wui/src/app/views/vm/new.rhtml b/wui/src/app/views/vm/new.rhtml
> index 60dbcd6..767f806 100644
> --- a/wui/src/app/views/vm/new.rhtml
> +++ b/wui/src/app/views/vm/new.rhtml
> @@ -1,12 +1,9 @@
> -<div id="window">
> -
> +<%- content_for :title do -%>
> + Add Virtual Machine
> +<%- end -%>
> +<%- content_for :description do -%>
> +<%- end -%>
>
> - <!-- DIALOG TITLE -->
> - <div class="dialog_title_small">
> - <div class="header">Add Virtual Machine</div>
> - </div>
> -
> -
> <!-- DIALOG BODY -->
> <form method="POST" action="<%= url_for :action => 'create' %>" id="vm_form" >
> <div class="dialog_form">
> @@ -15,9 +12,6 @@
> <!-- DIALOG FOOTER -->
> <%= popup_footer("$('#vm_form').submit()", "Add Virtual Machine") %>
> </form>
> -</div>
> -
> -
>
> <script type="text/javascript">
> $(function() {
> @@ -25,21 +19,11 @@ $(function() {
> target: '<%= url_for :action => 'create' %>', // target element to update
> //beforeSubmit: showStorageRequest, // pre-submit callback
> dataType: 'json',
> - success: afterNewVm // post-submit callback
> + success: afterVm // post-submit callback
> };
>
> // bind form using 'ajaxForm'
> $('#vm_form').ajaxForm(vmoptions);
> });
> -function afterNewVm(response, status){
> - ajax_validation(response, status)
> - if (response.success) {
> - jQuery(document).trigger('close.facebox');
> - $("#vms_grid").flexReload()
> - }
> -}
> </script>
>
> -<%- content_for :title do -%>
> -<%= "New Virtual Machine" %>
> -<%- end -%>
> diff --git a/wui/src/public/javascripts/ovirt.js b/wui/src/public/javascripts/ovirt.js
> new file mode 100644
> index 0000000..675f20d
> --- /dev/null
> +++ b/wui/src/public/javascripts/ovirt.js
> @@ -0,0 +1,138 @@
> +// ovirt-specific javascript functions are defined here
> +
> +
> +// helper functions for dialogs and action links
> +
> +
> +// returns an array of selected values for flexigrid checkboxes
> +function get_selected_checkboxes(obj_form)
> +{
> + var selected_array = new Array()
> + var selected_index = 0
> + var checkboxes
> + if (obj_form.grid_checkbox) {
> + if (obj_form.grid_checkbox.length == undefined) {
> + checkboxes = [obj_form.grid_checkbox]
> + } else {
> + checkboxes = obj_form.grid_checkbox
> + }
> + for(var i=0; i < checkboxes.length; i++){
> + if(checkboxes[i].checked)
> + {
> + selected_array[selected_index]= checkboxes[i].value
> + selected_index++
> + }
> + }
> + }
> + return selected_array
> +}
> +
> +
> +// make sure that at least one item is selected to continue
> +function validate_selected(selected_array, name)
> +{
> + if (selected_array.length == 0) {
> + alert("Please select at least one " + name + " to continue")
> + return false
> + } else {
> + return true
> + }
> +}
> +
> +function add_hosts(url)
> +{
> + hosts= get_selected_checkboxes(document.addhosts_grid_form)
> + if (validate_selected(hosts, "host")) {
> + $.post(url,
> + { resource_ids: hosts.toString() },
> + function(data,status){
> + jQuery(document).trigger('close.facebox');
> + $("#hosts_grid").flexReload()
> + if (data.alert) {
> + alert(data.alert);
> + }
> + }, 'json');
> + }
> +}
> +function add_storage(url)
> +{
> + storage= get_selected_checkboxes(document.addstorage_grid_form)
> + if (validate_selected(storage, "storage pool")) {
> + $.post(url,
> + { resource_ids: storage.toString() },
> + function(data,status){
> + jQuery(document).trigger('close.facebox');
> + $("#storage_grid").flexReload()
> + if (data.alert) {
> + alert(data.alert);
> + }
> + }, 'json');
> + }
> +}
> +// deal with ajax form response, filling in validation messages where required.
> +function ajax_validation(response, status)
> +{
> + if (response.object) {
> + $(".fieldWithErrors").removeClass("fieldWithErrors");
> + $("div.errorExplanation").remove();
> + if (!response.success) {
> + for(i=0; i<response.errors.length; i++) {
> + var element = $("div.form_field:has(#"+response.object + "_" + response.errors[i][0]+")");
> + if (element) {
> + element.addClass("fieldWithErrors");
> + for(j=0; j<response.errors[i][1].length; j++) {
> + element.append('<div class="errorExplanation">'+response.errors[i][1][j]+'</div>');
> + }
> + }
> + }
> + }
> + if (response.alert) {
> + alert(response.alert)
> + }
> + }
> +}
> +
> +// callback actions for dialog submissions
> +function afterHwPool(response, status){
> + ajax_validation(response, status)
> + if (response.success) {
> + jQuery(document).trigger('close.facebox');
> + // FIXME do we need to reload the tree here
> +
> + // this is for reloading the host/storage grid when
> + // adding hosts/storage to a new HW pool
> + if (response.resource_type) {
> + $('#' + response.resource_type + '_grid').flexReload()
> + }
> + // do we have HW pools grid?
> + //$("#vmpools_grid").flexReload()
> + }
> +}
> +function afterVmPool(response, status){
> + ajax_validation(response, status)
> + if (response.success) {
> + jQuery(document).trigger('close.facebox');
> + $("#vmpools_grid").flexReload()
> + }
> +}
> +function afterStoragePool(response, status){
> + ajax_validation(response, status)
> + if (response.success) {
> + jQuery(document).trigger('close.facebox');
> + $("#storage_grid").flexReload()
> + }
> +}
> +function afterPermission(response, status){
> + ajax_validation(response, status)
> + if (response.success) {
> + jQuery(document).trigger('close.facebox');
> + $("#users_grid").flexReload()
> + }
> +}
> +function afterVm(response, status){
> + ajax_validation(response, status)
> + if (response.success) {
> + jQuery(document).trigger('close.facebox');
> + $("#vms_grid").flexReload()
> + }
> +}
> diff --git a/wui/src/vendor/plugins/betternestedset/lib/#better_nested_set.rb# b/wui/src/vendor/plugins/betternestedset/lib/#better_nested_set.rb#
> deleted file mode 100644
> index d430419..0000000
> --- a/wui/src/vendor/plugins/betternestedset/lib/#better_nested_set.rb#
> +++ /dev/null
> @@ -1,583 +0,0 @@
> -module SymetrieCom
> - module Acts #:nodoc:
> - module NestedSet #:nodoc:
> - def self.included(base)
> - base.extend(ClassMethods)
> - end
> - # This module provides an enhanced acts_as_nested_set mixin for ActiveRecord.
> - # Please see the README for background information, examples, and tips on usage.
> - module ClassMethods
> - # Configuration options are:
> - # * +parent_column+ - Column name for the parent/child foreign key (default: +parent_id+).
> - # * +left_column+ - Column name for the left index (default: +lft+).
> - # * +right_column+ - Column name for the right index (default: +rgt+). NOTE:
> - # Don't use +left+ and +right+, since these are reserved database words.
> - # * +scope+ - Restricts what is to be considered a tree. Given a symbol, it'll attach "_id"
> - # (if it isn't there already) and use that as the foreign key restriction. It's also possible
> - # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
> - # Example: <tt>acts_as_nested_set :scope => 'tree_id = #{tree_id} AND completed = 0'</tt>
> - # * +text_column+ - Column name for the title field (optional). Used as default in the
> - # {your-class}_options_for_select helper method. If empty, will use the first string field
> - # of your model class.
> - def acts_as_nested_set(options = {})
> -
> - options[:scope] = "#{options[:scope]}_id".intern if options[:scope].is_a?(Symbol) && options[:scope].to_s !~ /_id$/
> -
> - write_inheritable_attribute(:acts_as_nested_set_options,
> - { :parent_column => (options[:parent_column] || 'parent_id'),
> - :left_column => (options[:left_column] || 'lft'),
> - :right_column => (options[:right_column] || 'rgt'),
> - :scope => (options[:scope] || '1 = 1'),
> - :text_column => (options[:text_column] || columns.collect{|c| (c.type == :string) ? c.name : nil }.compact.first),
> - :class => self # for single-table inheritance
> - } )
> -
> - class_inheritable_reader :acts_as_nested_set_options
> -
> - if acts_as_nested_set_options[:scope].is_a?(Symbol)
> - scope_condition_method = %(
> - def scope_condition
> - if #{acts_as_nested_set_options[:scope].to_s}.nil?
> - "#{acts_as_nested_set_options[:scope].to_s} IS NULL"
> - else
> - "#{acts_as_nested_set_options[:scope].to_s} = \#{#{acts_as_nested_set_options[:scope].to_s}}"
> - end
> - end
> - )
> - else
> - scope_condition_method = "def scope_condition() \"#{acts_as_nested_set_options[:scope]}\" end"
> - end
> -
> - # no bulk assignment
> - attr_protected acts_as_nested_set_options[:left_column].intern,
> - acts_as_nested_set_options[:right_column].intern,
> - acts_as_nested_set_options[:parent_column].intern
> - # no assignment to structure fields
> - module_eval <<-"end_eval", __FILE__, __LINE__
> - def #{acts_as_nested_set_options[:left_column]}=(x)
> - raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{acts_as_nested_set_options[:left_column]}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead."
> - end
> - def #{acts_as_nested_set_options[:right_column]}=(x)
> - raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{acts_as_nested_set_options[:right_column]}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead."
> - end
> - def #{acts_as_nested_set_options[:parent_column]}=(x)
> - raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{acts_as_nested_set_options[:parent_column]}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead."
> - end
> - #{scope_condition_method}
> - end_eval
> -
> -
> - include SymetrieCom::Acts::NestedSet::InstanceMethods
> - extend SymetrieCom::Acts::NestedSet::ClassMethods
> -
> - # adds the helper for the class
> -# ActionView::Base.send(:define_method, "#{Inflector.underscore(self.class)}_options_for_select") { special=nil
> -# "#{acts_as_nested_set_options[:text_column]} || "#{self.class} id #{id}"
> -# }
> -
> - end
> -
> -
> - # Returns the single root for the class (or just the first root, if there are several).
> - # Deprecation note: the original acts_as_nested_set allowed roots to have parent_id = 0,
> - # so we currently do the same. This silliness will not be tolerated in future versions, however.
> - def root
> - acts_as_nested_set_options[:class].find(:first, :conditions => "(#{acts_as_nested_set_options[:parent_column]} IS NULL OR #{acts_as_nested_set_options[:parent_column]} = 0)")
> - end
> -
> - # Returns the roots and/or virtual roots of all trees. See the explanation of virtual roots in the README.
> - def roots
> - acts_as_nested_set_options[:class].find(:all, :conditions => "(#{acts_as_nested_set_options[:parent_column]} IS NULL OR #{acts_as_nested_set_options[:parent_column]} = 0)", :order => "#{acts_as_nested_set_options[:left_column]}")
> - end
> -
> - # Checks the left/right indexes of all records,
> - # returning the number of records checked. Throws ActiveRecord::ActiveRecordError if it finds a problem.
> - def check_all
> - total = 0
> - transaction do
> - # if there are virtual roots, only call check_full_tree on the first, because it will check other virtual roots in that tree.
> - total = roots.inject(0) {|sum, r| sum + (r[r.left_col_name] == 1 ? r.check_full_tree : 0 )}
> - raise ActiveRecord::ActiveRecordError, "Scope problems or nodes without a valid root" unless acts_as_nested_set_options[:class].count == total
> - end
> - return total
> - end
> -
> - # Re-calculate the left/right values of all nodes. Can be used to convert ordinary trees into nested sets.
> - def renumber_all
> - scopes = []
> - # only call it once for each scope_condition (if the scope conditions are messed up, this will obviously cause problems)
> - roots.each do |r|
> - r.renumber_full_tree unless scopes.include?(r.scope_condition)
> - scopes << r.scope_condition
> - end
> - end
> -
> - # Returns an SQL fragment that matches _items_ *and* all of their descendants, for use in a WHERE clause.
> - # You can pass it a single object, a single ID, or an array of objects and/or IDs.
> - # # if a.lft = 2, a.rgt = 7, b.lft = 12 and b.rgt = 13
> - # Set.sql_for([a,b]) # returns "((lft BETWEEN 2 AND 7) OR (lft BETWEEN 12 AND 13))"
> - # Returns "1 != 1" if passed no items. If you need to exclude items, just use "NOT (#{sql_for(items)})".
> - # Note that if you have multiple trees, it is up to you to apply your scope condition.
> - def sql_for(items)
> - items = [items] unless items.is_a?(Array)
> - # get objects for IDs
> - items.collect! {|s| s.is_a?(acts_as_nested_set_options[:class]) ? s : acts_as_nested_set_options[:class].find(s)}.uniq
> - items.reject! {|e| e.new_record?} # exclude unsaved items, since they don't have left/right values yet
> -
> - return "1 != 1" if items.empty? # PostgreSQL didn't like '0', and SQLite3 didn't like 'FALSE'
> - items.map! {|e| "(#{acts_as_nested_set_options[:left_column]} BETWEEN #{e[acts_as_nested_set_options[:left_column]]} AND #{e[acts_as_nested_set_options[:right_column]]})" }
> - "(#{items.join(' OR ')})"
> - end
> -
> - end
> -
> - # This module provides instance methods for an enhanced acts_as_nested_set mixin. Please see the README for background information, examples, and tips on usage.
> - module InstanceMethods
> - # convenience methods to make the code more readable
> - def left_col_name()#:nodoc:
> - acts_as_nested_set_options[:left_column]
> - end
> - def right_col_name()#:nodoc:
> - acts_as_nested_set_options[:right_column]
> - end
> - def parent_col_name()#:nodoc:
> - acts_as_nested_set_options[:parent_column]
> - end
> - alias parent_column parent_col_name#:nodoc: Deprecated
> - def base_set_class()#:nodoc:
> - acts_as_nested_set_options[:class] # for single-table inheritance
> - end
> -
> - # On creation, automatically add the new node to the right of all existing nodes in this tree.
> - def before_create # already protected by a transaction
> - maxright = base_set_class.maximum(right_col_name, :conditions => scope_condition) || 0
> - self[left_col_name] = maxright+1
> - self[right_col_name] = maxright+2
> - end
> -
> - # On destruction, delete all children and shift the lft/rgt values back to the left so the counts still work.
> - def before_destroy # already protected by a transaction
> - return if self[right_col_name].nil? || self[left_col_name].nil?
> - self.reload # in case a concurrent move has altered the indexes
> - dif = self[right_col_name] - self[left_col_name] + 1
> - base_set_class.delete_all( "#{scope_condition} AND (#{left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})" )
> - base_set_class.update_all("#{left_col_name} = CASE \
> - WHEN #{left_col_name} > #{self[right_col_name]} THEN (#{left_col_name} - #{dif}) \
> - ELSE #{left_col_name} END, \
> - #{right_col_name} = CASE \
> - WHEN #{right_col_name} > #{self[right_col_name]} THEN (#{right_col_name} - #{dif} ) \
> - ELSE #{right_col_name} END",
> - scope_condition)
> - end
> -
> - # By default, records are compared and sorted using the left column.
> - def <=>(x)
> - self[left_col_name] <=> x[left_col_name]
> - end
> -
> - # Deprecated. Returns true if this is a root node.
> - def root?
> - parent_id = self[parent_col_name]
> - (parent_id == 0 || parent_id.nil?) && self[right_col_name] && self[left_col_name] && (self[right_col_name] > self[left_col_name])
> - end
> -
> - # Deprecated. Returns true if this is a child node
> - def child?
> - parent_id = self[parent_col_name]
> - !(parent_id == 0 || parent_id.nil?) && (self[left_col_name] > 1) && (self[right_col_name] > self[left_col_name])
> - end
> -
> - # Deprecated. Returns true if we have no idea what this is
> - def unknown?
> - !root? && !child?
> - end
> -
> - # Returns this record's root ancestor.
> - def root
> - # the BETWEEN clause is needed to ensure we get the right virtual root, if using those
> - base_set_class.find(:first, :conditions => "#{scope_condition} \
> - AND (#{parent_col_name} IS NULL OR #{parent_col_name} = 0) AND (#{self[left_col_name]} BETWEEN #{left_col_name} AND #{right_col_name})")
> - end
> -
> - # Returns the root or virtual roots of this record's tree (a tree cannot have more than one real root). See the explanation of virtual roots in the README.
> - def roots
> - base_set_class.find(:all, :conditions => "#{scope_condition} AND (#{parent_col_name} IS NULL OR #{parent_col_name} = 0)", :order => "#{left_col_name}")
> - end
> -
> - # Returns this record's parent.
> - def parent
> - base_set_class.find(self[parent_col_name]) if self[parent_col_name]
> - end
> -
> - # Returns an array of all parents, starting with the root.
> - def ancestors
> - self_and_ancestors - [self]
> - end
> -
> - # Returns an array of all parents plus self, starting with the root.
> - def self_and_ancestors
> - base_set_class.find(:all, :conditions => "#{scope_condition} AND (#{self[left_col_name]} BETWEEN #{left_col_name} AND #{right_col_name})", :order => left_col_name )
> - end
> -
> - # Returns all the children of this node's parent, except self.
> - def siblings
> - self_and_siblings - [self]
> - end
> -
> - # Returns all the children of this node's parent, including self.
> - def self_and_siblings
> - if self[parent_col_name].nil? || self[parent_col_name].zero?
> - [self]
> - else
> - base_set_class.find(:all, :conditions => "#{scope_condition} AND #{parent_col_name} = #{self[parent_col_name]}", :order => left_col_name)
> - end
> - end
> -
> - # Returns the level of this object in the tree, root level being 0.
> - def level
> - return 0 if self[parent_col_name].nil?
> - base_set_class.count(:conditions => "#{scope_condition} AND (#{self[left_col_name]} BETWEEN #{left_col_name} AND #{right_col_name})") - 1
> - end
> -
> - # Returns the number of nested children of this object.
> - def all_children_count
> - return (self[right_col_name] - self[left_col_name] - 1)/2
> - end
> -
> - # Returns itself and all nested children.
> - # Pass :exclude => item, or id, or [items or id] to exclude one or more items *and* all of their descendants.
> - def full_set(special=nil)
> - if special && special[:exclude]
> - exclude_str = " AND NOT (#{base_set_class.sql_for(special[:exclude])}) "
> - elsif new_record? || self[right_col_name] - self[left_col_name] == 1
> - return [self]
> - endas
> - base_set_class.find(:all, :conditions => "#{scope_condition} #{exclude_str} AND (#{left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})", :order => left_col_name)
> - end
> -
> - # Returns all children and nested children.
> - # Pass :exclude => item, or id, or [items or id] to exclude one or more items *and* all of their descendants.
> - def all_children(special=nil)
> - full_set(special) - [self]
> - end
> -
> - # Returns this record's immediate children.
> - def children
> - base_set_class.find(:all, :conditions => "#{scope_condition} AND #{parent_col_name} = #{self.id}", :order => left_col_name)
> - end
> -
> - # Deprecated
> - alias direct_children children
> -
> - # Returns this record's terminal children (nodes without children).
> - def leaves
> - base_set_class.find(:all, :conditions => "#{scope_condition} AND (#{left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]}) AND #{left_col_name} + 1 = #{right_col_name}", :order => left_col_name)
> - end
> -
> - # Returns the count of this record's terminal children (nodes without children).
> - def leaves_count
> - base_set_class.count(:conditions => "#{scope_condition} AND (#{left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]}) AND #{left_col_name} + 1 = #{right_col_name}")
> - end
> -
> - # Checks the left/right indexes of one node and all descendants.
> - # Throws ActiveRecord::ActiveRecordError if it finds a problem.
> - def check_subtree
> - transaction do
> - self.reload
> - check # this method is implemented via #check, so that we don't generate lots of unnecessary nested transactions
> - end
> - end
> -
> - # Checks the left/right indexes of the entire tree that this node belongs to,
> - # returning the number of records checked. Throws ActiveRecord::ActiveRecordError if it finds a problem.
> - # This method is needed because check_subtree alone cannot find gaps between virtual roots, orphaned nodes or endless loops.
> - def check_full_tree
> - total_nodes = 0
> - transaction do
> - # virtual roots make this method more complex than it otherwise would be
> - n = 1
> - roots.each do |r|
> - raise ActiveRecord::ActiveRecordError, "Gaps between roots in the tree containing record ##{r.id}" if r[left_col_name] != n
> - r.check_subtree
> - n = r[right_col_name] + 1
> - end
> - total_nodes = roots.inject(0) {|sum, r| sum + r.all_children_count + 1 }
> - unless base_set_class.count(:conditions => "#{scope_condition}") == total_nodes
> - raise ActiveRecord::ActiveRecordError, "Orphaned nodes or endless loops in the tree containing record ##{self.id}"
> - end
> - end
> - return total_nodes
> - end
> -
> - # Re-calculate the left/right values of all nodes in this record's tree. Can be used to convert an ordinary tree into a nested set.
> - def renumber_full_tree
> - indexes = []
> - n = 1
> - transaction do
> - for r in roots # because we may have virtual roots
> - n = r.calc_numbers(n, indexes)
> - end
> - for i in indexes
> - base_set_class.update_all("#{left_col_name} = #{i[:lft]}, #{right_col_name} = #{i[:rgt]}", "#{self.class.primary_key} = #{i[:id]}")
> - end
> - end
> - ## reload?
> - end
> -
> - # Deprecated. Adds a child to this object in the tree. If this object hasn't been initialized,
> - # it gets set up as a root node.
> - #
> - # This method exists only for compatibility and will be removed in future versions.
> - def add_child(child)
> - transaction do
> - self.reload; child.reload # for compatibility with old version
> - # the old version allows records with nil values for lft and rgt
> - unless self[left_col_name] && self[right_col_name]
> - if child[left_col_name] || child[right_col_name]
> - raise ActiveRecord::ActiveRecordError, "If parent lft or rgt are nil, you can't add a child with non-nil lft or rgt"
> - end
> - base_set_class.update_all("#{left_col_name} = CASE \
> - WHEN id = #{self.id} \
> - THEN 1 \
> - WHEN id = #{child.id} \
> - THEN 3 \
> - ELSE #{left_col_name} END, \
> - #{right_col_name} = CASE \
> - WHEN id = #{self.id} \
> - THEN 2 \
> - WHEN id = #{child.id} \
> - THEN 4 \
> - ELSE #{right_col_name} END",
> - scope_condition)
> - self.reload; child.reload
> - end
> - unless child[left_col_name] && child[right_col_name]
> - maxright = base_set_class.maximum(right_col_name, :conditions => scope_condition) || 0
> - base_set_class.update_all("#{left_col_name} = CASE \
> - WHEN id = #{child.id} \
> - THEN #{maxright + 1} \
> - ELSE #{left_col_name} END, \
> - #{right_col_name} = CASE \
> - WHEN id = #{child.id} \
> - THEN #{maxright + 2} \
> - ELSE #{right_col_name} END",
> - scope_condition)
> - child.reload
> - end
> -
> - child.move_to_child_of(self)
> - # self.reload ## even though move_to calls target.reload, at least one object in the tests was not reloading (near the end of test_common_usage)
> - end
> - # self.reload
> - # child.reload
> - #
> - # if child.root?
> - # raise ActiveRecord::ActiveRecordError, "Adding sub-tree isn\'t currently supported"
> - # else
> - # if ( (self[left_col_name] == nil) || (self[right_col_name] == nil) )
> - # # Looks like we're now the root node! Woo
> - # self[left_col_name] = 1
> - # self[right_col_name] = 4
> - #
> - # # What do to do about validation?
> - # return nil unless self.save
> - #
> - # child[parent_col_name] = self.id
> - # child[left_col_name] = 2
> - # child[right_col_name]= 3
> - # return child.save
> - # else
> - # # OK, we need to add and shift everything else to the right
> - # child[parent_col_name] = self.id
> - # right_bound = self[right_col_name]
> - # child[left_col_name] = right_bound
> - # child[right_col_name] = right_bound + 1
> - # self[right_col_name] += 2
> - # self.class.transaction {
> - # self.class.update_all( "#{left_col_name} = (#{left_col_name} + 2)", "#{scope_condition} AND #{left_col_name} >= #{right_bound}" )
> - # self.class.update_all( "#{right_col_name} = (#{right_col_name} + 2)", "#{scope_condition} AND #{right_col_name} >= #{right_bound}" )
> - # self.save
> - # child.save
> - # }
> - # end
> - # end
> - end
> -
> - # Move this node to the left of _target_ (you can pass an object or just an id).
> - # Unsaved changes in either object will be lost. Raises ActiveRecord::ActiveRecordError if it encounters a problem.
> - def move_to_left_of(target)
> - self.move_to target, :left
> - end
> -
> - # Move this node to the right of _target_ (you can pass an object or just an id).
> - # Unsaved changes in either object will be lost. Raises ActiveRecord::ActiveRecordError if it encounters a problem.
> - def move_to_right_of(target)
> - self.move_to target, :right
> - end
> -
> - # Make this node a child of _target_ (you can pass an object or just an id).
> - # Unsaved changes in either object will be lost. Raises ActiveRecord::ActiveRecordError if it encounters a problem.
> - def move_to_child_of(target)
> - self.move_to target, :child
> - end
> -
> - protected
> - def move_to(target, position) #:nodoc:
> - raise ActiveRecord::ActiveRecordError, "You cannot move a new node" if new_record?
> - raise ActiveRecord::ActiveRecordError, "You cannot move a node if left or right is nil" unless self[left_col_name] && self[right_col_name]
> -
> - transaction do
> - self.reload # the lft/rgt values could be stale (target is reloaded below)
> - if target.is_a?(base_set_class)
> - target.reload # could be stale
> - else
> - target = base_set_class.find(target) # load object if we were given an ID
> - end
> -
> - if (target[left_col_name] >= self[left_col_name]) && (target[right_col_name] <= self[right_col_name])
> - raise ActiveRecord::ActiveRecordError, "Impossible move, target node cannot be inside moved tree."
> - end
> -
> - # prevent moves between different trees
> - if target.scope_condition != scope_condition
> - raise ActiveRecord::ActiveRecordError, "Scope conditions do not match. Is the target in the same tree?"
> - end
> -
> - # the move: we just need to define two adjoining segments of the left/right index and swap their positions
> - bound = case position
> - when :child then target[right_col_name]
> - when :left then target[left_col_name]
> - when :right then target[right_col_name] + 1
> - else raise ActiveRecord::ActiveRecordError, "Position should be :child, :left or :right ('#{position}' received)."
> - end
> -
> - if bound > self[right_col_name]
> - bound = bound - 1
> - other_bound = self[right_col_name] + 1
> - else
> - other_bound = self[left_col_name] - 1
> - end
> -
> - return if bound == self[right_col_name] || bound == self[left_col_name] # there would be no change, and other_bound is now wrong anyway
> -
> - # we have defined the boundaries of two non-overlapping intervals,
> - # so sorting puts both the intervals and their boundaries in order
> - a, b, c, d = [self[left_col_name], self[right_col_name], bound, other_bound].sort
> -
> - # change nil to NULL for new parent
> - if position == :child
> - new_parent = target.id
> - else
> - new_parent = target[parent_col_name].nil? ? 'NULL' : target[parent_col_name]
> - end
> -
> - base_set_class.update_all("\
> - #{left_col_name} = CASE \
> - WHEN #{left_col_name} BETWEEN #{a} AND #{b} THEN #{left_col_name} + #{d - b} \
> - WHEN #{left_col_name} BETWEEN #{c} AND #{d} THEN #{left_col_name} + #{a - c} \
> - ELSE #{left_col_name} END, \
> - #{right_col_name} = CASE \
> - WHEN #{right_col_name} BETWEEN #{a} AND #{b} THEN #{right_col_name} + #{d - b} \
> - WHEN #{right_col_name} BETWEEN #{c} AND #{d} THEN #{right_col_name} + #{a - c} \
> - ELSE #{right_col_name} END, \
> - #{parent_col_name} = CASE \
> - WHEN #{self.class.primary_key} = #{self.id} THEN #{new_parent} \
> - ELSE #{parent_col_name} END",
> - scope_condition)
> - self.reload
> - target.reload
> - end
> - end
> -
> - def check #:nodoc:
> - # performance improvements (3X or more for tables with lots of columns) by using :select to load just id, lft and rgt
> - ## i don't use the scope condition here, because it shouldn't be needed
> - my_children = base_set_class.find(:all, :conditions => "#{parent_col_name} = #{self.id}",
> - :order => left_col_name, :select => "#{self.class.primary_key}, #{left_col_name}, #{right_col_name}")
> -
> - if my_children.empty?
> - unless self[left_col_name] && self[right_col_name]
> - raise ActiveRecord::ActiveRecordError, "#{self.class.name}##{self.id}.#{right_col_name} or #{left_col_name} is blank"
> - end
> - unless self[right_col_name] - self[left_col_name] == 1
> - raise ActiveRecord::ActiveRecordError, "#{self.class.name}##{self.id}.#{right_col_name} should be 1 greater than #{left_col_name}"
> - end
> - else
> - n = self[left_col_name]
> - for c in (my_children) # the children come back ordered by lft
> - unless c[left_col_name] && c[right_col_name]
> - raise ActiveRecord::ActiveRecordError, "#{self.class.name}##{c.id}.#{right_col_name} or #{left_col_name} is blank"
> - end
> - unless c[left_col_name] == n + 1
> - raise ActiveRecord::ActiveRecordError, "#{self.class.name}##{c.id}.#{left_col_name} should be 1 greater than #{n}"
> - end
> - c.check
> - n = c[right_col_name]
> - end
> - unless self[right_col_name] == n + 1
> - raise ActiveRecord::ActiveRecordError, "#{self.class.name}##{self.id}.#{right_col_name} should be 1 greater than #{n}"
> - end
> - end
> - end
> -
> - # used by the renumbering methods
> - def calc_numbers(n, indexes) #:nodoc:
> - my_lft = n
> - # performance improvements (3X or more for tables with lots of columns) by using :select to load just id, lft and rgt
> - ## i don't use the scope condition here, because it shouldn't be needed
> - my_children = base_set_class.find(:all, :conditions => "#{parent_col_name} = #{self.id}",
> - :order => left_col_name, :select => "#{self.class.primary_key}, #{left_col_name}, #{right_col_name}")
> - if my_children.empty?
> - my_rgt = (n += 1)
> - else
> - for c in (my_children)
> - n = c.calc_numbers(n + 1, indexes)
> - end
> - my_rgt = (n += 1)
> - end
> - indexes << {:id => self.id, :lft => my_lft, :rgt => my_rgt} unless self[left_col_name] == my_lft && self[right_col_name] == my_rgt
> - return n
> - end
> -
> -
> -
> - # The following code is my crude method of making things concurrency-safe.
> - # Basically, we need to ensure that whenever a record is saved, the lft/rgt
> - # values are _not_ written to the database, because if any changes to the tree
> - # structure occurrred since the object was loaded, the lft/rgt values could
> - # be out of date and corrupt the indexes.
> - # I hope that someone with a little more ruby-foo can look at this and come
> - # up with a more elegant solution.
> - private
> - # override ActiveRecord to prevent lft/rgt values from being saved (can corrupt indexes under concurrent usage)
> - def update #:nodoc:
> - connection.update(
> - "UPDATE #{self.class.table_name} " +
> - "SET #{quoted_comma_pair_list(connection, special_attributes_with_quotes(false))} " +
> - "WHERE #{self.class.primary_key} = #{quote_value(id)}",
> - "#{self.class.name} Update"
> - )
> - end
> -
> - # exclude the lft/rgt columns from update statements
> - def special_attributes_with_quotes(include_primary_key = true) #:nodoc:
> - attributes.inject({}) do |quoted, (name, value)|
> - if column = column_for_attribute(name)
> - quoted[name] = quote_value(value, column) unless (!include_primary_key && column.primary) || [acts_as_nested_set_options[:left_column], acts_as_nested_set_options[:right_column]].include?(column.name)
> - end
> - quoted
> - end
> - end
> -
> - # i couldn't figure out how to call attributes_with_quotes without cutting and pasting this private method in. :(
> - # Quote strings appropriately for SQL statements.
> - def quote_value(value, column = nil) #:nodoc:
> - self.class.connection.quote(value, column)
> - end
> -
> - end
> - end
> - end
> -end
> -
> -
> diff --git a/wui/src/vendor/plugins/betternestedset/lib/better_nested_set.rb b/wui/src/vendor/plugins/betternestedset/lib/better_nested_set.rb
> index 43b46ca..ed5d3e3 100644
> --- a/wui/src/vendor/plugins/betternestedset/lib/better_nested_set.rb
> +++ b/wui/src/vendor/plugins/betternestedset/lib/better_nested_set.rb
> @@ -246,24 +246,33 @@ module SymetrieCom
>
> # Returns itself and all nested children.
> # Pass :exclude => item, or id, or [items or id] to exclude one or more items *and* all of their descendants.
> - def full_set(special=nil)
> - if special && special[:exclude]
> - exclude_str = " AND NOT (#{base_set_class.sql_for(special[:exclude])}) "
> + # in addition to the standard find opts
> + def full_set(find_opts={})
> + exclude = find_opts.delete(:exclude)
> + if exclude
> + exclude_str = " AND NOT (#{base_set_class.sql_for(exclude)}) "
> elsif new_record? || self[right_col_name] - self[left_col_name] == 1
> return [self]
> end
> - base_set_class.find(:all, :conditions => "#{scope_condition} #{exclude_str} AND (#{left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})", :order => left_col_name)
> + opts = merge_incoming_opts({:conditions => "#{scope_condition} #{exclude_str} AND (#{left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})",
> + :order => left_col_name},
> + find_opts)
> + base_set_class.find(:all, opts)
> end
>
> # Returns all children and nested children.
> # Pass :exclude => item, or id, or [items or id] to exclude one or more items *and* all of their descendants.
> - def all_children(special=nil)
> - full_set(special) - [self]
> + # in addition to the standard find opts
> + def all_children(find_opts=nil)
> + full_set(find_opts) - [self]
> end
>
> # Returns this record's immediate children.
> - def children
> - base_set_class.find(:all, :conditions => "#{scope_condition} AND #{parent_col_name} = #{self.id}", :order => left_col_name)
> + def children(find_opts={})
> + opts = merge_incoming_opts({:conditions => "#{scope_condition} AND #{parent_col_name} = #{self.id}",
> + :order => left_col_name},
> + find_opts)
> + base_set_class.find(:all, opts)
> end
>
> # Deprecated
> @@ -575,6 +584,14 @@ module SymetrieCom
> self.class.connection.quote(value, column)
> end
>
> + # accept incoming opts to allow filtering of results.
> + # So far only tested in limited use cases encountered in oVirt devel.
> + def merge_incoming_opts(set_opts, incoming_opts)
> + new_conditions = incoming_opts.delete(:conditions)
> + set_opts[:conditions] = "(#{set_opts[:conditions]}) AND (#{new_conditions})" if new_conditions
> + set_opts.merge(incoming_opts)
> + end
> +
> end
> end
> end
> --
> 1.5.4.1
>
This looks like it does what it's supposed to. ACK, and
committed. Note that the add-host-to-pool dialog exposed a bug in the
pager wherein if there are more than 10 hosts you can't click "OK" --
but nothing in this patch caused that bug.
--Hugh
More information about the ovirt-devel
mailing list