zena 1.0.0.beta2 → 1.0.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. data/.gitignore +2 -0
  2. data/History.txt +12 -0
  3. data/app/controllers/application_controller.rb +0 -1
  4. data/app/controllers/columns_controller.rb +11 -1
  5. data/app/controllers/nodes_controller.rb +79 -19
  6. data/app/controllers/versions_controller.rb +0 -2
  7. data/app/controllers/virtual_classes_controller.rb +19 -6
  8. data/app/models/column.rb +5 -1
  9. data/app/models/comment.rb +1 -6
  10. data/app/models/node.rb +21 -3
  11. data/app/models/role.rb +21 -0
  12. data/app/models/site.rb +7 -2
  13. data/app/models/template.rb +3 -3
  14. data/app/models/text_document.rb +4 -4
  15. data/app/models/user.rb +21 -8
  16. data/app/views/columns/_li.html.erb +1 -0
  17. data/app/views/nodes/_groups.rhtml +1 -1
  18. data/app/views/sites/_form.erb +3 -1
  19. data/app/views/sites/_li.erb +1 -0
  20. data/app/views/sites/index.erb +1 -1
  21. data/app/views/virtual_classes/_form.erb +11 -2
  22. data/app/views/virtual_classes/_li.erb +5 -2
  23. data/bin/zena +1 -1
  24. data/bricks/math/lib/bricks/math.rb +1 -1
  25. data/bricks/mongrel/README +3 -0
  26. data/bricks/mongrel/zena/deploy.rb +56 -0
  27. data/bricks/passenger/README +3 -0
  28. data/bricks/passenger/zena/deploy.rb +49 -0
  29. data/config/bricks.yml +6 -0
  30. data/config/deploy.rb +24 -18
  31. data/config/gems.yml +3 -3
  32. data/db/migrate/20100915062903_add_api_group_id_to_site.rb +9 -0
  33. data/lib/tasks/zena.rake +39 -35
  34. data/lib/zena.rb +5 -6
  35. data/lib/zena/acts/enrollable.rb +37 -6
  36. data/lib/zena/app.rb +4 -2
  37. data/lib/zena/deploy.rb +110 -150
  38. data/lib/zena/deploy/awstats.conf.rhtml +4 -4
  39. data/lib/zena/deploy/httpd.rhtml +2 -1
  40. data/lib/zena/deploy/stats.vhost.rhtml +7 -7
  41. data/lib/zena/deploy/vhost.rhtml +1 -1
  42. data/lib/zena/deploy/vhost_www.rhtml +4 -4
  43. data/lib/zena/foxy_parser.rb +6 -5
  44. data/lib/zena/info.rb +1 -1
  45. data/lib/zena/integration/test_case.rb +8 -3
  46. data/lib/zena/parser.rb +11 -11
  47. data/lib/zena/parser/zafu_tags.rb +2 -2
  48. data/lib/zena/remote.rb +16 -0
  49. data/lib/zena/remote/connection.rb +67 -0
  50. data/lib/zena/remote/interface.rb +405 -0
  51. data/lib/zena/remote/klass.rb +14 -0
  52. data/lib/zena/remote/mock.rb +58 -0
  53. data/lib/zena/remote/node.rb +76 -0
  54. data/lib/zena/routes.rb +2 -1
  55. data/lib/zena/use.rb +9 -4
  56. data/lib/zena/use/ajax.rb +3 -3
  57. data/lib/zena/use/authlogic.rb +8 -1
  58. data/lib/zena/use/context.rb +22 -21
  59. data/lib/zena/use/dates.rb +26 -3
  60. data/lib/zena/use/display.rb +33 -5
  61. data/lib/zena/use/forms.rb +90 -12
  62. data/lib/zena/use/fulltext.rb +1 -1
  63. data/lib/zena/use/i18n.rb +118 -31
  64. data/lib/zena/use/query_builder.rb +7 -5
  65. data/lib/zena/use/query_node.rb +30 -4
  66. data/lib/zena/use/rendering.rb +1 -1
  67. data/lib/zena/use/search.rb +10 -7
  68. data/lib/zena/use/urls.rb +3 -3
  69. data/lib/zena/use/zafu_attributes.rb +2 -2
  70. data/lib/zena/use/zafu_eval.rb +1 -1
  71. data/lib/zena/use/zafu_safe_definitions.rb +1 -0
  72. data/lib/zena/use/zafu_templates.rb +1 -1
  73. data/lib/zena/zafu_compiler.rb +5 -1
  74. data/public/javascripts/zena.js +4 -4
  75. data/public/stylesheets/admin.css +1 -0
  76. data/test/custom_queries/complex.host.yml +3 -3
  77. data/test/fixtures/files/translations_fr.yml +4 -1
  78. data/test/functional/application_controller_test.rb +2 -2
  79. data/test/functional/nodes_controller_test.rb +57 -5
  80. data/test/functional/users_controller_test.rb +10 -9
  81. data/test/functional/virtual_classes_controller_test.rb +48 -0
  82. data/test/integration/navigation_test.rb +13 -1
  83. data/test/integration/query_node/filters.yml +5 -0
  84. data/test/integration/query_node_test.rb +1 -1
  85. data/test/integration/zafu_compiler/ajax.yml +13 -19
  86. data/test/integration/zafu_compiler/basic.yml +0 -72
  87. data/test/integration/zafu_compiler/complex.yml +1 -1
  88. data/test/integration/zafu_compiler/complex_ok.yml +19 -0
  89. data/test/integration/zafu_compiler/dates.yml +62 -1
  90. data/test/integration/zafu_compiler/display.yml +4 -4
  91. data/test/integration/zafu_compiler/forms.yml +19 -7
  92. data/test/integration/zafu_compiler/i18n.yml +56 -1
  93. data/test/integration/zafu_compiler/later.yml +23 -1
  94. data/test/integration/zafu_compiler/relations.yml +1 -1
  95. data/test/integration/zafu_compiler/roles.yml +29 -1
  96. data/test/integration/zafu_compiler/safe_definitions.yml +1 -1
  97. data/test/integration/zafu_compiler/zafu_attributes.yml +2 -1
  98. data/test/integration/zafu_compiler_test.rb +5 -3
  99. data/test/sites/zena/columns.yml +3 -0
  100. data/test/sites/zena/roles.yml +0 -1
  101. data/test/sites/zena/sites.yml +1 -0
  102. data/test/sites/zena/versions.yml +2 -0
  103. data/test/unit/node_test.rb +27 -9
  104. data/test/unit/relation_proxy_test.rb +7 -4
  105. data/test/unit/remote_test.rb +379 -0
  106. data/test/unit/user_test.rb +47 -0
  107. data/test/unit/zena/acts/enrollable_test.rb +36 -7
  108. data/test/unit/zena/acts/serializable_test.rb +14 -2
  109. data/test/unit/zena/use/i18n_test.rb +32 -5
  110. data/test/unit/zena/use/query_node_test.rb +13 -1
  111. data/zena.gemspec +25 -11
  112. metadata +24 -10
@@ -48,7 +48,7 @@
48
48
  # If there is several log files from load balancing servers :
49
49
  # Example: "/pathtotools/logresolvemerge.pl *.log |"
50
50
  #
51
- LogFile="/var/www/zena/<%= host %>/log/apache2.access.log"
51
+ LogFile="<%= config[:sites_root] %>/<%= config[:host] %>/log/apache2.access.log"
52
52
 
53
53
 
54
54
  # Enter the log file type you want to analyze.
@@ -150,7 +150,7 @@ LogSeparator=" "
150
150
  # Example: "ftp.domain.com"
151
151
  # Example: "domain.com"
152
152
  #
153
- SiteDomain="<%= host %>"
153
+ SiteDomain="<%= config[:host] %>"
154
154
 
155
155
 
156
156
  # Enter here all other possible domain names, addresses or virtual host
@@ -165,7 +165,7 @@ SiteDomain="<%= host %>"
165
165
  # Note: You can also use @/mypath/myfile if list of aliases are in a file.
166
166
  # Example: "www.myserver.com localhost 127.0.0.1 REGEX[mydomain\.(net|org)$]"
167
167
  #
168
- HostAliases="localhost 127.0.0.1 <%= host %> stats.<%= host %>"
168
+ HostAliases="localhost 127.0.0.1 <%= config[:host] %> stats.<%= config[:host] %>"
169
169
 
170
170
 
171
171
  # If you want to have hosts reported by name instead of ip address, AWStats
@@ -200,7 +200,7 @@ DNSLookup=2
200
200
  # Example: "C:/awstats_data_dir"
201
201
  # Default: "." (means same directory as awstats.pl)
202
202
  #
203
- DirData="/var/www/zena/<%= host %>/log/awstats"
203
+ DirData="<%= config[:sites_root] %>/<%= config[:host] %>/log/awstats"
204
204
 
205
205
 
206
206
  # Relative or absolute web URL of your awstats cgi-bin directory.
@@ -3,7 +3,8 @@
3
3
 
4
4
  NameVirtualHost *
5
5
  <% if config[:app_type] == :passenger %>
6
- LoadModule upload_progress_module <%= config[:app_root] %>/vendor/apache2_upload_progress/mod_upload_progress.so
6
+ LoadModule upload_progress_module /usr/lib/apache2/modules/mod_upload_progress.so
7
+ PassengerDefaultUser www-data
7
8
  <% elsif config[:app_type] == :mongrel %>
8
9
  <Proxy *>
9
10
  Order allow,deny
@@ -1,19 +1,19 @@
1
- # zena awstats vhost for <%= host %>
1
+ # zena awstats vhost for <%= config[:host] %>
2
2
  # automatically generated file
3
3
 
4
4
  <VirtualHost *>
5
- ServerName stats.<%= host %>
5
+ ServerName stats.<%= config[:host] %>
6
6
 
7
7
  DocumentRoot /usr/share/doc/awstats/examples
8
- ErrorLog /var/www/zena/<%= host %>/log/apache2.error.log
9
- CustomLog /var/www/zena/<%= host %>/log/apache2.access.log combined
8
+ ErrorLog <%= config[:sites_root] %>/<%= config[:host] %>/log/apache2.error.log
9
+ CustomLog <%= config[:sites_root] %>/<%= config[:host] %>/log/apache2.access.log combined
10
10
 
11
11
  <location />
12
- SetEnv AWSTATS_FORCE_CONFIG <%= host %>
12
+ SetEnv AWSTATS_FORCE_CONFIG <%= config[:host] %>
13
13
 
14
14
  AuthType Basic
15
- AuthName "<%= host %> stats"
16
- AuthUserFile /var/www/zena/<%= host %>/log/.awstatspw
15
+ AuthName "<%= config[:host] %> stats"
16
+ AuthUserFile <%= config[:sites_root] %>/<%= config[:host] %>/log/.awstatspw
17
17
  Require valid-user
18
18
  </location>
19
19
 
@@ -66,7 +66,7 @@
66
66
  RewriteCond %{REQUEST_FILENAME} !-f
67
67
  RewriteRule ^/(.*)$ balancer://<%= config[:balancer] %>%{REQUEST_URI} [P,QSA,L]
68
68
  <% elsif config[:app_type] == :passenger %>
69
- PassengerAppRoot <% config[:app_root] %>
69
+ PassengerAppRoot <%= config[:app_root] %>
70
70
 
71
71
  <Location />
72
72
  # enable tracking uploads in /
@@ -1,10 +1,10 @@
1
- # zena apache2 vhost for <%= host %>
1
+ # zena apache2 vhost for <%= config[:host] %>
2
2
  # automatically generated file
3
3
 
4
4
  <VirtualHost *>
5
- ServerName www.<%= host %>
5
+ ServerName www.<%= config[:host] %>
6
6
 
7
7
  RewriteEngine On
8
- RewriteCond %{HTTP_HOST} ^www\.<%= host %>$ [NC]
9
- RewriteRule ^(.*)$ http://<%= host %>$1 [R=301,L]
8
+ RewriteCond %{HTTP_HOST} ^www\.<%= config[:host] %>$ [NC]
9
+ RewriteRule ^(.*)$ http://<%= config[:host] %>$1 [R=301,L]
10
10
  </VirtualHost>
@@ -80,10 +80,12 @@ module Zena
80
80
  @options = opts
81
81
  end
82
82
 
83
+ def sites
84
+ @sites ||= Dir["#{Zena::ROOT}/test/sites/*", "#{RAILS_ROOT}/bricks/**/sites/*"].map {|s| File.directory?(s) ? File.basename(s) : nil}.compact.uniq
85
+ end
86
+
83
87
  def run
84
- base = "#{Zena::ROOT}/test/sites"
85
- Dir.foreach(base) do |site|
86
- next if (site =~ /^\./) || !File.directory?(File.join(base, site))
88
+ sites.each do |site|
87
89
  @site = site
88
90
  parse_fixtures
89
91
  after_parse
@@ -834,8 +836,7 @@ module Zena
834
836
  end
835
837
 
836
838
  def run
837
- Dir.foreach("#{Zena::ROOT}/test/sites") do |site|
838
- next if site =~ /^\./ || !File.directory?(File.join("#{Zena::ROOT}/test/sites",site))
839
+ sites.each do |site|
839
840
  @inserted_keys = []
840
841
  out ""
841
842
  out "#{site}:"
data/lib/zena/info.rb CHANGED
@@ -9,6 +9,6 @@ ZENA_CALENDAR_LANGS = ["en", "fr", "de"] # FIXME: build this dynamically from ex
9
9
  ENABLE_XSENDFILE = false
10
10
 
11
11
  module Zena
12
- VERSION = '1.0.0.beta2'
12
+ VERSION = '1.0.0.beta3'
13
13
  ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
14
14
  end
@@ -10,7 +10,7 @@ module Zena
10
10
  Page.logger
11
11
  end
12
12
 
13
- # Mock request to remote service by doing a call to the integration test.
13
+ # Mock ActiveResource request to remote service by doing a call to the integration test.
14
14
  def request(method, path, *arguments)
15
15
  case method
16
16
  when :get, :delete, :head
@@ -21,14 +21,19 @@ module Zena
21
21
  # The params here contain an xml string representing the request body.
22
22
  params = arguments.first
23
23
  end
24
- logger.info "#{method.to_s.upcase} #{site.scheme}://#{site.host}#{path} (#{arguments.last.inspect})" if logger
24
+ test_request(method, path, params, headers)
25
+ end
26
+
27
+ # Mock HTTParty::Request request
28
+ def test_request(method, path, params, headers, parse_response = true)
29
+ logger.info "#{method.to_s.upcase} #{site.scheme}://#{site.host}#{path} (#{headers.inspect})" if logger
25
30
  result = nil
26
31
  ms = Benchmark.ms do
27
32
  @test.send(method, "#{site.scheme}://#{site.host}#{path}", params, headers)
28
33
  result = @test.response
29
34
  end
30
35
  logger.info "--> %d %s (%d %.0fms)" % [result.code, result.message, result.body ? result.body.length : 0, ms] if logger
31
- handle_response(result)
36
+ parse_response ? handle_response(result) : result
32
37
  rescue Timeout::Error => e
33
38
  raise TimeoutError.new(e.message)
34
39
  end
data/lib/zena/parser.rb CHANGED
@@ -14,10 +14,10 @@ module Zena
14
14
  @strings = strings
15
15
  end
16
16
 
17
- def get_template_text(src, current_dir)
18
- current_dir = (current_dir && current_dir != '') ? current_dir[1..-1].split('/') : []
17
+ def get_template_text(src, base_path)
18
+ base_path = (base_path && base_path != '') ? base_path[1..-1].split('/') : []
19
19
  src = src[1..-1] if src[0..0] == '/' # just ignore the 'relative' or 'absolute' tricks.
20
- url = (current_dir + src.split('/')).join('_')
20
+ url = (base_path + src.split('/')).join('_')
21
21
  if test = @strings[url]
22
22
  return [test['src'], url.split('_').join('/')]
23
23
  else
@@ -55,16 +55,16 @@ module Zena
55
55
  def new_with_url(url, opts={})
56
56
  helper = opts[:helper] || ParserModule::DummyHelper.new
57
57
  text, absolute_url = self.get_template_text(url, helper)
58
- current_dir = absolute_url ? absolute_url.split('/')[1..-2].join('/') : nil
59
- self.new(text, :helper=>helper, :current_dir=>current_dir, :included_history=>[absolute_url], :root => url)
58
+ base_path = absolute_url ? absolute_url.split('/')[1..-2].join('/') : nil
59
+ self.new(text, :helper=>helper, :base_path=>base_path, :included_history=>[absolute_url], :root => url)
60
60
  end
61
61
 
62
62
  # Retrieve the template text in the current folder or as an absolute path.
63
63
  # This method is used when 'including' text
64
- def get_template_text(url, helper, current_dir=nil)
64
+ def get_template_text(url, helper, base_path=nil)
65
65
 
66
- if (url[0..0] != '/') && current_dir
67
- url = "#{current_dir}/#{url}"
66
+ if (url[0..0] != '/') && base_path
67
+ url = "#{base_path}/#{url}"
68
68
  end
69
69
 
70
70
  res = helper.send(:get_template_text, url, nil)
@@ -237,7 +237,7 @@ module Zena
237
237
  # fetch text
238
238
  @options[:included_history] ||= []
239
239
 
240
- included_text, absolute_url = self.class.get_template_text(@params[:template], @options[:helper], @options[:current_dir])
240
+ included_text, absolute_url = self.class.get_template_text(@params[:template], @options[:helper], @options[:base_path])
241
241
 
242
242
  if included_text
243
243
  absolute_url += "::#{@params[:part].gsub('/','_')}" if @params[:part]
@@ -246,13 +246,13 @@ module Zena
246
246
  included_text = "<span class='parser_error'>[include] infinity loop: #{(@options[:included_history] + [absolute_url]).join(' --&gt; ')}</span>"
247
247
  else
248
248
  included_history = @options[:included_history] + [absolute_url]
249
- current_dir = absolute_url.split('/')[1..-2].join('/')
249
+ base_path = absolute_url.split('/')[1..-2].join('/')
250
250
  end
251
251
  else
252
252
  return "<span class='parser_error'>[include] template '#{url}' not found</span>"
253
253
  end
254
254
 
255
- res = self.class.new(included_text, :helper=>@options[:helper], :current_dir=>current_dir, :included_history=>included_history, :part => @params[:part], :root=>@options[:root]) # we set :part to avoid loop failure when doing self inclusion
255
+ res = self.class.new(included_text, :helper=>@options[:helper], :base_path=>base_path, :included_history=>included_history, :part => @params[:part], :root=>@options[:root]) # we set :part to avoid loop failure when doing self inclusion
256
256
 
257
257
  if @params[:part]
258
258
  if iblock = res.ids[@params[:part]]
@@ -125,7 +125,7 @@ module Zena
125
125
  $&
126
126
  else
127
127
  quote = $1
128
- new_src = @options[:helper].send(:template_url_for_asset, :current_dir=>@options[:current_dir], :src => $2)
128
+ new_src = @options[:helper].send(:template_url_for_asset, :base_path => @options[:base_path], :src => $2)
129
129
  "url(#{quote}#{new_src}#{quote})"
130
130
  end
131
131
  end
@@ -136,7 +136,7 @@ module Zena
136
136
 
137
137
  src = @params[key]
138
138
  if src && src[0..0] != '/' && src[0..6] != 'http://'
139
- @params[key] = @options[:helper].send(:template_url_for_asset, :src => src, :current_dir => @options[:current_dir], :type => type)
139
+ @params[key] = @options[:helper].send(:template_url_for_asset, :src => src, :base_path => @options[:base_path], :type => type)
140
140
  end
141
141
 
142
142
  res = "<#{@html_tag}#{params_to_html(@params)}"
@@ -0,0 +1,16 @@
1
+ require 'active_support'
2
+ require 'zena/remote/interface'
3
+ require 'zena/remote/klass'
4
+ require 'zena/remote/node'
5
+ require 'zena/remote/connection'
6
+
7
+ module Zena
8
+ module Remote
9
+ extend self
10
+
11
+ # Create a new connection to a remote Zena application
12
+ def connect(uri, token)
13
+ Connection.connect(uri, token)
14
+ end
15
+ end # Remote
16
+ end # Zena
@@ -0,0 +1,67 @@
1
+ require 'HTTParty'
2
+
3
+ module Zena
4
+ module Remote
5
+ class Connection
6
+ def initialize
7
+ end
8
+
9
+ # Return a sub-class of Zena::Remote::Connection with the specified connection tokens built in.
10
+ # We create a new class because HTTParty works this way (class globals).
11
+ def self.connect(uri, token)
12
+ Class.new(self) do
13
+ include HTTParty
14
+ extend Zena::Remote::Interface::ConnectionMethods
15
+
16
+ class << self
17
+ alias http_delete delete
18
+ alias delete destroy
19
+ end
20
+
21
+ @found_classes = {}
22
+ @uri = uri
23
+ @message_logger = STDOUT
24
+
25
+ def self.[](class_name)
26
+ @found_classes[class_name] ||= Zena::Remote::Klass.new(self, class_name)
27
+ end
28
+
29
+ def self.logger
30
+ @logger ||= default_logger
31
+ end
32
+
33
+ def self.log_message(msg)
34
+ logger = @message_logger || self.logger
35
+ if logger.respond_to?(:info)
36
+ logger.info "-\n"
37
+ logger.info " %-10s: %s" % ['operation', 'message']
38
+ logger.info " %-10s: %s" % ['message', msg.inspect]
39
+ else
40
+ @message_logger.send(:puts, msg)
41
+ end
42
+ end
43
+
44
+ def self.message_logger=(logger)
45
+ @message_logger = logger
46
+ end
47
+
48
+ def self.default_logger
49
+ host = URI.parse(@uri =~ %r{^\w+://} ? @uri : "http://#{@uri}").host
50
+ log_path = "log/#{host}.log"
51
+ dir = File.dirname(log_path)
52
+ Dir.mkdir(dir) unless File.exist?(dir)
53
+ Logger.new(File.open(log_path, 'ab'))
54
+ end
55
+
56
+ def self.logger=(logger)
57
+ @logger = logger
58
+ end
59
+
60
+ headers 'Accept' => 'application/xml'
61
+ headers 'HTTP_X_AUTHENTICATION_TOKEN' => token
62
+ base_uri uri
63
+ end
64
+ end
65
+ end
66
+ end # Remote
67
+ end # Zena
@@ -0,0 +1,405 @@
1
+ module Zena
2
+ module Remote
3
+ module Interface
4
+
5
+ module Logger
6
+ def logger
7
+ @connection.logger
8
+ end
9
+
10
+ def log_message(msg)
11
+ @connection.log_message(msg)
12
+ end
13
+ end
14
+
15
+ # Methods to create new remote nodes.
16
+ module Create
17
+ # Used by @connection.create(...)
18
+ module ConnectionMethods
19
+ def create(attributes)
20
+ node = Zena::Remote::Node.new(self, attributes)
21
+ node.save
22
+ node
23
+ end
24
+ end
25
+
26
+ # Used by instance.save
27
+ module InstanceMethods
28
+ def post(*args)
29
+ @connection.post(*args)
30
+ end
31
+ end # InstanceMethods
32
+
33
+ # Used by connection['Post'].find(...)
34
+ module ClassMethods
35
+ def create(attributes)
36
+ node = new(attributes)
37
+ node.save
38
+ node
39
+ end
40
+
41
+ def new(attributes)
42
+ Zena::Remote::Node.new(@connection, attributes.stringify_keys.merge('class' => @name))
43
+ end
44
+ end # ClassMethods
45
+ end # Create
46
+
47
+ # Methods to retrieve remote nodes.
48
+ module Read
49
+ # Used by @connection.find(...)
50
+ module ConnectionMethods
51
+ def find_url
52
+ "/nodes/search"
53
+ end
54
+
55
+ def root
56
+ process_find(:first, 'root', {})
57
+ end
58
+ end
59
+
60
+ # Used by instance.find(...)
61
+ module InstanceMethods
62
+ def find_url
63
+ raise Exception.new("Cannot find from a new instance (no id).") unless id
64
+ "/nodes/#{id}/find"
65
+ end
66
+
67
+ def get(*args)
68
+ @connection.get(*args)
69
+ end
70
+ end
71
+
72
+ # Used by connection['Post'].find(...)
73
+ module ClassMethods
74
+ def find_url
75
+ "/nodes/search"
76
+ end
77
+
78
+ def get(*args)
79
+ @connection.get(*args)
80
+ end
81
+
82
+ def process_find(count, query, options = {})
83
+ if query.kind_of?(Hash)
84
+ query = query.symbolize_keys
85
+ klass = query.delete(:class) || @name
86
+
87
+ query_args = []
88
+
89
+ query.each do |key, value|
90
+ query_args << "#{key} = #{Zena::Db.quote(value)}"
91
+ end
92
+
93
+ query = "nodes where class like #{klass} and #{query_args.join(' and ')} in site"
94
+ elsif query.kind_of?(String)
95
+ # query must be a filter
96
+ query = "nodes where class like #{@name} and #{query} in site"
97
+ elsif query.kind_of?(Fixnum)
98
+ # query is an id
99
+ query = "nodes where class like #{@name} and id = #{query} in site"
100
+ else
101
+ # no filter
102
+ query = "nodes where class like #{@name} in site"
103
+ end
104
+
105
+ super(count, query, options)
106
+ end
107
+
108
+ def all(query = nil, options = {})
109
+ process_find(:all, query, options)
110
+ end
111
+
112
+ def first(query = nil, options = {})
113
+ process_find(:first, query, options)
114
+ end
115
+
116
+ def count(query = nil, options = {})
117
+ process_find(:count, query, options)
118
+ end
119
+ end # ClassMethods
120
+
121
+ # Find remote nodes with query builder or indexed search
122
+ def find(count, query = nil, options = {})
123
+ if query.nil?
124
+ query = count
125
+ count = count.kind_of?(Fixnum) ? :first : :all
126
+ end
127
+ process_find(count, query, options)
128
+ end
129
+
130
+ def search(query, options = {})
131
+ process_find(:all, {:q => query}, options)
132
+ end
133
+
134
+ def all(query, options = {})
135
+ process_find(:all, query, options)
136
+ end
137
+
138
+ def first(query, options = {})
139
+ process_find(:first, query, options)
140
+ end
141
+
142
+ def count(query, options = {})
143
+ process_find(:count, query, options)
144
+ end
145
+
146
+
147
+ private
148
+ def process_find(count, query, options)
149
+ if query.kind_of?(String)
150
+ # Consider string as pseudo sql
151
+ result = get(find_url, :query => options.merge(:qb => query, :_find => count))
152
+
153
+ elsif query.kind_of?(Fixnum)
154
+ # Find by id (== zip)
155
+ result = get("/nodes/#{query}")
156
+
157
+ if node = result['node']
158
+ result = {'nodes' => [node]}
159
+ end
160
+
161
+ else
162
+ # Find from indices title = ..., etc
163
+ result = get(find_url, :query => query.merge(options).merge(:_find => count))
164
+ end
165
+
166
+ if errors = result['errors']
167
+ errors.each do |error|
168
+ log_message error['message']
169
+ end
170
+ end
171
+
172
+ case count
173
+ when :first
174
+ if nodes = result['nodes']
175
+ if found_first = nodes.first
176
+ return build_record(found_first)
177
+ else
178
+ nil
179
+ end
180
+ else
181
+ nil
182
+ end
183
+ when :all
184
+ if nodes = result['nodes']
185
+ return nodes.map do |hash|
186
+ build_record(hash)
187
+ end
188
+ else
189
+ []
190
+ end
191
+ when :count
192
+ if count = result['count']
193
+ return count
194
+ else
195
+ nil
196
+ end
197
+ else
198
+ raise Exception.new("Invalid count should be :all, :first or :count (found #{count.inspect})")
199
+ end
200
+ end
201
+
202
+ def build_record(hash)
203
+ Zena::Remote::Node.new(self, hash)
204
+ end
205
+ end # Read
206
+
207
+ # Methods to update a remote node.
208
+ module Update
209
+ # Used to mass update
210
+ module ConnectionMethods
211
+ def update(query, attributes)
212
+ if nodes = all(query)
213
+ # TODO: ask ?
214
+ logger.info "-\n"
215
+ logger.info " %-10s: %s" % ['operation', 'mass update']
216
+ logger.info " %-10s: %s" % ['timestamp', Time.now]
217
+ logger.info " %-10s: %s" % ['query', query.inspect]
218
+ logger.info " %-10s: %s" % ['count', nodes.size]
219
+ logger.info " change:"
220
+ attributes.each do |key, value|
221
+ logger.info " #{key}: #{value.inspect}"
222
+ end
223
+ nodes.each do |node|
224
+ if node.update_attributes(attributes)
225
+ else
226
+ log_message "Could not update node #{node.id} (#{node.title}): #{node.errors.inspect}"
227
+ end
228
+ end
229
+ nodes
230
+ else
231
+ nil
232
+ end
233
+ end
234
+ end
235
+
236
+ # Used by instance.find(...)
237
+ module InstanceMethods
238
+ def update_url
239
+ node_url
240
+ end
241
+
242
+ def node_url
243
+ "/nodes/#{id}"
244
+ end
245
+
246
+ def create_url
247
+ "/nodes"
248
+ end
249
+
250
+ def put(*args)
251
+ @connection.put(*args)
252
+ end
253
+
254
+ def update_attributes(new_attributes)
255
+ saved_attributes = @attributes.dup
256
+ self.attributes = new_attributes
257
+ if save
258
+ logger.info "-\n"
259
+ logger.info " %-10s: %s" % ['operation', 'update']
260
+ logger.info " %-10s: %s" % ['timestamp', Time.now]
261
+ logger.info " %-10s: %i" % ['node_id', id]
262
+ logger.info " changes:"
263
+ @attributes.keys.each do |key|
264
+ next if saved_attributes[key] == @attributes[key]
265
+ logger.info " #{key}:"
266
+ logger.info " old: #{saved_attributes[key].inspect}"
267
+ logger.info " new: #{@attributes[key].inspect}"
268
+ end
269
+ else
270
+ false
271
+ end
272
+ end
273
+
274
+ def save
275
+ if new_record?
276
+ result = post(create_url, :body => {:node => @attributes})
277
+ else
278
+ result = put(update_url, :body => {:node => @attributes})
279
+ end
280
+
281
+ if result.response.code =~ /^20\d$/
282
+ if node = result['node']
283
+ @attributes = node
284
+ node
285
+ elsif errors = result['errors']
286
+ @errors = errors
287
+ log_message errors
288
+ false
289
+ else
290
+ log_message "Could not save:"
291
+ log_message result.inspect
292
+ false
293
+ end
294
+ elsif errors = result['errors']
295
+ log_message "Could not save:"
296
+ log_message errors
297
+ false
298
+ else
299
+ log_message "Could not save:"
300
+ log_message result.inspect
301
+ false
302
+ end
303
+ end
304
+
305
+ def new_record?
306
+ id.nil?
307
+ end
308
+ end
309
+ end # Update
310
+
311
+ # Methods to delete a remote node.
312
+ module Delete
313
+ module ConnectionMethods
314
+ def destroy(query)
315
+ if nodes = all(query)
316
+ # TODO: ask ?
317
+ logger.info "-\n"
318
+ logger.info " %-10s: %s" % ['operation', 'mass destroy']
319
+ logger.info " %-10s: %s" % ['timestamp', Time.now]
320
+ logger.info " %-10s: %s" % ['query', query.inspect]
321
+ logger.info " %-10s: %s" % ['count', nodes.size]
322
+ nodes.each do |node|
323
+ if node.destroy
324
+ else
325
+ log_message "Could not destroy node #{node.id} (#{node.title}): #{node.errors.inspect}"
326
+ end
327
+ end
328
+ nodes
329
+ else
330
+ nil
331
+ end
332
+ end
333
+ end # ConnectionMethods
334
+
335
+ module InstanceMethods
336
+ def destroy_url
337
+ node_url
338
+ end
339
+
340
+ def destroy
341
+ if id.nil?
342
+ @errors = ["cannot destroy inexistant remote node"]
343
+ return false
344
+ end
345
+ @previous_id = id
346
+ result = @connection.http_delete(destroy_url)
347
+ if result.code == 200
348
+ logger.info " %-10s: %s" % ['operation', 'destroy']
349
+ logger.info " %-10s: %s" % ['timestamp', Time.now]
350
+ logger.info " %-10s: %s" % ['node_id', id]
351
+ logger.info " attributes:"
352
+ @attributes.keys.each do |key|
353
+ logger.info " #{key}: #{@attributes[key].inspect}"
354
+ end
355
+ true
356
+ elsif errors = result['errors']
357
+ @errors = errors
358
+ false
359
+ else
360
+ log_message "Could not destroy.. error:"
361
+ log_message result.inspect
362
+ false
363
+ end
364
+ end
365
+ end # InstanceMethods
366
+ end # Delete
367
+
368
+ # Extends the Connection class
369
+ module ConnectionMethods
370
+ include Create::ConnectionMethods
371
+
372
+ include Read
373
+ include Read::ConnectionMethods
374
+
375
+ include Update::ConnectionMethods
376
+
377
+ include Delete::ConnectionMethods
378
+ end
379
+
380
+ # Included in the Remote::Klass class
381
+ module ClassMethods
382
+ include Logger
383
+
384
+ include Create::ClassMethods
385
+
386
+ include Read
387
+ include Read::ClassMethods
388
+ end
389
+
390
+ # Included in the Remote::Node class
391
+ module InstanceMethods
392
+ include Logger
393
+
394
+ include Create::InstanceMethods
395
+
396
+ include Read
397
+ include Read::InstanceMethods
398
+
399
+ include Update::InstanceMethods
400
+
401
+ include Delete::InstanceMethods
402
+ end
403
+ end # Interface
404
+ end # Remote
405
+ end # Zena