sbf-dm-serializer 1.3.0.beta

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