rom-fmp 0.0.4
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.
- 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
|