eaco 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +18 -0
  5. data/.yardopts +1 -0
  6. data/Appraisals +22 -0
  7. data/Gemfile +4 -0
  8. data/Guardfile +34 -0
  9. data/LICENSE.txt +23 -0
  10. data/README.md +225 -0
  11. data/Rakefile +26 -0
  12. data/eaco.gemspec +27 -0
  13. data/features/active_record.example.yml +8 -0
  14. data/features/active_record.travis.yml +7 -0
  15. data/features/rails_integration.feature +10 -0
  16. data/features/step_definitions/database.rb +7 -0
  17. data/features/step_definitions/resource_authorization.rb +15 -0
  18. data/features/support/env.rb +9 -0
  19. data/gemfiles/rails_3.2.gemfile +9 -0
  20. data/gemfiles/rails_4.0.gemfile +8 -0
  21. data/gemfiles/rails_4.1.gemfile +8 -0
  22. data/gemfiles/rails_4.2.gemfile +8 -0
  23. data/lib/eaco.rb +93 -0
  24. data/lib/eaco/acl.rb +206 -0
  25. data/lib/eaco/actor.rb +86 -0
  26. data/lib/eaco/adapters.rb +14 -0
  27. data/lib/eaco/adapters/active_record.rb +70 -0
  28. data/lib/eaco/adapters/active_record/compatibility.rb +83 -0
  29. data/lib/eaco/adapters/active_record/compatibility/v32.rb +27 -0
  30. data/lib/eaco/adapters/active_record/compatibility/v40.rb +59 -0
  31. data/lib/eaco/adapters/active_record/compatibility/v41.rb +16 -0
  32. data/lib/eaco/adapters/active_record/compatibility/v42.rb +17 -0
  33. data/lib/eaco/adapters/active_record/postgres_jsonb.rb +36 -0
  34. data/lib/eaco/adapters/couchrest_model.rb +37 -0
  35. data/lib/eaco/adapters/couchrest_model/couchdb_lucene.rb +71 -0
  36. data/lib/eaco/controller.rb +158 -0
  37. data/lib/eaco/cucumber.rb +11 -0
  38. data/lib/eaco/cucumber/active_record.rb +163 -0
  39. data/lib/eaco/cucumber/active_record/department.rb +19 -0
  40. data/lib/eaco/cucumber/active_record/document.rb +18 -0
  41. data/lib/eaco/cucumber/active_record/position.rb +21 -0
  42. data/lib/eaco/cucumber/active_record/schema.rb +36 -0
  43. data/lib/eaco/cucumber/active_record/user.rb +24 -0
  44. data/lib/eaco/cucumber/world.rb +136 -0
  45. data/lib/eaco/designator.rb +264 -0
  46. data/lib/eaco/dsl.rb +40 -0
  47. data/lib/eaco/dsl/acl.rb +163 -0
  48. data/lib/eaco/dsl/actor.rb +139 -0
  49. data/lib/eaco/dsl/actor/designators.rb +110 -0
  50. data/lib/eaco/dsl/base.rb +52 -0
  51. data/lib/eaco/dsl/resource.rb +129 -0
  52. data/lib/eaco/dsl/resource/permissions.rb +131 -0
  53. data/lib/eaco/error.rb +36 -0
  54. data/lib/eaco/railtie.rb +46 -0
  55. data/lib/eaco/rake.rb +10 -0
  56. data/lib/eaco/rake/default_task.rb +164 -0
  57. data/lib/eaco/resource.rb +234 -0
  58. data/lib/eaco/version.rb +7 -0
  59. data/spec/eaco/acl_spec.rb +147 -0
  60. data/spec/eaco/actor_spec.rb +13 -0
  61. data/spec/eaco/adapters/active_record/postgres_jsonb_spec.rb +9 -0
  62. data/spec/eaco/adapters/active_record_spec.rb +13 -0
  63. data/spec/eaco/adapters/couchrest_model/couchdb_lucene_spec.rb +9 -0
  64. data/spec/eaco/adapters/couchrest_model_spec.rb +9 -0
  65. data/spec/eaco/controller_spec.rb +12 -0
  66. data/spec/eaco/designator_spec.rb +25 -0
  67. data/spec/eaco/dsl/acl_spec.rb +9 -0
  68. data/spec/eaco/dsl/actor/designators_spec.rb +7 -0
  69. data/spec/eaco/dsl/actor_spec.rb +15 -0
  70. data/spec/eaco/dsl/resource/permissions_spec.rb +7 -0
  71. data/spec/eaco/dsl/resource_spec.rb +17 -0
  72. data/spec/eaco/error_spec.rb +9 -0
  73. data/spec/eaco/resource_spec.rb +31 -0
  74. data/spec/eaco_spec.rb +49 -0
  75. data/spec/spec_helper.rb +71 -0
  76. metadata +296 -0
@@ -0,0 +1,136 @@
1
+ module Eaco
2
+ module Cucumber
3
+
4
+ ##
5
+ # The world in which scenarios are run. This is a story and an example,
6
+ # real-world data model that can be effectively protected by Eaco.
7
+ #
8
+ # But +before { some :art }+
9
+ #
10
+ # == AYANAMI REI
11
+ # __.-"..--,__
12
+ # __..---" | _| "-_\
13
+ # __.---" | V|::.-"-._D
14
+ # _--"".-.._ ,,::::::'"\/""'-:-:/
15
+ # _.-""::_:_:::::'-8b---" "'
16
+ # .-/ ::::< |\::::::"\
17
+ # \/:::/::::'\\ |:::b::\
18
+ # /|::/:::/::::-::b:%b:\|
19
+ # \/::::d:|8:::b:"%%%%%\
20
+ # |\:b:dP:d.:::%%%%%"""-,
21
+ # \:\.V-/ _\b%P_ / .-._
22
+ # '|T\ "%j d:::--\.( "-.
23
+ # ::d< -" d%|:::do%P"-:. "-,
24
+ # |:I _ /%%%o::o8P "\. "\
25
+ # \8b d%%%%%%P""-._ _ \::. \
26
+ # \%%8 _./Y%%P/ .::'-oMMo )
27
+ # H"'|V | A:::...:odMMMMMM( ./
28
+ # H /_.--"JMMMMbo:d##########b/
29
+ # .-'o dMMMMMMMMMMMMMMP""
30
+ # /" / YMMMMMMMMM|
31
+ # / . . "MMMMMMMM/
32
+ # :..::..:::.. MMMMMMM:|
33
+ # \:/ \::::::::JMMMP":/
34
+ # :Ao ':__.-'MMMP:::Y
35
+ # dMM"./:::::::::-.Y
36
+ # _|b::od8::/:YM::/
37
+ # I HMMMP::/:/"Y/"
38
+ # \'""' '':|
39
+ # | -::::\
40
+ # | :-._ '::\
41
+ # |,.| \ _:"o
42
+ # | d" / " \_:\.
43
+ # ".Y. \ \::\
44
+ # \ \ \ MM\:Y
45
+ # Y \ | MM \:b
46
+ # >\ Y .MM MM
47
+ # .IY L_ MP' MP
48
+ # | \:| JM JP
49
+ # | :\| MP MM
50
+ # | ::: JM' JP|
51
+ # | ':' JP JM |
52
+ # L : JP MP |
53
+ # 0 | Y JM |
54
+ # 0 | JP" |
55
+ # 0 | JP |
56
+ # m | JP #
57
+ # I | JM" Y
58
+ # l | MP :"
59
+ # |\ :- :|
60
+ # | | '.\ :|
61
+ # | | "| \ :|
62
+ # \ \ \ :|
63
+ # | | | \ :|
64
+ # | | | \ :|
65
+ # | \ \ | '.
66
+ # | |:\ | :|
67
+ # \ |::\..| :\
68
+ # ". /::::::' :||
69
+ # :|::/:::| /:\
70
+ # | \/::|: \' ::|
71
+ # | :::|| ::|
72
+ # | ::|| ::|
73
+ # | ::|| ::|
74
+ # | ::|| ::|
75
+ # | ': | .:|
76
+ # | : | :|
77
+ # | : | :|
78
+ # | :|| .:|
79
+ # | ::\ .:|
80
+ # | ::: .::|
81
+ # / ::| :::|
82
+ # __/ .::| ':|
83
+ # ...----"" ::/ ::
84
+ # /m_ AMm '/ .:::
85
+ # ""MmmMMM#mmMMMMMMM" .:::m
86
+ # """YMMM""""""P ':mMI
87
+ # _' _MMMM
88
+ # _.-" mm mMMMMMMMM"
89
+ # / MMMMMMM""
90
+ # mmmmmmMMMM"
91
+ # ch1x0r
92
+ #
93
+ # http://ascii.co.uk/art/anime
94
+ #
95
+ # = Scenario
96
+ #
97
+ # In this imaginary world we are N E R V, a Top Secret organization that
98
+ # handles very confidential documents. Some users can read them, some can
99
+ # edit them, and very few bosses can destroy them.
100
+ #
101
+ # The organization employs internal staff and employs consultants. Staff
102
+ # members have official positions in the organization hierarchy, and they
103
+ # belong to units within departments. They have the big picture.
104
+ #
105
+ # Consultants, on the other hand, come and go, and work on small parts of
106
+ # the documents, for specific purposes. They do not have the big picture.
107
+ #
108
+ # Departments own the documents, not people. Documents are of interest of
109
+ # departments, sometimes they should be accessed by the whole house, some
110
+ # other time only few selected users, some times two specific departments
111
+ # or some units.
112
+ #
113
+ # Either way, most of the time, access is granted to who owns a peculiar
114
+ # authority within the organization and not to a specific person. People
115
+ # may change, authorities and rules change less often.
116
+ #
117
+ # = Mapping Eaco concepts
118
+ #
119
+ # The +Document+ is a {Eaco::Resource}
120
+ #
121
+ # Each instance of a +Document+ has an {Eaco::ACL} +.acl+ attribute.
122
+ #
123
+ # The +:reader+, +:editor+ and +:owner+ are Roles on the Document
124
+ # resource, and each role is granted a Permission.
125
+ #
126
+ # The User is a {Eaco::Actor}.
127
+ #
128
+ # Having an user account is the {Eaco::Designator} of type +:user+.
129
+ # Occupying an official position is the Designator of type +:position+.
130
+ # Belonging to a department is the Designator of type +:department+
131
+ #
132
+ class World
133
+ end
134
+
135
+ end
136
+ end
@@ -0,0 +1,264 @@
1
+ module Eaco
2
+
3
+ ##
4
+ # A Designator characterizes an Actor.
5
+ #
6
+ # Example: an User Actor is uniquely identified by its numerical +id+, as
7
+ # such we can define an +user+ designator that designs User 42 as +user:42+.
8
+ #
9
+ # The same User also could belong to the group +frobber+, uniquely
10
+ # identified by its name. We can then define a +group+ designator that would
11
+ # design the same User as +group:frobber+.
12
+ #
13
+ # In ACLs designators are given roles, and the intersection between the
14
+ # designators of an Actor and the ones defined in the ACL gives the role of
15
+ # the Actor for the Resource that the ACL secures.
16
+ #
17
+ # Designators for actors are defined through the DSL, see {DSL::Actor}
18
+ #
19
+ # @see ACL
20
+ # @see Actor
21
+ #
22
+ class Designator < String
23
+ class << self
24
+
25
+ ##
26
+ # Instantiate a designator of the given +type+ with the given +value+.
27
+ #
28
+ # Example:
29
+ #
30
+ # >> Designator.make('user', 42)
31
+ # => #<Designator(User) value:42>
32
+ #
33
+ # @param type [String] the designator type (e.g. +user+)
34
+ # @param value [String] the designator value. It will be stringified using +.to_s+.
35
+ #
36
+ # @return [Designator]
37
+ #
38
+ def make(type, value)
39
+ Eaco::DSL::Actor.find_designator(type).new(value)
40
+ end
41
+
42
+ ##
43
+ # Parses a Designator string representation and instantiates a new
44
+ # Designator instance from it.
45
+ #
46
+ # >> Designator.parse('user:42')
47
+ # => #<Designator(User) value:42>
48
+ #
49
+ # @param string [String] the designator string representation.
50
+ #
51
+ # @return [Designator]
52
+ #
53
+ def parse(string)
54
+ return string if string.is_a?(Designator)
55
+ make(*string.split(':', 2))
56
+ end
57
+
58
+ ##
59
+ # Resolves one or more designators into the target actors.
60
+ #
61
+ # @param designators [Array] designator string representations.
62
+ #
63
+ # @return [Array] resolved actors, application-dependant.
64
+ #
65
+ def resolve(designators)
66
+ Array.new(designators||[]).inject([]) {|ret, d| ret.concat parse(d).resolve}
67
+ end
68
+
69
+ ##
70
+ # Sets up the designator implementation with the given options.
71
+ # Currently:
72
+ #
73
+ # * +:from+ - defines the method to call on the Actor to obtain the unique
74
+ # IDs for this Designator class.
75
+ #
76
+ # Example configuration:
77
+ #
78
+ # actor User do
79
+ # designators do
80
+ # user from: :id
81
+ # group from: :group_ids
82
+ # end
83
+ # end
84
+ #
85
+ # This method is called from the DSL.
86
+ #
87
+ # @see DSL::Actor::Designators
88
+ # @see DSL::Actor::Designators#define_designator
89
+ #
90
+ def configure!(options)
91
+ @method = options.fetch(:from)
92
+ self
93
+
94
+ rescue KeyError
95
+ raise Malformed, "The designator option :from is required"
96
+ end
97
+
98
+ ##
99
+ # Harvests valid designators for the given Actor.
100
+ #
101
+ # It calls the +@method+ defined through the +:from+ option passed when
102
+ # configuring the designators (see {Designator.configure!}).
103
+ #
104
+ # @param actor [Actor]
105
+ #
106
+ # @return [Array] an array of Designator objects the Actor owns.
107
+ #
108
+ # @see Actor
109
+ #
110
+ def harvest(actor)
111
+ Array.new(actor.send(@method)||[]).map {|value| new(value) }
112
+ end
113
+
114
+ ##
115
+ # Sets this Designator label to the given value.
116
+ #
117
+ # Example:
118
+ #
119
+ # class User::Designators::Group < Eaco::Designator
120
+ # label "Active Directory Group"
121
+ # end
122
+ #
123
+ # @param value [String] the designator label
124
+ #
125
+ # @return [String] the configured label
126
+ #
127
+ def label(value = nil)
128
+ @label = value if value
129
+ @label ||= designator_name
130
+ end
131
+
132
+ ##
133
+ # Returns this class' demodulized name
134
+ #
135
+ def designator_name
136
+ self.name.split('::').last
137
+ end
138
+
139
+ ##
140
+ # Returns the designator type.
141
+ #
142
+ # The type symbol is derived from the class name, on the other way
143
+ # around, the {DSL} looks up designator implementation classes from the
144
+ # designator type symbol.
145
+ #
146
+ # Example:
147
+ #
148
+ # >> User::Designators::Group.id
149
+ # => :group
150
+ #
151
+ # @return [Symbol]
152
+ #
153
+ # @see DSL::Actor::Designators#implementation_for
154
+ #
155
+ def id
156
+ @_id ||= self.designator_name.gsub(/([a-z])?([A-Z])/) do |x|
157
+ [$1, $2.downcase].compact.join '_'
158
+ end.intern
159
+ end
160
+ alias type id
161
+
162
+ ##
163
+ # Searches designator definitions using the given query.
164
+ #
165
+ # To be implemented by derived classes. E.g. for a "User" designator
166
+ # this would return your own User instances that you may want to display
167
+ # in a typeahead menu, for your Enterprise authorization management
168
+ # UI... :-)
169
+ #
170
+ # @raise [NotImplementedError]
171
+ #
172
+ def search(query)
173
+ raise NotImplementedError
174
+ end
175
+ end
176
+
177
+ ##
178
+ # Initializes the designator with the given value. The string
179
+ # representation is then calculated by concatenating the type and the
180
+ # given value.
181
+ #
182
+ # An optional instance can be attached, if you use to pass around
183
+ # designators in your app.
184
+ #
185
+ # @param value [String] the unique ID valid in this designator namespace
186
+ # @param instance [Actor] optional Actor instance
187
+ #
188
+ def initialize(value, instance = nil)
189
+ @value, @instance = value, instance
190
+ super([ self.class.id, value ].join(':'))
191
+ end
192
+
193
+ # This designator unique ID in the namespace of the designator type.
194
+ attr_reader :value
195
+
196
+ # The instance given to {Designator#initialize}
197
+ attr_reader :instance
198
+
199
+ ##
200
+ # Should return an extended description for this designator. You can then
201
+ # use this to display it in your application.
202
+ #
203
+ # E.g. for an "User" designator this would be the user name, for a "Group"
204
+ # designator this would be the group name.
205
+ #
206
+ # @param style [Symbol] the description style. {#as_json} uses +:full+,
207
+ # but you are free to define whatever styles you do see fit.
208
+ #
209
+ # @return [String] the description
210
+ #
211
+ def describe(style = nil)
212
+ nil
213
+ end
214
+
215
+ ##
216
+ # Translates this designator to concrete Actor instances in your
217
+ # application. To be implemented by derived classes.
218
+ #
219
+ # @raise [NotImplementedError]
220
+ #
221
+ def resolve
222
+ raise NotImplementedError
223
+ end
224
+
225
+ ##
226
+ # Converts this designator to an Hash for +.to_json+ to work.
227
+ #
228
+ # @param options [Ignored]
229
+ #
230
+ # @return [Hash]
231
+ #
232
+ def as_json(options = nil)
233
+ { :value => to_s, :label => describe(:full) }
234
+ end
235
+
236
+ ##
237
+ # Pretty prints the designator in your console.
238
+ #
239
+ # @return [String]
240
+ #
241
+ def inspect
242
+ %[#<Designator(#{self.class.designator_name}) value:#{value.inspect}>]
243
+ end
244
+
245
+ ##
246
+ # @return [String] the designator's class label.
247
+ #
248
+ # @see Designator.label
249
+ #
250
+ def label
251
+ self.class.label
252
+ end
253
+
254
+ ##
255
+ # @return [Symbol] the designator's class type.
256
+ #
257
+ # @see Designator.type
258
+ #
259
+ def type
260
+ self.class.type
261
+ end
262
+ end
263
+
264
+ end
data/lib/eaco/dsl.rb ADDED
@@ -0,0 +1,40 @@
1
+ module Eaco
2
+
3
+ ##
4
+ # Eaco DSL entry point.
5
+ #
6
+ # @see DSL::Resource
7
+ # @see DSL::Actor
8
+ # @see DSL::ACL
9
+ #
10
+ module DSL
11
+ extend self # Oh the irony.
12
+
13
+ autoload :Base, 'eaco/dsl/base'
14
+
15
+ autoload :ACL, 'eaco/dsl/acl'
16
+ autoload :Actor, 'eaco/dsl/actor'
17
+ autoload :Resource, 'eaco/dsl/resource'
18
+
19
+ ##
20
+ # Entry point for the {Resource} authorization definition.
21
+ #
22
+ # @see DSL::Resource
23
+ # @see DSL::ACL
24
+ #
25
+ def authorize(resource_class, options = {}, &block)
26
+ DSL::Resource.eval(resource_class, options, &block)
27
+ DSL::ACL.eval(resource_class, options)
28
+ end
29
+
30
+ ##
31
+ # Entry point for the {Actor} designators definition.
32
+ #
33
+ # @see DSL::Actor
34
+ #
35
+ def actor(actor_class, options = {}, &block)
36
+ DSL::Actor.eval(actor_class, options, &block)
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,163 @@
1
+ require 'eaco/dsl/base'
2
+
3
+ module Eaco
4
+ module DSL
5
+
6
+ ##
7
+ # Block-less DSL to set up the {ACL} machinery onto an authorized {Resource}.
8
+ #
9
+ # * Defines an {ACL} subclass in the Resource namespace
10
+ # * Defines syntactic sugar on the ACL to easily retrieve {Actor}s with a
11
+ # specific Role
12
+ # * Installs {ACL} objects persistance for the supported ORMs
13
+ # * Installs the authorized collection extraction strategy +.accessible_by+
14
+ #
15
+ class ACL < Base
16
+
17
+ ##
18
+ # Performs ACL setup on the target Resource class.
19
+ #
20
+ # @see #define_acl_subclass
21
+ # @see #define_role_getters
22
+ # @see #install_persistance
23
+ #
24
+ def initialize(*)
25
+ super
26
+
27
+ define_acl_subclass
28
+ define_role_getters
29
+ install_persistance
30
+ end
31
+
32
+ private
33
+
34
+ ##
35
+ # Creates the ACL constant on the target, inheriting from {Eaco::ACL}.
36
+ # Removes if it is already set, so that a reload of the authorization
37
+ # rules refreshes also these constants.
38
+ #
39
+ # The ACL subclass can be retrieved using the +.acl+ singleton method
40
+ # on the {Resource} class.
41
+ #
42
+ # @return [void]
43
+ #
44
+ def define_acl_subclass
45
+ target_eval do
46
+ remove_const(:ACL) if const_defined?(:ACL)
47
+
48
+ Class.new(Eaco::ACL).tap do |acl_class|
49
+ define_singleton_method(:acl) { acl_class }
50
+ const_set(:ACL, acl_class)
51
+ end
52
+ end
53
+ end
54
+
55
+ ##
56
+ # Define getter methods on the ACL for each role, syntactic sugar
57
+ # for calling {ACL#find_by_role}.
58
+ #
59
+ # Example:
60
+ #
61
+ # If a +reader+ role is defined, allows doing +resource.acl.readers+
62
+ # and returns all the designators having the +reader+ role set.
63
+ #
64
+ # @return [void]
65
+ #
66
+ def define_role_getters
67
+ roles = self.target.roles
68
+
69
+ target.acl.instance_eval do
70
+ roles.each do |role|
71
+ define_method(role.to_s.pluralize) { find_by_role(role) }
72
+ end
73
+ end
74
+ end
75
+
76
+ ##
77
+ # Sets up the persistance layer for ACLs (+#acl+ and +#acl=+) and the
78
+ # authorized collection extraction strategy (+.accessible_by+).
79
+ #
80
+ # All these APIs can be implemented directly in your models, as
81
+ # long as the +acl+ accessor accepts and returns the model's ACL
82
+ # subclass (see {.define_acl_subclass}); and the +.accessible_by+
83
+ # returns an +Enumerable+ collection.
84
+ #
85
+ # See each adapter for the details of the extraction strategies
86
+ # they provide.
87
+ #
88
+ # @return [void]
89
+ #
90
+ def install_persistance
91
+ if adapter
92
+ target.send(:include, adapter)
93
+ install_authorized_collection_strategy
94
+
95
+ elsif target.respond_to?(:acl) && target.respond_to?(:acl=)
96
+ raise Malformed, <<-EOF
97
+ Don't know how to persist ACLs using <#{target}>'s ORM
98
+ (identified as <#{orm}>). Please define an `acl' instance
99
+ accessor on <#{target}> that accepts and returns a <#{target.acl}>.
100
+ EOF
101
+ end
102
+
103
+ unless target.respond_to?(:accessible_by)
104
+ strategies = adapter ? adapter.strategies.keys : []
105
+
106
+ raise Malformed, <<-EOF
107
+ Don't know how to look up authorized records on <#{target}>'s
108
+ ORM (identified as <#{orm}>). To authorize <#{target}>
109
+
110
+ #{ if strategies.size > 0
111
+ "either use one of the available strategies: #{strategies.join(', ')} or"
112
+ end }
113
+
114
+ please define your own #{target}.accessible_by method.
115
+ You may at one point want to move this in a new strategy,
116
+ and send a pull request :-).
117
+ EOF
118
+ end
119
+ end
120
+
121
+ ##
122
+ # Looks up the authorized collection strategy within the Adapter,
123
+ # using the +:using+ option given to the +authorize+ Resource DSL
124
+ #
125
+ # @see DSL::Resource
126
+ #
127
+ # @return [void]
128
+ #
129
+ def install_authorized_collection_strategy
130
+ if adapter && (strategy = adapter.strategies[ options.fetch(:using, nil) ])
131
+ target.extend strategy
132
+ end
133
+ end
134
+
135
+ ##
136
+ # Tries to identify which ORM adapter to use for the +target+ class.
137
+ #
138
+ # @return [Class] the adapter implementation or nil if not available.
139
+ #
140
+ def adapter
141
+ { 'ActiveRecord::Base' => Eaco::Adapters::ActiveRecord,
142
+ 'CouchRest::Model::Base' => Eaco::Adapters::CouchrestModel,
143
+ }.fetch(orm.name, nil)
144
+ end
145
+
146
+ ##
147
+ # Tries to naively identify which ORM the target model is using.
148
+ #
149
+ # TODO support more stuff
150
+ #
151
+ # @return [Class] the ORM base class.
152
+ #
153
+ def orm
154
+ if target.respond_to?(:base_class)
155
+ target.base_class.superclass # Active Record
156
+ else
157
+ target.superclass # Naive
158
+ end
159
+ end
160
+ end
161
+
162
+ end
163
+ end