graphiti-rb 1.0.alpha.1
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/.gitignore +11 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +20 -0
- data/.yardopts +2 -0
- data/Appraisals +11 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +12 -0
- data/Guardfile +32 -0
- data/LICENSE.txt +21 -0
- data/README.md +75 -0
- data/Rakefile +15 -0
- data/bin/appraisal +17 -0
- data/bin/console +14 -0
- data/bin/rspec +17 -0
- data/bin/setup +8 -0
- data/gemfiles/rails_4.gemfile +17 -0
- data/gemfiles/rails_5.gemfile +17 -0
- data/graphiti.gemspec +34 -0
- data/lib/generators/jsonapi/resource_generator.rb +169 -0
- data/lib/generators/jsonapi/templates/application_resource.rb.erb +15 -0
- data/lib/generators/jsonapi/templates/controller.rb.erb +61 -0
- data/lib/generators/jsonapi/templates/create_request_spec.rb.erb +30 -0
- data/lib/generators/jsonapi/templates/destroy_request_spec.rb.erb +20 -0
- data/lib/generators/jsonapi/templates/index_request_spec.rb.erb +22 -0
- data/lib/generators/jsonapi/templates/resource.rb.erb +11 -0
- data/lib/generators/jsonapi/templates/resource_reads_spec.rb.erb +62 -0
- data/lib/generators/jsonapi/templates/resource_writes_spec.rb.erb +63 -0
- data/lib/generators/jsonapi/templates/show_request_spec.rb.erb +21 -0
- data/lib/generators/jsonapi/templates/update_request_spec.rb.erb +34 -0
- data/lib/graphiti-rb.rb +1 -0
- data/lib/graphiti.rb +121 -0
- data/lib/graphiti/adapters/abstract.rb +516 -0
- data/lib/graphiti/adapters/active_record.rb +6 -0
- data/lib/graphiti/adapters/active_record/base.rb +249 -0
- data/lib/graphiti/adapters/active_record/belongs_to_sideload.rb +17 -0
- data/lib/graphiti/adapters/active_record/has_many_sideload.rb +17 -0
- data/lib/graphiti/adapters/active_record/has_one_sideload.rb +17 -0
- data/lib/graphiti/adapters/active_record/inferrence.rb +12 -0
- data/lib/graphiti/adapters/active_record/many_to_many_sideload.rb +30 -0
- data/lib/graphiti/adapters/null.rb +236 -0
- data/lib/graphiti/base.rb +70 -0
- data/lib/graphiti/configuration.rb +21 -0
- data/lib/graphiti/context.rb +16 -0
- data/lib/graphiti/deserializer.rb +208 -0
- data/lib/graphiti/errors.rb +309 -0
- data/lib/graphiti/extensions/boolean_attribute.rb +33 -0
- data/lib/graphiti/extensions/extra_attribute.rb +70 -0
- data/lib/graphiti/extensions/temp_id.rb +26 -0
- data/lib/graphiti/filter_operators.rb +25 -0
- data/lib/graphiti/hash_renderer.rb +57 -0
- data/lib/graphiti/jsonapi_serializable_ext.rb +50 -0
- data/lib/graphiti/query.rb +251 -0
- data/lib/graphiti/rails.rb +28 -0
- data/lib/graphiti/railtie.rb +74 -0
- data/lib/graphiti/renderer.rb +60 -0
- data/lib/graphiti/resource.rb +110 -0
- data/lib/graphiti/resource/configuration.rb +239 -0
- data/lib/graphiti/resource/dsl.rb +138 -0
- data/lib/graphiti/resource/interface.rb +32 -0
- data/lib/graphiti/resource/polymorphism.rb +68 -0
- data/lib/graphiti/resource/sideloading.rb +102 -0
- data/lib/graphiti/resource_proxy.rb +127 -0
- data/lib/graphiti/responders.rb +19 -0
- data/lib/graphiti/runner.rb +25 -0
- data/lib/graphiti/scope.rb +98 -0
- data/lib/graphiti/scoping/base.rb +99 -0
- data/lib/graphiti/scoping/default_filter.rb +58 -0
- data/lib/graphiti/scoping/extra_attributes.rb +29 -0
- data/lib/graphiti/scoping/filter.rb +93 -0
- data/lib/graphiti/scoping/filterable.rb +36 -0
- data/lib/graphiti/scoping/paginate.rb +87 -0
- data/lib/graphiti/scoping/sort.rb +64 -0
- data/lib/graphiti/sideload.rb +281 -0
- data/lib/graphiti/sideload/belongs_to.rb +34 -0
- data/lib/graphiti/sideload/has_many.rb +16 -0
- data/lib/graphiti/sideload/has_one.rb +9 -0
- data/lib/graphiti/sideload/many_to_many.rb +24 -0
- data/lib/graphiti/sideload/polymorphic_belongs_to.rb +108 -0
- data/lib/graphiti/stats/dsl.rb +89 -0
- data/lib/graphiti/stats/payload.rb +49 -0
- data/lib/graphiti/types.rb +172 -0
- data/lib/graphiti/util/attribute_check.rb +88 -0
- data/lib/graphiti/util/field_params.rb +16 -0
- data/lib/graphiti/util/hash.rb +51 -0
- data/lib/graphiti/util/hooks.rb +33 -0
- data/lib/graphiti/util/include_params.rb +39 -0
- data/lib/graphiti/util/persistence.rb +219 -0
- data/lib/graphiti/util/relationship_payload.rb +64 -0
- data/lib/graphiti/util/serializer_attributes.rb +97 -0
- data/lib/graphiti/util/sideload.rb +33 -0
- data/lib/graphiti/util/validation_response.rb +78 -0
- data/lib/graphiti/version.rb +3 -0
- metadata +317 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
module Graphiti
|
2
|
+
# Rails Integration. Mix this in to ApplicationController.
|
3
|
+
#
|
4
|
+
# * Mixes in Base
|
5
|
+
# * Adds a global around_action (see Base#wrap_context)
|
6
|
+
#
|
7
|
+
# @see Base#render_jsonapi
|
8
|
+
# @see Base#wrap_context
|
9
|
+
module Rails
|
10
|
+
def self.included(klass)
|
11
|
+
klass.class_eval do
|
12
|
+
include Graphiti::Context
|
13
|
+
include JsonapiErrorable
|
14
|
+
around_action :wrap_context
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def wrap_context
|
19
|
+
Graphiti.with_context(jsonapi_context, action_name.to_sym) do
|
20
|
+
yield
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def jsonapi_context
|
25
|
+
self
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Graphiti
|
2
|
+
class Railtie < ::Rails::Railtie
|
3
|
+
|
4
|
+
initializer "graphiti.require_activerecord_adapter" do
|
5
|
+
config.after_initialize do |app|
|
6
|
+
ActiveSupport.on_load(:active_record) do
|
7
|
+
require 'graphiti/adapters/active_record'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
initializer 'graphiti.init' do
|
13
|
+
if Mime[:jsonapi].nil? # rails 4
|
14
|
+
Mime::Type.register('application/vnd.api+json', :jsonapi)
|
15
|
+
end
|
16
|
+
register_parameter_parser
|
17
|
+
register_renderers
|
18
|
+
establish_concurrency
|
19
|
+
end
|
20
|
+
|
21
|
+
# from jsonapi-rails
|
22
|
+
PARSER = lambda do |body|
|
23
|
+
data = JSON.parse(body)
|
24
|
+
data[:format] = :jsonapi
|
25
|
+
data.with_indifferent_access
|
26
|
+
end
|
27
|
+
|
28
|
+
def register_parameter_parser
|
29
|
+
if ::Rails::VERSION::MAJOR >= 5
|
30
|
+
ActionDispatch::Request.parameter_parsers[:jsonapi] = PARSER
|
31
|
+
else
|
32
|
+
ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime[:jsonapi]] = PARSER
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def register_renderers
|
37
|
+
ActiveSupport.on_load(:action_controller) do
|
38
|
+
::ActionController::Renderers.add(:jsonapi) do |proxy, options|
|
39
|
+
self.content_type ||= Mime[:jsonapi]
|
40
|
+
|
41
|
+
opts = {}
|
42
|
+
if respond_to?(:default_jsonapi_render_options)
|
43
|
+
opts = default_jsonapi_render_options
|
44
|
+
end
|
45
|
+
|
46
|
+
if proxy.is_a?(Hash) # for destroy
|
47
|
+
render(options.merge(json: proxy))
|
48
|
+
else
|
49
|
+
proxy.to_jsonapi(options)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
ActiveSupport.on_load(:action_controller) do
|
55
|
+
::ActionController::Renderers.add(:jsonapi_errors) do |proxy, options|
|
56
|
+
self.content_type ||= Mime[:jsonapi]
|
57
|
+
|
58
|
+
validation = JsonapiErrorable::Serializers::Validation.new \
|
59
|
+
proxy.data, proxy.payload.relationships
|
60
|
+
|
61
|
+
render \
|
62
|
+
json: { errors: validation.errors },
|
63
|
+
status: :unprocessable_entity
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Only run concurrently if our environment supports it
|
69
|
+
def establish_concurrency
|
70
|
+
Graphiti.config.concurrency = !::Rails.env.test? &&
|
71
|
+
::Rails.application.config.cache_classes
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Graphiti
|
2
|
+
class Renderer
|
3
|
+
CONTENT_TYPE = 'application/vnd.api+json'
|
4
|
+
|
5
|
+
attr_reader :proxy, :options
|
6
|
+
|
7
|
+
def initialize(proxy, options)
|
8
|
+
@proxy = proxy
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def records
|
13
|
+
@records ||= @proxy.data
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_jsonapi
|
17
|
+
render(JSONAPI::Renderer.new).to_json
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_json
|
21
|
+
render(Graphiti::HashRenderer.new(@proxy.resource)).to_json
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_xml
|
25
|
+
render(Graphiti::HashRenderer.new(@proxy.resource)).to_xml(root: :data)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def render(implementation)
|
31
|
+
notify do
|
32
|
+
instance = JSONAPI::Serializable::Renderer.new(implementation)
|
33
|
+
options[:fields] = proxy.fields
|
34
|
+
options[:expose] ||= {}
|
35
|
+
options[:expose][:extra_fields] = proxy.extra_fields
|
36
|
+
options[:include] = proxy.include_hash
|
37
|
+
options[:meta] ||= {}
|
38
|
+
options[:meta].merge!(stats: proxy.stats) unless proxy.stats.empty?
|
39
|
+
instance.render(records, options)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# TODO: more generic notification pattern
|
44
|
+
# Likely comes out of debugger work
|
45
|
+
def notify
|
46
|
+
if defined?(ActiveSupport::Notifications)
|
47
|
+
opts = [
|
48
|
+
'render.jsonapi-compliable',
|
49
|
+
records: records,
|
50
|
+
options: options
|
51
|
+
]
|
52
|
+
ActiveSupport::Notifications.instrument(*opts) do
|
53
|
+
yield
|
54
|
+
end
|
55
|
+
else
|
56
|
+
yield
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Graphiti
|
2
|
+
class Resource
|
3
|
+
include DSL
|
4
|
+
include Interface
|
5
|
+
include Configuration
|
6
|
+
include Sideloading
|
7
|
+
|
8
|
+
attr_reader :context
|
9
|
+
|
10
|
+
def around_scoping(scope, query_hash)
|
11
|
+
yield scope
|
12
|
+
end
|
13
|
+
|
14
|
+
def serializer_for(model)
|
15
|
+
serializer
|
16
|
+
end
|
17
|
+
|
18
|
+
def with_context(object, namespace = nil)
|
19
|
+
Graphiti.with_context(object, namespace) do
|
20
|
+
yield
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def context
|
25
|
+
Graphiti.context[:object]
|
26
|
+
end
|
27
|
+
|
28
|
+
def context_namespace
|
29
|
+
Graphiti.context[:namespace]
|
30
|
+
end
|
31
|
+
|
32
|
+
def build_scope(base, query, opts = {})
|
33
|
+
Scope.new(base, self, query, opts)
|
34
|
+
end
|
35
|
+
|
36
|
+
def base_scope
|
37
|
+
adapter.base_scope(model)
|
38
|
+
end
|
39
|
+
|
40
|
+
def typecast(name, value, flag)
|
41
|
+
att = get_attr!(name, flag)
|
42
|
+
type = Graphiti::Types[att[:type]]
|
43
|
+
begin
|
44
|
+
flag = :read if flag == :readable
|
45
|
+
flag = :write if flag == :writable
|
46
|
+
flag = :params if [:sortable, :filterable].include?(flag)
|
47
|
+
type[flag][value]
|
48
|
+
rescue Exception => e
|
49
|
+
raise Errors::TypecastFailed.new(self, name, value, e)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def create(create_params)
|
54
|
+
adapter.create(model, create_params)
|
55
|
+
end
|
56
|
+
|
57
|
+
def update(update_params)
|
58
|
+
adapter.update(model, update_params)
|
59
|
+
end
|
60
|
+
|
61
|
+
def destroy(id)
|
62
|
+
adapter.destroy(model, id)
|
63
|
+
end
|
64
|
+
|
65
|
+
def associate_all(parent, children, association_name, type)
|
66
|
+
adapter.associate_all(parent, children, association_name, type)
|
67
|
+
end
|
68
|
+
|
69
|
+
def associate(parent, child, association_name, type)
|
70
|
+
adapter.associate(parent, child, association_name, type)
|
71
|
+
end
|
72
|
+
|
73
|
+
def disassociate(parent, child, association_name, type)
|
74
|
+
adapter.disassociate(parent, child, association_name, type)
|
75
|
+
end
|
76
|
+
|
77
|
+
def persist_with_relationships(meta, attributes, relationships, caller_model = nil)
|
78
|
+
persistence = Graphiti::Util::Persistence \
|
79
|
+
.new(self, meta, attributes, relationships, caller_model)
|
80
|
+
persistence.run
|
81
|
+
end
|
82
|
+
|
83
|
+
def stat(attribute, calculation)
|
84
|
+
stats_dsl = stats[attribute] || stats[attribute.to_sym]
|
85
|
+
raise Errors::StatNotFound.new(attribute, calculation) unless stats_dsl
|
86
|
+
stats_dsl.calculation(calculation)
|
87
|
+
end
|
88
|
+
|
89
|
+
def resolve(scope)
|
90
|
+
adapter.resolve(scope)
|
91
|
+
end
|
92
|
+
|
93
|
+
def before_commit(model, method)
|
94
|
+
hook = self.class.config[:before_commit][method]
|
95
|
+
hook.call(model) if hook
|
96
|
+
end
|
97
|
+
|
98
|
+
def transaction
|
99
|
+
response = nil
|
100
|
+
begin
|
101
|
+
adapter.transaction(model) do
|
102
|
+
response = yield
|
103
|
+
end
|
104
|
+
rescue Errors::ValidationError => e
|
105
|
+
response = e.validation_response
|
106
|
+
end
|
107
|
+
response
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,239 @@
|
|
1
|
+
module Graphiti
|
2
|
+
class Resource
|
3
|
+
module Configuration
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module Overrides
|
7
|
+
def serializer=(val)
|
8
|
+
if val
|
9
|
+
if super(Class.new(val))
|
10
|
+
apply_attributes_to_serializer
|
11
|
+
apply_sideloads_to_serializer
|
12
|
+
end
|
13
|
+
else
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def polymorphic=(klasses)
|
19
|
+
super
|
20
|
+
send(:prepend, Polymorphism)
|
21
|
+
end
|
22
|
+
|
23
|
+
def type=(val)
|
24
|
+
if val = super
|
25
|
+
self.serializer.type(val)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def model
|
30
|
+
klass = super
|
31
|
+
unless klass || abstract_class?
|
32
|
+
if klass = infer_model
|
33
|
+
self.model = klass
|
34
|
+
else
|
35
|
+
raise Errors::ModelNotFound.new(self)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
klass
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
included do
|
43
|
+
class << self
|
44
|
+
attr_writer :config
|
45
|
+
end
|
46
|
+
|
47
|
+
class_attribute :adapter,
|
48
|
+
:model,
|
49
|
+
:type,
|
50
|
+
:polymorphic,
|
51
|
+
:polymorphic_child,
|
52
|
+
:serializer,
|
53
|
+
:default_page_size,
|
54
|
+
:default_sort,
|
55
|
+
:attributes_readable_by_default,
|
56
|
+
:attributes_writable_by_default,
|
57
|
+
:attributes_sortable_by_default,
|
58
|
+
:attributes_filterable_by_default,
|
59
|
+
:relationships_readable_by_default,
|
60
|
+
:relationships_writable_by_default
|
61
|
+
|
62
|
+
class << self
|
63
|
+
prepend Overrides
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.inherited(klass)
|
67
|
+
super
|
68
|
+
klass.config = Util::Hash.deep_dup(config)
|
69
|
+
klass.adapter ||= Adapters::Abstract.new
|
70
|
+
klass.default_sort ||= []
|
71
|
+
klass.default_page_size ||= 20
|
72
|
+
# re-assigning causes a new Class.new
|
73
|
+
if klass.serializer
|
74
|
+
klass.serializer = klass.serializer
|
75
|
+
else
|
76
|
+
klass.serializer = JSONAPI::Serializable::Resource
|
77
|
+
end
|
78
|
+
klass.type ||= klass.infer_type
|
79
|
+
default(klass, :attributes_readable_by_default, true)
|
80
|
+
default(klass, :attributes_writable_by_default, true)
|
81
|
+
default(klass, :attributes_sortable_by_default, true)
|
82
|
+
default(klass, :attributes_filterable_by_default, true)
|
83
|
+
default(klass, :relationships_readable_by_default, true)
|
84
|
+
default(klass, :relationships_writable_by_default, true)
|
85
|
+
|
86
|
+
unless klass.config[:attributes][:id]
|
87
|
+
klass.attribute :id, :integer_id
|
88
|
+
end
|
89
|
+
klass.stat total: [:count]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class_methods do
|
94
|
+
def get_attr!(name, flag, opts = {})
|
95
|
+
opts[:raise_error] = true
|
96
|
+
get_attr(name, flag, opts)
|
97
|
+
end
|
98
|
+
|
99
|
+
def get_attr(name, flag, opts = {})
|
100
|
+
defaults = { request: false }
|
101
|
+
opts = defaults.merge(opts)
|
102
|
+
new.get_attr(name, flag, opts)
|
103
|
+
end
|
104
|
+
|
105
|
+
def abstract_class?
|
106
|
+
!!abstract_class
|
107
|
+
end
|
108
|
+
|
109
|
+
def abstract_class
|
110
|
+
@abstract_class
|
111
|
+
end
|
112
|
+
|
113
|
+
def abstract_class=(val)
|
114
|
+
if @abstract_class = val
|
115
|
+
self.serializer = nil
|
116
|
+
self.type = nil
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def infer_type
|
121
|
+
if name.present?
|
122
|
+
name.demodulize.gsub('Resource','').underscore.pluralize.to_sym
|
123
|
+
else
|
124
|
+
:undefined_jsonapi_type
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def infer_model
|
129
|
+
name.gsub('Resource', '').safe_constantize if name
|
130
|
+
end
|
131
|
+
|
132
|
+
def default(object, attr, value)
|
133
|
+
prior = object.send(attr)
|
134
|
+
unless prior || prior == false
|
135
|
+
object.send(:"#{attr}=", value)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
private :default
|
139
|
+
|
140
|
+
def config
|
141
|
+
@config ||=
|
142
|
+
{
|
143
|
+
filters: {},
|
144
|
+
default_filters: {},
|
145
|
+
stats: {},
|
146
|
+
sort_all: nil,
|
147
|
+
sorts: {},
|
148
|
+
pagination: nil,
|
149
|
+
before_commit: {},
|
150
|
+
attributes: {},
|
151
|
+
extra_attributes: {},
|
152
|
+
sideloads: {}
|
153
|
+
}
|
154
|
+
end
|
155
|
+
|
156
|
+
def attributes
|
157
|
+
config[:attributes]
|
158
|
+
end
|
159
|
+
|
160
|
+
def extra_attributes
|
161
|
+
config[:extra_attributes]
|
162
|
+
end
|
163
|
+
|
164
|
+
def all_attributes
|
165
|
+
attributes.merge(extra_attributes)
|
166
|
+
end
|
167
|
+
|
168
|
+
def sideloads
|
169
|
+
config[:sideloads]
|
170
|
+
end
|
171
|
+
|
172
|
+
def filters
|
173
|
+
config[:filters]
|
174
|
+
end
|
175
|
+
|
176
|
+
def sorts
|
177
|
+
config[:sorts]
|
178
|
+
end
|
179
|
+
|
180
|
+
def stats
|
181
|
+
config[:stats]
|
182
|
+
end
|
183
|
+
|
184
|
+
def pagination
|
185
|
+
config[:pagination]
|
186
|
+
end
|
187
|
+
|
188
|
+
def default_filters
|
189
|
+
config[:default_filters]
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def get_attr!(name, flag, options = {})
|
194
|
+
options[:raise_error] = true
|
195
|
+
get_attr(name, flag, options)
|
196
|
+
end
|
197
|
+
|
198
|
+
def get_attr(name, flag, request: false, raise_error: false)
|
199
|
+
Util::AttributeCheck.run(self, name, flag, request, raise_error)
|
200
|
+
end
|
201
|
+
|
202
|
+
def filters
|
203
|
+
self.class.filters
|
204
|
+
end
|
205
|
+
|
206
|
+
def sort_all
|
207
|
+
self.class.sort_all
|
208
|
+
end
|
209
|
+
|
210
|
+
def sorts
|
211
|
+
self.class.sorts
|
212
|
+
end
|
213
|
+
|
214
|
+
def stats
|
215
|
+
self.class.stats
|
216
|
+
end
|
217
|
+
|
218
|
+
def pagination
|
219
|
+
self.class.pagination
|
220
|
+
end
|
221
|
+
|
222
|
+
def attributes
|
223
|
+
self.class.attributes
|
224
|
+
end
|
225
|
+
|
226
|
+
def extra_attributes
|
227
|
+
self.class.extra_attributes
|
228
|
+
end
|
229
|
+
|
230
|
+
def all_attributes
|
231
|
+
self.class.all_attributes
|
232
|
+
end
|
233
|
+
|
234
|
+
def default_filters
|
235
|
+
self.class.default_filters
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|