eaco 0.5.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 (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