active_facets 1.2.2

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: 63e1ea8c7c86286554a205c84e6ddc7224be9b00
4
+ data.tar.gz: dbc2ed04992db135f887f8bc56bc21ec0fe30f37
5
+ SHA512:
6
+ metadata.gz: 773bafd97bc95b401a0b12dbc6a0edd5ac82f18f769e7dc2715725cc226fd6ca0888ed6ba7198b5532fcdb6d32df2bc68cd4f91901307660844dd4999ebda1b1
7
+ data.tar.gz: 99d24b3b9c658b458329ee385147f7aded91b1593ace925b5d1f50d35db62a69da21dc175a363bd2dbdefd84dfa0f766f07149de996b4dc2063163ca4ed9b9cf
@@ -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
+ # ActiveFacets
2
+
3
+ ActiveFacets 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_facets'
19
+ ```
20
+
21
+ And then execute:
22
+
23
+ $ bundle
24
+
25
+ Or install it yourself as:
26
+
27
+ $ gem install active_facets
28
+
29
+ ## Configuration
30
+
31
+ And then execute:
32
+
33
+ $ rails g active_facets: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 ActiveFacets::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_facets /path/to/local/git/repository`
191
+
192
+ To unconfigure a local version of the gem, run `bundle config --delete local.active_facets`
193
+
194
+ ## Roadmap
195
+ Available at https://github.com/honest/active_facets/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_facets. 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 = 'ActiveFacets'
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,93 @@
1
+ require 'w_g'
2
+ require 'active_support/all'
3
+ require 'active_record'
4
+
5
+ require 'active_facets/errors/attribute_error'
6
+ require 'active_facets/errors/configuration_error'
7
+ require 'active_facets/errors/lookup_error'
8
+ require 'active_facets/acts_as_active_facet'
9
+ require 'active_facets/serializer/base'
10
+ require 'active_facets/serializer/facade'
11
+ require 'active_facets/config'
12
+ require 'active_facets/document_cache'
13
+ require 'active_facets/resource_manager'
14
+ require 'active_facets/version'
15
+
16
+ module ActiveFacets
17
+ mattr_accessor :opts_key,
18
+ :fields_key,
19
+ :field_overrides_key,
20
+ :version_key,
21
+ :filters_key,
22
+ :cache_force_key,
23
+ :filters_force_key,
24
+ :strict_lookups,
25
+ :preload_associations,
26
+ :cache_enabled,
27
+ :acts_as_active_facet_enabled,
28
+ :filters_enabled,
29
+ :default_cache_options,
30
+ :document_cache,
31
+ :default_version
32
+
33
+ self.default_version = 1.0
34
+ self.opts_key = :af_opts
35
+ self.fields_key = :fields
36
+ self.field_overrides_key = :field_overrides
37
+ self.version_key = :version
38
+ self.filters_key = :filters
39
+ self.cache_force_key = :cache_force
40
+ self.filters_force_key = :filters_force
41
+
42
+ self.strict_lookups = false
43
+ self.preload_associations = false
44
+ self.filters_enabled = false
45
+ self.cache_enabled = false
46
+ self.acts_as_active_facet_enabled = false
47
+ self.default_cache_options = { expires_in: 5.minutes }
48
+ self.document_cache = ActiveFacets::DocumentCache
49
+
50
+ def self.configure
51
+ yield(self)
52
+ ActiveRecord::Base.acts_as_active_facet if ActiveFacets.acts_as_active_facet_enabled
53
+ end
54
+
55
+ def self.global_filter(name)
56
+ ActiveFacets::ActsAsActiveFacet::Filters.global_filters[name] = Proc.new
57
+ end
58
+
59
+ def self.resource_mapper
60
+ ActiveFacets::ResourceManager.resource_mapper = Proc.new
61
+ end
62
+
63
+ def self.serializer_mapper
64
+ ActiveFacets::ResourceManager.serializer_mapper = Proc.new
65
+ end
66
+
67
+ def self.fields_from_options(options)
68
+ (options[ActiveFacets.opts_key] || {})[ActiveFacets.fields_key]
69
+ end
70
+
71
+ def self.options_with_fields(options, fields)
72
+ (options[ActiveFacets.opts_key] ||= {})[ActiveFacets.fields_key] = fields
73
+ options
74
+ end
75
+
76
+ def self.restore_opts_after(options, key, value)
77
+ opts = (options[ActiveFacets.opts_key] ||= {})
78
+ old = opts[key]
79
+ opts[key] = value
80
+ yield
81
+ ensure
82
+ opts[key] = old
83
+ end
84
+
85
+ def self.deep_copy(o)
86
+ Marshal.load(Marshal.dump(o))
87
+ end
88
+ end
89
+
90
+ ActiveRecord::Base.send :include, ActiveFacets::ActsAsActiveFacet
91
+
92
+
93
+
@@ -0,0 +1,131 @@
1
+ #NOTE:: strive for minimal method footprint because this mixes into ActiveRecord
2
+
3
+ # TODO --jdc, change serializer scoped_includes, as_json & from_hash to be generic and add voerrides in initializer for www
4
+ # when serializing, access cattr to determine method name to invoke
5
+ # add tests for the this module
6
+
7
+ module ActiveFacets
8
+ module ActsAsActiveFacet
9
+ extend ActiveSupport::Concern
10
+ included do
11
+ end
12
+
13
+ module Filters
14
+ mattr_accessor :filters, :global_filters
15
+ self.filters, self.global_filters = {}, {}
16
+
17
+ def self.apply_globals_to(receiver)
18
+ global_filters.each do |filter_name, filter_method|
19
+ filter_method_name = receiver.acts_as_active_facet_options[:filter_method_name]
20
+ receiver.send(filter_method_name, filter_name, &filter_method)
21
+ end
22
+ end
23
+
24
+ # @return [Hash] all filters registered on this resource (and superclass)
25
+ def self.registered_filters_for(receiver)
26
+ receiver_filters = filters[receiver.name] ||= {}
27
+ receiver_filters.reverse_merge(registered_filters_for(receiver.superclass)) if has_registered_filters_for?(receiver.superclass)
28
+ receiver_filters
29
+ end
30
+
31
+ def self.register_filter_for(receiver, filter_name, filter_method_name)
32
+ receiver_filters = filters[receiver.name] ||= {}
33
+ receiver_filters[filter_name.to_sym] = filter_method_name.to_sym
34
+ receiver_filters
35
+ end
36
+
37
+ def self.has_registered_filters_for?(receiver)
38
+ filters.key?(receiver.name)
39
+ end
40
+
41
+ end
42
+
43
+ module ClassMethods
44
+ def acts_as_active_facet(options = {})
45
+
46
+ # save to a local variable so its in scope during instance_eval below
47
+ acts_as_active_facet_options = options.deep_dup
48
+ acts_as_active_facet_options[:includes_method_name] ||= :facet_includes
49
+ acts_as_active_facet_options[:apply_includes_method_name] ||= :apply_facet_includes
50
+ acts_as_active_facet_options[:filter_method_name] ||= :facet_filter
51
+ acts_as_active_facet_options[:apply_filters_method_name] ||= :apply_facet_filters
52
+ acts_as_active_facet_options[:unserialize_method_name] ||= :from_json
53
+ acts_as_active_facet_options[:serialize_method_name] ||= :as_json
54
+
55
+ cattr_accessor :acts_as_active_facet_options
56
+ self.acts_as_active_facet_options = acts_as_active_facet_options
57
+
58
+ (class << self; self; end).instance_eval do
59
+
60
+ # Invokes ProxyCollection.includes with a safe translation of field_set
61
+ # @param facets [Object]
62
+ # @param options [Hash]
63
+ # @return [ProxyCollection]
64
+ define_method(acts_as_active_facet_options[:includes_method_name]) do |facets = :basic, options = {}|
65
+ ActiveFacets::ResourceManager.instance.serializer_for(self, options).scoped_includes(facets)
66
+ end
67
+
68
+ # Invokes ProxyCollection.includes with a safe translation of field_set
69
+ # @param field_set [Object]
70
+ # @param options [Hash]
71
+ # @return [ProxyCollection]
72
+ define_method(acts_as_active_facet_options[:apply_includes_method_name]) do |facets = :basic, options = {}|
73
+ includes(self.send(includes_method_name, facets, options))
74
+ end
75
+
76
+ # Registers a scope filter on this resource and subclasses
77
+ # @param filter_name [Symbol] filter name
78
+ # @param filter_method_name [Symbol] scope name
79
+ define_method(acts_as_active_facet_options[:filter_method_name]) do |filter_name, filter_method_name = nil, &filter_method|
80
+ filter_method_name ||= "registered_filter_#{filter_name}"
81
+ define_singleton_method(filter_method_name, filter_method) if filter_method
82
+
83
+ ActiveFacets::ActsAsActiveFacet::Filters.register_filter_for(self, filter_name, filter_method_name)
84
+ end
85
+
86
+ # Applies all filters registered with this resource on a ProxyCollection
87
+ # @param filter_values [Hash] keys = registerd filter name, values = filter arguments
88
+ define_method(acts_as_active_facet_options[:apply_filters_method_name]) do |filter_values = nil|
89
+ filter_values = (filter_values || {}).with_indifferent_access
90
+ ActiveFacets::ActsAsActiveFacet::Filters.registered_filters_for(self).inject(self) do |result, (k,v)|
91
+ filter = ActiveFacets::ResourceManager.instance.resource_map(self).detect { |map_entry|
92
+ filter_values.keys.include? "#{k}_#{map_entry}"
93
+ }
94
+ args = filter_values["#{k}_#{filter}"] || filter_values[k]
95
+ result.send(v, *args) || result
96
+ end
97
+ end
98
+
99
+ # Builds a new resource instance and unserializes it
100
+ # @param attributes [Hash]
101
+ # @param options [Hash]
102
+ # @return [Resource]
103
+ define_method(acts_as_active_facet_options[:unserialize_method_name]) do |attributes, options = {}|
104
+ self.new.send(acts_as_active_facet_options[:unserialize_method_name], attributes, options)
105
+ end
106
+ end
107
+
108
+ # Unserializes a resource
109
+ # @param attributes [Hash]
110
+ # @param options [Hash]
111
+ # @return [Resource]
112
+ define_method(acts_as_active_facet_options[:unserialize_method_name]) do |attributes, options = {}|
113
+ ActiveFacets::ResourceManager.instance.serializer_for(self.class, options).from_hash(self, attributes)
114
+ end
115
+
116
+ # Serializes a resource using facets
117
+ # Falls back to default behavior when RCB key is not present
118
+ # @param options [Hash]
119
+ # @return [Hash]
120
+ define_method(acts_as_active_facet_options[:serialize_method_name]) do |options = nil|
121
+ if options.present? && options.key?(ActiveFacets.opts_key) &&
122
+ (serializer = ActiveFacets::ResourceManager.instance.serializer_for(self.class, options)).present?
123
+ serializer.as_json(self, options)
124
+ else
125
+ super(options)
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end