model_xml 1.0.0

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