fossil 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +19 -0
- data/VERSION +1 -0
- data/fossil.gemspec +136 -0
- data/lib/models/ac_qualification.rb +91 -0
- data/lib/models/aircraft.rb +397 -0
- data/lib/models/aircraft_cost.rb +65 -0
- data/lib/models/aircraft_document.rb +41 -0
- data/lib/models/aircraft_log.rb +179 -0
- data/lib/models/aircraft_maint.rb +93 -0
- data/lib/models/aircraft_note.rb +115 -0
- data/lib/models/aircraft_rate.rb +124 -0
- data/lib/models/aircraft_time.rb +87 -0
- data/lib/models/aircraft_type.rb +159 -0
- data/lib/models/airport.rb +256 -0
- data/lib/models/airport_cost.rb +46 -0
- data/lib/models/airport_fbo.rb +98 -0
- data/lib/models/airport_fuel.rb +78 -0
- data/lib/models/airport_service.rb +90 -0
- data/lib/models/ap_operational_msg.rb +33 -0
- data/lib/models/audit_trail.rb +26 -0
- data/lib/models/btrieve_code_name.rb +104 -0
- data/lib/models/cargo.rb +39 -0
- data/lib/models/check_group.rb +17 -0
- data/lib/models/checklist.rb +98 -0
- data/lib/models/city_pair.rb +43 -0
- data/lib/models/code.rb +57 -0
- data/lib/models/comment.rb +30 -0
- data/lib/models/comments2.rb +20 -0
- data/lib/models/company_information.rb +565 -0
- data/lib/models/contact.rb +42 -0
- data/lib/models/contract.rb +53 -0
- data/lib/models/contract_item.rb +58 -0
- data/lib/models/cost_center.rb +16 -0
- data/lib/models/crew_activity.rb +42 -0
- data/lib/models/crew_currency_by_cct.rb +255 -0
- data/lib/models/crew_currency_gen.rb +84 -0
- data/lib/models/crew_duty.rb +128 -0
- data/lib/models/crew_history_item.rb +132 -0
- data/lib/models/crew_information.rb +137 -0
- data/lib/models/crew_leg.rb +127 -0
- data/lib/models/crew_training_group.rb +33 -0
- data/lib/models/crew_training_item.rb +146 -0
- data/lib/models/crew_training_log.rb +39 -0
- data/lib/models/crew_trip.rb +60 -0
- data/lib/models/currency_rate.rb +27 -0
- data/lib/models/exp_record.rb +75 -0
- data/lib/models/field_list.rb +25 -0
- data/lib/models/flight_log_expense.rb +90 -0
- data/lib/models/flt_cas.rb +52 -0
- data/lib/models/flt_crew.rb +80 -0
- data/lib/models/flt_data.rb +132 -0
- data/lib/models/flt_exp.rb +57 -0
- data/lib/models/flt_leg.rb +125 -0
- data/lib/models/group_cost.rb +22 -0
- data/lib/models/icaocode.rb +23 -0
- data/lib/models/language.rb +50 -0
- data/lib/models/leg_request.rb +39 -0
- data/lib/models/leg_time.rb +100 -0
- data/lib/models/logbook.rb +95 -0
- data/lib/models/maint_time.rb +44 -0
- data/lib/models/msg_itin.rb +42 -0
- data/lib/models/msg_trip.rb +56 -0
- data/lib/models/no_fly_list.rb +37 -0
- data/lib/models/note.rb +27 -0
- data/lib/models/one_cost.rb +98 -0
- data/lib/models/passenger.rb +336 -0
- data/lib/models/passenger_rate.rb +51 -0
- data/lib/models/passport.rb +35 -0
- data/lib/models/patient.rb +144 -0
- data/lib/models/pax_ap_serv.rb +30 -0
- data/lib/models/pax_note.rb +109 -0
- data/lib/models/personnel.rb +192 -0
- data/lib/models/place.rb +54 -0
- data/lib/models/planner.rb +57 -0
- data/lib/models/quote.rb +465 -0
- data/lib/models/quote_leg.rb +401 -0
- data/lib/models/report_define.rb +34 -0
- data/lib/models/report_filter.rb +56 -0
- data/lib/models/report_user.rb +54 -0
- data/lib/models/requirements_limit.rb +405 -0
- data/lib/models/sifl_table.rb +43 -0
- data/lib/models/sms.rb +106 -0
- data/lib/models/training_course.rb +34 -0
- data/lib/models/training_group.rb +21 -0
- data/lib/models/training_item.rb +102 -0
- data/lib/models/travel_request.rb +51 -0
- data/lib/models/trip.rb +342 -0
- data/lib/models/trip_leg.rb +1149 -0
- data/lib/models/trip_passenger.rb +188 -0
- data/lib/models/update.rb +38 -0
- data/lib/models/user_information.rb +248 -0
- data/lib/models/user_log.rb +23 -0
- data/lib/models/vendor.rb +93 -0
- data/lib/models/vendor_document.rb +97 -0
- data/lib/models/visa.rb +36 -0
- data/lib/sequel/code_group.rb +89 -0
- data/lib/sequel/fos_dates.rb +54 -0
- data/lib/sequel/metaprogramming.rb +8 -0
- data/lib/sequel/model_patch.rb +277 -0
- data/lib/sequel/pervasive_adapter.rb +214 -0
- data/lib/sequel/serializer/json_serializer.rb +129 -0
- data/lib/sequel/serializer/serializer.rb +94 -0
- data/lib/sequel/serializer/xml_serializer.rb +393 -0
- 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
|