authorize 0.0.1

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 (124) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +42 -0
  4. data/LICENSE +20 -0
  5. data/README +155 -0
  6. data/Rakefile +25 -0
  7. data/TODO.txt +9 -0
  8. data/authorize.gemspec +25 -0
  9. data/generators/authorize/USAGE +8 -0
  10. data/generators/authorize/authorize_generator.rb +7 -0
  11. data/generators/authorize/templates/migrate/create_authorizations.rb +26 -0
  12. data/install.rb +1 -0
  13. data/lib/authorize.rb +2 -0
  14. data/lib/authorize/action_controller.rb +59 -0
  15. data/lib/authorize/action_view.rb +4 -0
  16. data/lib/authorize/active_record.rb +37 -0
  17. data/lib/authorize/bitmask.rb +84 -0
  18. data/lib/authorize/exceptions.rb +30 -0
  19. data/lib/authorize/graph.rb +4 -0
  20. data/lib/authorize/graph/directed_acyclic_graph.rb +10 -0
  21. data/lib/authorize/graph/directed_acyclic_graph_reverse_traverser.rb +27 -0
  22. data/lib/authorize/graph/directed_acyclic_graph_traverser.rb +30 -0
  23. data/lib/authorize/graph/directed_graph.rb +27 -0
  24. data/lib/authorize/graph/edge.rb +58 -0
  25. data/lib/authorize/graph/factory.rb +39 -0
  26. data/lib/authorize/graph/fixtures.rb +33 -0
  27. data/lib/authorize/graph/graph.rb +55 -0
  28. data/lib/authorize/graph/traverser.rb +89 -0
  29. data/lib/authorize/graph/undirected_graph.rb +14 -0
  30. data/lib/authorize/graph/vertex.rb +53 -0
  31. data/lib/authorize/permission.rb +97 -0
  32. data/lib/authorize/redis.rb +2 -0
  33. data/lib/authorize/redis/array.rb +36 -0
  34. data/lib/authorize/redis/base.rb +165 -0
  35. data/lib/authorize/redis/connection_manager.rb +88 -0
  36. data/lib/authorize/redis/connection_specification.rb +16 -0
  37. data/lib/authorize/redis/factory.rb +64 -0
  38. data/lib/authorize/redis/fixtures.rb +22 -0
  39. data/lib/authorize/redis/hash.rb +34 -0
  40. data/lib/authorize/redis/model_reference.rb +21 -0
  41. data/lib/authorize/redis/model_set.rb +19 -0
  42. data/lib/authorize/redis/set.rb +42 -0
  43. data/lib/authorize/redis/string.rb +17 -0
  44. data/lib/authorize/resource.rb +4 -0
  45. data/lib/authorize/resource_pool.rb +87 -0
  46. data/lib/authorize/role.rb +115 -0
  47. data/lib/authorize/test_helper.rb +42 -0
  48. data/lib/authorize/trustee.rb +4 -0
  49. data/lib/authorize/version.rb +3 -0
  50. data/rails/init.rb +5 -0
  51. data/tasks/authorize_tasks.rake +4 -0
  52. data/test/Rakefile +7 -0
  53. data/test/app/controllers/application_controller.rb +5 -0
  54. data/test/app/controllers/thingy_controller.rb +11 -0
  55. data/test/app/controllers/widgets_controller.rb +2 -0
  56. data/test/app/models/public.rb +14 -0
  57. data/test/app/models/user.rb +8 -0
  58. data/test/app/models/widget.rb +7 -0
  59. data/test/config/boot.rb +109 -0
  60. data/test/config/database.yml +25 -0
  61. data/test/config/environment.rb +28 -0
  62. data/test/config/environments/development.rb +4 -0
  63. data/test/config/environments/test.rb +0 -0
  64. data/test/config/initializers/mask.rb +1 -0
  65. data/test/config/initializers/redis.rb +8 -0
  66. data/test/config/routes.rb +5 -0
  67. data/test/db/.gitignore +1 -0
  68. data/test/db/schema.rb +26 -0
  69. data/test/log/.gitignore +2 -0
  70. data/test/public/javascripts/application.js +2 -0
  71. data/test/public/javascripts/controls.js +963 -0
  72. data/test/public/javascripts/dragdrop.js +972 -0
  73. data/test/public/javascripts/effects.js +1120 -0
  74. data/test/public/javascripts/prototype.js +4225 -0
  75. data/test/script/about +3 -0
  76. data/test/script/console +3 -0
  77. data/test/script/dbconsole +3 -0
  78. data/test/script/destroy +3 -0
  79. data/test/script/generate +3 -0
  80. data/test/script/performance/benchmarker +3 -0
  81. data/test/script/performance/profiler +3 -0
  82. data/test/script/performance/request +3 -0
  83. data/test/script/plugin +3 -0
  84. data/test/script/process/inspector +3 -0
  85. data/test/script/process/reaper +3 -0
  86. data/test/script/process/spawner +3 -0
  87. data/test/script/runner +3 -0
  88. data/test/script/server +3 -0
  89. data/test/test/fixtures/authorize/role_graph.yml +11 -0
  90. data/test/test/fixtures/permissions.yml +27 -0
  91. data/test/test/fixtures/redis/redis.yml +8 -0
  92. data/test/test/fixtures/redis/role_graph.yml +29 -0
  93. data/test/test/fixtures/roles.yml +28 -0
  94. data/test/test/fixtures/users.yml +12 -0
  95. data/test/test/fixtures/widgets.yml +12 -0
  96. data/test/test/functional/controller_class_test.rb +36 -0
  97. data/test/test/functional/controller_test.rb +46 -0
  98. data/test/test/test_helper.rb +35 -0
  99. data/test/test/unit/bitmask_test.rb +112 -0
  100. data/test/test/unit/fixture_test.rb +59 -0
  101. data/test/test/unit/graph_directed_acyclic_graph_reverse_traverser_test.rb +43 -0
  102. data/test/test/unit/graph_directed_acyclic_graph_traverser_test.rb +57 -0
  103. data/test/test/unit/graph_directed_graph_test.rb +66 -0
  104. data/test/test/unit/graph_edge_test.rb +53 -0
  105. data/test/test/unit/graph_graph_test.rb +50 -0
  106. data/test/test/unit/graph_traverser_test.rb +43 -0
  107. data/test/test/unit/graph_vertex_test.rb +57 -0
  108. data/test/test/unit/permission_test.rb +123 -0
  109. data/test/test/unit/redis_array_test.rb +60 -0
  110. data/test/test/unit/redis_connection_manager_test.rb +54 -0
  111. data/test/test/unit/redis_factory_test.rb +85 -0
  112. data/test/test/unit/redis_fixture_test.rb +18 -0
  113. data/test/test/unit/redis_hash_test.rb +43 -0
  114. data/test/test/unit/redis_model_reference_test.rb +39 -0
  115. data/test/test/unit/redis_set_test.rb +68 -0
  116. data/test/test/unit/redis_string_test.rb +25 -0
  117. data/test/test/unit/redis_test.rb +121 -0
  118. data/test/test/unit/resource_pool_test.rb +93 -0
  119. data/test/test/unit/resource_test.rb +33 -0
  120. data/test/test/unit/role_test.rb +143 -0
  121. data/test/test/unit/trustee_test.rb +35 -0
  122. data/test/tmp/.gitignore +2 -0
  123. data/uninstall.rb +1 -0
  124. metadata +319 -0
data/test/script/about ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'commands/about'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'commands/console'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'commands/dbconsole'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'commands/destroy'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'commands/generate'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../../config/boot'
3
+ require 'commands/performance/benchmarker'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../../config/boot'
3
+ require 'commands/performance/profiler'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../../config/boot'
3
+ require 'commands/performance/request'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'commands/plugin'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../../config/boot'
3
+ require 'commands/process/inspector'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../../config/boot'
3
+ require 'commands/process/reaper'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../../config/boot'
3
+ require 'commands/process/spawner'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'commands/runner'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'commands/server'
@@ -0,0 +1,11 @@
1
+ --- !hapgoods.com,2010/graph
2
+ # 417269330
3
+ - administrator
4
+ # 1001664029
5
+ - public
6
+ # 428714999
7
+ - registered_users: [public]
8
+ # 782668386
9
+ - user_chris: [registered_users]
10
+ # 12918588
11
+ - user_pascale: [registered_users, administrator]
@@ -0,0 +1,27 @@
1
+ a_read_foo:
2
+ mask: <%= Authorize::Permission::Mask[:list, :read].to_i %>
3
+ role: a
4
+ _resource: foo (Widget)
5
+ a_list_bar:
6
+ mask: <%= Authorize::Permission::Mask[:list].to_i %>
7
+ role: a
8
+ _resource: bar (Widget)
9
+ b_overlord:
10
+ mask: <%= Authorize::Permission::Mask[:all].to_i %>
11
+ role: administrator
12
+ c_all_widgets:
13
+ mask: <%= Authorize::Permission::Mask[:all].to_i %>
14
+ role: c
15
+ resource_type: Widget
16
+ d_update_bar:
17
+ mask: <%= Authorize::Permission::Mask[:list, :read, :update].to_i %>
18
+ role: d
19
+ _resource: bar (Widget)
20
+ e_delete_bar:
21
+ mask: <%= Authorize::Permission::Mask[:list, :delete].to_i %>
22
+ role: e
23
+ _resource: bar (Widget)
24
+ user_chris_all_chris:
25
+ mask: <%= Authorize::Permission::Mask[:all].to_i %>
26
+ role: user_chris
27
+ _resource: chris (User)
@@ -0,0 +1,8 @@
1
+ - string: x
2
+ - list: [one, two, three]
3
+ - set: !ruby/object:Set
4
+ hash: {1: true, 2: true}
5
+ - hash: {:a: 1, :b: 2}
6
+ - value: &value <%= Marshal.dump(Date.new(1965, 11, 16))%>
7
+ - value_set: !ruby/object:Set
8
+ hash: {*value: true}
@@ -0,0 +1,29 @@
1
+ # Vertices (just markers)
2
+ - <%= "Authorize::Role::vertices::#{Fixtures.identify(:user_chris)}::_" %>: ~
3
+ - <%= "Authorize::Role::vertices::#{Fixtures.identify(:public)}::_" %>: ~
4
+ - <%= "Authorize::Role::vertices::#{Fixtures.identify(:registered_users)}::_" %>: ~
5
+ # Edge counter
6
+ - "Authorize::Role::graph::_edges": "10"
7
+ # Edges
8
+ - Authorize::Role::graph::_edges::1::l_id: <%= "Authorize::Role::vertices::#{Fixtures.identify(:user_chris)}" %>
9
+ - Authorize::Role::graph::_edges::1::r_id: <%= "Authorize::Role::vertices::#{Fixtures.identify(:registered_users)}" %>
10
+ - Authorize::Role::graph::_edges::2::l_id: <%= "Authorize::Role::vertices::#{Fixtures.identify(:registered_users)}" %>
11
+ - Authorize::Role::graph::_edges::2::r_id: <%= "Authorize::Role::vertices::#{Fixtures.identify(:public)}" %>
12
+ # Set of vertices belonging to the role graph
13
+ - "Authorize::Role::graph": !ruby/object:Set
14
+ hash:
15
+ <%= "Authorize::Role::vertices::#{Fixtures.identify(:public)}" %>: true
16
+ <%= "Authorize::Role::vertices::#{Fixtures.identify(:registered_users)}" %>: true
17
+ <%= "Authorize::Role::vertices::#{Fixtures.identify(:user_chris)}" %>: true
18
+ # Set of edges belonging to the role graph
19
+ - "Authorize::Role::graph::edge_ids": !ruby/object:Set
20
+ hash:
21
+ Authorize::Role::graph::edges::1: true
22
+ Authorize::Role::graph::edges::2: true
23
+ # Set of edges per vertex
24
+ - <%= "Authorize::Role::vertices::#{Fixtures.identify(:user_chris)}::edge_ids" %>: !ruby/object:Set
25
+ hash:
26
+ Authorize::Role::graph::_edges::1: true
27
+ - <%= "Authorize::Role::vertices::#{Fixtures.identify(:registered_users)}::edge_ids" %>: !ruby/object:Set
28
+ hash:
29
+ Authorize::Role::graph::_edges::2: true
@@ -0,0 +1,28 @@
1
+ a:
2
+ name: a
3
+ resource: foo (Widget)
4
+ administrator:
5
+ name: administrator
6
+ relation: ADM
7
+ c:
8
+ name: "%s administrator"
9
+ resource_type: Widget
10
+ d:
11
+ name: "owner of %s"
12
+ resource: bar (Widget)
13
+ e:
14
+ name: "housekeeper of %s"
15
+ relation: HSK
16
+ resource: bar (Widget)
17
+ user_chris:
18
+ name: ~
19
+ resource: chris (User)
20
+ user_pascale:
21
+ name: ~
22
+ resource: pascale (User)
23
+ registered_users:
24
+ name: Registered Users
25
+ relation: RUS
26
+ public:
27
+ name: Public
28
+ relation: PUB
@@ -0,0 +1,12 @@
1
+ chris:
2
+ login: cch1
3
+ created_at: <%= 2.days.ago.to_s :db %>
4
+ updated_at: <%= 1.days.ago.to_s :db %>
5
+ pascale:
6
+ login: pah1
7
+ created_at: <%= 2.days.ago.to_s :db %>
8
+ updated_at: <%= 1.days.ago.to_s :db %>
9
+ alex:
10
+ login: ach1
11
+ created_at: <%= 2.days.ago.to_s :db %>
12
+ updated_at: <%= 1.days.ago.to_s :db %>
@@ -0,0 +1,12 @@
1
+ foo:
2
+ name: super
3
+ created_at: <%= 2.days.ago.to_s :db %>
4
+ updated_at: <%= 1.days.ago.to_s :db %>
5
+ bar:
6
+ name: deluxe
7
+ created_at: <%= 2.days.ago.to_s :db %>
8
+ updated_at: <%= 1.days.ago.to_s :db %>
9
+ baz:
10
+ name: baz
11
+ created_at: <%= 2.days.ago.to_s :db %>
12
+ updated_at: <%= 1.days.ago.to_s :db %>
@@ -0,0 +1,36 @@
1
+ require 'test_helper'
2
+
3
+ class ControllerClassTest < ActionController::TestCase
4
+ fixtures :all
5
+
6
+ tests ThingyController
7
+
8
+ test 'raise exception when not permitted' do
9
+ @controller.expects(:roles).returns([])
10
+ assert_raises Authorize::AuthorizationError do
11
+ get :index
12
+ end
13
+ end
14
+
15
+ test 'rescue response' do
16
+ @controller.expects(:roles).returns([])
17
+ @request.remote_addr = "192.168.1.1"
18
+ get :index
19
+ assert_response :forbidden
20
+ end
21
+
22
+ test 'skip filter' do
23
+ assert_nothing_raised do
24
+ get :show
25
+ assert_response :success
26
+ end
27
+ end
28
+
29
+ test 'should perform action because of authorization' do
30
+ @controller.expects(:roles).returns([roles(:administrator)])
31
+ assert_nothing_raised do
32
+ get :index
33
+ assert_response :success
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,46 @@
1
+ require 'test_helper'
2
+ require 'authorize/graph/fixtures'
3
+
4
+ class ControllerTest < ActionController::TestCase
5
+ fixtures :all
6
+
7
+ tests WidgetsController
8
+
9
+ def setup
10
+ ::Authorize::Graph::Fixtures.create_fixtures
11
+ end
12
+
13
+ test 'predicate not stuck on false when permitted' do
14
+ assert @controller.permit?({:list => widgets(:foo)}, {:roles => [roles(:administrator)]})
15
+ end
16
+
17
+ test 'predicate not stuck on true when not permitted' do
18
+ assert !@controller.permit?({:all => Widget}, {:roles => []})
19
+ end
20
+
21
+ test 'query controller for default roles' do
22
+ @controller.expects(:roles).returns([roles(:administrator)])
23
+ @controller.permit?(:update => widgets(:foo))
24
+ end
25
+
26
+ test 'yields to block when permitted' do
27
+ sentinel = mock('sentinel', {:trip! => true})
28
+ @controller.permit({:list => widgets(:foo)}, {:roles => [roles(:administrator)]}) {sentinel.trip!}
29
+ end
30
+
31
+ test 'calls handler and does not yield to block when not permitted' do
32
+ sentinel = mock('sentinel')
33
+ @controller.expects(:handle_authorization_failure).returns(true)
34
+ @controller.permit({:all => Widget}, {:roles => []}) {sentinel.trip!}
35
+ end
36
+
37
+ test 'handler raises authorization exception' do
38
+ assert_raises Authorize::AuthorizationError do
39
+ @controller.permit({:all => Widget}, {:roles => []}) {sentinel.trip!}
40
+ end
41
+ end
42
+
43
+ test 'mutiple authorization hash pairs' do
44
+ assert @controller.permit?({:list => widgets(:foo), :update => users(:chris)}, {:roles => [roles(:user_chris)]})
45
+ end
46
+ end
@@ -0,0 +1,35 @@
1
+ ENV['RAILS_ENV'] = 'test'
2
+ require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
3
+ require 'test_help'
4
+
5
+ # Set Test::Unit options for optimal performance/fidelity.
6
+ class ActiveSupport::TestCase
7
+ self.use_transactional_fixtures = true
8
+ self.use_instantiated_fixtures = false
9
+
10
+ set_fixture_class :permissions => Authorize::Permission, :roles => Authorize::Role
11
+
12
+ def self.uses_mocha(description)
13
+ require 'mocha'
14
+ yield
15
+ rescue LoadError
16
+ $stderr.puts "Skipping #{description} tests. `gem install mocha` and try again."
17
+ end
18
+ setup {Authorize::Redis::Base.db.flushdb}
19
+ end
20
+
21
+ # Unfortunately, setting expectations on any instance for #initialize causes #mocha_teardown
22
+ # to squirt out errors that cannot easily be suppressed in a less intrusive manner.
23
+ module Mocha
24
+ module API
25
+ def mocha_teardown_with_warning_suppression
26
+ old_verbose, $VERBOSE = $VERBOSE, nil
27
+ mocha_teardown_without_warning_suppression
28
+ $VERBOSE = old_verbose
29
+ end
30
+ alias_method :mocha_teardown_without_warning_suppression, :mocha_teardown
31
+ alias_method :mocha_teardown, :mocha_teardown_with_warning_suppression
32
+ end
33
+ end
34
+
35
+ raise "Test Database doesn't look safe (#{Authorize::Redis::Base.db.inspect} has #{Authorize::Redis::Base.db.dbsize} keys)" unless Authorize::Redis::Base.db.dbsize < 100
@@ -0,0 +1,112 @@
1
+ require 'test_helper'
2
+
3
+ class BitmaskTest < ActiveSupport::TestCase
4
+ def setup
5
+ @bitmask = Class.new(Authorize::Bitmask)
6
+ @bitmask.name_values = {:none => 0, :first => 1, :second => 2, :third => 4, :fourth => 8, :first_nibble => 15, :fifth => 16, :sixth => 32, :seventh => 64, :eighth => 128, :all => 255}
7
+ end
8
+
9
+ test 'create degenerate' do
10
+ b = @bitmask[]
11
+ assert b.empty?
12
+ end
13
+
14
+ test 'create with integer' do
15
+ b = @bitmask.new(4)
16
+ assert_equal Set[:none, :third], b
17
+ end
18
+
19
+ test 'create with invalid integer' do
20
+ assert_raises RangeError do
21
+ b = @bitmask.new(256)
22
+ end
23
+ end
24
+
25
+ test 'create with enum' do
26
+ b = @bitmask[:first, :third]
27
+ assert_equal Set[:first, :third], b
28
+ end
29
+
30
+ test 'create with invalid enum' do
31
+ assert_raises ArgumentError do
32
+ @bitmask[:first, :third, :ninth]
33
+ end
34
+ end
35
+
36
+ test 'add bit' do
37
+ b = @bitmask[]
38
+ b << :first_nibble
39
+ assert_equal 15, b.to_i
40
+ end
41
+
42
+ test 'add invalid bit' do
43
+ b = @bitmask[]
44
+ assert_raises ArgumentError do
45
+ b.add :first_word
46
+ end
47
+ end
48
+
49
+ test 'comparable' do
50
+ b0 = @bitmask[]
51
+ b1 = @bitmask[:first, :second, :third, :fourth]
52
+ b2 = @bitmask[:first_nibble]
53
+ assert_operator b0, :<, b1
54
+ assert_operator b2, :==, b1
55
+ end
56
+
57
+ test 'comparable with integers' do
58
+ b0 = @bitmask[:all]
59
+ assert_operator b0, :==, 255
60
+ assert_operator 255, :==, b0
61
+ end
62
+
63
+ test 'dynamic bit getters' do
64
+ b = @bitmask[:first, :second]
65
+ assert b._first
66
+ assert !b._third
67
+ end
68
+
69
+ test 'dynamic bit setters' do
70
+ b = @bitmask[:first, :second]
71
+ b._first = false
72
+ assert !b.include?(:first)
73
+ b._third = false
74
+ assert !b.include?(:third)
75
+ end
76
+
77
+ test 'dynamic bit predicates' do
78
+ b = @bitmask[:first, :second]
79
+ assert b._first?
80
+ assert !b._third?
81
+ end
82
+
83
+ test 'complete' do
84
+ b = @bitmask[]
85
+ assert_equal Set[:none], b.complete
86
+ b = @bitmask[:first_nibble]
87
+ assert_equal Set[:none, :first, :second, :third, :fourth, :first_nibble], b.complete
88
+ b = @bitmask[:first, :second, :third, :fourth]
89
+ assert_equal Set[:none, :first, :second, :third, :fourth, :first_nibble], b.complete
90
+ end
91
+
92
+ test 'fundamental' do
93
+ b = @bitmask[:first_nibble]
94
+ assert_equal Set[:first, :second, :third, :fourth], b.fundamental
95
+ end
96
+
97
+ test 'minimal' do
98
+ b = @bitmask[:none, :first, :second, :third, :fourth]
99
+ assert_equal Set[:first_nibble], b.minimal
100
+ end
101
+
102
+ test 'stringify' do
103
+ b = @bitmask.new(7)
104
+ assert_match /\w+(\|\w+){3}/, b.to_s
105
+ assert_match /none.*third/, b.to_s # canonical order
106
+ end
107
+
108
+ test 'stringify empty set' do
109
+ b = @bitmask[]
110
+ assert_equal "", b.to_s
111
+ end
112
+ end