daylight 0.9.0.rc1
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.
- checksums.yaml +7 -0
- data/README.md +113 -0
- data/app/controllers/daylight_documentation/documentation_controller.rb +27 -0
- data/app/helpers/daylight_documentation/documentation_helper.rb +57 -0
- data/app/views/daylight_documentation/documentation/_header.haml +4 -0
- data/app/views/daylight_documentation/documentation/index.haml +12 -0
- data/app/views/daylight_documentation/documentation/model.haml +114 -0
- data/app/views/layouts/documentation.haml +22 -0
- data/config/routes.rb +8 -0
- data/doc/actions.md +70 -0
- data/doc/benchmarks.md +17 -0
- data/doc/contribute.md +80 -0
- data/doc/develop.md +1205 -0
- data/doc/environment.md +109 -0
- data/doc/example.md +3 -0
- data/doc/framework.md +31 -0
- data/doc/install.md +128 -0
- data/doc/principles.md +42 -0
- data/doc/testing.md +107 -0
- data/doc/usage.md +970 -0
- data/lib/daylight/api.rb +293 -0
- data/lib/daylight/associations.rb +247 -0
- data/lib/daylight/client_reloader.rb +45 -0
- data/lib/daylight/collection.rb +161 -0
- data/lib/daylight/errors.rb +94 -0
- data/lib/daylight/inflections.rb +7 -0
- data/lib/daylight/mock.rb +282 -0
- data/lib/daylight/read_only.rb +88 -0
- data/lib/daylight/refinements.rb +63 -0
- data/lib/daylight/reflection_ext.rb +67 -0
- data/lib/daylight/resource_proxy.rb +226 -0
- data/lib/daylight/version.rb +10 -0
- data/lib/daylight.rb +27 -0
- data/rails/daylight/api_controller.rb +354 -0
- data/rails/daylight/documentation.rb +13 -0
- data/rails/daylight/helpers.rb +32 -0
- data/rails/daylight/params.rb +23 -0
- data/rails/daylight/refiners.rb +186 -0
- data/rails/daylight/server.rb +29 -0
- data/rails/daylight/tasks.rb +37 -0
- data/rails/extensions/array_ext.rb +9 -0
- data/rails/extensions/autosave_association_fix.rb +49 -0
- data/rails/extensions/has_one_serializer_ext.rb +111 -0
- data/rails/extensions/inflections.rb +6 -0
- data/rails/extensions/nested_attributes_ext.rb +94 -0
- data/rails/extensions/read_only_attributes.rb +35 -0
- data/rails/extensions/render_json_meta.rb +99 -0
- data/rails/extensions/route_options.rb +47 -0
- data/rails/extensions/versioned_url_for.rb +22 -0
- data/spec/config/dependencies.rb +2 -0
- data/spec/config/factory_girl.rb +4 -0
- data/spec/config/simplecov_rcov.rb +26 -0
- data/spec/config/test_api.rb +1 -0
- data/spec/controllers/documentation_controller_spec.rb +24 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config/application.rb +24 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +29 -0
- data/spec/dummy/config/environments/production.rb +80 -0
- data/spec/dummy/config/environments/test.rb +36 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/daylight.rb +1 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +12 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +59 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +58 -0
- data/spec/dummy/public/422.html +58 -0
- data/spec/dummy/public/500.html +57 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/helpers/documentation_helper_spec.rb +82 -0
- data/spec/lib/daylight/api_spec.rb +178 -0
- data/spec/lib/daylight/associations_spec.rb +325 -0
- data/spec/lib/daylight/collection_spec.rb +235 -0
- data/spec/lib/daylight/errors_spec.rb +111 -0
- data/spec/lib/daylight/mock_spec.rb +144 -0
- data/spec/lib/daylight/read_only_spec.rb +118 -0
- data/spec/lib/daylight/refinements_spec.rb +80 -0
- data/spec/lib/daylight/reflection_ext_spec.rb +50 -0
- data/spec/lib/daylight/resource_proxy_spec.rb +325 -0
- data/spec/rails/daylight/api_controller_spec.rb +421 -0
- data/spec/rails/daylight/helpers_spec.rb +41 -0
- data/spec/rails/daylight/params_spec.rb +45 -0
- data/spec/rails/daylight/refiners_spec.rb +178 -0
- data/spec/rails/extensions/array_ext_spec.rb +51 -0
- data/spec/rails/extensions/has_one_serializer_ext_spec.rb +135 -0
- data/spec/rails/extensions/nested_attributes_ext_spec.rb +177 -0
- data/spec/rails/extensions/render_json_meta_spec.rb +140 -0
- data/spec/rails/extensions/route_options_spec.rb +309 -0
- data/spec/rails/extensions/versioned_url_for_spec.rb +46 -0
- data/spec/spec_helper.rb +43 -0
- data/spec/support/migration_helper.rb +40 -0
- metadata +422 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
require 'active_resource/reflection'
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# Extension to use the API namespace and version modules to lookup classes for
|
|
5
|
+
# a reflection. The benefit is to be able to define an association wihtout
|
|
6
|
+
# the module name or using the :class_name option.
|
|
7
|
+
#
|
|
8
|
+
# Without the extension, we have to specify the `:class_name`:
|
|
9
|
+
#
|
|
10
|
+
# class API::V1::Post < Daylight::API
|
|
11
|
+
# has_many :comments, class_name: 'api/v1/comment'
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# With the extension, it will be determined using the namespace and version modules
|
|
15
|
+
#
|
|
16
|
+
# class API::V1::Post < Daylight::API
|
|
17
|
+
# belongs_to :blog
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# API::V1::Post.find(1).blog #=> #<API::V1::Blog:0x007ffa8a43f1e8 ...>
|
|
21
|
+
#
|
|
22
|
+
# The `:class_name` option still can be specified for alternate behavior
|
|
23
|
+
#
|
|
24
|
+
# class API::V1::Post < Daylight::API
|
|
25
|
+
# belongs_to :author, class_name: 'api/v1/user', foreign_key: 'created_by'
|
|
26
|
+
# end
|
|
27
|
+
|
|
28
|
+
module Daylight::ReflectionExt
|
|
29
|
+
extend ActiveSupport::Concern
|
|
30
|
+
|
|
31
|
+
included do
|
|
32
|
+
##
|
|
33
|
+
# Determines and tests for the name with namespace and version.
|
|
34
|
+
#
|
|
35
|
+
# Falls back to the name of the association which was the original behavior
|
|
36
|
+
# of `AssociationReflection`.
|
|
37
|
+
#
|
|
38
|
+
# See
|
|
39
|
+
# ActiveResource::Reflection::AssociationReflection
|
|
40
|
+
|
|
41
|
+
def expanded_name
|
|
42
|
+
@expanded_name = begin
|
|
43
|
+
candidate_name = [Daylight::API.namespace, Daylight::API.version, name.to_s.classify].join("::")
|
|
44
|
+
candidate_name.classify.constantize # test for the name, raise if non-existant
|
|
45
|
+
candidate_name.underscore
|
|
46
|
+
rescue
|
|
47
|
+
name.to_s # fallback to using the original functionality
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
##
|
|
53
|
+
# Uses the expanded or default name to derive the class name. Changes
|
|
54
|
+
# original `AssociationReflection` functionality.
|
|
55
|
+
#
|
|
56
|
+
# See
|
|
57
|
+
# ActiveResource::Reflection::AssociationReflection.derive_class_name
|
|
58
|
+
|
|
59
|
+
def derive_class_name
|
|
60
|
+
return (options[:class_name] ? options[:class_name].to_s : expanded_name).classify
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
ActiveResource::Reflection::AssociationReflection.class_eval do
|
|
66
|
+
include Daylight::ReflectionExt
|
|
67
|
+
end
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Proxies requests to ActiveResource once the data has been accessed.
|
|
3
|
+
# Allows chaining of scope calls ala ActiveRecord
|
|
4
|
+
class Daylight::ResourceProxy
|
|
5
|
+
include Enumerable
|
|
6
|
+
|
|
7
|
+
delegate :to_xml, :to_yaml, :length, :each, :to_ary, :size, :last, :[], :==, to: :to_a
|
|
8
|
+
delegate :first_or_initialize, :first_or_create, to: :records
|
|
9
|
+
delegate :resource_class, to: :class
|
|
10
|
+
|
|
11
|
+
attr_reader :association_name, :association_resource
|
|
12
|
+
|
|
13
|
+
# turn off constructor, only use factory methods
|
|
14
|
+
private_class_method :new
|
|
15
|
+
|
|
16
|
+
def initialize association={}
|
|
17
|
+
@current_params = {}
|
|
18
|
+
@association_name, @association_resource = association.first
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# Delegates appending to the association resource when available
|
|
23
|
+
|
|
24
|
+
def << value
|
|
25
|
+
if association_resource
|
|
26
|
+
association_resource.send("#{association_name}=", records.elements << value)
|
|
27
|
+
else
|
|
28
|
+
raise NoMethodError, "undefined method `<<' for #{self}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# Sets `from` URL on a request
|
|
34
|
+
def from from
|
|
35
|
+
@from = from
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
##
|
|
40
|
+
# Loads records from server based on current paremeters and from URL
|
|
41
|
+
def load
|
|
42
|
+
resource_class.find(:all, params: to_params, from: @from)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# Returns a copy of the current parameters used to fetch records
|
|
47
|
+
def to_params
|
|
48
|
+
current_params.dup
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
##
|
|
52
|
+
# Returns the records, requests them from server if not fetched
|
|
53
|
+
def records
|
|
54
|
+
@records ||= load
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
# Returns the records, forces fetch from server
|
|
59
|
+
def reload
|
|
60
|
+
@records = load
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
##
|
|
64
|
+
# Converts records to an Array
|
|
65
|
+
def to_a
|
|
66
|
+
records.to_a
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
# Adds scopes to the current parameters
|
|
71
|
+
def append_scope scope
|
|
72
|
+
spawn.tap do |proxy|
|
|
73
|
+
proxy.current_params[:scopes] ||= []
|
|
74
|
+
proxy.current_params[:scopes] << scope
|
|
75
|
+
proxy.current_params[:scopes].uniq!
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
##
|
|
80
|
+
# Merges conditions to the current parameters
|
|
81
|
+
def where conditions
|
|
82
|
+
spawn.tap do |proxy|
|
|
83
|
+
proxy.current_params[:filters] ||= {}
|
|
84
|
+
proxy.current_params[:filters].merge! conditions
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
##
|
|
89
|
+
# Sets limit in the current parameters
|
|
90
|
+
def limit value
|
|
91
|
+
spawn.tap do |proxy|
|
|
92
|
+
proxy.current_params[:limit] = value
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
##
|
|
97
|
+
# Sets order in the current parameters
|
|
98
|
+
def order value
|
|
99
|
+
spawn.tap do |proxy|
|
|
100
|
+
proxy.current_params[:order] = value
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
##
|
|
105
|
+
# Sets offset in the current parameters
|
|
106
|
+
def offset value
|
|
107
|
+
spawn.tap do |proxy|
|
|
108
|
+
proxy.current_params[:offset] = value
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
##
|
|
113
|
+
# Merges conditions to the current parameters, and fetches the first result.
|
|
114
|
+
# Immediately issues the request to the API.
|
|
115
|
+
|
|
116
|
+
def find_by conditions
|
|
117
|
+
where(conditions).limit(1).first
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
##
|
|
121
|
+
# Sets the limit to the current parameters, and fetches the first result.
|
|
122
|
+
# Immediately issues the request to the API.
|
|
123
|
+
|
|
124
|
+
def first
|
|
125
|
+
limit(1).to_a.first
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
##
|
|
129
|
+
# Special inspect that shows the fetched results (up to 10 fo them) and the
|
|
130
|
+
# current params to fetch those results.
|
|
131
|
+
#
|
|
132
|
+
# Immediately issues the request to the API.
|
|
133
|
+
|
|
134
|
+
def inspect
|
|
135
|
+
records = to_a.take(11)
|
|
136
|
+
records[10] = '...' if entries.size == 11
|
|
137
|
+
|
|
138
|
+
"#<#{self.class.name} #{records} @current_params=#{current_params}>"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
class << self
|
|
142
|
+
# Each ResourceProxy will have thier own resource
|
|
143
|
+
attr_accessor :resource_class
|
|
144
|
+
|
|
145
|
+
##
|
|
146
|
+
# Factory method to generate a child class for ResourceProxy with the
|
|
147
|
+
# required resource class.
|
|
148
|
+
#
|
|
149
|
+
# proxy = ResourceProxy[User] #=> User::ResourceProxy
|
|
150
|
+
#
|
|
151
|
+
# Onece a child class has been created, it can be use it to create
|
|
152
|
+
# instances:
|
|
153
|
+
#
|
|
154
|
+
# ResourceProxy[User].new
|
|
155
|
+
|
|
156
|
+
def [] resource_class
|
|
157
|
+
if resource_class.const_defined?(:ResourceProxy)
|
|
158
|
+
return resource_class.const_get(:ResourceProxy)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
Class.new(Daylight::ResourceProxy) do
|
|
162
|
+
# Allow instances to be created
|
|
163
|
+
public_class_method :new
|
|
164
|
+
|
|
165
|
+
# Set our resource
|
|
166
|
+
self.resource_class = resource_class
|
|
167
|
+
|
|
168
|
+
# Set our ResourceProxy constant
|
|
169
|
+
resource_class.const_set(:ResourceProxy, self)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
##
|
|
174
|
+
# Define a name for the class
|
|
175
|
+
def name
|
|
176
|
+
"#{resource_class}::ResourceProxy"
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
##
|
|
180
|
+
# Use the class name as the inspect
|
|
181
|
+
alias_method :inspect, :name
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
##
|
|
185
|
+
# Will attempt to fulfill the method if it exists on the resource or if it
|
|
186
|
+
# exists on an Array. Defines or delegates the method on for subsequent
|
|
187
|
+
# execution.
|
|
188
|
+
|
|
189
|
+
def method_missing(method_name, *args, &block)
|
|
190
|
+
if resource_class.respond_to?(method_name)
|
|
191
|
+
self.class.send(:define_method, method_name) do |*method_args, &method_block|
|
|
192
|
+
resource_class.send(method_name, *method_args, &method_block)
|
|
193
|
+
end
|
|
194
|
+
resource_class.send(method_name, *args, &block)
|
|
195
|
+
elsif Array.method_defined?(method_name)
|
|
196
|
+
self.class.delegate method_name, :to => :to_a
|
|
197
|
+
to_a.send(method_name, *args, &block)
|
|
198
|
+
else
|
|
199
|
+
super
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
protected
|
|
204
|
+
attr_accessor :current_params
|
|
205
|
+
|
|
206
|
+
##
|
|
207
|
+
# Clone current ResourceProxy and with `current_params` to keep its query
|
|
208
|
+
# context
|
|
209
|
+
#
|
|
210
|
+
# See
|
|
211
|
+
# reset
|
|
212
|
+
|
|
213
|
+
def spawn
|
|
214
|
+
clone.reset(current_params)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
##
|
|
218
|
+
# Resets a ResourceProxy marked to refetch results based on blank or
|
|
219
|
+
# supplied `current_params`
|
|
220
|
+
|
|
221
|
+
def reset old_params={}
|
|
222
|
+
@current_params = old_params.deep_dup
|
|
223
|
+
@records = nil
|
|
224
|
+
self
|
|
225
|
+
end
|
|
226
|
+
end
|
data/lib/daylight.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
$:.unshift File.expand_path('../../rails', __FILE__)
|
|
2
|
+
|
|
3
|
+
require 'active_support/core_ext'
|
|
4
|
+
require 'active_resource'
|
|
5
|
+
|
|
6
|
+
##
|
|
7
|
+
# Include into API client to enable Daylight::API based queries
|
|
8
|
+
|
|
9
|
+
module Daylight
|
|
10
|
+
extend ActiveSupport::Autoload
|
|
11
|
+
|
|
12
|
+
eager_autoload do
|
|
13
|
+
autoload :Version
|
|
14
|
+
autoload :Associations
|
|
15
|
+
autoload :Collection
|
|
16
|
+
autoload :Errors
|
|
17
|
+
autoload :Inflections
|
|
18
|
+
autoload :Refinements
|
|
19
|
+
autoload :ReadOnly
|
|
20
|
+
autoload :ResourceProxy
|
|
21
|
+
autoload :ReflectionExt
|
|
22
|
+
autoload :API
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# run eager load
|
|
27
|
+
Daylight.eager_load!
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Common conntroller built on top of `ApplicationController` that assists with
|
|
3
|
+
# setting up standard, conventional REST and Daylight supported actions.
|
|
4
|
+
#
|
|
5
|
+
# Have your API Controllers subclass from APIController:
|
|
6
|
+
#
|
|
7
|
+
# class ExampleController < APIController
|
|
8
|
+
# end
|
|
9
|
+
#
|
|
10
|
+
# You must "turn on" common actions to allow for functionality. All common
|
|
11
|
+
# actions are turned off by default so that what is exposed is
|
|
12
|
+
# determiend by the API devloper. For example, to turn on `index` action:
|
|
13
|
+
#
|
|
14
|
+
# class ExampleController < APIController
|
|
15
|
+
# handles :index
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# The common actions provided by APIController are: `:index`, `:create`,
|
|
19
|
+
# `:show`, `:update`, `:destroy`, `:associated`, and `:remoted`.
|
|
20
|
+
#
|
|
21
|
+
# The method implementations are simply methods in a superclass and can be
|
|
22
|
+
# overwritten or called through `super`:
|
|
23
|
+
#
|
|
24
|
+
# class ExampleController < APIController
|
|
25
|
+
# handles :index
|
|
26
|
+
#
|
|
27
|
+
# def index
|
|
28
|
+
# @title = 'My Title'
|
|
29
|
+
# super
|
|
30
|
+
# end
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# The model and instance variable for these actions are based on the
|
|
34
|
+
# controller name. If you need to customize these, you can change them:
|
|
35
|
+
#
|
|
36
|
+
# class ExampleController < APIController
|
|
37
|
+
# self.model_name = :news
|
|
38
|
+
# self.record_name = :post
|
|
39
|
+
#
|
|
40
|
+
# handles :index
|
|
41
|
+
# end
|
|
42
|
+
#
|
|
43
|
+
# Continue to use `ApplicationController` for shared methods.
|
|
44
|
+
#
|
|
45
|
+
# See:
|
|
46
|
+
# ApplicationController
|
|
47
|
+
|
|
48
|
+
class Daylight::APIController < ApplicationController
|
|
49
|
+
include Daylight::Helpers
|
|
50
|
+
include VersionedUrlFor
|
|
51
|
+
|
|
52
|
+
API_ACTIONS = [:index, :create, :show, :update, :destroy, :associated, :remoted].freeze
|
|
53
|
+
class_attribute :record_name, :collection_name, :model_name
|
|
54
|
+
|
|
55
|
+
alias_method :set_record_name, :record_name= #:nodoc:
|
|
56
|
+
alias_method :set_collection_name, :collection_name= #:nodoc:
|
|
57
|
+
alias_method :set_model_name, :model_name= #:nodoc:
|
|
58
|
+
|
|
59
|
+
##
|
|
60
|
+
# Ensure messaging when sending unknown attributes or improper SQL
|
|
61
|
+
rescue_from ArgumentError,
|
|
62
|
+
ActiveRecord::UnknownAttributeError,
|
|
63
|
+
ActiveRecord::StatementInvalid do |e|
|
|
64
|
+
|
|
65
|
+
render json: { errors: e.message }, status: :bad_request
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
##
|
|
69
|
+
# Ensure messaging when there are validation errors on save and update
|
|
70
|
+
rescue_from ActiveRecord::RecordInvalid do |e|
|
|
71
|
+
render json: { errors: e.record.errors }, status: :unprocessable_entity
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
##
|
|
75
|
+
# Ensure messaging when there unpermitted attributes on save and update
|
|
76
|
+
rescue_from ActionController::UnpermittedParameters do |e|
|
|
77
|
+
errors = {}
|
|
78
|
+
e.params.each { |key| errors[key] = ['unpermitted parameter'] }
|
|
79
|
+
render json: { errors: errors }, status: :unprocessable_entity
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
##
|
|
83
|
+
# Ensure messaging when attributes have not been permitted on the controller
|
|
84
|
+
rescue_from ActiveModel::ForbiddenAttributesError do |e|
|
|
85
|
+
render json: { errors: 'parameters have not been permitted on this action' }, status: :bad_request
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class << self
|
|
90
|
+
protected
|
|
91
|
+
##
|
|
92
|
+
# Turns on common actions based on subclass needs (sets them as public methods).
|
|
93
|
+
# Uses whitelist of allowed actions to be handled.
|
|
94
|
+
#
|
|
95
|
+
# See:
|
|
96
|
+
# API_ACTIONS
|
|
97
|
+
#--
|
|
98
|
+
# `public` is called in context of the subclass
|
|
99
|
+
def handles *actions
|
|
100
|
+
actions = API_ACTIONS.dup if actions.any? {|a| a == :all }
|
|
101
|
+
|
|
102
|
+
whitelisted = actions.map(&:to_sym) & API_ACTIONS
|
|
103
|
+
|
|
104
|
+
if (unhandled = actions - whitelisted).present?
|
|
105
|
+
logger.warn "Daylight::APIController isn't handling unwhitelisted actions"
|
|
106
|
+
logger.warn "\tspecified in #{self.name}#handles: #{unhandled.join(',')}"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
public(*whitelisted) if whitelisted.present?
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
##
|
|
113
|
+
# Retrieves conventional key for a model name used in params
|
|
114
|
+
def model_key
|
|
115
|
+
model_name.to_s.singularize.to_sym
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
##
|
|
119
|
+
# Retieves `const` for ActiveRecord model as defined by `model_name`
|
|
120
|
+
def model
|
|
121
|
+
model_name.to_s.classify.constantize
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
##
|
|
125
|
+
# Delegates to `model#primary_key`
|
|
126
|
+
# Not using delegate as model may change via `model_name` configuration
|
|
127
|
+
def primary_key
|
|
128
|
+
model.primary_key
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
##
|
|
132
|
+
# Sets the default `model_name` and `record_name` by default.
|
|
133
|
+
# By default, they are based on the value determined by `controller_name`
|
|
134
|
+
#
|
|
135
|
+
# See:
|
|
136
|
+
# ActionController::Base.controller_name
|
|
137
|
+
def inherited api
|
|
138
|
+
name = api.controller_name.singularize
|
|
139
|
+
|
|
140
|
+
api.model_name = name
|
|
141
|
+
api.record_name = name
|
|
142
|
+
api.collection_name = 'collection'
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
protected
|
|
147
|
+
##
|
|
148
|
+
# Retrieves the value for the `record_name` instance variable
|
|
149
|
+
def record
|
|
150
|
+
instance_variable_get("@#{record_name}")
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
##
|
|
154
|
+
# Sets the value for the `record_name` instance variable
|
|
155
|
+
def record= value
|
|
156
|
+
instance_variable_set("@#{record_name}", value)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
##
|
|
160
|
+
# Retrieves the value for the `record_name` instance variable
|
|
161
|
+
def collection
|
|
162
|
+
instance_variable_get("@#{collection_name}")
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
##
|
|
166
|
+
# Sets the value for the `record_name` instance variable
|
|
167
|
+
def collection= value
|
|
168
|
+
instance_variable_set("@#{collection_name}", value)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
##
|
|
172
|
+
# Instance-level delegate of the `model` method
|
|
173
|
+
#
|
|
174
|
+
# See:
|
|
175
|
+
# #model
|
|
176
|
+
def model
|
|
177
|
+
self.class.send(:model)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
##
|
|
181
|
+
# Instance-level delegate of the `model_key` method
|
|
182
|
+
#
|
|
183
|
+
# See:
|
|
184
|
+
# #model_key
|
|
185
|
+
def model_key
|
|
186
|
+
self.class.send(:model_key)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
##
|
|
190
|
+
# Retrieves the strong parameters for a model based on the `model_key` name.
|
|
191
|
+
# Ex. post_params
|
|
192
|
+
#
|
|
193
|
+
# If strong parameters are not defined, it will use the `model_key` to
|
|
194
|
+
# dereference the raw params.
|
|
195
|
+
# Ex. params[:post]
|
|
196
|
+
def model_params
|
|
197
|
+
model_params_name = "#{model_key}_params"
|
|
198
|
+
respond_to?(model_params_name, true) ? send(model_params_name) : params[model_key]
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
##
|
|
202
|
+
# Instance-level delegate of the `primary_key` method
|
|
203
|
+
#
|
|
204
|
+
# See:
|
|
205
|
+
# #primary_key
|
|
206
|
+
def primary_key
|
|
207
|
+
self.class.send(:primary_key)
|
|
208
|
+
end
|
|
209
|
+
private
|
|
210
|
+
#
|
|
211
|
+
# The common actions for Daylight::APIController
|
|
212
|
+
#
|
|
213
|
+
|
|
214
|
+
##
|
|
215
|
+
# Retrieves the collection for the records for `model` with any refinements
|
|
216
|
+
# in the params passed to `refine_by`. Accessed via:
|
|
217
|
+
#
|
|
218
|
+
# GET /posts.json
|
|
219
|
+
#
|
|
220
|
+
# Subclass implementation:
|
|
221
|
+
#
|
|
222
|
+
# def index
|
|
223
|
+
# render json: Post.refine_by(params)
|
|
224
|
+
# end
|
|
225
|
+
#
|
|
226
|
+
# See:
|
|
227
|
+
# Daylight::Refiners.refine_by
|
|
228
|
+
def index
|
|
229
|
+
render json: self.collection = model.refine_by(params)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
##
|
|
233
|
+
# Creates a record for the `model` with the attributes supplied in params.
|
|
234
|
+
# Accessed via:
|
|
235
|
+
#
|
|
236
|
+
# POST /posts.json
|
|
237
|
+
#
|
|
238
|
+
# Subclass implementation:
|
|
239
|
+
#
|
|
240
|
+
# def create
|
|
241
|
+
# @post = Post.new(params[:post])
|
|
242
|
+
# @post.save!
|
|
243
|
+
#
|
|
244
|
+
# render json: @post, status: :created, location: @post
|
|
245
|
+
# end
|
|
246
|
+
def create
|
|
247
|
+
self.record = model.new(model_params)
|
|
248
|
+
record.save!
|
|
249
|
+
|
|
250
|
+
render json: record, status: :created, location: record
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
##
|
|
254
|
+
# Retrieves a record for the `model` with the `id` supplied in params.
|
|
255
|
+
# If the primary_key is configured to something besides `id` it will
|
|
256
|
+
# use that as a key.
|
|
257
|
+
#
|
|
258
|
+
# Accessed via:
|
|
259
|
+
#
|
|
260
|
+
# GET /posts/1.json
|
|
261
|
+
#
|
|
262
|
+
# Subclass implementation:
|
|
263
|
+
#
|
|
264
|
+
# def show
|
|
265
|
+
# render json: Post.find(params[Post.primary_key])
|
|
266
|
+
# end
|
|
267
|
+
def show
|
|
268
|
+
render json: self.record = model.find(params[primary_key])
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
##
|
|
272
|
+
# Updates a record for the `model` with the `id` supplied in params.
|
|
273
|
+
# If the primary_key is configured to something besides `id` it will
|
|
274
|
+
# use that as a key.
|
|
275
|
+
#
|
|
276
|
+
# Accessed via:
|
|
277
|
+
#
|
|
278
|
+
# PATCH/PUT /posts/1.json
|
|
279
|
+
#
|
|
280
|
+
# Subclass implementation:
|
|
281
|
+
#
|
|
282
|
+
# def update
|
|
283
|
+
# Post.find(params[Post.primary_key]).update!(params[:post])
|
|
284
|
+
#
|
|
285
|
+
# head :no_content
|
|
286
|
+
# end
|
|
287
|
+
def update
|
|
288
|
+
(self.record = model.find(params[primary_key])).update!(model_params)
|
|
289
|
+
|
|
290
|
+
head :no_content
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
##
|
|
294
|
+
# Destroys a record for the `model` with the `id` supplied in params.
|
|
295
|
+
# If the primary_key is configured to something besides `id` it will
|
|
296
|
+
# use that as a key.
|
|
297
|
+
#
|
|
298
|
+
# Accessed via:
|
|
299
|
+
#
|
|
300
|
+
# DELETE /posts/1.json
|
|
301
|
+
#
|
|
302
|
+
# Subclass implementation:
|
|
303
|
+
#
|
|
304
|
+
# def destroy
|
|
305
|
+
# Post.find(params[Post.primary_key]).destroy
|
|
306
|
+
#
|
|
307
|
+
# head :no_content
|
|
308
|
+
# end
|
|
309
|
+
def destroy
|
|
310
|
+
(self.record = model.find(params[primary_key])).destroy
|
|
311
|
+
|
|
312
|
+
head :no_content
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
##
|
|
316
|
+
# Retrieves the collection for the associated records for a `model` with
|
|
317
|
+
# any refinements in the params passed to `associated`. Accessed via:
|
|
318
|
+
#
|
|
319
|
+
# GET /posts/1/comments.json
|
|
320
|
+
#
|
|
321
|
+
# Subclass implementation:
|
|
322
|
+
#
|
|
323
|
+
# def associated
|
|
324
|
+
# render json: Post.associated(params), root: associated_params
|
|
325
|
+
# end
|
|
326
|
+
#
|
|
327
|
+
# See:
|
|
328
|
+
# Daylight::Refiners.associated
|
|
329
|
+
# Daylight::Helpers.associated_params
|
|
330
|
+
# RouteOptions
|
|
331
|
+
def associated
|
|
332
|
+
render json: self.collection = model.associated(params), root: associated_params
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
##
|
|
336
|
+
# Retrieves the collection returned by the `remoted` method with
|
|
337
|
+
# any refinements in the params passed to `remoted`. Accessed via:
|
|
338
|
+
#
|
|
339
|
+
# GET /posts/1/all_authorized_users.json
|
|
340
|
+
#
|
|
341
|
+
# Subclass implementation:
|
|
342
|
+
#
|
|
343
|
+
# def remoted
|
|
344
|
+
# render json: Post.remoted(params), root: remoted_params
|
|
345
|
+
# end
|
|
346
|
+
#
|
|
347
|
+
# See:
|
|
348
|
+
# Daylight::Refiners.remoted
|
|
349
|
+
# Daylight::Helpers.remoted_params
|
|
350
|
+
# RouteOptions
|
|
351
|
+
def remoted
|
|
352
|
+
render json: self.collection = model.remoted(params), root: remoted_params
|
|
353
|
+
end
|
|
354
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require 'haml'
|
|
2
|
+
require 'actionpack/page_caching'
|
|
3
|
+
|
|
4
|
+
module DaylightDocumentation
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# Rails::Engine to add Documentation features to a Daylight::Server
|
|
9
|
+
module Daylight
|
|
10
|
+
class Documentation < ::Rails::Engine
|
|
11
|
+
isolate_namespace DaylightDocumentation
|
|
12
|
+
end
|
|
13
|
+
end
|