fossil 0.1.0

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