active_facet 1.2.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 12d1962bd14e00c927ee8d18634327e75add2241
4
+ data.tar.gz: bac02648674193b937c1c022e6527a9db1dccc24
5
+ SHA512:
6
+ metadata.gz: 47843cbae2eed28b0f3bbae3407e8ccf1d543432139022387be4e780148bdbb607b594e6761f955cd6597e147b5289fc774ecf16406e35b7f7cc2744dc546e11
7
+ data.tar.gz: 3cc78a3c627e8e82c17f6d61dbab3f8067baf47e98ae384842bebb249afb8dc9817f63927374a31a00cbfb2b2e06647f49a12939dad92c6cba33386e6483be8c
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Jay Crouch
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,206 @@
1
+ # ActiveFacet
2
+
3
+ ActiveFacet is a Rails plugin that enables custom as_json serialization intended for APIs. It is designed for speed, and is magnitudes of order faster than jbuilder.
4
+
5
+ The framework supports:
6
+ * fields - define the fields you want to serialize, by resource type
7
+ * field groups - configure sets of fields you want to refer to by alias, by resource type
8
+ * versioning - describe serialization of resources by version, with automatic fallback
9
+ * caching - optomize performance by composing documents from smaller documents
10
+ * nested resources - serialize models and associations with one call chain
11
+ * filters - restrict records from associations with custom scopes
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ ```ruby
18
+ gem 'active_facet'
19
+ ```
20
+
21
+ And then execute:
22
+
23
+ $ bundle
24
+
25
+ Or install it yourself as:
26
+
27
+ $ gem install active_facet
28
+
29
+ ## Configuration
30
+
31
+ And then execute:
32
+
33
+ $ rails g active_facet:install
34
+
35
+ To add an initializer to your application.
36
+
37
+ ## Usage
38
+
39
+ ```ruby
40
+ Parent.limit(2).as_json(af_opts: {
41
+ version: "2.3.8",
42
+ cache_force: true,
43
+ fields: [:basic, :extended, { children: [:minimal, :extension] }]
44
+ field_overrides: {
45
+ parent: [:id, :children, :foo, :bar],
46
+ child: [:fizz, :buzz]
47
+ },
48
+ filters: {
49
+ single_parent: true,
50
+ with_children_parent: 3,
51
+ active: :disabled
52
+ }
53
+ })
54
+ # =>
55
+ [ {
56
+ id: 1,
57
+ created_at: 2343242342,
58
+ updated_at: "2014-03-23 00:23:32",
59
+ weight: 2432252,
60
+ children: [ {
61
+ fizz: 'care bear'
62
+ }, {
63
+ fizz: 'hello',
64
+ buzz: 'world'
65
+ } ]
66
+ }, {
67
+ id: 2,
68
+ created_at: 2343242777,
69
+ updated_at: "2014-04-24 01:34:25",
70
+ children: [ {
71
+ fizz: 'tamato'
72
+ } ]
73
+ }
74
+ }
75
+ ```
76
+
77
+ ### Defaults
78
+
79
+ All options are optional
80
+
81
+ #### :version
82
+ Version of serializer to marshal resources with. Defaults to:
83
+ ```ruby
84
+ "1.0"
85
+ ```
86
+
87
+ #### :cache_force
88
+ Force serializers to ignore cached documents. Defaults to:
89
+ ```ruby
90
+ false
91
+ ```
92
+
93
+ #### :fields
94
+ Attributes to marshal. See Field Sets. Defaults to:
95
+ ```ruby
96
+ :basic
97
+ ```
98
+
99
+ #### :field_overrides
100
+ Attributes to marshal, by resource type. See Field Sets. Defaults to:
101
+ ```ruby
102
+ {}
103
+ ```
104
+
105
+ #### :filters
106
+ Filters to apply when marshalling associations. See Field Sets. Defaults to:
107
+ ```ruby
108
+ {}
109
+ ```
110
+
111
+ ### Serializer DSL
112
+
113
+ ```ruby
114
+ class ParentSerializer
115
+ include ActiveFacet::Serializer::Base
116
+
117
+ # TRANSFORMS
118
+
119
+ # Transforms rename attributes and apply custom serializers to attributes data.
120
+
121
+ # Renames parent.kid_trackings to json['trackings'] on parent.as_json...
122
+ transform :trackings, from: :kid_trackings
123
+
124
+ # Renames json['trackings'] to parent.kid_trackings on parent.from_hash...:
125
+ transform :trackings, to: :kid_trackings
126
+
127
+ # Renames parent.kid_trackings to json['trackings'] on parent.as_json... & parent.from_hash...
128
+ transform :trackings, as: :kid_trackings
129
+
130
+ # Converts json['created_at'] with TimeCustomAttributeSerializer on parent.as_json... & parent.from_hash...
131
+ transform :created_at, with: :time
132
+
133
+ # Renames json['created_at'] to parent.data['weight'] on parent.as_json... & parent.from_hash...
134
+ transform :weight, within: :data
135
+
136
+ # Renames json['weighed'] to parent.data['weighed_at'] using TimeCustomAttributeSerializer converter on parent.as_json... & parent.from_hash...
137
+ transform :weighed, within: :data, with: :time, as: :wieghed_at
138
+
139
+
140
+
141
+ # EXTENSIONS
142
+
143
+ # Extensions decorate the json response when attribute data is not directly accessible from the resource.
144
+
145
+ # Decorates json['free_shipping_minimum'] on parent.as_json...
146
+ extension :free_shipping_minimum
147
+
148
+
149
+
150
+ # FIELD SETS
151
+
152
+ # Field sets indicate the desired fields to serialize for a given resource type.
153
+ # Field sets can be aliased, and reference aliases, recursively.
154
+ # Field sets can reference Field Sets defined for ActiveRecord Association resource types in a hierarchical structure
155
+
156
+ # NOTE:: make sure to define :basic and :minimal Field Sets for all resources
157
+ # :basic is implicitely added to all Field Sets that do not inclue :minimal during serialization
158
+
159
+ # Arrays map to collections
160
+ expose :timestamps, as: [:id, :created_at, :updated_at]
161
+ expose :basic, as: [:timestamps, :name]
162
+
163
+ # Hashes map to relations and the Field Sets declared on the relation
164
+ expose :deep, as: { trackings: :basic }
165
+
166
+ # Composite structures can be formed from Symbols, Arrays, and Hashes
167
+ expose :deep_basic, as: [:timestamps, :basic, { trackings: [:basic, :extended] }]
168
+
169
+
170
+
171
+ # EXPOSE_TIMESTAMPS
172
+
173
+ # expose_timestamps is equivalent to
174
+ # transform :created_at, with: :time
175
+ # transform :updated_at, with: :time
176
+ # expose :timestamps, as: [:id, :created_at, :updated_at]
177
+ expose_timestamps
178
+
179
+ end
180
+ ```
181
+
182
+ ## Development
183
+
184
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rspec spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. To work with an interactive prompt including a dummy host application, run `bin/rails c` or `rails c` from the spec/dummy directory.
185
+
186
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
187
+
188
+ To push or update an unreleased version, without pushing the `.gem` file, run `bin/update_version`.
189
+
190
+ To configure a host application to use a local version of the gem without modifying the host application's Gemfile run from the root directory of the host application `bundle config local.active_facet /path/to/local/git/repository`
191
+
192
+ To unconfigure a local version of the gem, run `bundle config --delete local.active_facet`
193
+
194
+ ## Roadmap
195
+ Available at https://github.com/honest/active_facet/blob/master/ROADMAP.md
196
+
197
+
198
+ ## Contributing
199
+
200
+ Bug reports and pull requests are welcome on GitHub at https://github.com/honest/active_facet. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
201
+
202
+
203
+ ## License
204
+
205
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
206
+
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'ActiveFacet'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
25
+
26
+ Bundler::GemHelper.install_tasks
27
+
28
+
29
+
@@ -0,0 +1,98 @@
1
+ require 'w_g'
2
+ require 'active_support/all'
3
+ require 'active_record'
4
+
5
+ require 'active_facet/errors/attribute_error'
6
+ require 'active_facet/errors/configuration_error'
7
+ require 'active_facet/errors/lookup_error'
8
+ require 'active_facet/filter'
9
+ require 'active_facet/acts_as_active_facet'
10
+ require 'active_facet/serializer/base'
11
+ require 'active_facet/serializer/facade'
12
+ require 'active_facet/config'
13
+ require 'active_facet/document_cache'
14
+ require 'active_facet/helper'
15
+ require 'active_facet/version'
16
+
17
+ module ActiveFacet
18
+ mattr_accessor :opts_key,
19
+ :fields_key,
20
+ :field_overrides_key,
21
+ :version_key,
22
+ :filters_key,
23
+ :cache_bypass_key,
24
+ :cache_force_key,
25
+ :filters_force_key,
26
+ :strict_lookups,
27
+ :preload_associations,
28
+ :cache_enabled,
29
+ :acts_as_active_facet_enabled,
30
+ :filters_enabled,
31
+ :default_cache_options,
32
+ :document_cache,
33
+ :default_version
34
+
35
+ self.default_version = 1.0
36
+ self.opts_key = :af_opts
37
+ self.fields_key = :fields
38
+ self.field_overrides_key = :field_overrides
39
+ self.version_key = :version
40
+ self.filters_key = :filters
41
+ self.cache_bypass_key = :cache_bypass
42
+ self.cache_force_key = :cache_force
43
+ self.filters_force_key = :filters_force
44
+
45
+ self.strict_lookups = false
46
+ self.preload_associations = false
47
+ self.filters_enabled = false
48
+ self.cache_enabled = false
49
+ self.acts_as_active_facet_enabled = false
50
+ self.default_cache_options = { expires_in: 5.minutes }
51
+ self.document_cache = ActiveFacet::DocumentCache
52
+ #TODO --jdc implement dependency injection for all classes
53
+
54
+ def self.configure
55
+ yield(self)
56
+ ActiveRecord::Base.acts_as_active_facet if ActiveFacet.acts_as_active_facet_enabled
57
+ end
58
+
59
+ def self.global_filter(name)
60
+ ActiveFacet::Filter.register_global(name, Proc.new)
61
+ end
62
+
63
+ def self.resource_mapper
64
+ ActiveFacet::Helper.resource_mapper = Proc.new
65
+ end
66
+
67
+ def self.serializer_mapper
68
+ ActiveFacet::Helper.serializer_mapper = Proc.new
69
+ end
70
+
71
+ def self.fields_from_options(options)
72
+ (options[ActiveFacet.opts_key] || {})[ActiveFacet.fields_key]
73
+ end
74
+
75
+ def self.options_with_fields(options, fields)
76
+ (options[ActiveFacet.opts_key] ||= {})[ActiveFacet.fields_key] = fields
77
+ options
78
+ end
79
+
80
+ #TODO --jdc move the below into helper
81
+ def self.restore_opts_after(options, key, value)
82
+ opts = (options[ActiveFacet.opts_key] ||= {})
83
+ old = opts[key]
84
+ opts[key] = value
85
+ yield
86
+ ensure
87
+ opts[key] = old
88
+ end
89
+
90
+ def self.deep_copy(o)
91
+ Marshal.load(Marshal.dump(o))
92
+ end
93
+ end
94
+
95
+ ActiveRecord::Base.send :include, ActiveFacet::ActsAsActiveFacet
96
+
97
+
98
+
@@ -0,0 +1,102 @@
1
+ #NOTE:: strive for minimal method footprint because this mixes into ActiveRecord
2
+
3
+ # Adds interface methods to resource classes
4
+ module ActiveFacet
5
+ module ActsAsActiveFacet
6
+ extend ActiveSupport::Concern
7
+ included do
8
+ end
9
+
10
+ module ClassMethods
11
+ def acts_as_active_facet(options = {})
12
+ raise ActiveFacet::Errors::ConfigurationError.new(ActiveFacet::Errors::ConfigurationError::DUPLICATE_ACTS_AS_ERROR_MSG) if respond_to?(:acts_as_active_facet_options)
13
+ cattr_accessor :acts_as_active_facet_options
14
+
15
+ # save to a local variable so its in scope during instance_eval below
16
+ acts_as_active_facet_options = options.deep_dup
17
+ acts_as_active_facet_options[:includes_method_name] ||= :facet_includes
18
+ acts_as_active_facet_options[:apply_includes_method_name] ||= :apply_facet_includes
19
+ acts_as_active_facet_options[:filter_method_name] ||= :facet_filter
20
+ acts_as_active_facet_options[:apply_filters_method_name] ||= :apply_facet_filters
21
+ acts_as_active_facet_options[:unserialize_method_name] ||= :from_json
22
+ acts_as_active_facet_options[:serialize_method_name] ||= :as_json
23
+
24
+ self.acts_as_active_facet_options = acts_as_active_facet_options
25
+
26
+ (class << self; self; end).instance_eval do
27
+
28
+ # Translates a Field Set into a deeply nested hash of included associations suitable for use by includes
29
+ # @param facets [Object]
30
+ # @param options [Hash]
31
+ # @return [Hash]
32
+ define_method(acts_as_active_facet_options[:includes_method_name]) do |facets = :basic, options = {}|
33
+ ActiveFacet::Helper.serializer_for(self, options).scoped_includes(facets)
34
+ end
35
+
36
+ # Invokes includes with all deeply nested associations found in the given Field Set
37
+ # @param field_set [Object]
38
+ # @param options [Hash]
39
+ # @return [ProxyCollection]
40
+ define_method(acts_as_active_facet_options[:apply_includes_method_name]) do |facets = :basic, options = {}|
41
+ includes(self.send(acts_as_active_facet_options[:includes_method_name], facets, options))
42
+ end
43
+
44
+ # Registers a Filter for this resource
45
+ # @param filter_name [Symbol]
46
+ # @param filter_method_name [Symbol]
47
+ # @return [Class] for chaining
48
+ define_method(acts_as_active_facet_options[:filter_method_name]) do |filter_name, filter_method_name = nil, &filter_method|
49
+ filter_method, filter_method_name = filter_method_name, nil if filter_method_name.is_a?(Proc)
50
+ filter_method_name ||= "registered_filter_#{filter_name}"
51
+ define_singleton_method(filter_method_name, filter_method) if filter_method
52
+ ActiveFacet::Filter.register(self, filter_name, filter_method_name)
53
+ end
54
+
55
+ # Applies all filters registered for this resource
56
+ # Arguments for filters are looked up by resource type, then without namespace
57
+ # @param filter_values [Hash] keys = registerd filter name, values = filter arguments
58
+ # @return [ProxyCollection]
59
+ define_method(acts_as_active_facet_options[:apply_filters_method_name]) do |filter_values = nil|
60
+ filter_values = (filter_values || {}).with_indifferent_access
61
+ ActiveFacet::Filter.registered_filters_for(self).inject(scoped) do |scope, (filter_name, filter_method_name)|
62
+ filter_resource_name = ActiveFacet::Helper.resource_map(self).detect { |filter_resource_name|
63
+ filter_values.keys.include? "#{filter_name}_#{filter_resource_name}"
64
+ }
65
+ args = filter_values["#{filter_name}_#{filter_resource_name}"] || filter_values[filter_name]
66
+ scope.send(filter_method_name, *args) || scope
67
+ end
68
+ end
69
+
70
+ # Builds a new resource instance and unserializes it
71
+ # @param attributes [Hash]
72
+ # @param options [Hash]
73
+ # @return [Resource]
74
+ define_method(acts_as_active_facet_options[:unserialize_method_name]) do |attributes, options = {}|
75
+ self.new.send(acts_as_active_facet_options[:unserialize_method_name], attributes, options)
76
+ end
77
+ end
78
+
79
+ # Unserializes a resource
80
+ # @param attributes [Hash]
81
+ # @param options [Hash]
82
+ # @return [Resource]
83
+ define_method(acts_as_active_facet_options[:unserialize_method_name]) do |attributes, options = {}|
84
+ ActiveFacet::Helper.serializer_for(self.class, options).from_hash(self, attributes)
85
+ end
86
+
87
+ # Serializes a resource with given Facets
88
+ # Falls back to default behavior when key is not present
89
+ # @param options [Hash]
90
+ # @return [Hash]
91
+ define_method(acts_as_active_facet_options[:serialize_method_name]) do |options = nil|
92
+ if options.present? && options.key?(ActiveFacet.opts_key) &&
93
+ (serializer = ActiveFacet::Helper.serializer_for(self.class, options)).present?
94
+ serializer.as_json(self, options)
95
+ else
96
+ super(options)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end