sbf-dm-serializer 1.3.0.beta

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.
@@ -0,0 +1,137 @@
1
+ require "rubygems"
2
+ require 'pathname'
3
+ require 'better-benchmark'
4
+
5
+ gem 'dm-core', '1.0.0'
6
+ require 'dm-core'
7
+
8
+ spec_dir_path = Pathname(__FILE__).dirname.expand_path
9
+ $LOAD_PATH.unshift(spec_dir_path.parent + 'lib/')
10
+ require 'dm-serializer'
11
+
12
+ def load_driver(name, default_uri)
13
+ begin
14
+ DataMapper.setup(name, ENV["#{name.to_s.upcase}_SPEC_URI"] || default_uri)
15
+ DataMapper::Repository.adapters[:default] = DataMapper::Repository.adapters[name]
16
+ DataMapper::Repository.adapters[:alternate] = DataMapper::Repository.adapters[name]
17
+ true
18
+ rescue LoadError => e
19
+ warn "Could not load do_#{name}: #{e}"
20
+ false
21
+ end
22
+ end
23
+
24
+ HAS_SQLITE3 = load_driver(:sqlite3, 'sqlite3::memory:')
25
+
26
+ module DataMapper
27
+ module Serialize
28
+ # Serialize a Resource to JavaScript Object Notation (JSON; RFC 4627)
29
+ #
30
+ # @return <String> a JSON representation of the Resource
31
+ def to_json_old(*args)
32
+ options = args.first || {}
33
+ result = '{ '
34
+ fields = []
35
+
36
+ propset = properties_to_serialize(options)
37
+
38
+ fields += propset.map do |property|
39
+ "#{property.name.to_json}: #{send(property.name).to_json}"
40
+ end
41
+
42
+ # add methods
43
+ (options[:methods] || []).each do |meth|
44
+ if self.respond_to?(meth)
45
+ fields << "#{meth.to_json}: #{send(meth).to_json}"
46
+ end
47
+ end
48
+
49
+ # Note: if you want to include a whole other model via relation, use :methods
50
+ # comments.to_json(:relationships=>{:user=>{:include=>[:first_name],:methods=>[:age]}})
51
+ # add relationships
52
+ # TODO: This needs tests and also needs to be ported to #to_xml and #to_yaml
53
+ (options[:relationships] || {}).each do |rel,opts|
54
+ if self.respond_to?(rel)
55
+ fields << "#{rel.to_json}: #{send(rel).to_json(opts)}"
56
+ end
57
+ end
58
+
59
+ result << fields.join(', ')
60
+ result << ' }'
61
+ result
62
+ end
63
+ end
64
+
65
+ class Collection
66
+ def to_json_old(*args)
67
+ opts = args.first || {}
68
+ '[' << map { |e| e.to_json_old(opts) }.join(',') << ']'
69
+ end
70
+ end
71
+ end
72
+
73
+
74
+
75
+
76
+ class Cow
77
+ include DataMapper::Resource
78
+
79
+ property :id, Integer, :key => true
80
+ property :composite, Integer, :key => true
81
+ property :name, String
82
+ property :breed, String
83
+
84
+ has n, :baby_cows, :model => 'Cow'
85
+ belongs_to :mother_cow, :model => 'Cow', :required => false
86
+ end
87
+
88
+ DataMapper.auto_migrate!
89
+ cow = Cow.create(
90
+ :id => 89,
91
+ :composite => 34,
92
+ :name => 'Berta',
93
+ :breed => 'Guernsey'
94
+ )
95
+
96
+ 10.times do |n|
97
+ Cow.create(
98
+ :id => n*10,
99
+ :composite => n,
100
+ :name => "Bertha"*n,
101
+ :breed => "Mooing#{n}"
102
+ )
103
+ end
104
+
105
+ all_cows = Cow.all.reload
106
+
107
+ puts "Benchmarking single resource serialization."
108
+ puts "Set 1: old method"
109
+ puts "Set 2: new method"
110
+ result = Benchmark.compare_realtime(
111
+ :iterations => 10,
112
+ :inner_iterations => 20000,
113
+ :verbose => true
114
+ ) { |iteration|
115
+ cow.to_json_old
116
+ }.with { |iteration|
117
+ cow.to_json
118
+ }
119
+
120
+ Benchmark.report_on result
121
+
122
+ puts
123
+
124
+ puts "Benchmarking collection serialization."
125
+ puts "Set 1: old method"
126
+ puts "Set 2: new method"
127
+ result = Benchmark.compare_realtime(
128
+ :iterations => 10,
129
+ :inner_iterations => 5000,
130
+ :verbose => true
131
+ ) { |iteration|
132
+ all_cows.to_json_old
133
+ }.with { |iteration|
134
+ all_cows.to_json
135
+ }
136
+
137
+ Benchmark.report_on result
@@ -0,0 +1,89 @@
1
+ require "rubygems"
2
+ require 'pathname'
3
+
4
+ gem 'dm-core', '1.0.0'
5
+ require 'dm-core'
6
+
7
+ spec_dir_path = Pathname(__FILE__).dirname.expand_path
8
+ $LOAD_PATH.unshift(spec_dir_path.parent + 'lib/')
9
+ require 'dm-serializer'
10
+
11
+ def load_driver(name, default_uri)
12
+ begin
13
+ DataMapper.setup(name, ENV["#{name.to_s.upcase}_SPEC_URI"] || default_uri)
14
+ DataMapper::Repository.adapters[:default] = DataMapper::Repository.adapters[name]
15
+ DataMapper::Repository.adapters[:alternate] = DataMapper::Repository.adapters[name]
16
+ true
17
+ rescue LoadError => e
18
+ warn "Could not load do_#{name}: #{e}"
19
+ false
20
+ end
21
+ end
22
+
23
+ HAS_SQLITE3 = load_driver(:sqlite3, 'sqlite3::memory:')
24
+
25
+ class Cow
26
+ include DataMapper::Resource
27
+
28
+ property :id, Integer, :key => true
29
+ property :composite, Integer, :key => true
30
+ property :name, String
31
+ property :breed, String
32
+
33
+ has n, :baby_cows, :model => 'Cow'
34
+ belongs_to :mother_cow, :model => 'Cow'
35
+ end
36
+
37
+ require "benchwarmer"
38
+
39
+ TIMES = 2000
40
+ DataMapper.auto_migrate!
41
+ cow = Cow.create(
42
+ :id => 89,
43
+ :composite => 34,
44
+ :name => 'Berta',
45
+ :breed => 'Guernsey'
46
+ )
47
+ all_cows = Cow.all
48
+
49
+ puts "REXML"
50
+ Benchmark.warmer(TIMES) do
51
+ group("Serialization:") do
52
+ report "Single Resource" do
53
+ cow.to_xml
54
+ end
55
+ report "Collection" do
56
+ all_cows.to_xml
57
+ end
58
+ end
59
+ end
60
+
61
+ require 'nokogiri'
62
+ load 'dm-serializer/xml_serializers.rb'
63
+
64
+ puts "Nokogiri"
65
+ Benchmark.warmer(TIMES) do
66
+ group("Serialization:") do
67
+ report "Single Resource" do
68
+ cow.to_xml
69
+ end
70
+ report "Collection" do
71
+ all_cows.to_xml
72
+ end
73
+ end
74
+ end
75
+
76
+ require 'libxml'
77
+ load 'dm-serializer/xml_serializers.rb'
78
+
79
+ puts "LibXML"
80
+ Benchmark.warmer(TIMES) do
81
+ group("Serialization:") do
82
+ report "Single Resource" do
83
+ cow.to_xml
84
+ end
85
+ report "Collection" do
86
+ all_cows.to_xml
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,24 @@
1
+ require File.expand_path('../lib/dm-serializer/version', __FILE__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.authors = ['Guy van den Berg', 'Dan Kubb']
5
+ gem.email = ['dan.kubb@gmail.com']
6
+ gem.summary = 'DataMapper plugin for serializing Resources and Collections'
7
+ gem.description = 'dm-serializer allows DataMapper models and collections to be serialized to a variety of formats ' \
8
+ '(currently JSON, XML, YAML and CSV)'
9
+ gem.license = 'Nonstandard'
10
+ gem.homepage = 'https://datamapper.org'
11
+
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.extra_rdoc_files = %w(LICENSE README.rdoc)
14
+
15
+ gem.name = 'sbf-dm-serializer'
16
+ gem.require_paths = ['lib']
17
+ gem.version = DataMapper::Serializer::VERSION
18
+ gem.required_ruby_version = '>= 2.7'
19
+
20
+ gem.add_runtime_dependency('sbf-dm-core', '~> 1.3.0.beta')
21
+ gem.add_runtime_dependency('fastercsv', '~> 1.5.4')
22
+ gem.add_runtime_dependency('multi_json', '~> 1.3.2')
23
+ gem.add_runtime_dependency('rexml', '~> 3.2')
24
+ end
@@ -0,0 +1,27 @@
1
+ require 'dm-core'
2
+
3
+ module DataMapper
4
+ module Serializer
5
+ # Returns properties to serialize based on :only or :exclude arrays,
6
+ # if provided :only takes precedence over :exclude
7
+ #
8
+ # @return [Array]
9
+ # Properties that need to be serialized.
10
+ def properties_to_serialize(options)
11
+ only_properties = Array(options[:only])
12
+ excluded_properties = Array(options[:exclude])
13
+
14
+ model.properties(repository.name).reject do |p|
15
+ if only_properties.include? p.name
16
+ false
17
+ else
18
+ excluded_properties.include?(p.name) ||
19
+ !(only_properties.empty? ||
20
+ only_properties.include?(p.name))
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ Model.append_inclusions(Serializer)
27
+ end
@@ -0,0 +1,71 @@
1
+ require 'dm-serializer/common'
2
+
3
+ if RUBY_VERSION >= '1.9.0'
4
+ require 'csv'
5
+ else
6
+ begin
7
+ require 'fastercsv'
8
+ CSV = FasterCSV
9
+ rescue LoadError
10
+ # do nothing
11
+ end
12
+ end
13
+
14
+ module DataMapper
15
+ module Serializer
16
+ # Serialize a Resource to comma-separated values (CSV).
17
+ #
18
+ # @return <String> a CSV representation of the Resource
19
+ def to_csv(*args)
20
+ options = args.first || {}
21
+ options = options.to_h if options.respond_to?(:to_h)
22
+ options[:writer] = '' unless options.key? :writer
23
+
24
+ CSV.generate(options[:writer]) do |csv|
25
+ row = properties_to_serialize(options).map do |property|
26
+ __send__(property.name).to_s
27
+ end
28
+ csv << row
29
+ end
30
+ end
31
+
32
+ module ValidationErrors
33
+ module ToCsv
34
+ def to_csv(*args)
35
+ options = args.first || {}
36
+ options = options.to_h if options.respond_to?(:to_h)
37
+ options[:writer] = '' unless options.key? :writer
38
+
39
+ CSV.generate(options[:writer]) do |csv|
40
+ violations.each do |key, value|
41
+ value.each do |error|
42
+ row = []
43
+ row << key.to_s
44
+ row << error.to_s
45
+ csv << row
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ class Collection
55
+ def to_csv(*args)
56
+ result = ''
57
+ each do |item|
58
+ result << ("#{item.to_csv(args.first)}\n")
59
+ end
60
+ result
61
+ end
62
+ end
63
+
64
+ if const_defined?(:Validations)
65
+ module Validations
66
+ class ValidationErrors
67
+ include DataMapper::Serializer::ValidationErrors::ToCsv
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,99 @@
1
+ require 'dm-serializer/common'
2
+
3
+ require 'multi_json'
4
+
5
+ module DataMapper
6
+ module Serializer
7
+ #
8
+ # Converts the resource into a hash of properties.
9
+ #
10
+ # @param [Hash] options
11
+ # Additional options.
12
+ #
13
+ # @return [Hash{String => String}]
14
+ # The hash of resources properties.
15
+ #
16
+ # @since 1.0.1
17
+ #
18
+ def as_json(options = {})
19
+ options = {} if options.nil?
20
+ result = {}
21
+
22
+ properties_to_serialize(options).each do |property|
23
+ property_name = property.name
24
+ value = __send__(property_name)
25
+ result[property_name] = value.is_a?(DataMapper::Model) ? value.name : value
26
+ end
27
+
28
+ # add methods
29
+ Array(options[:methods]).each do |method|
30
+ next unless respond_to?(method)
31
+
32
+ result[method] = __send__(method)
33
+ end
34
+
35
+ # NOTE: if you want to include a whole other model via relation, use
36
+ # :methods:
37
+ #
38
+ # comments.to_json(:relationships=>{:user=>{:include=>[:first_name],:methods=>[:age]}})
39
+ #
40
+ # TODO: This needs tests and also needs to be ported to #to_xml and
41
+ # #to_yaml
42
+ options[:relationships]&.each do |relationship_name, opts|
43
+ result[relationship_name] = __send__(relationship_name).to_json(opts.merge(to_json: false)) if respond_to?(relationship_name)
44
+ end
45
+
46
+ result
47
+ end
48
+
49
+ # Serialize a Resource to JavaScript Object Notation (JSON; RFC 4627)
50
+ #
51
+ # @return <String> a JSON representation of the Resource
52
+ def to_json(*args)
53
+ options = args.first
54
+ options = {} unless options.is_a?(Hash)
55
+
56
+ result = as_json(options)
57
+
58
+ # default to making JSON
59
+ if options.fetch(:to_json, true)
60
+ MultiJson.dump(result)
61
+ else
62
+ result
63
+ end
64
+ end
65
+
66
+ module ValidationErrors
67
+ module ToJson
68
+ def to_json(*_args)
69
+ MultiJson.dump(violations.to_h)
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ class Collection
76
+ def to_json(*args)
77
+ options = args.first
78
+ options = {} unless options.is_a?(Hash)
79
+
80
+ resource_options = options.merge(to_json: false)
81
+ collection = map { |resource| resource.to_json(resource_options) }
82
+
83
+ # default to making JSON
84
+ if options.fetch(:to_json, true)
85
+ MultiJson.dump(collection)
86
+ else
87
+ collection
88
+ end
89
+ end
90
+ end
91
+
92
+ if const_defined?(:Validations)
93
+ module Validations
94
+ class ValidationErrors
95
+ include DataMapper::Serializer::ValidationErrors::ToJson
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,123 @@
1
+ require 'dm-serializer/common'
2
+ require 'dm-serializer/xml'
3
+
4
+ module DataMapper
5
+ module Serializer
6
+ # Serialize a Resource to XML.
7
+ #
8
+ # @return [LibXML::Document, Nokogiri::Document, REXML::Document]
9
+ # An XML representation of this Resource.
10
+ #
11
+ def to_xml(opts = {})
12
+ xml = XML.serializer
13
+ xml.output(to_xml_document(opts)).to_s
14
+ end
15
+
16
+ # This method requires certain methods to be implemented in the
17
+ # individual serializer library subclasses:
18
+ #
19
+ # * new_document
20
+ # * root_node
21
+ # * add_property_node
22
+ # * add_node
23
+ def to_xml_document(opts = {}, doc = nil)
24
+ xml = XML.serializer
25
+ doc ||= xml.new_document
26
+
27
+ default_xml_element_name = lambda {
28
+ DataMapper::Inflector.underscore(model.name).tr('/', '-')
29
+ }
30
+
31
+ root = xml.root_node(
32
+ doc,
33
+ (opts[:element_name] || default_xml_element_name[])
34
+ )
35
+
36
+ properties_to_serialize(opts).each do |property|
37
+ value = __send__(property.name)
38
+ attrs = {}
39
+
40
+ dump_class = property.dump_class
41
+ attrs['type'] = dump_class.to_s.downcase unless dump_class == ::String
42
+ xml.add_node(root, property.name.to_s, value, attrs)
43
+ end
44
+
45
+ Array(opts[:methods]).each do |meth|
46
+ next unless respond_to?(meth)
47
+
48
+ xml_name = meth.to_s.gsub(/[^a-z0-9_]/, '')
49
+ value = __send__(meth)
50
+
51
+ unless value.nil?
52
+ if value.respond_to?(:to_xml_document)
53
+ xml.add_xml(root, value.to_xml_document)
54
+ else
55
+ xml.add_node(root, xml_name, value.to_s)
56
+ end
57
+ end
58
+ end
59
+
60
+ doc
61
+ end
62
+
63
+ module ValidationErrors
64
+ module ToXml
65
+ def to_xml(opts = {})
66
+ to_xml_document(opts).to_s
67
+ end
68
+
69
+ def to_xml_document(_opts = {})
70
+ xml = DataMapper::Serializer::XML.serializer
71
+ doc = xml.new_document
72
+ root = xml.root_node(doc, 'errors', {'type' => 'hash'})
73
+
74
+ violations.each do |key, value|
75
+ property = xml.add_node(root, key.to_s, nil, {'type' => 'array'})
76
+ property.attributes['type'] = 'array'
77
+
78
+ value.each do |error|
79
+ xml.add_node(property, 'error', error)
80
+ end
81
+ end
82
+
83
+ doc
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ class Collection
90
+ def to_xml(opts = {})
91
+ to_xml_document(opts).to_s
92
+ end
93
+
94
+ def to_xml_document(opts = {})
95
+ xml = DataMapper::Serializer::XML.serializer
96
+ doc = xml.new_document
97
+
98
+ default_collection_element_name = lambda {
99
+ DataMapper::Inflector.pluralize(DataMapper::Inflector.underscore(model.to_s)).tr('/', '-')
100
+ }
101
+
102
+ xml.root_node(
103
+ doc,
104
+ opts[:collection_element_name] || default_collection_element_name[],
105
+ {'type' => 'array'}
106
+ )
107
+
108
+ each do |item|
109
+ item.to_xml_document(opts, doc)
110
+ end
111
+
112
+ doc
113
+ end
114
+ end
115
+
116
+ if const_defined?(:Validations)
117
+ module Validations
118
+ class ValidationErrors
119
+ include DataMapper::Serializer::ValidationErrors::ToXml
120
+ end
121
+ end
122
+ end
123
+ end