haveapi 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/lib/haveapi/action.rb +521 -0
  3. data/lib/haveapi/actions/default.rb +55 -0
  4. data/lib/haveapi/actions/paginable.rb +12 -0
  5. data/lib/haveapi/api.rb +66 -0
  6. data/lib/haveapi/authentication/base.rb +37 -0
  7. data/lib/haveapi/authentication/basic/provider.rb +37 -0
  8. data/lib/haveapi/authentication/chain.rb +110 -0
  9. data/lib/haveapi/authentication/token/provider.rb +166 -0
  10. data/lib/haveapi/authentication/token/resources.rb +107 -0
  11. data/lib/haveapi/authorization.rb +108 -0
  12. data/lib/haveapi/common.rb +38 -0
  13. data/lib/haveapi/context.rb +78 -0
  14. data/lib/haveapi/example.rb +36 -0
  15. data/lib/haveapi/extensions/action_exceptions.rb +25 -0
  16. data/lib/haveapi/extensions/base.rb +9 -0
  17. data/lib/haveapi/extensions/resource_prefetch.rb +7 -0
  18. data/lib/haveapi/hooks.rb +190 -0
  19. data/lib/haveapi/metadata.rb +56 -0
  20. data/lib/haveapi/model_adapter.rb +119 -0
  21. data/lib/haveapi/model_adapters/active_record.rb +352 -0
  22. data/lib/haveapi/model_adapters/hash.rb +27 -0
  23. data/lib/haveapi/output_formatter.rb +57 -0
  24. data/lib/haveapi/output_formatters/base.rb +29 -0
  25. data/lib/haveapi/output_formatters/json.rb +9 -0
  26. data/lib/haveapi/params/param.rb +114 -0
  27. data/lib/haveapi/params/resource.rb +109 -0
  28. data/lib/haveapi/params.rb +314 -0
  29. data/lib/haveapi/public/css/bootstrap-theme.min.css +7 -0
  30. data/lib/haveapi/public/css/bootstrap.min.css +7 -0
  31. data/lib/haveapi/public/js/bootstrap.min.js +6 -0
  32. data/lib/haveapi/public/js/jquery-1.11.1.min.js +4 -0
  33. data/lib/haveapi/resource.rb +120 -0
  34. data/lib/haveapi/route.rb +22 -0
  35. data/lib/haveapi/server.rb +440 -0
  36. data/lib/haveapi/spec/helpers.rb +103 -0
  37. data/lib/haveapi/types.rb +24 -0
  38. data/lib/haveapi/version.rb +3 -0
  39. data/lib/haveapi/views/doc_layout.erb +27 -0
  40. data/lib/haveapi/views/doc_sidebars/create-client.erb +20 -0
  41. data/lib/haveapi/views/doc_sidebars/protocol.erb +42 -0
  42. data/lib/haveapi/views/index.erb +12 -0
  43. data/lib/haveapi/views/main_layout.erb +50 -0
  44. data/lib/haveapi/views/version_page.erb +195 -0
  45. data/lib/haveapi/views/version_sidebar.erb +42 -0
  46. data/lib/haveapi.rb +22 -0
  47. metadata +242 -0
@@ -0,0 +1,9 @@
1
+ module HaveAPI
2
+ module Extensions
3
+ class Base
4
+ def self.enabled
5
+
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module HaveAPI::Extensions
2
+ class ResourcePrefetch < Base
3
+ def self.enabled
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,190 @@
1
+ module HaveAPI
2
+ # All registered hooks and connected endpoints are stored
3
+ # in this module.
4
+ #
5
+ # It supports connecting to both class and instance level hooks.
6
+ # Instance level hooks inherit all class registered hooks, but
7
+ # it is possible to connect to a specific instance and not for
8
+ # all instances of a class.
9
+ #
10
+ # === \Usage
11
+ # ==== \Register hooks
12
+ # class MyClass
13
+ # include Hookable
14
+ #
15
+ # has_hook :myhook
16
+ # end
17
+ #
18
+ # ==== \Class level hooks
19
+ # # Connect hook
20
+ # MyClass.connect_hook(:myhook) do |ret, a, b, c|
21
+ # # a = 1, b = 2, c = 3
22
+ # puts "Class hook!"
23
+ # ret
24
+ # end
25
+ #
26
+ # # Call hooks
27
+ # MyClass.call_hooks(:myhook, args: [1, 2, 3])
28
+ #
29
+ # ==== \Instance level hooks
30
+ # # Create an instance of MyClass
31
+ # my = MyClass.new
32
+ #
33
+ # # Connect hook
34
+ # my.connect_hook(:myhook) do |ret, a, b, c|
35
+ # # a = 1, b = 2, c = 3
36
+ # puts "Instance hook!"
37
+ # ret
38
+ # end
39
+ #
40
+ # # Call instance hooks
41
+ # my.call_instance_hooks_for(:myhook, args: [1, 2, 3])
42
+ # # Call class hooks
43
+ # my.call_class_hooks_for(:myhook, args: [1, 2, 3])
44
+ # # Call both instance and class hooks at once
45
+ # my.call_hooks_for(:myhook, args: [1, 2, 3])
46
+ module Hooks
47
+ # Register a hook defined by +klass+ with +name+.
48
+ # +klass+ is an instance of Class, that is class name, not it's instance.
49
+ def self.register_hook(klass, name)
50
+ classified = hook_classify(klass)
51
+
52
+ @hooks ||= {}
53
+ @hooks[classified] ||= {}
54
+ @hooks[classified][name] = []
55
+ end
56
+
57
+ # Connect class hook defined in +klass+ with +name+ to +block+.
58
+ # +klass+ is a class name.
59
+ def self.connect_hook(klass, name, &block)
60
+ @hooks[hook_classify(klass)][name] << block
61
+ end
62
+
63
+ # Connect instance hook from instance +klass+ with +name+ to +block+.
64
+ def self.connect_instance_hook(klass, name, &block)
65
+ unless @hooks[klass]
66
+ @hooks[klass] = {}
67
+
68
+ @hooks[klass.class].each do |k, v|
69
+ @hooks[klass][k] = []
70
+ end
71
+ end
72
+
73
+ @hooks[klass][name] << block
74
+ end
75
+
76
+ # Call all blocks that are connected to hook in +klass+ with +name+.
77
+ # +klass+ may be a class name or an object instance.
78
+ # If +where+ is set, the blocks are executed in it with instance_exec.
79
+ # +args+ is an array of arguments given to all blocks. The first argument
80
+ # to all block is always a return value from previous block or +initial+,
81
+ # which defaults to an empty hash.
82
+ #
83
+ # Blocks are executed one by one in the order they were connected.
84
+ # Blocks must return a hash, that is then passed to the next block
85
+ # and the return value from the last block is returned to the caller.
86
+ #
87
+ # A block may decide that no further blocks should be executed.
88
+ # In such a case it calls Hooks.stop with the return value. It is then
89
+ # returned to the caller immediately.
90
+ def self.call_for(klass, name, where = nil, args: [], initial: {})
91
+ classified = hook_classify(klass)
92
+
93
+ catch(:stop) do
94
+ return initial unless @hooks[classified]
95
+ hooks = @hooks[classified][name]
96
+ return initial unless hooks
97
+
98
+ hooks.each do |hook|
99
+ if where
100
+ ret = where.instance_exec(initial, *args, &hook)
101
+ else
102
+ ret = hook.call(initial, *args)
103
+ end
104
+
105
+ initial.update(ret) if ret
106
+ end
107
+
108
+ initial
109
+ end
110
+ end
111
+
112
+ def self.hook_classify(klass)
113
+ klass.is_a?(String) ? Object.const_get(klass) : klass
114
+ end
115
+
116
+ def self.stop(ret)
117
+ throw(ret)
118
+ end
119
+ end
120
+
121
+ # Classes that define hooks must include this module.
122
+ module Hookable
123
+ module ClassMethods
124
+ # Register a hook named +name+.
125
+ def has_hook(name)
126
+ Hooks.register_hook(self.to_s, name)
127
+ end
128
+
129
+ # Connect +block+ to registered hook with +name+.
130
+ def connect_hook(name, &block)
131
+ Hooks.connect_hook(self.to_s, name, &block)
132
+ end
133
+
134
+ # Call all hooks for +name+. see Hooks.call_for.
135
+ def call_hooks(*args)
136
+ Hooks.call_for(self.to_s, *args)
137
+ end
138
+ end
139
+
140
+ module InstanceMethods
141
+ # Call all instance and class hooks.
142
+ def call_hooks_for(*args)
143
+ ret = call_instance_hooks_for(*args)
144
+
145
+ if args.last.is_a?(::Hash)
146
+ args.last.update(initial: ret)
147
+ call_class_hooks_for(*args)
148
+ else
149
+ call_class_hooks_for(*args, initial: ret)
150
+ end
151
+ end
152
+
153
+ # Call only instance hooks.
154
+ def call_instance_hooks_for(name, where = nil, args: [], initial: {})
155
+ Hooks.call_for(self, name, where, args: args, initial: initial)
156
+ end
157
+
158
+ # Call only class hooks.
159
+ def call_class_hooks_for(name, where = nil, args: [], initial: {})
160
+ Hooks.call_for(self.class, name, where, args: args, initial: initial)
161
+ end
162
+
163
+ # Call hooks for different +klass+.
164
+ def call_hooks_as_for(klass, *args)
165
+ ret = call_instance_hooks_as_for(klass, *args)
166
+ call_class_hooks_as_for(klass.class, *args, initial: ret)
167
+ end
168
+
169
+ # Call only instance hooks for different +klass+.
170
+ def call_instance_hooks_as_for(klass, *args)
171
+ Hooks.call_for(klass, *args)
172
+ end
173
+
174
+ # Call only class hooks for different +klass+.
175
+ def call_class_hooks_as_for(klass, *args)
176
+ Hooks.call_for(klass, *args)
177
+ end
178
+
179
+ # Connect instance level hook +name+ to +block+.
180
+ def connect_hook(name, &block)
181
+ Hooks.connect_instance_hook(self, name, &block)
182
+ end
183
+ end
184
+
185
+ def self.included(base)
186
+ base.send(:extend, ClassMethods)
187
+ base.send(:include, InstanceMethods)
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,56 @@
1
+ module HaveAPI
2
+ class Metadata
3
+ def self.namespace
4
+ :_meta
5
+ end
6
+
7
+ def self.describe
8
+ {
9
+ namespace: namespace
10
+ }
11
+ end
12
+
13
+ class ActionMetadata
14
+ attr_writer :action
15
+
16
+ def clone
17
+ m = self.class.new
18
+ m.action = @action
19
+ m.instance_variable_set(:@input, @input && @input.clone)
20
+ m.instance_variable_set(:@output, @output && @output.clone)
21
+ m
22
+ end
23
+
24
+ def input(layout = :hash, &block)
25
+ if block
26
+ @input ||= Params.new(:input, @action)
27
+ @input.action = @action
28
+ @input.layout = layout
29
+ @input.namespace = false
30
+ @input.add_block(block)
31
+ else
32
+ @input
33
+ end
34
+ end
35
+
36
+ def output(layout = :hash, &block)
37
+ if block
38
+ @output ||= Params.new(:output, @action)
39
+ @output.action = @action
40
+ @output.layout = layout
41
+ @output.namespace = false
42
+ @output.add_block(block)
43
+ else
44
+ @output
45
+ end
46
+ end
47
+
48
+ def describe(context)
49
+ {
50
+ input: @input && @input.describe(context),
51
+ output: @output && @output.describe(context)
52
+ }
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,119 @@
1
+ module HaveAPI
2
+ # Model adapters are used to automate handling of action
3
+ # input/output.
4
+ #
5
+ # Adapters are chosen based on the +model+ set on a HaveAPI::Resource.
6
+ # If no +model+ is specified, ModelAdapters::Hash is used as a default
7
+ # adapter.
8
+ #
9
+ # All model adapters are based on this class.
10
+ class ModelAdapter
11
+ class << self
12
+ attr_accessor :adapters
13
+
14
+ # Every model adapter must register itself using this method.
15
+ def register
16
+ ModelAdapter.adapters ||= []
17
+ ModelAdapter.adapters << Kernel.const_get(self.to_s)
18
+ end
19
+
20
+ # Returns an adapter suitable for +layout+ and +obj+.
21
+ # Adapters are iterated over and the first to return true to handle?()
22
+ # is returned.
23
+ def for(layout, obj)
24
+ return ModelAdapters::Hash if !obj || %i(hash hash_list).include?(layout)
25
+ adapter = @adapters.detect { |adapter| adapter.handle?(layout, obj) }
26
+ adapter || ModelAdapters::Hash
27
+ end
28
+
29
+ # Shortcut to Input::clean.
30
+ def input_clean(*args)
31
+ self::Input.clean(*args)
32
+ end
33
+
34
+ # Shortcut to get an instance of Input model adapter.
35
+ def input(*args)
36
+ self::Input.new(*args)
37
+ end
38
+
39
+ # Shortcut to get an instance of Output model adapter.
40
+ def output(*args)
41
+ self::Output.new(*args)
42
+ end
43
+
44
+ # Override this method to load validators from +model+
45
+ # to +params+.
46
+ def load_validators(model, params)
47
+
48
+ end
49
+
50
+ # Called when mounting the API. Model adapters may use this method
51
+ # to add custom meta parameters to +action+. +direction+ is one of
52
+ # +:input+ and +:output+.
53
+ def used_by(direction, action)
54
+ case direction
55
+ when :input
56
+ self::Input.used_by(action)
57
+ when :output
58
+ self::Output.used_by(action)
59
+ end
60
+ end
61
+ end
62
+
63
+ # Subclass this class in your adapter and reimplement
64
+ # necessary methods.
65
+ class Input
66
+ def self.used_by(action)
67
+
68
+ end
69
+
70
+ def initialize(input)
71
+ @input = input
72
+ end
73
+
74
+ # Return true if input parameters contain parameter
75
+ # with +name+.
76
+ def has_param?(name)
77
+ @input.has_key?(name)
78
+ end
79
+
80
+ # Return parameter with +name+.
81
+ def [](name)
82
+ @input[name]
83
+ end
84
+
85
+ # Return model instance from a raw input resource parameter.
86
+ def self.clean(model, raw, extra)
87
+
88
+ end
89
+ end
90
+
91
+ # Subclass this class in your adapter and reimplement
92
+ # necessary methods.
93
+ class Output
94
+ def self.used_by(action)
95
+
96
+ end
97
+
98
+ def initialize(context, obj)
99
+ @context = context
100
+ @object = obj
101
+ end
102
+
103
+ # Return true if input parameters contain parameter
104
+ # with +name+.
105
+ def has_param?(name)
106
+
107
+ end
108
+
109
+ # Return a parameter in an appropriate format to be sent to a client.
110
+ def [](name)
111
+
112
+ end
113
+
114
+ def meta
115
+ {}
116
+ end
117
+ end
118
+ end
119
+ end