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.
- 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
|