Browse Source

tree structure

pull/1/head
Nicolae Claudius 13 years ago
parent
commit
1bb5fbcce7
  1. 9
      Gemfile
  2. 82
      Gemfile.lock
  3. 10
      app/assets/stylesheets/pages/domains.css.scss
  4. 12
      app/controllers/domains_controller.rb
  5. 29
      app/helpers/domains_helper.rb
  6. 33
      app/models/domain/tree_structure.rb
  7. 15
      app/views/domains/_list_record.html.erb
  8. 22
      app/views/domains/_list_record_columns.html.erb
  9. 26
      db/migrate/20120302172252_add_nested_interval_to_domains.rb
  10. 18
      spec/models/domain_spec.rb

9
Gemfile

@ -1,6 +1,6 @@
source 'http://rubygems.org'
gem 'rails', '3.2.0'
gem 'rails', '3.2.1'
# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'
@ -12,8 +12,8 @@ gem 'cancan', '~> 1.6.7'
gem 'squeel', '~> 0.9.3'
gem 'sentient_user', '~> 0.3.2'
gem 'userstamp_basic', '~> 0.1.0'
gem 'active_scaffold', git: 'https://github.com/activescaffold/active_scaffold.git'
# :path => '/home/clyfe/dev/active_scaffold'
gem 'active_scaffold', path: '/home/clyfe/dev/active_scaffold'
# git: 'https://github.com/activescaffold/active_scaffold.git'
# '~> 3.2.0'
gem 'pjax_rails', '~> 0.1.7', git: 'https://github.com/rails/pjax_rails.git'
gem 'validates_hostname', '~> 1.0.0', git: 'https://github.com/KimNorgaard/validates_hostname.git'
@ -27,6 +27,8 @@ gem 'simple_form', '~> 2.0.0'
gem 'concerned_with', '~> 0.1.0'
gem 'navigasmic', '~> 0.5.6', git: 'https://github.com/jejacks0n/navigasmic.git'
gem 'rails-backbone', '~> 0.7.0'
gem 'acts_as_nested_interval', '~> 0.0.1', git: 'https://github.com/clyfe/acts_as_nested_interval.git'
# path: '/home/clyfe/dev/acts_as_nested_interval'
# Gems used only for assets and not required
# in production environments by default.
@ -50,6 +52,7 @@ group :development do
end
group :test, :development do
gem 'sourcify', '~> 0.6.0.rc1'
gem 'rspec-rails', '~> 2.8.1'
gem 'faker','~> 1.0.1'
gem 'factory_girl_rails', '~> 1.6.0'

82
Gemfile.lock

@ -5,11 +5,11 @@ GIT
validates_hostname (1.0.0)
GIT
remote: https://github.com/activescaffold/active_scaffold.git
revision: 86348552c09a64db00e3b5375562c5e4664ebdde
remote: https://github.com/clyfe/acts_as_nested_interval.git
revision: f40731785fd3a0d11e89ce6212db751105b7b086
specs:
active_scaffold (3.2.0)
rails (>= 3.1.3)
acts_as_nested_interval (0.0.1)
rails (~> 3.2.1)
GIT
remote: https://github.com/jejacks0n/navigasmic.git
@ -24,18 +24,24 @@ GIT
pjax_rails (0.1.7)
jquery-rails
PATH
remote: /home/clyfe/dev/active_scaffold
specs:
active_scaffold (3.2.0)
rails (>= 3.1.3)
GEM
remote: http://rubygems.org/
specs:
actionmailer (3.2.0)
actionpack (= 3.2.0)
actionmailer (3.2.1)
actionpack (= 3.2.1)
mail (~> 2.4.0)
actionpack (3.2.0)
activemodel (= 3.2.0)
activesupport (= 3.2.0)
actionpack (3.2.1)
activemodel (= 3.2.1)
activesupport (= 3.2.1)
builder (~> 3.0.0)
erubis (~> 2.7.0)
journey (~> 1.0.0)
journey (~> 1.0.1)
rack (~> 1.4.0)
rack-cache (~> 1.1)
rack-test (~> 0.6.1)
@ -43,18 +49,18 @@ GEM
active-model-email-validator (1.0.2)
activemodel
mail
activemodel (3.2.0)
activesupport (= 3.2.0)
activemodel (3.2.1)
activesupport (= 3.2.1)
builder (~> 3.0.0)
activerecord (3.2.0)
activemodel (= 3.2.0)
activesupport (= 3.2.0)
activerecord (3.2.1)
activemodel (= 3.2.1)
activesupport (= 3.2.1)
arel (~> 3.0.0)
tzinfo (~> 0.3.29)
activeresource (3.2.0)
activemodel (= 3.2.0)
activesupport (= 3.2.0)
activesupport (3.2.0)
activeresource (3.2.1)
activemodel (= 3.2.1)
activesupport (= 3.2.1)
activesupport (3.2.1)
i18n (~> 0.6)
multi_json (~> 1.0)
arel (3.0.2)
@ -117,6 +123,8 @@ GEM
faker (1.0.1)
i18n (~> 0.4)
ffi (1.0.11)
file-tail (1.0.8)
tins (~> 0.3)
fssm (0.2.8.1)
gem_plugin (0.2.3)
guard (1.0.0)
@ -170,23 +178,23 @@ GEM
rack
rack-test (0.6.1)
rack (>= 1.0)
rails (3.2.0)
actionmailer (= 3.2.0)
actionpack (= 3.2.0)
activerecord (= 3.2.0)
activeresource (= 3.2.0)
activesupport (= 3.2.0)
rails (3.2.1)
actionmailer (= 3.2.1)
actionpack (= 3.2.1)
activerecord (= 3.2.1)
activeresource (= 3.2.1)
activesupport (= 3.2.1)
bundler (~> 1.0)
railties (= 3.2.0)
railties (= 3.2.1)
rails-backbone (0.7.0)
coffee-script (~> 2.2.0)
ejs (~> 1.0.0)
railties (>= 3.1.0)
rails_config (0.2.5)
activesupport (>= 3.0)
railties (3.2.0)
actionpack (= 3.2.0)
activesupport (= 3.2.0)
railties (3.2.1)
actionpack (= 3.2.1)
activesupport (= 3.2.1)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
@ -209,6 +217,11 @@ GEM
activesupport (>= 3.0)
railties (>= 3.0)
rspec (~> 2.8.0)
ruby2ruby (1.3.1)
ruby_parser (~> 2.0)
sexp_processor (~> 3.0)
ruby_parser (2.3.1)
sexp_processor (~> 3.0)
rubyzip (0.9.6.1)
sass (3.1.15)
sass-rails (3.2.4)
@ -221,6 +234,7 @@ GEM
multi_json (~> 1.0)
rubyzip
sentient_user (0.3.2)
sexp_processor (3.1.0)
simple_form (2.0.1)
actionpack (~> 3.0)
activemodel (~> 3.0)
@ -228,6 +242,11 @@ GEM
multi_json (~> 1.0)
simplecov-html (~> 0.5.3)
simplecov-html (0.5.3)
sourcify (0.6.0.rc1)
file-tail (>= 1.0.5)
ruby2ruby (>= 1.2.5)
ruby_parser (>= 2.0.5)
sexp_processor (>= 3.0.5)
spork (1.0.0rc2)
sprockets (2.1.2)
hike (~> 1.2)
@ -242,6 +261,7 @@ GEM
libv8 (~> 3.3.10)
thor (0.14.6)
tilt (1.3.3)
tins (0.3.9)
treetop (1.4.10)
polyglot
polyglot (>= 0.3.1)
@ -262,6 +282,7 @@ PLATFORMS
DEPENDENCIES
active-model-email-validator (~> 1.0.2)
active_scaffold!
acts_as_nested_interval (~> 0.0.1)!
bootstrap-sass-rails (~> 2.0.0.2)
cancan (~> 1.6.7)
capistrano (~> 2.9.0)
@ -285,7 +306,7 @@ DEPENDENCIES
navigasmic (~> 0.5.6)!
nilify_blanks (~> 1.0.0)
pjax_rails (~> 0.1.7)!
rails (= 3.2.0)
rails (= 3.2.1)
rails-backbone (~> 0.7.0)
rails_config (~> 0.2.4)
rb-inotify (~> 0.8.8)
@ -294,6 +315,7 @@ DEPENDENCIES
sentient_user (~> 0.3.2)
simple_form (~> 2.0.0)
simplecov
sourcify (~> 0.6.0.rc1)
spork (~> 1.0.0.rc0)
squeel (~> 0.9.3)
switch_user (~> 0.6.0)

10
app/assets/stylesheets/pages/domains.css.scss

@ -3,6 +3,16 @@
.name-column {
font-weight: bold;
text-shadow: none;
.ui-icon {
display: inline-block;
height: 12px;
}
.ui-icon-blank {
background-image: none;
}
.icon-share {
float: right;
}
}
.content-column {
max-width: 200px;

12
app/controllers/domains_controller.rb

@ -3,14 +3,20 @@ class DomainsController < ApplicationController
conf.columns = [:name, :ip, :records, :soa_record, :ns_records]
conf.list.columns = [:name, :records, :permissions]
conf.create.columns = [:name, :ip, :soa_record, :ns_records]
conf.update.columns = [:name, :soa_record]
conf.update.columns = [:name]
conf.columns[:name].description = 'Ex. "domain.com"'
conf.columns[:ip].description = 'Ex. "10.10.5.12", optional IP to associate your domain with'
conf.columns[:ns_records].show_blank_record = false
conf.columns[:permissions].label = 'Sharing'
conf.actions.exclude :show
conf.list.sorting = {:name => :asc}
conf.create.refresh_list = true # because tree structure might change
conf.update.refresh_list = true # because tree structure might change
conf.create.link.label = 'Add Domain'
[:name, :records, :permissions].each do |c|
conf.columns[c].sort = false
end
conf.list.sorting = [{rgt: :desc}, {lftp: :asc}] # preorder
conf.columns[:name].css_class = 'sorted'
# conf.columns[:records].label = 'All Records'
end
@ -46,6 +52,7 @@ class DomainsController < ApplicationController
def after_create_save(record)
@record.reload
flash[:info] = "Domain #{@record.name} was created successfully"
end
def before_update_save(record) # just to make sure of params tampering
@ -54,6 +61,7 @@ class DomainsController < ApplicationController
end
def after_update_save(record)
flash[:info] = "Domain #{@record.name} was updated successfully"
if @name_changed
flash.now[:warning] = "Changing the name of a domain migrates all it's records to the new name!"
end

29
app/helpers/domains_helper.rb

@ -1,2 +1,31 @@
module DomainsHelper
def domain_list_row_class(record)
cannot?(:crud_permissions, record) ? "shared-domain" : ''
end
# Makes a link out of domain name.
# Indents and dedents to create a tree structure,
# assuming that the records are sorted in preorder.
# Adds a visual cue if the record is shared via permissions feature.
def domain_name_column(record)
elements = []
@previous_records ||= []
level = @previous_records.reduce(0) { |acc, r| record.subdomain_of?(r) ? acc + 1 : acc }
if level > 0
(level - 1).times do # indent
elements << '<span class="ui-icon ui-icon-blank"></span>'
end
elements << '<span class="ui-icon ui-icon-carat-1-sw"></span>'
end
elements << link_to(record.name, "http://#{record.name}")
unless can?(:crud_permissions, record)
who = "<strong>#{record.user.name}</strong> #{mail_to(record.user.email)}"
elements << <<-HTM
<i class="icon-share" rel="popover" data-original-title="Shared domain"
data-content="This domain was shared with you by #{h who}"></i>
HTM
end
@previous_records << record
elements.join.html_safe
end
end

33
app/models/domain/tree_structure.rb

@ -1,4 +1,6 @@
class Domain < ActiveRecord::Base
attr_accessor :parent_synced
acts_as_nested_interval virtual_root: true
validate :domain_ownership
def domain_ownership
@ -22,8 +24,11 @@ class Domain < ActiveRecord::Base
before_save do
self.name_reversed = name.reverse if name_changed?
sync_parent
end
after_save :sync_children
# Returns the first immediate parent, if exists (and caches the search)
def parent_domain
return nil if name.nil?
@ -35,6 +40,10 @@ class Domain < ActiveRecord::Base
Domain.where(:name_reversed.matches => "#{name_reversed}.%")
end
def subdomain_of?(domain)
name.end_with?('.' + domain.name)
end
protected
# Returns the first immediate parent, if exists (does not cache the search)
@ -49,4 +58,28 @@ class Domain < ActiveRecord::Base
return nil
end
# Syncs with nested interval when a child is added and parent exists
def sync_parent
if !@parent_synced && parent_domain.present? && self.parent_id != parent_domain.id
self.parent = parent_domain
end
end
# Syncs with nested interval when the parent is added later than the children
def sync_children
descendants = subdomains.preorder.all
first = descendants.first
return true unless first.present?
# only immediate children, rest will chain recursively
depth = first.depth
descendants = descendants.select { |d| d.depth == depth }
subdomains.each do |subdomain|
subdomain.parent = self
subdomain.parent_synced = true # no n+1
subdomain.save!
end
end
end

15
app/views/domains/_list_record.html.erb

@ -1,15 +0,0 @@
<% # customize row class to highlight shared domains
record = list_record if list_record # compat with render :partial :collection
columns ||= list_columns
tr_class = cycle("", "even-record")
tr_class += " shared-domain" if cannot?(:crud_permissions, record)
url_options = params_for(:action => :list, :id => record.id)
action_links ||= active_scaffold_config.action_links.member
-%>
<tr class="record <%= tr_class %>" id="<%= element_row_id(:action => :list, :id => record.id) %>" data-refresh="<%= url_for(params_for(:action => :row, :id => record.id, :_method => :get)).html_safe %>">
<%= render :partial => 'list_record_columns', :locals => {:record => record, :columns => columns} %>
<%= render :partial => 'list_actions', :locals => {:record => record, :url_options => url_options, :action_links => action_links} unless action_links.empty? %>
<%= render_nested_view(action_links, url_options, record) unless @nested_auto_open.nil? %>
</tr>

22
app/views/domains/_list_record_columns.html.erb

@ -3,26 +3,8 @@
<% columns.each do |column| %>
<% authorized = record.authorized_for?(:crud_type => :read, :column => column.name) -%>
<% column_value = authorized ? get_column_value(record, column) : active_scaffold_config.list.empty_field_text -%>
<% can_crud_permissions = can?(:crud_permissions, record) -%>
<% if column.name == :name %>
<td class="<%= column_class(column, column_value, record) %>" >
<% column_value = 'Manage All Records (0)' if column_value == '-' %>
<%= if authorized
link = render_list_column(column_value, column, record)
link_to link, "http://#{link}"
else
column_value
end %>
<% unless can_crud_permissions %>
<% who = "<strong>#{record.user.name}</strong> #{mail_to record.user.email}" %>
<i class="icon-share" style="float: right;" rel="popover"
data-original-title="Shared domain"
data-content="This domain was shared with you by <%=h who %>"
></i>
<% end %>
</td>
<% elsif column.name == :records %>
<% if column.name == :records %>
<td class="<%= column_class(column, column_value, record) %>" >
<% column_value = 'Manage All Records (0)' if column_value == '-' %>
<%= authorized ? render_list_column(column_value, column, record) : column_value %>
@ -32,7 +14,7 @@
<%
if column_value == '-'
column_value = 'Permissions (0)'
authorized &&= can_crud_permissions
authorized &&= can?(:crud_permissions, record)
end
%>
<%= authorized ? render_list_column(column_value, column, record) : column_value %>

26
db/migrate/20120302172252_add_nested_interval_to_domains.rb

@ -0,0 +1,26 @@
Domain.class_exec do
def ancestor_of?(node); false end
def descendants; subdomains end
end
class AddNestedIntervalToDomains < ActiveRecord::Migration
def change
add_column :domains, :parent_id, :integer
add_column :domains, :lftp, :integer, null: false, default: 0
add_column :domains, :lftq, :integer, null: false, default: 0
add_column :domains, :rgtp, :integer, null: false
add_column :domains, :rgtq, :integer, null: false
add_column :domains, :lft, :float, null: false, limit: 53
add_column :domains, :rgt, :float, null: false, limit: 53
add_index :domains, :parent_id
add_index :domains, :lftp
add_index :domains, :lftq
add_index :domains, :lft
add_index :domains, :rgt
Domain.reset_column_information
Domain.inheritance_column = "sti_disabled"
Domain.scoped.each &:save
end
end

18
spec/models/domain_spec.rb

@ -9,7 +9,7 @@ describe Domain do
it "has correct ns records" do
domain.should have(Settings.ns.count).ns_records
for record in domain.ns_records
domain.ns_records.each do |record|
record.should be_persisted
end
end
@ -97,4 +97,20 @@ describe Domain do
domain.name_reversed.should == domain.name.reverse
end
it "nests root's interval corectly" do
User.current = nil
hosts_domain = make_domain(:name => "hosts.com", :user => admin)
domain
subdomain
domain3
other = make_domain(:user => user)
Domain.preorder.map { |d| [d.id, d.lft, d.rgt] }.should == [
[hosts_domain.id, 0.5, 1.0],
[domain.id, 0.3333333333333333, 0.5],
[subdomain.id, 0.4, 0.5],
[domain3.id, 0.25, 0.3333333333333333],
[other.id, 0.2, 0.25]
]
end
end

Loading…
Cancel
Save