haveapi 0.3.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 (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