plucker_serializer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []