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