cantango 0.9.3.2 → 0.9.4

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 (80) hide show
  1. data/README.textile +11 -9
  2. data/VERSION +1 -1
  3. data/cantango.gemspec +24 -3
  4. data/lib/cantango/ability/cache/key.rb +6 -2
  5. data/lib/cantango/ability/cache/reader.rb +3 -0
  6. data/lib/cantango/ability/cache/session_cache.rb +7 -3
  7. data/lib/cantango/ability/cache/writer.rb +8 -2
  8. data/lib/cantango/ability/cache.rb +25 -8
  9. data/lib/cantango/ability/cache_helpers.rb +4 -13
  10. data/lib/cantango/ability/cached_executor.rb +0 -0
  11. data/lib/cantango/ability/engine_helpers.rb +4 -1
  12. data/lib/cantango/ability/executor.rb +67 -0
  13. data/lib/cantango/ability/permission_helpers.rb +0 -1
  14. data/lib/cantango/ability.rb +1 -1
  15. data/lib/cantango/cached_ability.rb +3 -2
  16. data/lib/cantango/configuration/engines/cache.rb +0 -3
  17. data/lib/cantango/configuration/engines/engine.rb +5 -0
  18. data/lib/cantango/configuration/engines/permission.rb +5 -4
  19. data/lib/cantango/configuration/engines/permit.rb +0 -5
  20. data/lib/cantango/configuration/engines/user_ac.rb +6 -3
  21. data/lib/cantango/configuration/models/active_record.rb +11 -0
  22. data/lib/cantango/configuration/models/data_mapper.rb +12 -0
  23. data/lib/cantango/configuration/models/generic.rb +12 -0
  24. data/lib/cantango/configuration/models/mongo.rb +12 -0
  25. data/lib/cantango/configuration/models/mongo_mapper.rb +11 -0
  26. data/lib/cantango/configuration/models/mongoid.rb +13 -0
  27. data/lib/cantango/configuration/models.rb +27 -2
  28. data/lib/cantango/configuration/permits.rb +2 -1
  29. data/lib/cantango/configuration.rb +14 -0
  30. data/lib/cantango/engine.rb +5 -19
  31. data/lib/cantango/model/scope.rb +19 -5
  32. data/lib/cantango/permission_engine/collector.rb +3 -0
  33. data/lib/cantango/permission_engine/evaluator.rb +5 -0
  34. data/lib/cantango/permission_engine/factory.rb +3 -0
  35. data/lib/cantango/permission_engine/loader/permissions.rb +7 -8
  36. data/lib/cantango/permission_engine/store.rb +0 -1
  37. data/lib/cantango/permission_engine/yaml_store.rb +15 -4
  38. data/lib/cantango/permission_engine.rb +21 -4
  39. data/lib/cantango/permit_engine/factory.rb +10 -4
  40. data/lib/cantango/permit_engine.rb +39 -9
  41. data/lib/cantango/permits/account_permit/builder.rb +6 -2
  42. data/lib/cantango/{user_ac_engine → permits}/executor.rb +28 -30
  43. data/lib/cantango/permits/permit/class_methods.rb +21 -0
  44. data/lib/cantango/permits/permit/execute.rb +81 -0
  45. data/lib/cantango/permits/permit/license.rb +26 -0
  46. data/lib/cantango/permits/permit.rb +19 -138
  47. data/lib/cantango/permits/role_group_permit/builder.rb +5 -1
  48. data/lib/cantango/permits/role_group_permit.rb +3 -3
  49. data/lib/cantango/permits/role_permit/builder.rb +4 -0
  50. data/lib/cantango/permits/user_permit/builder.rb +5 -1
  51. data/lib/cantango/permits/user_permit.rb +1 -1
  52. data/lib/cantango/permits.rb +1 -0
  53. data/lib/cantango/rails/engine.rb +0 -3
  54. data/lib/cantango/rails/helpers/base_helper.rb +1 -1
  55. data/lib/cantango/rails/helpers/rest_helper.rb +1 -1
  56. data/lib/cantango/rules/adaptor/active_record.rb +1 -4
  57. data/lib/cantango/rules/adaptor/data_mapper.rb +11 -0
  58. data/lib/cantango/rules/adaptor/mongo.rb +19 -0
  59. data/lib/cantango/rules/adaptor/mongo_mapper.rb +10 -0
  60. data/lib/cantango/rules/adaptor/mongoid.rb +1 -5
  61. data/lib/cantango/rules/adaptor/relational.rb +13 -0
  62. data/lib/cantango/rules/adaptor.rb +12 -7
  63. data/lib/cantango/rules/user_relation.rb +1 -2
  64. data/lib/cantango/user_ac_engine.rb +25 -7
  65. data/lib/cantango.rb +2 -0
  66. data/spec/cantango/ability/executor_spec.rb +67 -0
  67. data/spec/cantango/ability_executor/cached_only_spec.rb +1 -0
  68. data/spec/cantango/model/scope_spec.rb +11 -0
  69. data/spec/cantango/models/items.rb +5 -0
  70. data/spec/cantango/permission_engine_cached_spec.rb +51 -0
  71. data/spec/cantango/permission_engine_spec.rb +55 -0
  72. data/spec/cantango/permit_engine_cached_spec.rb +56 -0
  73. data/spec/cantango/permit_engine_spec.rb +57 -1
  74. data/spec/cantango/permits/executor_cached_spec.rb +0 -0
  75. data/spec/cantango/permits/executor_spec.rb +68 -0
  76. data/spec/cantango/user_ac_engine_cached_spec.rb +64 -0
  77. data/spec/cantango/user_ac_engine_spec.rb +14 -2
  78. data/spec/fixtures/models/items.rb +3 -0
  79. data/spec/fixtures/models/user.rb +18 -0
  80. metadata +55 -34
@@ -1,6 +1,8 @@
1
1
  module CanTango
2
2
  class Configuration
3
3
  class Models
4
+ autoload_modules :Generic, :ActiveRecord, :DataMapper, :MongoMapper, :Mongoid
5
+
4
6
  include Singleton
5
7
  include ClassExt
6
8
 
@@ -18,12 +20,35 @@ module CanTango
18
20
  end
19
21
  end
20
22
 
23
+ def exclude *names
24
+ @excluded = names.flatten.select_labels
25
+ end
26
+
27
+ def excluded
28
+ @excluded ||= []
29
+ end
30
+
21
31
  def available_models
22
- ar_models.map(&:name)
32
+ all_models - excluded.map {|m| m.to_s.camelize}
33
+ end
34
+
35
+ protected
36
+
37
+ def all_models
38
+ CanTango.config.orms.inject([]) do |result, orm|
39
+ result << adapter_for(orm).models.map(&:name)
40
+ result
41
+ end.flatten.compact
23
42
  end
24
43
 
25
44
  private
26
45
 
46
+
47
+
48
+ def adapter_for orm
49
+ "CanTango::Configuration::Models::#{orm.to_s.camlize}".constantize.new
50
+ end
51
+
27
52
  def try_model model_string
28
53
  model = try_class(model_string.singularize) || try_class(model_string)
29
54
  raise "No model #{model_string} defined!" if !model
@@ -33,7 +58,7 @@ module CanTango
33
58
  def grep reg_exp
34
59
  available_models.grep reg_exp
35
60
  end
36
-
61
+
37
62
  def ar_models
38
63
  # Sugar-high #to_strings didn't work here!
39
64
  ActiveRecord::Base.descendants
@@ -4,6 +4,7 @@ module CanTango
4
4
  include Singleton
5
5
 
6
6
  attr_reader :accounts
7
+ attr_writer :enabled_types
7
8
 
8
9
  def enabled_types
9
10
  @enabled_types || available_types
@@ -102,7 +103,7 @@ module CanTango
102
103
  end
103
104
 
104
105
  def key_for subject
105
- subject.kind_of?(CanTango::Ability) ? key_maker.create_for(subject) : key_maker.new(subject)
106
+ subject.respond_to?(:subject) ? key_maker.create_for(subject) : key_maker.new(subject)
106
107
  end
107
108
 
108
109
  def key_maker
@@ -35,6 +35,10 @@ module CanTango
35
35
  }
36
36
  end
37
37
 
38
+ def debug!
39
+ debug.set :on
40
+ end
41
+
38
42
  # Turn on all engines and enable compile adapter
39
43
  # i.e compilation of rules via sourcify
40
44
  def enable_defaults!
@@ -42,6 +46,15 @@ module CanTango
42
46
  adapters.use :compiler
43
47
  end
44
48
 
49
+ def enable_helpers *names
50
+ names = names.to_symbols
51
+ enable_rest_helper if names.include? :rest
52
+ end
53
+
54
+ def enable_rest_helper
55
+ ApplicationController.send :include, CanTango::Rails::Helpers::RestHelper
56
+ end
57
+
45
58
  def clear!
46
59
  CanTango::Configuration.components.each do |c|
47
60
  comp = send(c)
@@ -67,6 +80,7 @@ module CanTango
67
80
  engine
68
81
  end
69
82
 
83
+ attr_accessor :orms
70
84
  attr_writer :localhost_list
71
85
 
72
86
  def localhost_list
@@ -4,12 +4,14 @@ module CanTango
4
4
 
5
5
  attr_reader :ability
6
6
 
7
+ delegate :session, :user, :subject, :candidate, :cached?, :to => :ability
8
+
7
9
  def initialize ability
8
10
  @ability = ability
9
11
  end
10
12
 
11
13
  def execute!
12
- # raise NotImplementedError
14
+ raise NotImplementedError
13
15
  end
14
16
 
15
17
  def engine_name
@@ -23,31 +25,15 @@ module CanTango
23
25
  end
24
26
 
25
27
  def valid_cache_mode?
26
- modes.include?(:cache) && cache_mode?
28
+ modes.include?(:cache) && cached?
27
29
  end
28
30
 
29
31
  def valid_no_cache_mode?
30
- modes.include?(:no_cache) && !cache_mode?
32
+ modes.include?(:no_cache) && !cached?
31
33
  end
32
34
 
33
35
  def modes
34
36
  CanTango.config.engine(engine_name.to_sym).modes
35
37
  end
36
-
37
- def cache_mode?
38
- ability.cached?
39
- end
40
-
41
- def user
42
- ability.user
43
- end
44
-
45
- def subject
46
- ability.subject
47
- end
48
-
49
- def candidate
50
- ability.candidate
51
- end
52
38
  end
53
39
  end
@@ -14,10 +14,11 @@ module CanTango::Model
14
14
  include CanTango::Api::User::Ability
15
15
 
16
16
 
17
- attr_reader :actions, :clazz
17
+ attr_reader :actions, :mode, :clazz
18
18
 
19
- def initialize clazz, *actions
19
+ def initialize clazz, mode, *actions
20
20
  @clazz = clazz
21
+ @mode = mode
21
22
  @actions = actions.flatten
22
23
  end
23
24
 
@@ -33,17 +34,26 @@ module CanTango::Model
33
34
  protected
34
35
 
35
36
  def check ability
37
+ puts ability.rules.inspect
36
38
  clazz.all.select do |obj|
37
- actions.all? do |action|
38
- ability.can? action.to_sym, obj
39
+ actions.all? do |action|
40
+ ability.send mode_action, action.to_sym, obj
39
41
  end
40
42
  end
41
43
  end
44
+
45
+ def mode_action
46
+ "#{mode}?"
47
+ end
42
48
  end
43
49
 
44
50
  module ClassMethods
45
51
  def allowed_to *actions
46
- CanTango::Model::Scope::AllowedActions.new self, *actions
52
+ CanTango::Model::Scope::AllowedActions.new self, :can, *actions
53
+ end
54
+
55
+ def not_allowed_to *actions
56
+ CanTango::Model::Scope::AllowedActions.new self, :cannot, *actions
47
57
  end
48
58
 
49
59
  CanTango::Model::Scope.rest_actions.each do |action|
@@ -51,6 +61,10 @@ module CanTango::Model
51
61
  define_method :"#{meth_name}_by" do |user|
52
62
  all.select {|obj| obj.user_ability(user).can? action.to_sym, obj }
53
63
  end
64
+
65
+ define_method :"not_#{meth_name}_by" do |user|
66
+ all.select {|obj| obj.user_ability(user).cannot? action.to_sym, obj }
67
+ end
54
68
  end
55
69
  end
56
70
  end
@@ -1,9 +1,12 @@
1
1
  module CanTango
2
2
  class PermissionEngine < Engine
3
3
  class Collector
4
+ include CanTango::Helpers::Debug
5
+
4
6
  attr_reader :ability, :permissions, :type
5
7
 
6
8
  def initialize ability, permissions, type
9
+ debug "Collecting #{type} permissions"
7
10
  @ability = ability
8
11
  @permissions = permissions
9
12
  @type = type
@@ -1,6 +1,8 @@
1
1
  module CanTango
2
2
  class PermissionEngine < Engine
3
3
  class Evaluator
4
+ include CanTango::Helpers::Debug
5
+
4
6
  attr_reader :ability, :rule
5
7
 
6
8
  include CanTango::Rules
@@ -12,6 +14,9 @@ module CanTango
12
14
  end
13
15
 
14
16
  def evaluate! user
17
+ debug "Evaluating rule:"
18
+ debug rule.can
19
+ debug rule.cannot
15
20
  @user = user
16
21
  instance_eval rule.can if rule.can?
17
22
  instance_eval rule.cannot if rule.cannot?
@@ -1,6 +1,8 @@
1
1
  module CanTango
2
2
  class PermissionEngine < Engine
3
3
  class Factory
4
+ include CanTango::Helpers::Debug
5
+
4
6
  attr_accessor :ability
5
7
 
6
8
  # creates the factory for the ability
@@ -11,6 +13,7 @@ module CanTango
11
13
  end
12
14
 
13
15
  def build!
16
+ debug "building permissions"
14
17
  @evaluators ||= permission_types.inject([]) do |res, type|
15
18
  res << collector(type).build
16
19
  res
@@ -6,17 +6,16 @@ module CanTango
6
6
 
7
7
  def initialize file_name
8
8
  @file_name = file_name
9
-
10
9
  load!
11
10
  end
12
11
 
13
12
  def load_from_hash hash
14
13
  return if hash.empty?
15
14
  hash.each do |type, groups|
16
- permissions[type] ||= {}
17
-
18
- next if groups.nil?
19
-
15
+ permissions[type] ||= {}
16
+
17
+ next if groups.nil?
18
+
20
19
  groups.each do |group, rules|
21
20
  parser.parse(group, rules) do |permission|
22
21
  permissions[type][permission.name] = permission
@@ -46,13 +45,13 @@ module CanTango
46
45
 
47
46
  define_method(:"#{type}_compiled_permissions") do
48
47
  type_permissions = send(:"#{type}_permissions")
49
-
48
+
50
49
  return Hashie::Mash.new if !type_permissions || type_permissions.empty?
51
-
50
+
52
51
  compiled_sum = send(:"#{type}_permissions").inject({}) do |compiled_sum, (actor, permission)|
53
52
  compiled_sum.merge(permission.to_compiled_hash)
54
53
  end
55
-
54
+
56
55
  Hashie::Mash.new(compiled_sum)
57
56
  end
58
57
  end
@@ -21,7 +21,6 @@ module CanTango
21
21
  def save! permissions
22
22
  raise NotImplementedError
23
23
  end
24
-
25
24
  end
26
25
  end
27
26
  end
@@ -3,7 +3,7 @@ require 'yaml'
3
3
  module CanTango
4
4
  class PermissionEngine < Engine
5
5
  class YamlStore < Store
6
- attr_reader :path
6
+ attr_reader :path, :last_load_time
7
7
 
8
8
  # for a YamlStore, the name is the name of the yml file
9
9
  # options: extension, path
@@ -14,16 +14,27 @@ module CanTango
14
14
 
15
15
  def load!
16
16
  loader.load!
17
+ @last_load_time = Time.now
17
18
  end
18
19
 
19
20
  def load_from_hash hash
20
21
  loader.load_from_hash hash
21
22
  end
22
23
 
23
- # @stanislaw: don't like this, because what if loader#load! will be called
24
- # twice during object's (YamlStore.new) life time!
24
+ # return cached permissions if file has not changed since last load
25
+ # otherwise load permissions again to reflect changes!
25
26
  def permissions
26
- @permissions ||= loader.permissions
27
+ return @permissions if changed?
28
+ @permissions = loader.permissions
29
+ end
30
+
31
+ def changed?
32
+ return true if !last_load_time
33
+ last_modify_time <= last_load_time
34
+ end
35
+
36
+ def last_modify_time
37
+ File.mtime(file_path)
27
38
  end
28
39
 
29
40
  CanTango.config.permission_engine.types.each do |type|
@@ -4,13 +4,15 @@ module CanTango
4
4
  autoload_modules :Factory, :Loader, :Parser, :Permission
5
5
  autoload_modules :RulesParser, :Store, :YamlStore, :Statements, :Statement
6
6
 
7
+ include CanTango::Ability::Executor
8
+ include CanTango::Ability::RoleHelpers
9
+ include CanTango::Ability::UserHelpers
10
+
7
11
  def initialize ability
8
12
  super
9
13
  end
10
14
 
11
- def execute!
12
- return if !valid?
13
- debug "Permission Engine executing..."
15
+ def permit_rules
14
16
  permissions.each do |permission|
15
17
  permission.evaluate! user
16
18
  end
@@ -21,6 +23,7 @@ module CanTango
21
23
  end
22
24
 
23
25
  def valid?
26
+ puts "valid_mode? #{valid_mode?} #{modes} #{cached?}"
24
27
  return false if !valid_mode?
25
28
  permissions.empty? ? invalid : true
26
29
  end
@@ -31,13 +34,27 @@ module CanTango
31
34
 
32
35
  protected
33
36
 
37
+ alias_method :cache_key, :engine_name
38
+
39
+ def start_execute
40
+ debug "Permission Engine executing..."
41
+ end
42
+
43
+ def end_execute
44
+ debug "Done Permission Engine"
45
+ end
46
+
34
47
  def invalid
35
48
  debug "No permissions found!"
36
49
  false
37
50
  end
38
51
 
39
52
  def permission_factory
40
- @permission_factory ||= CanTango::PermissionEngine::Factory.new ability
53
+ @permission_factory ||= CanTango::PermissionEngine::Factory.new self
54
+ end
55
+
56
+ def changed?
57
+ permission_factory.store.changed?
41
58
  end
42
59
  end
43
60
  end
@@ -17,13 +17,19 @@ module CanTango
17
17
  permits
18
18
  end
19
19
 
20
+ # return hash of permits built, keyed by name of builder
20
21
  def permits
21
- @permits ||= builders.inject([]) do |permits, builder|
22
+ @permits ||= builders.inject({}) do |permits, builder|
22
23
  debug "++ Permit Builder: #{builder_class builder}"
23
24
  built_permits = permits_built_with(builder)
24
- debug "== Permits built: #{built_permits.size}"
25
- permits = permits + built_permits if built_permits
26
- end.flatten
25
+
26
+ if built_permits
27
+ debug "== Permits built: #{built_permits.size}"
28
+ permits[builder] = built_permits
29
+ end
30
+
31
+ permits
32
+ end
27
33
  end
28
34
 
29
35
  def permits_built_with builder
@@ -3,21 +3,26 @@ module CanTango
3
3
  autoload_modules :Builder, :Compatibility, :Executor
4
4
  autoload_modules :Factory, :Finder, :Loaders, :Util, :RoleMatcher
5
5
 
6
+ include CanTango::Ability::Executor
7
+ include CanTango::Ability::RoleHelpers
8
+ include CanTango::Ability::UserHelpers
9
+
6
10
  def initialize ability
7
11
  super
8
12
  end
9
13
 
10
- def execute!
11
- return if !valid?
12
- debug "Permit Engine executing..."
13
-
14
- # CanTango.config.permits.clear_executed! # should there be an option clear before each execution?
15
- permits.each do |permit|
16
- CanTango.config.permits.was_executed(permit, ability) if CanTango.config.debug.on?
17
- break if permit.execute == :break
14
+ def permit_rules
15
+ # push result of each permit type execution into main ability rules array
16
+ permits.each_pair do |type, permits|
17
+ perm_rules = executor(type, permits).execute!
18
+ rules << perm_rules if !perm_rules.blank?
18
19
  end
19
20
  end
20
21
 
22
+ def executor type, permits
23
+ CanTango::Permits::Executor.new self, type, permits
24
+ end
25
+
21
26
  def engine_name
22
27
  :permit
23
28
  end
@@ -40,13 +45,38 @@ module CanTango
40
45
 
41
46
  protected
42
47
 
48
+ alias_method :cache_key, :engine_name
49
+
50
+ def start_execute
51
+ debug "Permit Engine executing..."
52
+ end
53
+
54
+ def end_execute
55
+ debug "Done Permit Engine"
56
+ end
57
+
43
58
  def invalid
44
59
  debug "No permits found!"
45
60
  false
46
61
  end
47
62
 
48
63
  def permit_factory
49
- @permit_factory ||= CanTango::PermitEngine::Factory.new ability
64
+ @permit_factory ||= CanTango::PermitEngine::Factory.new self
65
+ end
66
+
67
+ def key_method_names
68
+ permits.keys.map {|type| key type }.compact
69
+ end
70
+
71
+ def key type
72
+ case type
73
+ when :role
74
+ roles_list_meth
75
+ when :role_group
76
+ role_groups_list_meth
77
+ else
78
+ nil
79
+ end
50
80
  end
51
81
  end
52
82
  end
@@ -8,15 +8,19 @@ module CanTango
8
8
  # @return [Array<RoleGroupPermit::Base>] the role permits built for this ability
9
9
  def build
10
10
  return [] if !user_account
11
- puts debug_msg if CanTango.debug?
11
+ puts debug_msg if CanTango.debug?
12
12
  [permit].compact
13
13
  end
14
14
 
15
+ def name
16
+ :account
17
+ end
18
+
15
19
  protected
16
20
 
17
21
  def debug_msg
18
22
  permit ? "Building AccountPermit for #{user_account}, permit: #{permit}" : "Not building any AccountPermit"
19
- end
23
+ end
20
24
 
21
25
  def permit
22
26
  create_permit(user_account.class.to_s)
@@ -2,44 +2,24 @@
2
2
  # which can be cached under some key and later reused
3
3
  #
4
4
  module CanTango
5
- class UserAcEngine < Engine
5
+ module Permits
6
6
  class Executor
7
- include CanTango::Ability::CacheHelpers
7
+ include CanTango::Ability::Executor
8
8
 
9
- attr_reader :ability, :permits
9
+ attr_reader :ability, :permit_type, :permits
10
10
 
11
- delegate :session, :user, :subject, :cached?, :to => :ability
12
-
13
- def initialize ability, permit_type, permissions
14
- @ability = ability
15
- @permissions = permissions
11
+ def initialize ability, permit_type, permits
12
+ @ability = ability
13
+ @permit_type = permit_type
14
+ @permits = permits
16
15
  end
17
16
 
18
- def cache_key
19
- :user_ac
20
- end
21
-
22
- def rules
23
- @rules ||= []
24
- end
25
-
26
- def clear_rules!
27
- @rules ||= []
28
- end
17
+ alias_method :cache_key, :permit_type
29
18
 
30
19
  def cache
31
20
  @cache ||= CanTango::Ability::Cache.new self, :cache_key => cache_key, :key_method_names => key_method_names
32
21
  end
33
22
 
34
- def execute!
35
- return if cached_rules?
36
-
37
- clear_rules!
38
- permit_rules
39
-
40
- cache_rules!
41
- end
42
-
43
23
  def permit_rules
44
24
  # TODO: somehow type specific caching of result of permits!
45
25
  permits.each do |permit|
@@ -50,10 +30,28 @@ module CanTango
50
30
 
51
31
  protected
52
32
 
33
+ def valid?
34
+ true
35
+ end
36
+
37
+ def start_execute
38
+ debug "Execute #{permit_type} permits"
39
+ end
40
+
41
+ def end_execute
42
+ debug "Done #{permit_type} permits"
43
+ end
44
+
53
45
  def key_method_names
54
- [:permissions_hash]
46
+ case permit_type
47
+ when :role
48
+ [roles_list_meth]
49
+ when :role_group
50
+ [role_groups_list_meth]
51
+ else
52
+ []
53
+ end
55
54
  end
56
55
  end
57
56
  end
58
57
  end
59
-
@@ -0,0 +1,21 @@
1
+ module CanTango
2
+ module Permits
3
+ class Permit
4
+ module ClassMethods
5
+ def first_name clazz
6
+ clazz.to_s.gsub(/^([A-Za-z]+).*/, '\1').underscore.to_sym # first part of class name
7
+ end
8
+
9
+ def type
10
+ :abstract
11
+ end
12
+
13
+ def account_name clazz
14
+ return nil if clazz.name == clazz.name.demodulize
15
+ clazz.name.gsub(/::.*/,'').gsub(/(.*)Permits/, '\1').underscore.to_sym
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+