model_xml 1.0.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.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.sw*
data/README.md ADDED
@@ -0,0 +1,104 @@
1
+ Introduction
2
+ ============
3
+
4
+ model_xml is a small gem which helps in the conversion of ruby objects to xml. It is designed with ActiveRecord objects in mind, but should work with any Ruby object.
5
+
6
+ Installation
7
+ ============
8
+
9
+ gem 'model_xml' in your gemfile, or gem install model_xml
10
+
11
+ If you are using Rails, just require model_xml any time after ActiveRecord has loaded - the bottom of your environment.rb file is fine. Otherwise, require model_xml, then include ModelXML in your object.
12
+
13
+ Usage
14
+ =====
15
+
16
+ Take a look at ActiveRecord's baked in to_xml method first. If it meets your requirements, obviously just use that. If not, read on.
17
+
18
+ The simplest usage method is just to declare the list of fields you want in your model's xml representation, like this:
19
+
20
+ class User < ActiveRecord::Base
21
+ model_xml :first_name, :last_name, :dob
22
+ end
23
+
24
+ Then user.to_xml gives:
25
+
26
+ <user>
27
+ <first_name>John</first_name>
28
+ <last_name>Jones</last_name>
29
+ <dob>1970-12-23</dob>
30
+ </user>
31
+
32
+ Note that (unlike ActiveRecords's to_xml) the field names can be any method in your object, not just database columns - eg
33
+
34
+ class User < ActiveRecord::Base
35
+ model_xml :full_name, :dob
36
+
37
+ def full_name
38
+ "#{first_name} {last+name}"
39
+ end
40
+ end
41
+
42
+ behaves as expected:
43
+
44
+ <user>
45
+ <full_name>John Jones</full_name>
46
+ <dob>1970-12-23</dob>
47
+ </user>
48
+
49
+ You can even declare a formatting proc on the fly, like this:
50
+
51
+ class User < ActiveRecord::Base
52
+ model_xml [:full_name, proc {|u| "#{u.first_name} #{u.last_name}"], :dob
53
+ end
54
+
55
+ which would give the same result.
56
+
57
+ For more complicated setups, you can use block notation like this:
58
+
59
+ class User < ActiveRecord::Base
60
+ model_xml do
61
+ full_name proc {|u| "#{u.first_name} #{u.last_name}"}
62
+ dob
63
+ password
64
+ last_logged_in
65
+ end
66
+ end
67
+
68
+ Finally, you can declare named blocks using block notation like this:
69
+
70
+ class User < ActiveRecord::Base
71
+ model_xml :first_name, :last_name
72
+ model_xml :personal_details do
73
+ dob
74
+ password
75
+ last_logged_in
76
+ end
77
+ end
78
+
79
+ By default named blocks are excluded from the xml, so user.to_xml gives
80
+
81
+ <user>
82
+ <first_name>John</first_name>
83
+ <last_name>Jones</last_name>
84
+ </user>
85
+
86
+ but can be included explicitly - so user.to_xml(:personal_details => true) gives
87
+
88
+ <user>
89
+ <first_name>John</first_name>
90
+ <last_name>Jones</last_name>
91
+ <dob>1970-12-23</dob>
92
+ <password>foo</password>
93
+ <last_logged_in>2012-04-10</last_logged_in>
94
+ </user>
95
+
96
+ Source
97
+ ======
98
+
99
+ Source is on github https://github.com/rob-anderson/model_xml
100
+
101
+ Bugs
102
+ ====
103
+
104
+ Send bugs or comments to me rob.anderson@paymentcardsolutions.co.uk
@@ -0,0 +1,34 @@
1
+ module ModelXML
2
+ class BlockParser
3
+
4
+ class << self
5
+
6
+ def parse &block
7
+ raise "block required" unless block_given?
8
+ parser = self.new
9
+ parser.instance_eval &block
10
+ parser.field_set
11
+ end
12
+
13
+ end
14
+
15
+ attr_reader :field_set
16
+
17
+ def initialize
18
+ @field_set = []
19
+ end
20
+
21
+ def method_missing *args
22
+
23
+ # if the method is called without arguments, add it as a member of the field set
24
+ if args.map(&:class) == [Symbol]
25
+ @field_set << args[0]
26
+
27
+ # otherwise add it as an array
28
+ else
29
+ @field_set << args
30
+ end
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,116 @@
1
+ require 'rubygems'
2
+ require 'builder'
3
+ require 'nokogiri'
4
+ require 'model_xml/block_parser'
5
+
6
+ module ModelXML
7
+ class Generator
8
+
9
+ attr_reader :field_sets
10
+
11
+ def initialize
12
+ @field_sets ||= []
13
+ end
14
+
15
+ # three types of argument list are expected:
16
+ #
17
+ # 1. an unnamed fieldset expressed as any number of symbols eg
18
+ # model_xml :id, :first_name, :last_name, [:embossed_name, :getter => proc {|u| "#{u.first_name} #{u.last_name}".upcase}]
19
+ #
20
+ # 2. a named fieldset in block notation eg
21
+ # model_xml :personal_data do
22
+ # :first_name
23
+ # :last_name
24
+ # :embossed_name proc {|u| "#{u.first_name} #{u.last_name}".upcase}
25
+ # end
26
+ #
27
+ # 3. an unnamed fieldset in block notation eg
28
+ # model_xml do
29
+ # :first_name
30
+ # last_name
31
+ # end
32
+
33
+ def add_field_set *args, &block
34
+
35
+ # if the argument list is empty and we have a block, it is an unnamed block
36
+ if args.empty? && block_given?
37
+ @field_sets << ModelXML::BlockParser.parse(&block)
38
+
39
+ # otherwise if the argument list is a symbol and we have a block, it is a named block
40
+ elsif args.map(&:class) == [Symbol] && block_given?
41
+ name = args[0]
42
+ @field_sets << {name => ModelXML::BlockParser.parse(&block)}
43
+
44
+ # otherwise assume it is a simple fieldset expressed as symbols
45
+ else
46
+ @field_sets << args
47
+ end
48
+ end
49
+
50
+ # apply any options to the default field sets to generate a single array of fields
51
+ def generate_field_list options={}
52
+
53
+ field_list = []
54
+ @field_sets.each do |field_set|
55
+
56
+ # if the field set is a hash then it is a hash of conditional field sets
57
+ # which should only be included if the conditions are present as options
58
+ if field_set.is_a?(Hash)
59
+ field_set.each do |k, v|
60
+ field_list += v if options[k]
61
+ end
62
+
63
+ # otherwise, always include
64
+ else
65
+ field_list += field_set
66
+ end
67
+ end
68
+ field_list
69
+ end
70
+
71
+ def generate_xml! object, options={}
72
+
73
+ field_list = generate_field_list(options)
74
+
75
+ xml = Builder::XmlMarkup.new
76
+ unless options[:skip_instruct]
77
+ xml.instruct!
78
+ end
79
+
80
+ xml.tag! object.class.to_s.downcase do
81
+
82
+ field_list.each do |field|
83
+
84
+ # if the field is a symbol, treat it as the field name and the getter method
85
+ if field.is_a?(Symbol)
86
+ tag = field
87
+ content = object.send(field)
88
+
89
+ # if the field is an array of a symbol followed by a proc, assume the symbol is the field name and the proc is the getter
90
+ elsif field.is_a?(Array) && field.map(&:class) == [Symbol, Proc]
91
+ tag = field[0]
92
+ content = field[1].call(object)
93
+
94
+ # otherwise we have garbage
95
+ else
96
+ raise "ModelXML unable to parse #{field.inspect}"
97
+ end
98
+
99
+ # if the content implements a to_xml method which returns a non-nil value, insert as raw xml
100
+ if content.respond_to?(:to_xml) && inline_content = content.to_xml(:skip_instruct => true, :skip_types => true)
101
+ xml << inline_content
102
+
103
+ # otherwise create the tag normally
104
+ else
105
+ xml.tag! tag, content
106
+ end
107
+ end
108
+ end
109
+
110
+ # builder will screw up the indentation of embedded xml objects, so use nokogiri to format it nicely
111
+ output = xml.target!.gsub("\n","").gsub(" ","")
112
+ Nokogiri.parse(output).to_s
113
+
114
+ end
115
+ end
116
+ end
data/lib/model_xml.rb ADDED
@@ -0,0 +1,45 @@
1
+ $:.unshift File.dirname __FILE__
2
+ require 'model_xml/generator'
3
+
4
+ module ModelXML
5
+
6
+ def self.included base
7
+
8
+ base.instance_eval do
9
+
10
+ def model_xml *args, &block
11
+
12
+ @model_xml_generator ||= ModelXML::Generator.new
13
+ if block_given?
14
+ @model_xml_generator.add_field_set(*args, &block)
15
+ else
16
+ @model_xml_generator.add_field_set *args
17
+ end
18
+
19
+ end
20
+
21
+ # this is probably only ever required for testing
22
+ def model_xml_reset!
23
+ @model_xml_generator = ModelXML::Generator.new
24
+ end
25
+
26
+ def model_xml_generator
27
+ @model_xml_generator
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ def to_xml options={}
34
+
35
+ # if no generator is defined, pass straight through to the parent to_xml method, which may or may not exist
36
+ if generator = self.class.model_xml_generator
37
+ generator.generate_xml! self, options
38
+ else
39
+ super options
40
+ end
41
+ end
42
+
43
+ end
44
+
45
+ ActiveRecord::Base.send :include, ModelXML if defined?(ActiveRecord::Base)
@@ -0,0 +1,16 @@
1
+ require 'test/unit'
2
+ $:.unshift File.dirname(__FILE__) + "/../lib/"
3
+ require 'model_xml/block_parser'
4
+
5
+ class BlockParserTest < Test::Unit::TestCase
6
+
7
+ def test_simple_parse
8
+ block = Proc.new do
9
+ foo
10
+ bar 3, 4
11
+ end
12
+ assert_equal [:foo, [:bar, 3, 4]], ModelXML::BlockParser.parse(&block)
13
+ end
14
+
15
+ end
16
+
@@ -0,0 +1,87 @@
1
+ require 'test/unit'
2
+ require 'ostruct'
3
+ $:.unshift File.dirname(__FILE__) + "/../lib/"
4
+ require 'model_xml'
5
+
6
+ class TestStruct < OpenStruct
7
+ include ModelXML
8
+ end
9
+
10
+ class ModelXMLTest < Test::Unit::TestCase
11
+
12
+ def setup
13
+ @t = TestStruct.new :foo => 1, :bar => 2
14
+ end
15
+
16
+ def test_class_simple
17
+ TestStruct.instance_eval do
18
+ model_xml_reset!
19
+ model_xml :foo
20
+ end
21
+
22
+ assert_equal [[:foo]], TestStruct.model_xml_generator.field_sets
23
+
24
+ res = '<?xml version="1.0" encoding="UTF-8"?>
25
+ <teststruct>
26
+ <foo>1</foo>
27
+ </teststruct>
28
+ '
29
+ assert_equal res, @t.to_xml
30
+ end
31
+
32
+ def test_block_notation
33
+ TestStruct.instance_eval do
34
+ model_xml_reset!
35
+ model_xml do
36
+ foo
37
+ bar
38
+ end
39
+ end
40
+
41
+ assert_equal [[:foo, :bar]], TestStruct.model_xml_generator.field_sets
42
+
43
+ res = '<?xml version="1.0" encoding="UTF-8"?>
44
+ <teststruct>
45
+ <foo>1</foo>
46
+ <bar>2</bar>
47
+ </teststruct>
48
+ '
49
+ assert_equal res, @t.to_xml
50
+ end
51
+
52
+ def test_inline_procs
53
+ TestStruct.instance_eval do
54
+ model_xml_reset!
55
+ model_xml do
56
+ foo
57
+ bar
58
+ foobar Proc.new {|obj| obj.foo + obj.bar}
59
+ end
60
+ end
61
+
62
+ res = '<?xml version="1.0" encoding="UTF-8"?>
63
+ <teststruct>
64
+ <foo>1</foo>
65
+ <bar>2</bar>
66
+ <foobar>3</foobar>
67
+ </teststruct>
68
+ '
69
+ assert_equal res, @t.to_xml
70
+ end
71
+
72
+ def test_skip_instruct
73
+ TestStruct.instance_eval do
74
+ model_xml_reset!
75
+ model_xml :foo, :bar
76
+ end
77
+
78
+ res = '<?xml version="1.0"?>
79
+ <teststruct>
80
+ <foo>1</foo>
81
+ <bar>2</bar>
82
+ </teststruct>
83
+ '
84
+ assert_equal res, @t.to_xml(:skip_instruct => true)
85
+
86
+ end
87
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: model_xml
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Rob Anderson
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-04-10 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: builder
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 15
29
+ segments:
30
+ - 2
31
+ - 1
32
+ - 2
33
+ version: 2.1.2
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: nokogiri
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 1
47
+ - 4
48
+ - 2
49
+ version: 1.4.2
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ description: Simple replacement for ActiveRecord's default to_xml
53
+ email: rob.anderson@paymentcardsolutions.co.uk
54
+ executables: []
55
+
56
+ extensions: []
57
+
58
+ extra_rdoc_files: []
59
+
60
+ files:
61
+ - .gitignore
62
+ - README.md
63
+ - lib/model_xml.rb
64
+ - lib/model_xml/block_parser.rb
65
+ - lib/model_xml/generator.rb
66
+ - test/test_block_parser.rb
67
+ - test/test_model_xml.rb
68
+ homepage:
69
+ licenses: []
70
+
71
+ post_install_message:
72
+ rdoc_options: []
73
+
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ hash: 3
82
+ segments:
83
+ - 0
84
+ version: "0"
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ hash: 3
91
+ segments:
92
+ - 0
93
+ version: "0"
94
+ requirements: []
95
+
96
+ rubyforge_project:
97
+ rubygems_version: 1.8.16
98
+ signing_key:
99
+ specification_version: 3
100
+ summary: Ruby object to xml converter
101
+ test_files: []
102
+