rom-fmp 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +60 -0
- data/Rakefile +12 -0
- data/lib/rom-fmp.rb +1 -0
- data/lib/rom/fmp.rb +21 -0
- data/lib/rom/fmp/commands.rb +19 -0
- data/lib/rom/fmp/commands/create.rb +29 -0
- data/lib/rom/fmp/commands/delete.rb +18 -0
- data/lib/rom/fmp/commands/update.rb +54 -0
- data/lib/rom/fmp/dataset.rb +121 -0
- data/lib/rom/fmp/gateway.rb +28 -0
- data/lib/rom/fmp/header.rb +57 -0
- data/lib/rom/fmp/micro01.rb +55 -0
- data/lib/rom/fmp/micro02.rb +161 -0
- data/lib/rom/fmp/mini.rb +91 -0
- data/lib/rom/fmp/relation.rb +18 -0
- data/lib/rom/fmp/relation/associations.rb +104 -0
- data/lib/rom/fmp/relation/class_methods.rb +48 -0
- data/lib/rom/fmp/relation/inspection.rb +16 -0
- data/lib/rom/fmp/rfm/fmresultset.yml +86 -0
- data/lib/rom/fmp/rfm/layout.rb +8 -0
- data/lib/rom/fmp/version.rb +5 -0
- data/rom-fmp.gemspec +28 -0
- data/spec/rom/fmp_spec.rb +12 -0
- data/spec/rom/gateway_spec.rb +53 -0
- data/spec/rom/relation_spec.rb +17 -0
- data/spec/spec_helper.rb +15 -0
- metadata +164 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
### NOT CURRENTLY USED ###
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
module FMP
|
5
|
+
# @private
|
6
|
+
class Header
|
7
|
+
include Equalizer.new(:columns, :table)
|
8
|
+
|
9
|
+
attr_reader :columns, :table
|
10
|
+
|
11
|
+
def initialize(columns, table)
|
12
|
+
@columns = columns
|
13
|
+
@table = table
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_ary
|
17
|
+
columns
|
18
|
+
end
|
19
|
+
alias_method :to_a, :to_ary
|
20
|
+
|
21
|
+
def to_h
|
22
|
+
columns.each_with_object({}) do |col, h|
|
23
|
+
left, right = col.to_s.split('___')
|
24
|
+
h[left.to_sym] = (right || left).to_sym
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def names
|
29
|
+
columns.map { |col| :"#{col.to_s.split('___').last}" }
|
30
|
+
end
|
31
|
+
|
32
|
+
def project(*names)
|
33
|
+
self.class.new(columns.find_all { |col| names.include?(col) }, table)
|
34
|
+
end
|
35
|
+
|
36
|
+
def qualified
|
37
|
+
self.class.new(columns.map { |col| :"#{table}__#{col}" }, table)
|
38
|
+
end
|
39
|
+
|
40
|
+
def rename(options)
|
41
|
+
self.class.new(columns.map { |col|
|
42
|
+
new_name = options[col]
|
43
|
+
|
44
|
+
if new_name
|
45
|
+
:"#{col}___#{new_name}"
|
46
|
+
else
|
47
|
+
col
|
48
|
+
end
|
49
|
+
}, table)
|
50
|
+
end
|
51
|
+
|
52
|
+
def prefix(col_prefix)
|
53
|
+
rename(Hash[columns.map { |col| [col, :"#{col_prefix}_#{col}"] }])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# This is an alternative version of rom-fmp, with all moduels & classes in a single file.
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'rom'
|
5
|
+
require 'rfm'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
class Rfm::Layout
|
9
|
+
def to_a
|
10
|
+
all(:max_records=>10)
|
11
|
+
end
|
12
|
+
|
13
|
+
def each
|
14
|
+
# passes block - if any - to upstream each.
|
15
|
+
to_a.each(&Proc.new)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module ROM
|
20
|
+
module FMP
|
21
|
+
|
22
|
+
|
23
|
+
class Gateway < ROM::Gateway
|
24
|
+
attr_reader :datasets
|
25
|
+
|
26
|
+
def initialize(*options)
|
27
|
+
@datasets = Rfm.database(options[0].to_h.merge(FMRESULTSET_TEMPLATE).to_h)
|
28
|
+
end
|
29
|
+
|
30
|
+
def dataset(name)
|
31
|
+
datasets[name.to_s]
|
32
|
+
end
|
33
|
+
|
34
|
+
# This is required per lint specs
|
35
|
+
alias_method :[], :dataset
|
36
|
+
|
37
|
+
def dataset?(name)
|
38
|
+
datasets.layouts.key?(name.to_s)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
class Relation < ROM::Relation
|
44
|
+
# we must configure adapter identifier here
|
45
|
+
adapter :fmp
|
46
|
+
|
47
|
+
forward :find, :any, :all
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
end # FMP
|
55
|
+
end # ROM
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# This is an alternative version of rom-fmp, with all moduels & classes in a single file.
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'rom/gateway'
|
5
|
+
require 'rfm'
|
6
|
+
require 'charlatan'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
class Rfm::Layout
|
10
|
+
# Strip out unnecessary output in Rfm::Layout#inspect
|
11
|
+
def inspect
|
12
|
+
"#<#{self.class.name}:#{self.object_id} @name=#{self.name} @database=#{database.name} @server=#{server.host_name} @loaded=#{@loaded}>"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ROM
|
17
|
+
module FMP
|
18
|
+
|
19
|
+
|
20
|
+
class Gateway < ROM::Gateway
|
21
|
+
attr_reader :datasets, :database
|
22
|
+
|
23
|
+
def initialize(*options)
|
24
|
+
@database = Rfm.database(options[0].to_h.merge(FMRESULTSET_TEMPLATE).to_h)
|
25
|
+
@datasets = Hash.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def dataset(name)
|
29
|
+
datasets[name.to_s] ||= Dataset.new(@database[name.to_s])
|
30
|
+
end
|
31
|
+
|
32
|
+
# This is required per lint specs
|
33
|
+
alias_method :[], :dataset
|
34
|
+
|
35
|
+
def dataset?(name)
|
36
|
+
datasets.key?(name.to_s)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
class Dataset
|
42
|
+
include ::Rfm::Scope
|
43
|
+
|
44
|
+
DEFAULT_REQUEST_OPTIONS = {}
|
45
|
+
|
46
|
+
# Dataset instance expects to hold Array of data in @data,
|
47
|
+
# but it will also hold a FM Layout instance.
|
48
|
+
# If any call to Dataset instance returns Array instance,
|
49
|
+
# it will be wrapped in a new Dataset instance.
|
50
|
+
include Charlatan.new(:data, kind: Array)
|
51
|
+
attr_reader :layout, :data, :queries
|
52
|
+
|
53
|
+
# Store layout, data, query in new dataset.
|
54
|
+
def initialize(_layout, _data=[], _queries=[])
|
55
|
+
@layout = _layout
|
56
|
+
@queries = _queries
|
57
|
+
#puts "DATASET NEW queries:#{@queries}"
|
58
|
+
super(_data)
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
|
63
|
+
# Creates new dataset with current args and resultset. Not lazy.
|
64
|
+
# This may not be how rom or sql uses 'where'. Find out more.
|
65
|
+
def where(*args)
|
66
|
+
#self.class.new(layout, layout.find(*args), args)
|
67
|
+
get_results(:find, args)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Creates new dataset with existing data & queries, plus new query
|
71
|
+
def find(*args)
|
72
|
+
#self.class.new(layout, data, (queries.dup << args))
|
73
|
+
wrap_data(data, (queries.dup << args))
|
74
|
+
end
|
75
|
+
|
76
|
+
def any(options={})
|
77
|
+
wrap_data(layout.any(options))
|
78
|
+
end
|
79
|
+
|
80
|
+
def all(options={})
|
81
|
+
wrap_data(layout.all(DEFAULT_REQUEST_OPTIONS.merge(options)))
|
82
|
+
end
|
83
|
+
|
84
|
+
def count(*args)
|
85
|
+
compiled_query = compile_query
|
86
|
+
compiled_query ? layout.count(*compiled_query) : layout.total_count
|
87
|
+
end
|
88
|
+
|
89
|
+
def create(args={})
|
90
|
+
get_results(:create, [args]) unless args.empty?
|
91
|
+
end
|
92
|
+
|
93
|
+
def update(record_id, args={})
|
94
|
+
get_results(:edit, [record_id, args]) unless args.empty?
|
95
|
+
end
|
96
|
+
|
97
|
+
def delete(record_id)
|
98
|
+
get_results(:delete, record_id)
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
# Triggers actual fm action.
|
104
|
+
def to_a
|
105
|
+
(data.nil? || data.empty?) ? call.data.to_a : data.to_a
|
106
|
+
end
|
107
|
+
|
108
|
+
# Triggers actual fm action.
|
109
|
+
def each
|
110
|
+
# passes block - if any - to upstream each.
|
111
|
+
to_a.each(&Proc.new)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Combines all queries, sends to FM, returns result in new dataset.
|
115
|
+
def call
|
116
|
+
compiled_query = compile_query
|
117
|
+
wrap_data(compiled_query ? layout.find(*compiled_query) : layout.all(DEFAULT_REQUEST_OPTIONS))
|
118
|
+
end
|
119
|
+
|
120
|
+
# Mixes chained queries together into single query.
|
121
|
+
# Now works with multiple-request queries (using new rfm scope feature).
|
122
|
+
# Other ways: consider mixing multi-request queries with intersection: (result1 & result2),
|
123
|
+
# or with the new scope feature: query1(scope:query2(scope:query3))
|
124
|
+
def compile_query
|
125
|
+
#puts "DATASET COMPILE self #{self}"
|
126
|
+
#puts "DATASET COMPILE queries #{queries}"
|
127
|
+
|
128
|
+
# Old way: works but doesn't handle fmp compound queries.
|
129
|
+
#query.each_with_object([{},{}]){|x,o| o[0].merge!(x[0] || {}); o[1].merge!(x[1] || {})}
|
130
|
+
|
131
|
+
# New way: handles compound queries. Reqires ginjo-rfm 3.0.11.
|
132
|
+
return unless queries # This should help introspecting dataset that results from record deletion. TODO: test this.
|
133
|
+
queries.inject {|new_query,scope| apply_scope(new_query, scope)} ##puts "SCOPE INJECTION scope:#{scope} new_query:#{new_query}";
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns new dataset containing, data, layout, query.
|
137
|
+
def wrap_data(_data=data, _queries=queries, _layout=layout)
|
138
|
+
self.class.new(_layout, _data, _queries)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Send method & query to layout, and wrap results in new dataset.
|
142
|
+
def get_results(_method, query=queries, _layout=layout)
|
143
|
+
wrap_data(_layout.send(_method, *query), query, _layout)
|
144
|
+
end
|
145
|
+
|
146
|
+
end # Dataset
|
147
|
+
|
148
|
+
|
149
|
+
class Relation < ROM::Relation
|
150
|
+
# we must configure adapter identifier here
|
151
|
+
adapter :fmp
|
152
|
+
|
153
|
+
forward :find, :all, :count, :create, :update, :delete
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
|
158
|
+
|
159
|
+
|
160
|
+
end # FMP
|
161
|
+
end # ROM
|
data/lib/rom/fmp/mini.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# This is an alternative version of rom-fmp, with all moduels & classes in a single file.
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'rom/gateway'
|
5
|
+
require 'rom/fmp/commands'
|
6
|
+
require 'rfm'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
|
10
|
+
module ROM
|
11
|
+
module FMP
|
12
|
+
|
13
|
+
class Relation < ROM::Relation
|
14
|
+
adapter :fmp
|
15
|
+
forward :find, :where, :all, :any, :count
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
class Gateway < ROM::Gateway
|
20
|
+
attr_reader :sets
|
21
|
+
|
22
|
+
def initialize(uri, options = {})
|
23
|
+
puts "INITIALIZING GATEWAY WITH uri: #{uri} options: #{options}"
|
24
|
+
@connection = connect(uri, options)
|
25
|
+
@sets = {}
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def dataset(name)
|
30
|
+
sets[name] = Dataset.new(name, connection)
|
31
|
+
end
|
32
|
+
|
33
|
+
def [](name)
|
34
|
+
sets.fetch(name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def dataset?(name)
|
38
|
+
sets.key?(name)
|
39
|
+
end
|
40
|
+
|
41
|
+
def connect(*args)
|
42
|
+
options = args.last.kind_of?(Hash) ? args.pop : Hash.new
|
43
|
+
options.merge! args.pop if args.last.kind_of?(Hash)
|
44
|
+
case args[0]
|
45
|
+
when Rfm::Database
|
46
|
+
args[0]
|
47
|
+
else
|
48
|
+
#::Rfm::Database.new(uri[:database], *Array([uri.to_s, *args]).flatten)
|
49
|
+
#Rfm.layout(storage_name, gateway.adapter.options.merge(FMRESULTSET_TEMPLATE).symbolize_keys)
|
50
|
+
Rfm.database(*args, options.to_h.merge(FMRESULTSET_TEMPLATE).to_h)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end # Gateway
|
55
|
+
|
56
|
+
|
57
|
+
class Dataset
|
58
|
+
#include Equalizer.new(:name, :connection)
|
59
|
+
|
60
|
+
attr_reader :name, :connection, :table
|
61
|
+
|
62
|
+
def initialize(name, connection)
|
63
|
+
puts "INITIALIZING DATASET WITH name: #{name} connection: #{connection}"
|
64
|
+
@name = name
|
65
|
+
@connection = connection
|
66
|
+
@table = connection[name.to_s]
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
def find(*args)
|
71
|
+
table.find(*args)
|
72
|
+
end
|
73
|
+
|
74
|
+
def each(&block)
|
75
|
+
table.all(:max_records=>10).each(&block)
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_a
|
79
|
+
table.all(:max_records=>10)
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# def table
|
85
|
+
# @table ||= connection[name]
|
86
|
+
# end
|
87
|
+
|
88
|
+
end # Dataset
|
89
|
+
|
90
|
+
end # FMP
|
91
|
+
end # ROM
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rom'
|
2
|
+
#require 'rom/fmp/header'
|
3
|
+
#require 'rom/fmp/relation/class_methods'
|
4
|
+
#require 'rom/fmp/relation/inspection'
|
5
|
+
#require 'rom/fmp/relation/associations'
|
6
|
+
|
7
|
+
module ROM
|
8
|
+
module FMP
|
9
|
+
|
10
|
+
class Relation < ROM::Relation
|
11
|
+
# we must configure adapter identifier here
|
12
|
+
adapter :fmp
|
13
|
+
|
14
|
+
forward :find, :all, :count, :create, :update, :delete
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module ROM
|
2
|
+
module SQL
|
3
|
+
class Relation < ROM::Relation
|
4
|
+
module Associations
|
5
|
+
# Join configured association.
|
6
|
+
#
|
7
|
+
# Uses INNER JOIN type.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
#
|
11
|
+
# setup.relation(:tasks)
|
12
|
+
#
|
13
|
+
# setup.relations(:users) do
|
14
|
+
# one_to_many :tasks, key: :user_id
|
15
|
+
#
|
16
|
+
# def with_tasks
|
17
|
+
# association_join(:tasks, select: [:title])
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# @api public
|
22
|
+
def association_join(name, options = {})
|
23
|
+
graph_join(name, :inner, options)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Join configured association
|
27
|
+
#
|
28
|
+
# Uses LEFT JOIN type.
|
29
|
+
#
|
30
|
+
# @example
|
31
|
+
#
|
32
|
+
# setup.relation(:tasks)
|
33
|
+
#
|
34
|
+
# setup.relations(:users) do
|
35
|
+
# one_to_many :tasks, key: :user_id
|
36
|
+
#
|
37
|
+
# def with_tasks
|
38
|
+
# association_left_join(:tasks, select: [:title])
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# @api public
|
43
|
+
def association_left_join(name, options = {})
|
44
|
+
graph_join(name, :left_outer, options)
|
45
|
+
end
|
46
|
+
|
47
|
+
# @api private
|
48
|
+
def graph_join(name, join_type, options = {})
|
49
|
+
assoc = model.association_reflection(name)
|
50
|
+
|
51
|
+
key = assoc[:key]
|
52
|
+
type = assoc[:type]
|
53
|
+
|
54
|
+
if type == :many_to_many
|
55
|
+
select = options[:select] || {}
|
56
|
+
graph_join_many_to_many(name, assoc, select)
|
57
|
+
else
|
58
|
+
graph_join_other(name, key, type, join_type, options)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# @api private
|
63
|
+
def graph(*args)
|
64
|
+
__new__(dataset.__send__(__method__, *args))
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def graph_join_many_to_many(name, assoc, select)
|
70
|
+
l_select, r_select =
|
71
|
+
if select.is_a?(Hash)
|
72
|
+
[select[assoc[:join_table]] || [], select[name]]
|
73
|
+
else
|
74
|
+
[[], select]
|
75
|
+
end
|
76
|
+
|
77
|
+
l_graph = graph(
|
78
|
+
assoc[:join_table],
|
79
|
+
{ assoc[:left_key] => primary_key },
|
80
|
+
select: l_select, implicit_qualifier: self.name
|
81
|
+
)
|
82
|
+
|
83
|
+
l_graph.graph(
|
84
|
+
name, { primary_key => assoc[:right_key] }, select: r_select
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
88
|
+
def graph_join_other(name, key, type, join_type, options)
|
89
|
+
join_keys =
|
90
|
+
if type == :many_to_one
|
91
|
+
{ primary_key => key }
|
92
|
+
else
|
93
|
+
{ key => primary_key }
|
94
|
+
end
|
95
|
+
|
96
|
+
graph(
|
97
|
+
name, join_keys,
|
98
|
+
options.merge(join_type: join_type, implicit_qualifier: self.name)
|
99
|
+
)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|