fossil 0.1.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.
Files changed (104) hide show
  1. data/Rakefile +19 -0
  2. data/VERSION +1 -0
  3. data/fossil.gemspec +136 -0
  4. data/lib/models/ac_qualification.rb +91 -0
  5. data/lib/models/aircraft.rb +397 -0
  6. data/lib/models/aircraft_cost.rb +65 -0
  7. data/lib/models/aircraft_document.rb +41 -0
  8. data/lib/models/aircraft_log.rb +179 -0
  9. data/lib/models/aircraft_maint.rb +93 -0
  10. data/lib/models/aircraft_note.rb +115 -0
  11. data/lib/models/aircraft_rate.rb +124 -0
  12. data/lib/models/aircraft_time.rb +87 -0
  13. data/lib/models/aircraft_type.rb +159 -0
  14. data/lib/models/airport.rb +256 -0
  15. data/lib/models/airport_cost.rb +46 -0
  16. data/lib/models/airport_fbo.rb +98 -0
  17. data/lib/models/airport_fuel.rb +78 -0
  18. data/lib/models/airport_service.rb +90 -0
  19. data/lib/models/ap_operational_msg.rb +33 -0
  20. data/lib/models/audit_trail.rb +26 -0
  21. data/lib/models/btrieve_code_name.rb +104 -0
  22. data/lib/models/cargo.rb +39 -0
  23. data/lib/models/check_group.rb +17 -0
  24. data/lib/models/checklist.rb +98 -0
  25. data/lib/models/city_pair.rb +43 -0
  26. data/lib/models/code.rb +57 -0
  27. data/lib/models/comment.rb +30 -0
  28. data/lib/models/comments2.rb +20 -0
  29. data/lib/models/company_information.rb +565 -0
  30. data/lib/models/contact.rb +42 -0
  31. data/lib/models/contract.rb +53 -0
  32. data/lib/models/contract_item.rb +58 -0
  33. data/lib/models/cost_center.rb +16 -0
  34. data/lib/models/crew_activity.rb +42 -0
  35. data/lib/models/crew_currency_by_cct.rb +255 -0
  36. data/lib/models/crew_currency_gen.rb +84 -0
  37. data/lib/models/crew_duty.rb +128 -0
  38. data/lib/models/crew_history_item.rb +132 -0
  39. data/lib/models/crew_information.rb +137 -0
  40. data/lib/models/crew_leg.rb +127 -0
  41. data/lib/models/crew_training_group.rb +33 -0
  42. data/lib/models/crew_training_item.rb +146 -0
  43. data/lib/models/crew_training_log.rb +39 -0
  44. data/lib/models/crew_trip.rb +60 -0
  45. data/lib/models/currency_rate.rb +27 -0
  46. data/lib/models/exp_record.rb +75 -0
  47. data/lib/models/field_list.rb +25 -0
  48. data/lib/models/flight_log_expense.rb +90 -0
  49. data/lib/models/flt_cas.rb +52 -0
  50. data/lib/models/flt_crew.rb +80 -0
  51. data/lib/models/flt_data.rb +132 -0
  52. data/lib/models/flt_exp.rb +57 -0
  53. data/lib/models/flt_leg.rb +125 -0
  54. data/lib/models/group_cost.rb +22 -0
  55. data/lib/models/icaocode.rb +23 -0
  56. data/lib/models/language.rb +50 -0
  57. data/lib/models/leg_request.rb +39 -0
  58. data/lib/models/leg_time.rb +100 -0
  59. data/lib/models/logbook.rb +95 -0
  60. data/lib/models/maint_time.rb +44 -0
  61. data/lib/models/msg_itin.rb +42 -0
  62. data/lib/models/msg_trip.rb +56 -0
  63. data/lib/models/no_fly_list.rb +37 -0
  64. data/lib/models/note.rb +27 -0
  65. data/lib/models/one_cost.rb +98 -0
  66. data/lib/models/passenger.rb +336 -0
  67. data/lib/models/passenger_rate.rb +51 -0
  68. data/lib/models/passport.rb +35 -0
  69. data/lib/models/patient.rb +144 -0
  70. data/lib/models/pax_ap_serv.rb +30 -0
  71. data/lib/models/pax_note.rb +109 -0
  72. data/lib/models/personnel.rb +192 -0
  73. data/lib/models/place.rb +54 -0
  74. data/lib/models/planner.rb +57 -0
  75. data/lib/models/quote.rb +465 -0
  76. data/lib/models/quote_leg.rb +401 -0
  77. data/lib/models/report_define.rb +34 -0
  78. data/lib/models/report_filter.rb +56 -0
  79. data/lib/models/report_user.rb +54 -0
  80. data/lib/models/requirements_limit.rb +405 -0
  81. data/lib/models/sifl_table.rb +43 -0
  82. data/lib/models/sms.rb +106 -0
  83. data/lib/models/training_course.rb +34 -0
  84. data/lib/models/training_group.rb +21 -0
  85. data/lib/models/training_item.rb +102 -0
  86. data/lib/models/travel_request.rb +51 -0
  87. data/lib/models/trip.rb +342 -0
  88. data/lib/models/trip_leg.rb +1149 -0
  89. data/lib/models/trip_passenger.rb +188 -0
  90. data/lib/models/update.rb +38 -0
  91. data/lib/models/user_information.rb +248 -0
  92. data/lib/models/user_log.rb +23 -0
  93. data/lib/models/vendor.rb +93 -0
  94. data/lib/models/vendor_document.rb +97 -0
  95. data/lib/models/visa.rb +36 -0
  96. data/lib/sequel/code_group.rb +89 -0
  97. data/lib/sequel/fos_dates.rb +54 -0
  98. data/lib/sequel/metaprogramming.rb +8 -0
  99. data/lib/sequel/model_patch.rb +277 -0
  100. data/lib/sequel/pervasive_adapter.rb +214 -0
  101. data/lib/sequel/serializer/json_serializer.rb +129 -0
  102. data/lib/sequel/serializer/serializer.rb +94 -0
  103. data/lib/sequel/serializer/xml_serializer.rb +393 -0
  104. metadata +157 -0
@@ -0,0 +1,214 @@
1
+ module Sequel
2
+ module Pervasive
3
+ module DatabaseMethods
4
+ # Pervasive SQL Server uses the :psql type.
5
+ def database_type
6
+ :psql
7
+ end
8
+
9
+ def dataset(opts = nil)
10
+ ds = super
11
+ ds.extend(DatasetMethods)
12
+ ds
13
+ end
14
+
15
+ def connect(server)
16
+ conn = super(server)
17
+ conn.autocommit = false if RAILS_ENV == 'test'
18
+ conn
19
+ end
20
+
21
+ def select_fields(table, *fields)
22
+ dataset.select_fields(table, *fields)
23
+ end
24
+
25
+ # overriding execute to be able to thow a DatabaseDisconnectError when the ODBC::Error is code 08S01.
26
+ def execute(sql, opts={})
27
+ log_info(sql)
28
+ synchronize(opts[:server]) do |conn|
29
+ begin
30
+ r = conn.run(sql)
31
+ yield(r) if block_given?
32
+ rescue Sequel::DatabaseConnectionError => se
33
+ p "got a Sequel::DatabaseConnectionError error: #{se.message}"
34
+ raise Sequel.convert_exception_class(se, Sequel::DatabaseDisconnectError)
35
+ rescue Sequel::DatabaseError => se
36
+ p "got a Sequel::DatabaseError error: #{se.message}"
37
+ se = Sequel.convert_exception_class(se, Sequel::DatabaseDisconnectError) if se.message.match(/ODBC::Error: 08S01/)
38
+ raise_error(se)
39
+ rescue ::ODBC::Error => e
40
+ raise_error(e)
41
+ ensure
42
+ r.drop if r
43
+ end
44
+ nil
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ # Log the given SQL and then execute it on the connection, used by the transaction code.
51
+ def log_connection_execute(conn, sql)
52
+ return if sql.blank?
53
+ log_info(sql)
54
+ conn.send(connection_execute_method, sql)
55
+ end
56
+
57
+ def auto_increment_sql
58
+ AUTO_INCREMENT
59
+ end
60
+
61
+ end
62
+
63
+ module DatasetMethods
64
+
65
+ def fetch_rows(sql, &block)
66
+ execute(sql) do |s|
67
+ i = -1
68
+ cols = s.columns(true).map{|c| [output_identifier(c.name), i+=1]}
69
+
70
+ @columns = cols.map{|c| c.at(0)}
71
+
72
+ if rows = s.fetch_all
73
+ rows.each do |row|
74
+ hash = {}
75
+ cols.each{|n, i| hash[n] = convert_odbc_value(row[i], get_column_type(@columns[i]))}
76
+ yield hash
77
+ end
78
+ end
79
+ end
80
+ self
81
+ end
82
+
83
+ def convert_odbc_value(v, column_type=nil)
84
+ case column_type
85
+ when :date then
86
+ Date.from_fos_days(v.to_i)
87
+ when :time then
88
+ Date.from_fos_days(0).to_time.utc + v.to_i.minutes
89
+ else
90
+ v = v.unpack('A*')[0] if v.is_a? String
91
+ super(v)
92
+ end
93
+ end
94
+
95
+ def get_column_type(column_name)
96
+ if model and model.respond_to?(:datatypes) and model.datatypes and model.datatypes[column_name]
97
+ return model.datatypes[column_name][:type]
98
+ end
99
+ nil
100
+ end
101
+
102
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'limit distinct columns from with join where group order having compounds')
103
+
104
+ def quoted_identifier(name, convert=true)
105
+ convert ? "\"#{convert_aliased_col_to_real_col(name)}\"" : "\"#{name}\""
106
+ end
107
+
108
+ # Take a table name and write out the field names for that table in this style:
109
+ # "`DUDES`.`KID - DATE`".lit where the table name is :dudes,
110
+ # and fields are [:kid_date]
111
+ def select_fields(hash)
112
+ new_hash = Hash.new{|k, v| k[v]= {}}
113
+
114
+ hash.each do |association, fields|
115
+ new_hash[association][:fields] = fields
116
+
117
+ if association == :self
118
+ new_hash[association][:model] = self.model
119
+ new_hash[association][:association_name] = self.model.table_name
120
+ elsif association.to_s.match /(\w+)__(\w+)/ # crew_legs__position_code
121
+ # not an assocation on the current model .. but another one
122
+ new_hash[association][:association_name] = $2.to_s.upcase
123
+ new_hash[association][:model] = model.association_reflection($1.to_sym)[:class].association_reflection($2.to_sym)[:class]
124
+ else
125
+ raise(Sequel::Error, "Invalid #{model} association: #{association}") unless model.association_reflection(association)
126
+ new_hash[association][:association_name] = association.to_s.upcase
127
+ new_hash[association][:model] = model.association_reflection(association)[:class]
128
+ end
129
+ fields = fields + new_hash[association][:model].primary_key unless fields[0] == :*
130
+ new_hash[association][:fields] = fields
131
+ end
132
+
133
+ s = []
134
+ new_hash.each do |association, hash|
135
+ if hash[:fields].size == 1 and hash[:fields][0] == :*
136
+ s << "#{quoted_identifier(hash[:association_name], false)}.*".lit
137
+ else
138
+ s << hash[:fields].collect do |field|
139
+ raw_field_name = convert_aliased_col_to_real_col_with_model(field, hash[:model])
140
+ as_alias = ''
141
+ raw_field_name
142
+
143
+ if association != :self and ( hash[:model].primary_key.include? field or field_duplicate(new_hash, association, field) )
144
+ as_field_name = "#{hash[:association_name]}_#{raw_field_name}"
145
+ as_alias = "AS #{quoted_identifier(as_field_name, false)}"
146
+ end
147
+ "#{quoted_identifier(hash[:association_name], false)}.#{quoted_identifier(raw_field_name, false)} #{as_alias}".lit
148
+ end
149
+ end
150
+ end
151
+
152
+ clone(:select => s.flatten)
153
+ end
154
+
155
+ private
156
+
157
+ # is the field name found in the fields of any other association besides the one you passed in
158
+ def field_duplicate(hash, association, field)
159
+ hash.each do |association_key, hash_values|
160
+ next if association_key == association
161
+ return true if hash_values[:fields].include? field
162
+ end
163
+ return false
164
+ end
165
+
166
+ def convert_aliased_col_to_real_col(col_name)
167
+ if respond_to?(:model) and model.respond_to?(:aliases) and model.aliases
168
+ sym_name = col_name.to_s.downcase.to_sym
169
+ col_name = model.aliases[sym_name].to_s.upcase if model.aliases.include? sym_name
170
+ end
171
+ col_name
172
+ end
173
+
174
+ def convert_aliased_col_to_real_col_with_model(col_name, model)
175
+ if model.respond_to?(:aliases) and model.aliases
176
+ sym_name = col_name.to_s.downcase.to_sym
177
+ col_name = model.aliases[sym_name].to_s.upcase if model.aliases.include? sym_name
178
+ end
179
+ col_name
180
+ end
181
+
182
+ def literal_string(v)
183
+ "#{super}"
184
+ end
185
+
186
+ def literal_date(v)
187
+ v.is_a?(Date) ? v.to_fos_days : super(v)
188
+ end
189
+
190
+ def select_clause_methods
191
+ SELECT_CLAUSE_METHODS
192
+ end
193
+
194
+ # Modify the sql to add the DISTINCT modifier
195
+ def select_distinct_sql(sql)
196
+ if distinct = @opts[:distinct]
197
+ sql << " DISTINCT#{" (#{expression_list(distinct)})" unless distinct.empty?}"
198
+ end
199
+ end
200
+
201
+ # PSQL uses TOP for limit, with no offset support
202
+ def select_limit_sql(sql)
203
+ raise(Error, "OFFSET not supported") if @opts[:offset]
204
+ sql << " TOP #{@opts[:limit]}" if @opts[:limit]
205
+ end
206
+
207
+ # PSQL uses the WITH statement to lock tables
208
+ def select_with_sql(sql)
209
+ sql << " WITH #{@opts[:with]}" if @opts[:with]
210
+ end
211
+ end
212
+ end
213
+ end
214
+
@@ -0,0 +1,129 @@
1
+ module Sequel
2
+ module Serialization
3
+ def self.included(base)
4
+ base.cattr_accessor :include_root_in_json, :instance_writer => false
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ # Returns a JSON string representing the model. Some configuration is
9
+ # available through +options+.
10
+ #
11
+ # The option <tt>ActiveRecord::Base.include_root_in_json</tt> controls the
12
+ # top-level behavior of to_json. In a new Rails application, it is set to
13
+ # <tt>true</tt> in initializers/new_rails_defaults.rb. When it is <tt>true</tt>,
14
+ # to_json will emit a single root node named after the object's type. For example:
15
+ #
16
+ # konata = User.find(1)
17
+ # ActiveRecord::Base.include_root_in_json = true
18
+ # konata.to_json
19
+ # # => { "user": {"id": 1, "name": "Konata Izumi", "age": 16,
20
+ # "created_at": "2006/08/01", "awesome": true} }
21
+ #
22
+ # ActiveRecord::Base.include_root_in_json = false
23
+ # konata.to_json
24
+ # # => {"id": 1, "name": "Konata Izumi", "age": 16,
25
+ # "created_at": "2006/08/01", "awesome": true}
26
+ #
27
+ # The remainder of the examples in this section assume include_root_in_json is set to
28
+ # <tt>false</tt>.
29
+ #
30
+ # Without any +options+, the returned JSON string will include all
31
+ # the model's attributes. For example:
32
+ #
33
+ # konata = User.find(1)
34
+ # konata.to_json
35
+ # # => {"id": 1, "name": "Konata Izumi", "age": 16,
36
+ # "created_at": "2006/08/01", "awesome": true}
37
+ #
38
+ # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes
39
+ # included, and work similar to the +attributes+ method. For example:
40
+ #
41
+ # konata.to_json(:only => [ :id, :name ])
42
+ # # => {"id": 1, "name": "Konata Izumi"}
43
+ #
44
+ # konata.to_json(:except => [ :id, :created_at, :age ])
45
+ # # => {"name": "Konata Izumi", "awesome": true}
46
+ #
47
+ # To include any methods on the model, use <tt>:methods</tt>.
48
+ #
49
+ # konata.to_json(:methods => :permalink)
50
+ # # => {"id": 1, "name": "Konata Izumi", "age": 16,
51
+ # "created_at": "2006/08/01", "awesome": true,
52
+ # "permalink": "1-konata-izumi"}
53
+ #
54
+ # To include associations, use <tt>:include</tt>.
55
+ #
56
+ # konata.to_json(:include => :posts)
57
+ # # => {"id": 1, "name": "Konata Izumi", "age": 16,
58
+ # "created_at": "2006/08/01", "awesome": true,
59
+ # "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"},
60
+ # {"id": 2, author_id: 1, "title": "So I was thinking"}]}
61
+ #
62
+ # 2nd level and higher order associations work as well:
63
+ #
64
+ # konata.to_json(:include => { :posts => {
65
+ # :include => { :comments => {
66
+ # :only => :body } },
67
+ # :only => :title } })
68
+ # # => {"id": 1, "name": "Konata Izumi", "age": 16,
69
+ # "created_at": "2006/08/01", "awesome": true,
70
+ # "posts": [{"comments": [{"body": "1st post!"}, {"body": "Second!"}],
71
+ # "title": "Welcome to the weblog"},
72
+ # {"comments": [{"body": "Don't think too hard"}],
73
+ # "title": "So I was thinking"}]}
74
+ def to_json(options = {})
75
+ if include_root_in_json
76
+ "{#{self.class.json_class_name}: #{JsonSerializer.new(self, options).to_s}}"
77
+ else
78
+ JsonSerializer.new(self, options).to_s
79
+ end
80
+ end
81
+
82
+ # special to_fos_json .. adds the only=> [] option by default, because we are using
83
+ # mostly the methods option
84
+ def to_fos_json(options = {})
85
+ to_json({:only=>[]}.merge(options))
86
+ end
87
+
88
+ def from_json(json)
89
+ self.attributes = ActiveSupport::JSON.decode(json)
90
+ self
91
+ end
92
+
93
+ class JsonSerializer < Sequel::Serialization::Serializer #:nodoc:
94
+ def serialize
95
+ serializable_record.to_json
96
+ end
97
+ end
98
+
99
+ module ClassMethods
100
+ def json_class_name
101
+ @json_class_name ||= name.demodulize.underscore.inspect
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ module ActiveSupport #:nodoc:
108
+ module CoreExtensions #:nodoc:
109
+ module Array #:nodoc:
110
+ module Conversions
111
+ def to_fos_json(options = {})
112
+ to_json({:only=>[]}.merge(options))
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ module ActiveSupport #:nodoc:
120
+ module CoreExtensions #:nodoc:
121
+ module Hash #:nodoc:
122
+ module Conversions
123
+ def to_fos_json(options = {})
124
+ to_json({:only=>[]}.merge(options))
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,94 @@
1
+ require 'active_support/json'
2
+
3
+ module Sequel
4
+ module Serialization
5
+ class Serializer
6
+ attr_reader :options
7
+
8
+ def initialize(record, options = {})
9
+ @record, @options = record, options.dup
10
+ end
11
+
12
+ # To replicate the behavior in ActiveRecord#attributes,
13
+ # <tt>:except</tt> takes precedence over <tt>:only</tt>. If <tt>:only</tt> is not set
14
+ # for a N level model but is set for the N+1 level models,
15
+ # then because <tt>:except</tt> is set to a default value, the second
16
+ # level model can have both <tt>:except</tt> and <tt>:only</tt> set. So if
17
+ # <tt>:only</tt> is set, always delete <tt>:except</tt>.
18
+ def serializable_attribute_names
19
+ attribute_names = @record.columns
20
+ attribute_names += @record.class.aliases.keys if @record.class.aliases
21
+
22
+ if options[:only]
23
+ options.delete(:except)
24
+ attribute_names = attribute_names & Array(options[:only]).collect{ |n| n }
25
+ else
26
+ # options[:except] = Array(options[:except]) | Array(@record.class.inheritance_column)
27
+ # attribute_names = attribute_names - options[:except].collect { |n| n }
28
+ end
29
+ attribute_names
30
+ end
31
+
32
+ def serializable_method_names
33
+ Array(options[:methods]).inject([]) do |method_attributes, name|
34
+ method_attributes << name if !name.blank? and @record.respond_to?(name.to_s)
35
+ method_attributes
36
+ end
37
+ end
38
+
39
+ def serializable_names
40
+ serializable_attribute_names + serializable_method_names
41
+ end
42
+
43
+ # Add associations specified via the <tt>:includes</tt> option.
44
+ # Expects a block that takes as arguments:
45
+ # +association+ - name of the association
46
+ # +records+ - the association record(s) to be serialized
47
+ # +opts+ - options for the association records
48
+ def add_includes(&block)
49
+ if include_associations = options.delete(:include)
50
+ base_only_or_except = { :except => options[:except],
51
+ :only => options[:only] }
52
+
53
+ include_has_options = include_associations.is_a?(Hash)
54
+ associations = include_has_options ? include_associations.keys : Array(include_associations)
55
+
56
+ for association in associations
57
+ records = @record.send(association)
58
+ unless records.nil?
59
+ association_options = include_has_options ? include_associations[association] : base_only_or_except
60
+ opts = options.merge(association_options)
61
+ yield(association, records, opts)
62
+ end
63
+ end
64
+
65
+ options[:include] = include_associations
66
+ end
67
+ end
68
+
69
+ def serializable_record
70
+ returning(serializable_record = {}) do
71
+ serializable_names.each { |name| serializable_record[name] = @record.send(name) }
72
+ add_includes do |association, records, opts|
73
+ if records.is_a?(Enumerable)
74
+ serializable_record[association] = records.collect { |r| self.class.new(r, opts).serializable_record }
75
+ else
76
+ serializable_record[association] = self.class.new(records, opts).serializable_record
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ def serialize
83
+ # overwrite to implement
84
+ end
85
+
86
+ def to_s(&block)
87
+ serialize(&block)
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ require File.dirname(__FILE__) +'/xml_serializer.rb'
94
+ require File.dirname(__FILE__) +'/json_serializer.rb'
@@ -0,0 +1,393 @@
1
+ module Sequel #:nodoc:
2
+ module Serialization
3
+ # Builds an XML document to represent the model. Some configuration is
4
+ # available through +options+. However more complicated cases should
5
+ # override ActiveRecord::Base#to_xml.
6
+ #
7
+ # By default the generated XML document will include the processing
8
+ # instruction and all the object's attributes. For example:
9
+ #
10
+ # <?xml version="1.0" encoding="UTF-8"?>
11
+ # <topic>
12
+ # <title>The First Topic</title>
13
+ # <author-name>David</author-name>
14
+ # <id type="integer">1</id>
15
+ # <approved type="boolean">false</approved>
16
+ # <replies-count type="integer">0</replies-count>
17
+ # <bonus-time type="datetime">2000-01-01T08:28:00+12:00</bonus-time>
18
+ # <written-on type="datetime">2003-07-16T09:28:00+1200</written-on>
19
+ # <content>Have a nice day</content>
20
+ # <author-email-address>david@loudthinking.com</author-email-address>
21
+ # <parent-id></parent-id>
22
+ # <last-read type="date">2004-04-15</last-read>
23
+ # </topic>
24
+ #
25
+ # This behavior can be controlled with <tt>:only</tt>, <tt>:except</tt>,
26
+ # <tt>:skip_instruct</tt>, <tt>:skip_types</tt>, <tt>:dasherize</tt> and <tt>:camelize</tt> .
27
+ # The <tt>:only</tt> and <tt>:except</tt> options are the same as for the
28
+ # +attributes+ method. The default is to dasherize all column names, but you
29
+ # can disable this setting <tt>:dasherize</tt> to +false+. Setting <tt>:camelize</tt>
30
+ # to +true+ will camelize all column names - this also overrides <tt>:dasherize</tt>.
31
+ # To not have the column type included in the XML output set <tt>:skip_types</tt> to +true+.
32
+ #
33
+ # For instance:
34
+ #
35
+ # topic.to_xml(:skip_instruct => true, :except => [ :id, :bonus_time, :written_on, :replies_count ])
36
+ #
37
+ # <topic>
38
+ # <title>The First Topic</title>
39
+ # <author-name>David</author-name>
40
+ # <approved type="boolean">false</approved>
41
+ # <content>Have a nice day</content>
42
+ # <author-email-address>david@loudthinking.com</author-email-address>
43
+ # <parent-id></parent-id>
44
+ # <last-read type="date">2004-04-15</last-read>
45
+ # </topic>
46
+ #
47
+ # To include first level associations use <tt>:include</tt>:
48
+ #
49
+ # firm.to_xml :include => [ :account, :clients ]
50
+ #
51
+ # <?xml version="1.0" encoding="UTF-8"?>
52
+ # <firm>
53
+ # <id type="integer">1</id>
54
+ # <rating type="integer">1</rating>
55
+ # <name>37signals</name>
56
+ # <clients type="array">
57
+ # <client>
58
+ # <rating type="integer">1</rating>
59
+ # <name>Summit</name>
60
+ # </client>
61
+ # <client>
62
+ # <rating type="integer">1</rating>
63
+ # <name>Microsoft</name>
64
+ # </client>
65
+ # </clients>
66
+ # <account>
67
+ # <id type="integer">1</id>
68
+ # <credit-limit type="integer">50</credit-limit>
69
+ # </account>
70
+ # </firm>
71
+ #
72
+ # To include deeper levels of associations pass a hash like this:
73
+ #
74
+ # firm.to_xml :include => {:account => {}, :clients => {:include => :address}}
75
+ # <?xml version="1.0" encoding="UTF-8"?>
76
+ # <firm>
77
+ # <id type="integer">1</id>
78
+ # <rating type="integer">1</rating>
79
+ # <name>37signals</name>
80
+ # <clients type="array">
81
+ # <client>
82
+ # <rating type="integer">1</rating>
83
+ # <name>Summit</name>
84
+ # <address>
85
+ # ...
86
+ # </address>
87
+ # </client>
88
+ # <client>
89
+ # <rating type="integer">1</rating>
90
+ # <name>Microsoft</name>
91
+ # <address>
92
+ # ...
93
+ # </address>
94
+ # </client>
95
+ # </clients>
96
+ # <account>
97
+ # <id type="integer">1</id>
98
+ # <credit-limit type="integer">50</credit-limit>
99
+ # </account>
100
+ # </firm>
101
+ #
102
+ # To include any methods on the model being called use <tt>:methods</tt>:
103
+ #
104
+ # firm.to_xml :methods => [ :calculated_earnings, :real_earnings ]
105
+ #
106
+ # <firm>
107
+ # # ... normal attributes as shown above ...
108
+ # <calculated-earnings>100000000000000000</calculated-earnings>
109
+ # <real-earnings>5</real-earnings>
110
+ # </firm>
111
+ #
112
+ # To call any additional Procs use <tt>:procs</tt>. The Procs are passed a
113
+ # modified version of the options hash that was given to +to_xml+:
114
+ #
115
+ # proc = Proc.new { |options| options[:builder].tag!('abc', 'def') }
116
+ # firm.to_xml :procs => [ proc ]
117
+ #
118
+ # <firm>
119
+ # # ... normal attributes as shown above ...
120
+ # <abc>def</abc>
121
+ # </firm>
122
+ #
123
+ # Alternatively, you can yield the builder object as part of the +to_xml+ call:
124
+ #
125
+ # firm.to_xml do |xml|
126
+ # xml.creator do
127
+ # xml.first_name "David"
128
+ # xml.last_name "Heinemeier Hansson"
129
+ # end
130
+ # end
131
+ #
132
+ # <firm>
133
+ # # ... normal attributes as shown above ...
134
+ # <creator>
135
+ # <first_name>David</first_name>
136
+ # <last_name>Heinemeier Hansson</last_name>
137
+ # </creator>
138
+ # </firm>
139
+ #
140
+ # As noted above, you may override +to_xml+ in your ActiveRecord::Base
141
+ # subclasses to have complete control about what's generated. The general
142
+ # form of doing this is:
143
+ #
144
+ # class IHaveMyOwnXML < ActiveRecord::Base
145
+ # def to_xml(options = {})
146
+ # options[:indent] ||= 2
147
+ # xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
148
+ # xml.instruct! unless options[:skip_instruct]
149
+ # xml.level_one do
150
+ # xml.tag!(:second_level, 'content')
151
+ # end
152
+ # end
153
+ # end
154
+ def to_fos_xml(options ={}, &block)
155
+ options = {:skip_instruct=>true, :dasherize=>false, :only=>[]}.merge(options)
156
+ to_xml(options, &block)
157
+ end
158
+
159
+ def to_xml(options = {}, &block)
160
+ serializer = XmlSerializer.new(self, options)
161
+ block_given? ? serializer.to_s(&block) : serializer.to_s
162
+ end
163
+
164
+ def from_xml(xml)
165
+ self.attributes = Hash.from_xml(xml).values.first
166
+ self
167
+ end
168
+ end
169
+
170
+ class XmlSerializer < Sequel::Serialization::Serializer #:nodoc:
171
+
172
+ FOS_XML_OPTIONS = {:skip_instruct=>true, :dasherize=>false}
173
+
174
+ def builder
175
+ @builder ||= begin
176
+ options[:indent] ||= 2
177
+ builder = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
178
+
179
+ unless options[:skip_instruct]
180
+ builder.instruct!
181
+ options[:skip_instruct] = true
182
+ end
183
+
184
+ builder
185
+ end
186
+ end
187
+
188
+ def root
189
+ root = (options[:root] || @record.class.to_s.underscore).to_s
190
+ reformat_name(root)
191
+ end
192
+
193
+ def dasherize?
194
+ !options.has_key?(:dasherize) || options[:dasherize]
195
+ end
196
+
197
+ def camelize?
198
+ options.has_key?(:camelize) && options[:camelize]
199
+ end
200
+
201
+ def reformat_name(name)
202
+ name = name.camelize if camelize?
203
+ name = dasherize? ? name.to_s.dasherize : name.to_s
204
+ end
205
+
206
+ def serializable_attributes
207
+ serializable_attribute_names.collect { |name| Attribute.new(name, @record) }
208
+ end
209
+
210
+ def serializable_method_attributes
211
+ Array(options[:methods]).inject([]) do |method_attributes, name|
212
+ method_attributes << MethodAttribute.new(name.to_s, @record) if @record.respond_to?(name.to_s)
213
+ method_attributes
214
+ end
215
+ end
216
+
217
+ def add_attributes
218
+ (serializable_attributes + serializable_method_attributes).each do |attribute|
219
+ add_tag(attribute)
220
+ end
221
+ end
222
+
223
+ def add_procs
224
+ if procs = options.delete(:procs)
225
+ [ *procs ].each do |proc|
226
+ proc.call(options)
227
+ end
228
+ end
229
+ end
230
+
231
+ def add_tag(attribute)
232
+ builder.tag!(
233
+ reformat_name(attribute.name),
234
+ attribute.value.to_s,
235
+ attribute.decorations(!options[:skip_types], !options[:skip_nils])
236
+ )
237
+ end
238
+
239
+ def add_associations(association, records, opts)
240
+ if records.is_a?(Enumerable)
241
+ tag = reformat_name(association.to_s)
242
+ type = options[:skip_types] ? {} : {:type => "array"}
243
+
244
+ if records.empty?
245
+ builder.tag!(tag, type)
246
+ else
247
+ builder.tag!(tag, type) do
248
+ association_name = association.to_s.singularize
249
+ records.each do |record|
250
+ if options[:skip_types]
251
+ record_type = {}
252
+ else
253
+ record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name
254
+ record_type = {:type => record_class}
255
+ end
256
+
257
+ record.to_xml opts.merge(:root => association_name).merge(record_type)
258
+ end
259
+ end
260
+ end
261
+ else
262
+ if record = @record.send(association)
263
+ record.to_xml(opts.merge(:root => association))
264
+ end
265
+ end
266
+ end
267
+
268
+ def serialize
269
+ args = [root]
270
+
271
+ if options[:namespace]
272
+ args << {:xmlns=>options[:namespace]}
273
+ end
274
+
275
+ if options[:type]
276
+ args << {:type=>options[:type]}
277
+ end
278
+
279
+ @tag_names = []
280
+ builder.tag!(*args) do
281
+ add_attributes
282
+ procs = options.delete(:procs)
283
+ add_includes { |association, records, opts| @tag_names << association; add_associations(association, records, opts) }
284
+ options[:procs] = procs
285
+ add_procs
286
+ yield builder if block_given?
287
+ # strip_associations if options[:flatten]
288
+ end
289
+ end
290
+
291
+ # def strip_associations
292
+ # p @tag_names
293
+ # end
294
+
295
+ class Attribute #:nodoc:
296
+ attr_reader :name, :value, :type
297
+
298
+ def initialize(name, record)
299
+ @name, @record = name, record
300
+
301
+ @type = compute_type
302
+ @value = compute_value
303
+ end
304
+
305
+ # There is a significant speed improvement if the value
306
+ # does not need to be escaped, as <tt>tag!</tt> escapes all values
307
+ # to ensure that valid XML is generated. For known binary
308
+ # values, it is at least an order of magnitude faster to
309
+ # Base64 encode binary values and directly put them in the
310
+ # output XML than to pass the original value or the Base64
311
+ # encoded value to the <tt>tag!</tt> method. It definitely makes
312
+ # no sense to Base64 encode the value and then give it to
313
+ # <tt>tag!</tt>, since that just adds additional overhead.
314
+ def needs_encoding?
315
+ ![ :binary, :date, :datetime, :boolean, :float, :integer ].include?(type)
316
+ end
317
+
318
+ # I added include_nils option, which rails will in rails 3 anyway
319
+ def decorations(include_types = true, include_nils = true)
320
+ decorations = {}
321
+
322
+ if type == :binary
323
+ decorations[:encoding] = 'base64'
324
+ end
325
+
326
+ if include_types && type != :string
327
+ decorations[:type] = type
328
+ end
329
+
330
+ if include_nils && value.nil?
331
+ decorations[:nil] = true
332
+ end
333
+
334
+ decorations
335
+ end
336
+
337
+ protected
338
+ def compute_type
339
+ type = @record.class.datatypes[name][:type] if @record.class.datatypes and @record.class.datatypes[name]
340
+ # type = @record.class.serialized_attributes.has_key?(name) ? :yaml : @record.class.db_schema[name][:type]
341
+ case type
342
+ when :text, nil
343
+ :string
344
+ when :time
345
+ :datetime
346
+ else
347
+ type
348
+ end
349
+ end
350
+
351
+ def compute_value
352
+ value = @record.send(name)
353
+
354
+ if formatter = Hash::XML_FORMATTING[type.to_s]
355
+ value ? formatter.call(value) : nil
356
+ else
357
+ value
358
+ end
359
+ end
360
+ end
361
+
362
+ class MethodAttribute < Attribute #:nodoc:
363
+ protected
364
+ def compute_type
365
+ Hash::XML_TYPE_NAMES[@record.send(name).class.name] || :string
366
+ end
367
+ end
368
+ end
369
+ end
370
+
371
+ module ActiveSupport #:nodoc:
372
+ module CoreExtensions #:nodoc:
373
+ module Array #:nodoc:
374
+ module Conversions
375
+ def to_fos_xml(options = {})
376
+ to_xml({:skip_instruct=>true, :dasherize=>false, :only=>[]}.merge(options))
377
+ end
378
+ end
379
+ end
380
+ end
381
+ end
382
+
383
+ module ActiveSupport #:nodoc:
384
+ module CoreExtensions #:nodoc:
385
+ module Hash #:nodoc:
386
+ module Conversions
387
+ def to_fos_xml(options = {})
388
+ to_xml({:dasherize=>false, :only=>[]}.merge(options))
389
+ end
390
+ end
391
+ end
392
+ end
393
+ end