ixtlan 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. data/History.txt +49 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Manifest.txt +73 -48
  4. data/Rakefile +1 -1
  5. data/generators/gwt_ixtlan_datamapper_rspec_scaffold/gwt_ixtlan_datamapper_rspec_scaffold_generator.rb +3 -3
  6. data/generators/gwt_ixtlan_datamapper_rspec_scaffold/templates/AbstractApplicationResourceTestGwt.java +1 -1
  7. data/generators/gwt_ixtlan_datamapper_rspec_scaffold/templates/Fields.java +1 -1
  8. data/generators/gwt_ixtlan_datamapper_rspec_scaffold/templates/Model.java +2 -2
  9. data/generators/gwt_ixtlan_datamapper_rspec_scaffold/templates/ModelFactory.java +2 -2
  10. data/generators/gwt_ixtlan_datamapper_rspec_scaffold/templates/Screen.java +2 -2
  11. data/generators/gwt_ixtlan_datamapper_rspec_scaffold/templates/TestGwt.java +2 -2
  12. data/generators/ixtlan_datamapper_rspec_model/ixtlan_datamapper_rspec_model_generator.rb +1 -1
  13. data/generators/ixtlan_datamapper_rspec_scaffold/ixtlan_datamapper_rspec_scaffold_generator.rb +4 -4
  14. data/generators/ixtlan_datamapper_rspec_scaffold/templates/guard.rb +7 -7
  15. data/generators/ixtlan_datamapper_rspec_scaffold/templates/i18n.rb +1 -1
  16. data/generators/ixtlan_datamapper_rspec_scaffold/templates/layout.html.erb +20 -0
  17. data/ixtlan_rails_templates.rb +537 -0
  18. data/lib/ixtlan/audit_config.rb +5 -5
  19. data/lib/ixtlan/child_path.rb +2 -2
  20. data/lib/ixtlan/cms_script.rb +4 -4
  21. data/lib/ixtlan/controllers/authentications_controller.rb +7 -2
  22. data/lib/ixtlan/controllers/configurations_controller.rb +15 -6
  23. data/lib/ixtlan/controllers/domains_controller.rb +99 -0
  24. data/lib/ixtlan/controllers/groups_controller.rb +105 -0
  25. data/lib/ixtlan/controllers/locales_controller.rb +99 -0
  26. data/lib/ixtlan/controllers/permissions_controller.rb +5 -0
  27. data/lib/ixtlan/controllers/phrases_controller.rb +26 -22
  28. data/lib/ixtlan/controllers/search_query.rb +24 -0
  29. data/lib/ixtlan/controllers/texts_controller.rb +5 -5
  30. data/lib/ixtlan/controllers/users_controller.rb +117 -0
  31. data/lib/ixtlan/controllers/word_bundles_controller.rb +13 -8
  32. data/lib/ixtlan/digest.rb +3 -3
  33. data/lib/ixtlan/guard.rb +11 -12
  34. data/lib/ixtlan/logger_config.rb +11 -11
  35. data/lib/ixtlan/mailer/error_notification.erb +1 -0
  36. data/lib/ixtlan/mailer/password.erb +1 -0
  37. data/lib/ixtlan/mailer.rb +27 -0
  38. data/lib/ixtlan/models/authentication.rb +9 -6
  39. data/lib/ixtlan/models/configuration.rb +21 -37
  40. data/lib/ixtlan/models/configuration_locale.rb +3 -3
  41. data/lib/ixtlan/models/domain.rb +44 -0
  42. data/lib/ixtlan/models/domain_group_user.rb +22 -0
  43. data/lib/ixtlan/models/group.rb +82 -16
  44. data/lib/ixtlan/models/group_locale_user.rb +4 -4
  45. data/lib/ixtlan/models/group_user.rb +7 -7
  46. data/lib/ixtlan/models/i18n_text.rb +26 -26
  47. data/lib/ixtlan/models/locale.rb +17 -5
  48. data/lib/ixtlan/models/permission.rb +3 -2
  49. data/lib/ixtlan/models/phrase.rb +15 -15
  50. data/lib/ixtlan/models/role.rb +5 -5
  51. data/lib/ixtlan/models/translation.rb +9 -9
  52. data/lib/ixtlan/models/update_children.rb +74 -0
  53. data/lib/ixtlan/models/user.rb +108 -16
  54. data/lib/ixtlan/models/word.rb +2 -1
  55. data/lib/ixtlan/models.rb +1 -0
  56. data/lib/ixtlan/modified_by.rb +9 -7
  57. data/lib/ixtlan/monkey_patches.rb +5 -5
  58. data/lib/ixtlan/optimistic_persistence.rb +2 -2
  59. data/lib/ixtlan/optimistic_persistence_module.rb +3 -3
  60. data/lib/ixtlan/optimistic_persistence_validation.rb +2 -2
  61. data/lib/ixtlan/passwords.rb +15 -13
  62. data/lib/ixtlan/rails/error_handling.rb +41 -40
  63. data/lib/ixtlan/rails/guard.rb +0 -1
  64. data/lib/ixtlan/rails/migrations.rb +75 -0
  65. data/lib/ixtlan/rails/session_timeout.rb +16 -14
  66. data/lib/ixtlan/rails/timestamps_modified_by_filter.rb +1 -1
  67. data/lib/ixtlan/rails/unrestful_authentication.rb +4 -4
  68. data/lib/ixtlan/rolling_file.rb +3 -3
  69. data/lib/ixtlan/session.rb +4 -3
  70. data/lib/ixtlan/user_logger.rb +13 -9
  71. data/lib/ixtlan/version.rb +1 -1
  72. data/spec/authentication_spec.rb +1 -1
  73. data/spec/guard_spec.rb +4 -4
  74. data/spec/guards/samples.rb +7 -7
  75. data/spec/modified_by_spec.rb +82 -0
  76. data/spec/optimistic_persistence_spec.rb +58 -0
  77. data/spec/phrase_spec.rb +119 -0
  78. data/spec/session_timeout_spec.rb +59 -0
  79. data/spec/spec_helper.rb +6 -5
  80. data/spec/text_collection_spec.rb +88 -0
  81. data/spec/text_spec.rb +105 -0
  82. data/spec/unrestful_authentication_spec.rb +142 -0
  83. data/spec/user_logger_spec.rb +105 -0
  84. data/spec/user_spec.rb +249 -0
  85. data/whitespace.rb +31 -0
  86. metadata +76 -50
  87. data/lib/ixtlan/error_notifier/error_notification.rhtml +0 -1
@@ -9,7 +9,7 @@ module Ixtlan
9
9
  end
10
10
 
11
11
  def internal_server_error(exception)
12
- dump_error(exception, Object.full_const_get(::Ixtlan::Models.CONFIGURATION).instance)
12
+ dump_error(exception)
13
13
  status = :internal_server_error
14
14
  error_page(:internal_server_error, exception) do |exception|
15
15
  "internal server error: #{exception.class.name}"
@@ -17,19 +17,27 @@ module Ixtlan
17
17
  end
18
18
 
19
19
  def error_page(status, exception, &block)
20
- respond_to do |format|
21
- format.html {
20
+ respond_to do |format|
21
+ format.html {
22
22
  @notice = block.call(exception)
23
23
  if logged_in?
24
- render :template => "errors/error", :status => status
24
+ render_error_page_with_session(status)
25
25
  else
26
- render :template => "sessions/login", :status => status
26
+ render_error_page(status)
27
27
  end
28
28
  }
29
29
  format.xml { head status }
30
30
  end
31
31
  end
32
32
 
33
+ def render_error_page_with_session(status)
34
+ render :template => "errors/error", :status => status
35
+ end
36
+
37
+ def render_error_page(status)
38
+ render :template => "sessions/login", :status => status
39
+ end
40
+
33
41
  def page_not_found(exception)
34
42
  log_user_error(exception)
35
43
  status = rescue_responses[exception.class.name]
@@ -39,44 +47,37 @@ module Ixtlan
39
47
 
40
48
  def stale_resource(exception)
41
49
  log_user_error(exception)
42
- respond_to do |format|
43
- format.html {
50
+ respond_to do |format|
51
+ format.html {
44
52
  render :template => "errors/stale", :status => :conflict
45
53
  }
46
54
  format.xml { head :conflict }
47
55
  end
48
56
  end
49
57
 
50
- def dump_error(exception, config)
58
+ def dump_error(exception)
51
59
  log_user_error(exception)
52
- dumper = DumpError.new(config.notification_sender_email,
53
- config.notification_recipient_email)
60
+ begin
61
+ config = Object.full_const_get(::Ixtlan::Models::CONFIGURATION).instance
62
+ dumper = DumpError.new(config.notification_sender_email,
63
+ config.notification_recipient_emails,
64
+ config.errors_dump_directory)
65
+ rescue => e
66
+ # maybe that gives a double entry in the log file
67
+ log_user_error(e)
68
+ # at least dump the file !!
69
+ dumper = DumpError.new
70
+ end
54
71
  dumper.dump(self, exception)
55
72
  end
56
73
  end
57
-
58
- class ErrorNotifier < ActionMailer::Base
59
- require 'pathname'
60
-
61
- def error_notification(email_from, email_to, exception, error_file)
62
- @subject = exception.to_s
63
- @body = {:text => "#{error_file}"}
64
- @recipients = email_to
65
- @from = email_from
66
- @sent_on = Time.now
67
- @headers = {}
68
- path = Pathname(__FILE__).parent.dirname.to_s
69
- view_paths << path unless view_paths.member? path
70
- @template = "error_notification.rhtml"
71
- end
72
74
 
73
- end
74
-
75
75
  class DumpError
76
-
77
- def initialize(email_from, email_to, errors_dir = "#{RAILS_ROOT}/log/errors")
78
- FileUtils.mkdir_p(errors_dir)
79
- @errors_dir = File.new(errors_dir)
76
+
77
+ def initialize(email_from = nil, email_to = nil, errors_dir = nil)
78
+ errors_dir ||= "#{RAILS_ROOT}/log/errors"
79
+ FileUtils.mkdir_p(errors_dir || "#{RAILS_ROOT}/log/errors")
80
+ @errors_dir = Dir.new(errors_dir)
80
81
  @email_from = email_from
81
82
  @email_to = email_to
82
83
  end
@@ -84,35 +85,35 @@ module Ixtlan
84
85
  def dump(controller, exception)
85
86
  time = Time.now
86
87
  error_log_id = "#{time.tv_sec}#{time.tv_usec}"
87
-
88
+
88
89
  log_file = File.join(@errors_dir.path.to_s, "error-#{error_log_id}.log")
89
90
  logger = Logger.new(log_file)
90
-
91
+
91
92
  dump_environment(logger, exception, controller)
92
- ErrorNotifier.deliver_error_notification(@email_from, @email_to, exception, log_file) unless @email_to.blank?
93
+ Ixtlan::Mailer.deliver_error_notification(@email_from, @email_to, exception, log_file) unless (@email_to.blank? || @email_from.blank?)
93
94
  log_file
94
- end
95
+ end
95
96
 
96
97
  private
97
98
 
98
99
  def dump_environment_header(logger, header)
99
100
  logger.error("\n===================================================================\n#{header}\n===================================================================\n");
100
101
  end
101
-
102
+
102
103
  def dump_environment(logger, exception, controller)
103
104
  dump_environment_header(logger, "REQUEST DUMP");
104
105
  dump_hashmap(logger, controller.request.env)
105
-
106
+
106
107
  dump_environment_header(logger, "RESPONSE DUMP");
107
108
  dump_hashmap(logger, controller.response.headers)
108
-
109
+
109
110
  dump_environment_header(logger, "SESSION DUMP");
110
111
  dump_hashmap(logger, controller.session)
111
-
112
+
112
113
  dump_environment_header(logger, "PARAMETER DUMP");
113
114
  map = {}
114
115
  dump_hashmap(logger, controller.params.each{ |k,v| map[k]=v })
115
-
116
+
116
117
  dump_environment_header(logger, "EXCEPTION");
117
118
  logger.error("#{exception.class}:#{exception.message}")
118
119
  logger.error("\t" + exception.backtrace.join("\n\t"))
@@ -8,4 +8,3 @@ module Erector
8
8
  include Ixtlan::Allowed
9
9
  end
10
10
  end
11
-
@@ -0,0 +1,75 @@
1
+ module Ixtlan
2
+ module Rails
3
+ class Migrations
4
+
5
+ # assume no namespaces !!!
6
+ USER = Object.const_get(Ixtlan::Models::USER)
7
+ GROUP = Object.const_get(Ixtlan::Models::GROUP)
8
+ LOCALE = Object.const_get(Ixtlan::Models::LOCALE)
9
+ DOMAIN = Object.const_get(Ixtlan::Models::DOMAIN)
10
+ CONFIGURATION = Object.const_get(Ixtlan::Models::CONFIGURATION)
11
+
12
+ def self.create_user
13
+ USER.auto_migrate!
14
+ LOCALE.auto_migrate!
15
+ GROUP.auto_migrate!
16
+ Ixtlan::Models::GroupUser.auto_migrate!
17
+ Ixtlan::Models::GroupLocaleUser.auto_migrate!
18
+ Ixtlan::Models::DomainGroupUser.auto_migrate!
19
+
20
+ u = USER.new(:login => 'root', :email => 'root@example.com', :name => 'Superuser', :id => 1)
21
+ u.created_at = DateTime.now
22
+ u.updated_at = u.created_at
23
+ u.created_by_id = 1
24
+ u.updated_by_id = 1
25
+ u.reset_password
26
+ u.save!
27
+
28
+ g = GROUP.create(:name => 'root', :current_user => u)
29
+ u.groups << g
30
+ u.save
31
+
32
+ a = USER.create(:login => 'admin', :email => 'admin@example.com', :name => 'Administrator', :id => 2, :current_user => u)
33
+ a.reset_password
34
+ a.save!
35
+ a.groups << GROUP.create(:name => 'admin', :current_user => u)
36
+ a.save
37
+
38
+ users = GROUP.create(:name => 'users', :current_user => u)
39
+ locales = GROUP.create(:name => 'locales', :current_user => u)
40
+ domains = GROUP.create(:name => 'domains', :current_user => u)
41
+
42
+ File.open("root", 'w') { |f| f.puts "root\n#{u.password}\n\nadmin\n#{a.password}\n\n" }
43
+ end
44
+
45
+ def self.create_configuration
46
+ Ixtlan::Models::ConfigurationLocale.auto_migrate!
47
+ CONFIGURATION.auto_migrate!
48
+ CONFIGURATION.create(:session_idle_timeout => 10, :keep_audit_logs => 3, :current_user => USER.first)
49
+ end
50
+
51
+ def self.create_locale
52
+ LOCALE.auto_migrate!
53
+ # get/create default locale
54
+ LOCALE.create(:code => LOCALE::DEFAULT, :current_user => USER.first)
55
+ # get/create "every" locale
56
+ LOCALE.create(:code => LOCALE::ALL, :current_user => USER.first)
57
+
58
+ # root user has access to ALL locales
59
+ Ixtlan::Models::GroupLocaleUser.create(:group => Group.first, :user => USER.first, :locale => LOCALE.every)
60
+ end
61
+
62
+ def self.create_domain
63
+ DOMAIN.auto_migrate!
64
+ # get/create "every" domain
65
+ DOMAIN.create(:name => DOMAIN::ALL, :current_user => User.first)
66
+
67
+ Ixtlan::Models::DomainGroupUser.create(:group => Group.first, :user => USER.first, :domain => DOMAIN.every)
68
+ end
69
+
70
+ def self.create_text
71
+ I18nText.auto_migrate!
72
+ end
73
+ end
74
+ end
75
+ end
@@ -5,7 +5,7 @@ module Ixtlan
5
5
  def self.included(base)
6
6
  base.send(:include, InstanceMethods)
7
7
  end
8
-
8
+
9
9
  module InstanceMethods
10
10
  private
11
11
 
@@ -14,16 +14,16 @@ module Ixtlan
14
14
  def session_user_logger
15
15
  @session_user_logger ||= UserLogger.new(Ixtlan::Rails::SessionTimeout)
16
16
  end
17
-
17
+
18
18
  def expire_session
19
19
  session.clear
20
20
  # reset_session
21
21
  render_session_timeout
22
22
  return false
23
23
  end
24
-
24
+
25
25
  public
26
-
26
+
27
27
  def check_session_expiry
28
28
  if !session[:expires_at].nil? and session[:expires_at] < DateTime.now
29
29
  # Session has expired.
@@ -35,23 +35,25 @@ module Ixtlan
35
35
  return true
36
36
  end
37
37
  end
38
-
38
+
39
+ # IP binding is not very useful in the wild since some ISP use
40
+ # a different IP for each request, i.e. the session uses many IPs
39
41
  def check_session_ip_binding
40
42
  if !session[:session_ip].nil? and session[:session_ip] != request.headers['REMOTE_ADDR']
41
43
  # client IP has changed
42
44
  session_user_logger.log(self, "IP changed from #{session[:session_ip]} to #{request.headers['REMOTE_ADDR']}")
43
45
  expire_session
44
46
  else
45
- # Assign client IP
47
+ # Assign client IP
46
48
  session[:session_ip] = request.headers['REMOTE_ADDR']
47
49
  return true
48
50
  end
49
51
  end
50
-
52
+
51
53
  def check_session
52
- check_session_ip_binding and check_session_expiry
54
+ check_session_browser_signature && check_session_expiry
53
55
  end
54
-
56
+
55
57
  def check_session_browser_signature
56
58
  if !session[:session_browser_signature].nil? and session[:session_browser_signature] != retrieve_browser_signature
57
59
  # browser signature has changed
@@ -64,24 +66,24 @@ module Ixtlan
64
66
  return true
65
67
  end
66
68
  end
67
-
69
+
68
70
  def retrieve_browser_signature
69
71
  [request.headers['HTTP_USER_AGENT'],
70
72
  request.headers['HTTP_ACCEPT_LANGUAGE'],
71
73
  request.headers['HTTP_ACCEPT_ENCODING'],
72
74
  request.headers['HTTP_ACCEPT']].join('|')
73
75
  end
74
-
76
+
75
77
  def render_session_timeout
76
- respond_to do |format|
77
- format.html {
78
+ respond_to do |format|
79
+ format.html {
78
80
  @notice = "session timeout" unless @notice
79
81
  render :template => "sessions/login"
80
82
  }
81
83
  format.xml { head :unauthorized }
82
84
  end
83
85
  end
84
-
86
+
85
87
  def session_timeout
86
88
  CONFIG.instance.session_idle_timeout
87
89
  end
@@ -6,7 +6,7 @@ module Ixtlan
6
6
  base.prepend_around_filter(Filter)
7
7
  end
8
8
  end
9
-
9
+
10
10
  class Filter
11
11
  def self.filter(controller)
12
12
  name = controller.params[:controller]
@@ -21,7 +21,7 @@ module Rails
21
21
  def current_user
22
22
  session[:user] = login_from_session if logged_in? and session[:user].nil?
23
23
  session[:user]
24
- end
24
+ end
25
25
 
26
26
  def current_user=(new_user)
27
27
  session[:user_id] = new_user ? new_user.id : nil
@@ -45,13 +45,13 @@ module Rails
45
45
  end
46
46
  else
47
47
  case(request.method)
48
- when :get
48
+ when :get
49
49
  session.clear
50
50
  render_login_page
51
51
  when :post
52
52
  user = login_from_params
53
53
  if user.instance_of? String
54
- authentication_logger.log_user(params[:login], user + " from IP #{request.headers['REMOTE_ADDR']}")
54
+ authentication_logger.log_user(params[:login] || params[:authentication][:login], user + " from IP #{request.headers['REMOTE_ADDR']}")
55
55
  session.clear
56
56
  render_access_denied
57
57
  else
@@ -128,7 +128,7 @@ module Rails
128
128
  end
129
129
  format.xml { head :ok }
130
130
  end
131
- end
131
+ end
132
132
  end
133
133
  end
134
134
  end
@@ -1,9 +1,9 @@
1
1
  require 'logging'
2
2
 
3
3
  module Ixtlan
4
-
4
+
5
5
  class RollingFile < ::Logging::Appenders::RollingFile
6
-
6
+
7
7
  def current_logfile
8
8
  "#{@filename_base}_#{Time.now.strftime(@date_pattern)}.#{@extension}"
9
9
  end
@@ -31,7 +31,7 @@ module Ixtlan
31
31
  ::File.delete file
32
32
  end
33
33
  end
34
- end
34
+ end
35
35
 
36
36
  def write( event )
37
37
  str = event.instance_of?(::Logging::LogEvent) ?
@@ -1,7 +1,7 @@
1
1
  require 'rack_datamapper/session/abstract/store'
2
2
  module Ixtlan
3
3
  class Session < DataMapper::Session::Abstract::Session
4
-
4
+
5
5
  def data=(data)
6
6
  d = {}
7
7
  data.each{|k,v| d[k.to_sym] = v}
@@ -10,9 +10,10 @@ module Ixtlan
10
10
  @expires_at = d.delete(:expires_at)
11
11
  attribute_set(:data, ::Base64.encode64(Marshal.dump(d)))
12
12
  end
13
-
13
+
14
14
  def data
15
- Marshal.load(::Base64.decode64(attribute_get(:data))).merge({:user => @user, :flash => @flash, :expires_at => @expires_at})
15
+ # user string for flash entry to allow the rails falsh to work properly !
16
+ Marshal.load(::Base64.decode64(attribute_get(:data))).merge({:user => @user, "flash" => @flash, :expires_at => @expires_at})
16
17
  end
17
18
  end
18
19
  end
@@ -1,20 +1,20 @@
1
1
  require 'slf4r'
2
2
  module Ixtlan
3
-
3
+
4
4
  class UserLogger
5
-
5
+
6
6
  def initialize(arg)
7
7
  @logger = Slf4r::LoggerFacade.new(arg)
8
8
  end
9
9
 
10
- private
10
+ private
11
11
 
12
12
  def login_from(controller)
13
13
  user = controller.respond_to?(:current_user) ? controller.send(:current_user) : nil
14
14
  user.nil? ? nil: user.login
15
15
  end
16
16
 
17
- public
17
+ public
18
18
 
19
19
  def log(controller, message = nil, &block)
20
20
  log_user(login_from(controller), message, &block)
@@ -30,20 +30,24 @@ module Ixtlan
30
30
  else
31
31
  audit = controller.instance_variable_get("@#{controller.params[:controller].singular.to_sym}")
32
32
  if(audit)
33
- "#{controller.params[:controller]}##{controller.params[:action]} #{audit.model}(#{audit.key})#{as_xml}#{message}"
33
+ errors = if(audit.respond_to?(:errors) && !audit.errors.empty?)
34
+ " - errors: " + audit.errors.full_messages.join(", ")
35
+ end
36
+ audit_log = audit.respond_to?(:to_log) ? audit.to_log : "#{audit.model}(#{audit.key})"
37
+ "#{controller.params[:controller]}##{controller.params[:action]} #{audit_log}#{as_xml}#{message}#{errors}"
34
38
  else
35
39
  "#{controller.params[:controller]}##{controller.params[:action]}#{as_xml}#{message}"
36
40
  end
37
41
  end
38
42
  else
39
- "params=#{controller.params.inspect}"
43
+ "params=#{controller.params.inspect}#{message}"
40
44
  end
41
45
  end
42
46
  end
43
-
47
+
44
48
  def log_user(user, message = nil, &block)
45
- user = "???" unless user
46
- @logger.info {"[#{user}] #{message}#{block.call if block}" }
49
+ user ||= "???"
50
+ @logger.info {"[#{user}] #{message}#{block.call if block}" }
47
51
  end
48
52
  end
49
53
  end
@@ -1,3 +1,3 @@
1
1
  class Ixtlan
2
- VERSION = '0.2.4'.freeze
2
+ VERSION = '0.3.0'.freeze
3
3
  end
@@ -24,7 +24,7 @@ describe Ixtlan::Models::Authentication do
24
24
 
25
25
  it "should" do
26
26
  xml = @authentication.to_xml
27
- xml.gsub!(/[0-9-]{10}T[0-9+-:]{14}/, "").gsub!(/ type='[^']+'/, '').gsub!(/<created_at><\/created_at>/, "<created_at/>").should == "<authentication><login>marvin2</login><user><id>1356</id><login>marvin2</login><name>marvin the robot</name><email>marvin@universe.example.com</email><language>xx</language><created_at/><updated_at></updated_at><created_by_id>1356</created_by_id><updated_by_id>1356</updated_by_id><groups><group><id>1356</id><name>marvin2_root</name><created_at/><created_by_id>1356</created_by_id><updated_by_id>1356</updated_by_id><locales><locale><code>DEFAULT</code><created_at/></locale><locale><code>en</code><created_at/></locale></locales></group></groups></user></authentication>"
27
+ xml.gsub!(/[0-9-]{10}T[0-9+-:]{14}/, "").gsub!(/ type='[^']+'/, '').gsub!(/<created_at><\/created_at>/, "<created_at/>").gsub!(/<locale><id>[0-9]*<\/id>/, "<locale>").should == "<authentication><id>1</id><login>marvin2</login><user><id>1356</id><login>marvin2</login><name>marvin the robot</name><email>marvin@universe.example.com</email><language>xx</language><created_at/><updated_at></updated_at><created_by_id>1356</created_by_id><updated_by_id>1356</updated_by_id><groups><group><id>1356</id><name>marvin2_root</name><created_at/><created_by_id>1356</created_by_id><updated_by_id>1356</updated_by_id><locales><locale><code>DEFAULT</code><created_at/></locale><locale><code>en</code><created_at/></locale></locales></group></groups></user></authentication>"
28
28
  end
29
29
 
30
30
  end
data/spec/guard_spec.rb CHANGED
@@ -7,13 +7,13 @@ module ActionView
7
7
  end
8
8
  module Erector
9
9
  class Widget
10
-
10
+
11
11
  attr_writer :controller
12
-
12
+
13
13
  class Helpers
14
14
  attr_accessor :controller
15
15
  end
16
-
16
+
17
17
  def helpers
18
18
  @helpers ||= Helpers.new
19
19
  @helpers.controller = @controller
@@ -38,7 +38,7 @@ describe Ixtlan::Models::Permission do
38
38
 
39
39
  end
40
40
 
41
- class Controller
41
+ class Controller
42
42
  include ActionController::Base
43
43
  end
44
44
 
@@ -1,12 +1,12 @@
1
- Ixtlan::Guard.initialize(:permissions,
2
- { :show => [:guest],
3
- :edit => [:root, :admin],
4
- :update => [:root, :admin],
1
+ Ixtlan::Guard.initialize(:permissions,
2
+ { :show => [:guest],
3
+ :edit => [:root, :admin],
4
+ :update => [:root, :admin],
5
5
  :destroy => [:root] }
6
6
  )
7
7
 
8
- Ixtlan::Guard.initialize(:configurations,
9
- { :show => [:admin],
10
- :edit => [:root],
8
+ Ixtlan::Guard.initialize(:configurations,
9
+ { :show => [:admin],
10
+ :edit => [:root],
11
11
  :update => [:root] }
12
12
  )
@@ -0,0 +1,82 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname + 'spec_helper.rb'
3
+ require 'ixtlan' / 'modified_by'
4
+
5
+ class User
6
+ include DataMapper::Resource
7
+
8
+ property :login, String, :key => true
9
+ end
10
+
11
+ class AuditedName
12
+ include DataMapper::Resource
13
+
14
+ # TODO remove serial
15
+ property :id, Serial
16
+ property :name, String, :length => 2..255#, :key => true
17
+
18
+ modified_by User
19
+ end
20
+
21
+ describe Ixtlan::ModifiedBy do
22
+
23
+ before :each do
24
+ @user = User.create(:login => 'spock')
25
+ @second = User.create(:login => 'dr pill')
26
+ @name = AuditedName.create(:name => 'kirk', :current_user => @user)
27
+ end
28
+
29
+ it 'should create resource with modified by properties' do
30
+ @name.new?.should be_false
31
+ @name.instance_variable_get(:@current_user).should be_nil
32
+ @name.created_by.should == @user
33
+ @name.updated_by.should == @user
34
+ end
35
+
36
+ it 'should new/save resource with modified by properties' do
37
+ name = AuditedName.new(:name => 'seven of nine')
38
+ name.current_user = @user
39
+ name.save.should be_true
40
+ name.created_by.should == @user
41
+ name.updated_by.should == @user
42
+ end
43
+
44
+ it 'should modify updated_by on change' do
45
+ @name.current_user = @second
46
+ @name.name = "scotty"
47
+ @name.save.should be_true
48
+ @name.created_by.should == @user
49
+ @name.updated_by.should == @second
50
+ end
51
+
52
+ it 'should modify updated_by on update' do
53
+ @name.update(:name => "captain kirk", :current_user => @second)
54
+ @name.instance_variable_get(:@current_user).should be_nil
55
+ @name.updated_by.should == @second
56
+ end
57
+
58
+ it 'should not modify updated_by on update without change' do
59
+ @name.update(:name => "kirk", :current_user => @second)
60
+ @name.updated_by.should == @user
61
+ @name.instance_variable_get(:@current_user).should be_nil
62
+ end
63
+
64
+ it 'should modify updated_by on update with current_user' do
65
+ @name.current_user = @second
66
+ @name.update(:name => "captain kirk")
67
+ @name.updated_by.should == @second
68
+ end
69
+
70
+ it 'should fail create without current_user' do
71
+ lambda { AuditedName.create(:name => "ohura") }.should raise_error(DataMapper::MissingCurrentUserError)
72
+ end
73
+
74
+ it 'should fail save without current_user' do
75
+ @name.name = "ohura"
76
+ lambda { @name.save }.should raise_error(DataMapper::MissingCurrentUserError)
77
+ end
78
+
79
+ it 'should fail update without current_user' do
80
+ lambda { @name.update(:name => "ohura") }.should raise_error(DataMapper::MissingCurrentUserError)
81
+ end
82
+ end
@@ -0,0 +1,58 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname + 'spec_helper.rb'
3
+
4
+ require 'ixtlan/optimistic_persistence'
5
+
6
+ class Name
7
+ include DataMapper::Resource
8
+
9
+ property :id, Serial
10
+ property :name, String, :length => 2..255
11
+
12
+ timestamps :updated_at
13
+ end
14
+
15
+ class Number
16
+ include DataMapper::Resource
17
+
18
+ property :id, Serial
19
+ property :number, Integer
20
+ end
21
+
22
+ describe "Ixtlan::OptimisticPersistence" do
23
+
24
+ before :each do
25
+ @name = Name.create(:name => "frodo")
26
+ @second = Name.get(@name.id)
27
+ @number = Number.first_or_create(:number => 123)
28
+ @other = Number.first
29
+ end
30
+
31
+ it 'should save' do
32
+ @name.name = "gandalf"
33
+ @name.save.should be_true
34
+ end
35
+
36
+ it 'should fail' do
37
+ @name.name = "gandalf"
38
+ sleep 1
39
+ @name.save.should be_true
40
+ @second.name = "saroman"
41
+ lambda { @second.save }.should raise_error(DataMapper::StaleResourceError)
42
+ end
43
+
44
+ it 'should fail on key change' do
45
+ pending "maybe bug in datamapper"
46
+ @name.id = 11
47
+ @name.save.should be_true
48
+ @second.id = 111
49
+ lambda { @second.save }.should raise_error(DataMapper::StaleResourceError)
50
+ end
51
+
52
+ it 'should treat non optimistic resources as usual' do
53
+ @number.number = 345
54
+ @number.save.should be_true
55
+ @other.number = 987
56
+ @other.save.should be_true
57
+ end
58
+ end