plucker_serializer 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c5ce3615a6b455cef96024d0dd6d9cfadf30e1af519f88e03496927956a378aa
4
+ data.tar.gz: ba1b98c83f64a135f9043d92e8ec0b9a5ff785144b774f2f6c78e915b589b75c
5
+ SHA512:
6
+ metadata.gz: 11751d1facbd07d422144a931b2e9487495ba294880dd064b754e3526bfa9212da5d73c3f9d65fff061a281ce9b4d63504167d66bfb02c4f0fa8305984823d6a
7
+ data.tar.gz: c7e5ceabff25a46edac6e3a2c72b79b1f9f2c92c20bb32486b995de4e3d2ae6ebd608818201c6722514363b390b6978e9b1409f2a5606dbf9ac66b440c2e0c45
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ module Plucker
3
+ class Attribute < Plucker::Field
4
+ end
5
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'caching'
3
+ require_relative 'descriptor'
4
+ require_relative 'has_many'
5
+ require_relative 'belongs_to'
6
+ require_relative 'has_one'
7
+ require 'active_support/all'
8
+
9
+ module Plucker
10
+ class Base
11
+ include Caching
12
+
13
+ attr_accessor :object
14
+
15
+ with_options instance_writer: false, instance_reader: false do |serializer|
16
+ serializer.class_attribute :_descriptor
17
+ self._descriptor ||= Plucker::Descriptor.new(self.class)
18
+ end
19
+
20
+ def self.inherited(base)
21
+ super
22
+ base._descriptor = Plucker::Descriptor.new(base)
23
+ end
24
+
25
+ def initialize(object, options = {})
26
+ self.object = object
27
+ end
28
+
29
+ def serializable_hash
30
+ if self.class.cache_enabled?
31
+ fetch do
32
+ get_hash
33
+ end
34
+ else
35
+ get_hash
36
+ end
37
+ end
38
+ alias to_hash serializable_hash
39
+ alias to_h serializable_hash
40
+
41
+ def get_hash
42
+ attributes_hash.merge! associations_hash
43
+ end
44
+
45
+ def as_json(options = nil)
46
+ to_h.as_json(options)
47
+ end
48
+
49
+ def associations_hash
50
+ hash = {}
51
+ self.class._descriptor._relationships.each do |(key, relationship)|
52
+ next if relationship.excluded?(self)
53
+ hash[key] = relationship.value(self)
54
+ end
55
+ hash
56
+ end
57
+
58
+ def attributes_hash
59
+ self.class._descriptor._attributes.each_with_object({}) do |(key, attr), hash|
60
+ next if attr.excluded?(self)
61
+ hash[key] = attr.value(self)
62
+ end
63
+ end
64
+
65
+ def self.is_pluckable?
66
+ self._descriptor.is_pluckable?
67
+ end
68
+
69
+ def self.pluckable_columns
70
+ self._descriptor._pluckable_columns
71
+ end
72
+
73
+ def self.attributes(*attrs)
74
+ attrs.each do |attr|
75
+ attribute(attr)
76
+ end
77
+ end
78
+
79
+ def self.attribute(attr, options = {}, &block)
80
+ self._descriptor.add_attribute(options.fetch(:key, attr), Plucker::Attribute.new(attr, options, block))
81
+ end
82
+
83
+ def self.belongs_to(attr, options = {}, &block)
84
+ self._descriptor.add_relationship(options.fetch(:key, attr), Plucker::BelongsTo.new(attr, options, block))
85
+ end
86
+
87
+ def self.has_one(attr, options = {}, &block)
88
+ self._descriptor.add_relationship(options.fetch(:key, attr), Plucker::HasOne.new(attr, options, block))
89
+ end
90
+
91
+ def self.has_many(attr, options = {}, &block)
92
+ self._descriptor.add_relationship(options.fetch(:key, attr), Plucker::HasMany.new(attr, options, block))
93
+ end
94
+
95
+ def self.model(attr)
96
+ self._descriptor.set_model(attr)
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ require_relative "relationship"
3
+
4
+ module Plucker
5
+ class BelongsTo < Plucker::Relationship
6
+ def value(serializer)
7
+ relationship_object = self.associated_object(serializer)
8
+ return nil if relationship_object.blank?
9
+ relationship_serializer(serializer, relationship_object).new(relationship_object).serializable_hash
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+ require 'active_support/all'
3
+
4
+ module Plucker
5
+ module Caching
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ with_options instance_writer: false, instance_reader: false do |serializer|
10
+ serializer.class_attribute :_cache
11
+ serializer.class_attribute :_cache_options
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+ def _cache_digest
17
+ return @_cache_digest if defined?(@_cache_digest)
18
+ @_cache_digest = Digest::SHA1.hexdigest(self.name)
19
+ end
20
+
21
+ def cache(options = {})
22
+ self._cache = true
23
+ self._cache_options = options.blank? ? {} : options
24
+ end
25
+
26
+ def cache_store
27
+ defined?(Rails) && Rails.cache
28
+ end
29
+
30
+ def cache_enabled?
31
+ cache_store.present? && _cache.present?
32
+ end
33
+ end
34
+
35
+ ### INSTANCE METHODS
36
+ def fetch
37
+ if serializer_class.cache_enabled?
38
+ serializer_class.cache_store.fetch(cache_key, version: cache_version, options: serializer_class._cache_options) do
39
+ yield
40
+ end
41
+ else
42
+ yield
43
+ end
44
+ end
45
+
46
+ def cache_version
47
+ return @cache_version if defined?(@cache_version)
48
+ @cache_version = object.cache_version
49
+ end
50
+
51
+ def cache_key
52
+ return @cache_key if defined?(@cache_key)
53
+ @cache_key = object.cache_key + "/" + serializer_class._cache_digest
54
+ end
55
+
56
+ def serializer_class
57
+ @serializer_class ||= self.class
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+ module Plucker
3
+ class Collection
4
+ include Enumerable
5
+ include Caching
6
+
7
+ attr_reader :objects, :serializer_class, :options
8
+
9
+ def initialize(objects, options = {})
10
+ @objects = objects
11
+ @options = options
12
+ @serializer_class = get_serialized_model(objects)
13
+ end
14
+
15
+ def serializable_hash
16
+ if (not objects.is_a?(ActiveRecord::Relation))
17
+ objects.map do |object|
18
+ serializer_class.new(object).serializable_hash
19
+ end.compact
20
+ else
21
+ if serializer_class.cache_enabled?
22
+ fetch do
23
+ if serializer_class.is_pluckable?
24
+ associated_hash
25
+ else
26
+ objects.map do |object|
27
+ serializer_class.new(object).serializable_hash
28
+ end.compact
29
+ end
30
+ end
31
+ else
32
+ if serializer_class.is_pluckable?
33
+ associated_hash
34
+ else
35
+ objects.map do |object|
36
+ serializer_class.new(object).serializable_hash
37
+ end.compact
38
+ end
39
+ end
40
+ end
41
+ end
42
+ alias to_hash serializable_hash
43
+ alias to_h serializable_hash
44
+
45
+ def as_json(options = nil)
46
+ to_h.as_json(options)
47
+ end
48
+
49
+ def cache_version
50
+ return @cache_version if defined?(@cache_version)
51
+ @cache_version = objects.cache_version
52
+ end
53
+
54
+ def cache_key
55
+ return @cache_key if defined?(@cache_key)
56
+ @cache_key = objects.cache_key + '/' + serializer_class._cache_digest
57
+ end
58
+
59
+ private
60
+ def associated_hash
61
+ pluck_to_hash(objects, serializer_class.pluckable_columns.to_a)
62
+ end
63
+
64
+ def pluck_to_hash(object, attrs)
65
+ namespaced_attrs = attrs.map { |attr| object.model.table_name.to_s + "." + attr.to_s }
66
+ object.pluck(Arel.sql(namespaced_attrs.join(','))).map { |el| attrs.zip(el).to_h }
67
+ end
68
+
69
+ def get_serialized_model(objects)
70
+ if options[:serializer].blank?
71
+ "#{objects.klass.name.demodulize.camelize}Serializer".constantize
72
+ else
73
+ options[:serializer]
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+ module Plucker
3
+ class Descriptor
4
+ attr_accessor :_serialized_model, :_attributes, :_relationships, :_pluckable_columns, :_is_pluckable
5
+
6
+ def initialize(serializer_class)
7
+ self._serialized_model = get_serialized_model(serializer_class)
8
+ self._attributes = {}
9
+ self._relationships = {}
10
+ self._pluckable_columns = Set.new([:updated_at])
11
+ self._is_pluckable = true
12
+ end
13
+
14
+ def is_pluckable?
15
+ self._is_pluckable && !self._relationships.present?
16
+ end
17
+
18
+ def add_attribute(key, attr)
19
+ self._attributes[key] = attr
20
+ if attr.is_pluckable? && self._serialized_model && self._serialized_model.column_names.include?(attr.name.to_s)
21
+ self._pluckable_columns << attr.name
22
+ else
23
+ self._is_pluckable = false
24
+ end
25
+ end
26
+
27
+ def add_relationship(key, relationship)
28
+ self._relationships[key] = relationship
29
+ self._is_pluckable = false
30
+ end
31
+
32
+ def set_model(model)
33
+ self._serialized_model = model
34
+ end
35
+
36
+ def get_serialized_model(serializer_class)
37
+ begin
38
+ serializer_class.name.split(/Serializer/).first.constantize
39
+ rescue NameError, LoadError => e
40
+ nil
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+ module Plucker
3
+ Field = Struct.new(:name, :options, :block) do
4
+ def initialize(*)
5
+ super
6
+ validate_condition!
7
+ end
8
+
9
+ def value(serializer)
10
+ block_value = instance_exec(serializer.object, &block) if block
11
+ if block && block_value != :nil
12
+ block_value
13
+ else
14
+ if serializer.respond_to?(name)
15
+ serializer.send(name)
16
+ else
17
+ serializer.object.send(name)
18
+ end
19
+ end
20
+ end
21
+
22
+ def is_pluckable?
23
+ block.blank?
24
+ end
25
+
26
+ def excluded?(serializer)
27
+ case condition_type
28
+ when :if
29
+ !evaluate_condition(serializer)
30
+ when :unless
31
+ evaluate_condition(serializer)
32
+ else
33
+ false
34
+ end
35
+ end
36
+
37
+ private
38
+ def validate_condition!
39
+ return if condition_type == :none
40
+
41
+ case condition
42
+ when Symbol, String, Proc
43
+ # noop
44
+ else
45
+ fail TypeError, "#{condition_type.inspect} should be a Symbol, String or Proc"
46
+ end
47
+ end
48
+
49
+ def evaluate_condition(serializer)
50
+ case condition
51
+ when Symbol
52
+ serializer.public_send(condition)
53
+ when String
54
+ serializer.instance_eval(condition)
55
+ when Proc
56
+ if condition.arity.zero?
57
+ serializer.instance_exec(&condition)
58
+ else
59
+ serializer.instance_exec(serializer, &condition)
60
+ end
61
+ else
62
+ nil
63
+ end
64
+ end
65
+
66
+ def condition_type
67
+ @condition_type ||=
68
+ if options.key?(:if)
69
+ :if
70
+ elsif options.key?(:unless)
71
+ :unless
72
+ else
73
+ :none
74
+ end
75
+ end
76
+
77
+ def condition
78
+ options[condition_type]
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ require_relative "relationship"
3
+ require_relative "collection"
4
+
5
+ module Plucker
6
+ class HasMany < Plucker::Relationship
7
+ def value(serializer)
8
+ Plucker::Collection.new(self.associated_object(serializer), serializer: relationship_serializer(serializer)).serializable_hash
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ require_relative "relationship"
3
+
4
+ module Plucker
5
+ class HasOne < Plucker::Relationship
6
+ def value(serializer)
7
+ relationship_object = self.associated_object(serializer)
8
+ return nil if relationship_object.blank?
9
+ relationship_serializer(serializer, relationship_object).new(relationship_object).serializable_hash
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'collection'
3
+ require_relative "field"
4
+
5
+ module Plucker
6
+ class Relationship < Plucker::Field
7
+ def associated_object(serializer)
8
+ serializer.object.send(name)
9
+ end
10
+
11
+ def value(serializer)
12
+ nil
13
+ end
14
+
15
+ private
16
+ def relationship_serializer(serializer, relationship_object=nil)
17
+ if self.options[:serializer].blank?
18
+ if relationship_object.present?
19
+ association_class = relationship_object.class.name
20
+ else
21
+ association_class = serializer.object.class.reflect_on_association(self.name.to_sym).class_name
22
+ end
23
+ "#{association_class.demodulize.camelize}Serializer".constantize
24
+ else
25
+ self.options[:serializer]
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ module Plucker
3
+ VERSION = "0.1.0"
4
+ end
@@ -0,0 +1,11 @@
1
+ require "plucker/version"
2
+ require "plucker/base"
3
+ require "plucker/attribute"
4
+ require "plucker/collection"
5
+ require "plucker/relationship"
6
+ require "plucker/belongs_to"
7
+ require "plucker/has_one"
8
+ require "plucker/has_many"
9
+ require "plucker/field"
10
+ require "plucker/descriptor"
11
+ require "plucker/caching"
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: plucker_serializer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Henry Boisgibault
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-07-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: A blazing fast JSON serializer
28
+ email: henry@logora.fr
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/plucker/attribute.rb
34
+ - lib/plucker/base.rb
35
+ - lib/plucker/belongs_to.rb
36
+ - lib/plucker/caching.rb
37
+ - lib/plucker/collection.rb
38
+ - lib/plucker/descriptor.rb
39
+ - lib/plucker/field.rb
40
+ - lib/plucker/has_many.rb
41
+ - lib/plucker/has_one.rb
42
+ - lib/plucker/relationship.rb
43
+ - lib/plucker/version.rb
44
+ - lib/plucker_serializer.rb
45
+ homepage:
46
+ licenses:
47
+ - MIT
48
+ metadata: {}
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: 2.5.0
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubygems_version: 3.1.2
65
+ signing_key:
66
+ specification_version: 4
67
+ summary: A blazing fast JSON serializer with an easy-to-use API
68
+ test_files: []