sobjectmodel 0.1.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 593a275d93ff933f9e7d9be260194f2da06f4b55285424589b739db5b80e4b8d
4
+ data.tar.gz: d8723a40b412a472ba84d56658046cde7b320ed617e22c92264d93b8649d5aaa
5
+ SHA512:
6
+ metadata.gz: 6dda884fe46731a0c8e25b372792908b3e4e43eb1d1a7693e97658cba06eeb6dadbff42ceedc7e04525072fe67b7027de372172ce637e567547b90b879bc799b
7
+ data.tar.gz: 0cb40fb055300eaef7f198878c12f03652a7d79c70a0bef09ddb9d4b9ac949a92e231912be6ac0ee18d0142e446bbc8a713266d6ef9eaf817af4d2fdaf40815c
@@ -0,0 +1,34 @@
1
+ module SObjectModel
2
+ module Adapter
3
+ class Base
4
+ def exec_query(soql, model_class: nil)
5
+ raise 'this method is not implemented'
6
+ end
7
+
8
+ def find(object_type, id, klass)
9
+ raise 'this method is not implemented'
10
+ end
11
+
12
+ def create(object_type, values, klass = nil)
13
+ raise 'this method is not implemented'
14
+ end
15
+
16
+ def update(object_type, id, values)
17
+ raise 'this method is not implemented'
18
+ end
19
+
20
+ def delete(object_type, id)
21
+ raise 'this method is not implemented'
22
+ end
23
+
24
+ def query(soql, klass)
25
+ raise 'this method is not implemented'
26
+ end
27
+
28
+ def describe(object_type)
29
+ raise 'this method is not implemented'
30
+ end
31
+ end
32
+ end
33
+ end
34
+
@@ -0,0 +1,53 @@
1
+ require_relative 'base'
2
+ require_relative '../rest/client'
3
+
4
+ module SObjectModel
5
+ module Adapter
6
+ class Rest < Base
7
+ def initialize(rest_client)
8
+ @client = rest_client
9
+ end
10
+
11
+ def exec_query(soql, model_class: nil)
12
+ result = client.query(soql)
13
+ result.to_records(model_class: model_class)
14
+ end
15
+
16
+ def describe(object_type)
17
+ client.describe(object_type)
18
+ end
19
+
20
+ def find(object_type, id, klass)
21
+ attributes = client.find(object_type, id)
22
+ klass.new(**attributes)
23
+ rescue ::SObjectModel::Rest::RecordNotFoundError
24
+ nil
25
+ end
26
+
27
+ def create(object_type, values, klass = nil)
28
+ id = client.create(object_type, values)
29
+ return id if klass.nil?
30
+
31
+ find(object_type, id, klass)
32
+ end
33
+
34
+ def update(object_type, id, values)
35
+ client.update(object_type, id, values)
36
+ end
37
+
38
+ def delete(object_type, id)
39
+ client.delete(object_type, id)
40
+ end
41
+
42
+ def query(soql, klass)
43
+ exec_query(soql, model_class: klass)
44
+ end
45
+
46
+ private
47
+
48
+ def client
49
+ @client
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,52 @@
1
+ require_relative 'base'
2
+
3
+ module SObjectModel
4
+ module Adapter
5
+ class Sf < Base
6
+ attr_reader :target_org
7
+
8
+ def initialize(sf_main, target_org:)
9
+ @sf = sf_main
10
+ @target_org = target_org
11
+ end
12
+
13
+ def exec_query(soql, format: nil, bulk: false, wait: nil, model_class: nil)
14
+ sf.data.query(soql, target_org: target_org, format: format, bulk: bulk, wait: wait, model_class: model_class)
15
+ end
16
+
17
+ def find(object_type, id, klass)
18
+ sf.data.get_record object_type, record_id: id, target_org: target_org, model_class: klass
19
+ end
20
+
21
+ def create(object_type, values, klass = nil)
22
+ id = sf.data.create_record object_type, values: values, target_org: target_org
23
+ return id if klass.nil?
24
+
25
+ find(object_type, id, klass)
26
+ end
27
+
28
+ def update(object_type, id, values)
29
+ sf.data.update_record object_type, record_id: id, where: nil, values: values, target_org: target_org
30
+ end
31
+
32
+ def delete(object_type, id)
33
+ sf.data.delete_record object_type, record_id: id, where: nil, target_org: target_org
34
+ end
35
+
36
+ def query(soql, klass, format = nil)
37
+ sf.data.query soql, target_org: target_org, format: format, model_class: klass
38
+ end
39
+
40
+ def describe(object_type)
41
+ schema = sf.sobject.describe(object_type, target_org: target_org)
42
+ schema.to_h # convert to raw command response
43
+ end
44
+
45
+ private
46
+
47
+ def sf
48
+ @sf
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,57 @@
1
+ module SObjectModel
2
+ module BaseMethods
3
+ def self.included(c)
4
+ c.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def connection
9
+ @connection
10
+ end
11
+
12
+ def connection=(conn)
13
+ @connection = conn
14
+ end
15
+
16
+ def describe
17
+ Schema.new(connection.describe(name.to_sym))
18
+ end
19
+ end
20
+
21
+ def initialize(attributes = {})
22
+ @original_attributes = {}
23
+ @current_attributes = {}
24
+ @updated_attributes = {}
25
+
26
+ attributes.each do |k, v|
27
+ field_name = k.to_sym
28
+ if self.class.field_names.include?(field_name)
29
+ @original_attributes[field_name] = v
30
+ __send__ (field_name.to_s + '='), v
31
+ elsif self.class.parent_relations.find{|r| r[:name] == field_name}
32
+ __send__ (field_name.to_s + '='), v
33
+ elsif self.class.child_relations.find{|r| r[:name] == field_name}
34
+ __send__ (field_name.to_s + '='), (v.nil? ? [] : v)
35
+ end
36
+ end
37
+ end
38
+
39
+ def to_h(keys: nil)
40
+ self.class.field_names.each_with_object({}) do |name, hash|
41
+ if keys&.instance_of?(Array)
42
+ hash[name] = __send__(name) if keys.include?(name)
43
+ else
44
+ hash[name] = __send__(name)
45
+ end
46
+ end
47
+ end
48
+
49
+ def new_record?
50
+ self.Id.nil?
51
+ end
52
+
53
+ def persisted?
54
+ new_record? == false
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,100 @@
1
+ require_relative './schema'
2
+ require_relative './base_methods'
3
+ require_relative './dml_methods'
4
+ require_relative './query_methods'
5
+
6
+ module SObjectModel
7
+ class ClassDefinition
8
+ attr_reader :schema
9
+
10
+ def initialize(schema)
11
+ @schema = Schema.new(schema)
12
+ end
13
+
14
+ def to_s
15
+ <<~Klass
16
+ Class.new do
17
+ include ::SObjectModel::BaseMethods
18
+ include ::SObjectModel::DmlMethods
19
+ include ::SObjectModel::QueryMethods
20
+
21
+ attr_reader :original_attributes, :current_attributes, :updated_attributes
22
+
23
+ #{ class_methods }
24
+
25
+ #{ field_attribute_methods }
26
+ #{ parent_relation_methods }
27
+ #{ child_relation_methods }
28
+ end
29
+ Klass
30
+ end
31
+
32
+ def class_methods
33
+ <<~EOS
34
+ class << self
35
+ def field_names
36
+ @field_names ||= #{ schema.field_names }
37
+ end
38
+
39
+ def parent_relations
40
+ @parent_relations ||= #{ schema.parent_relations }
41
+ end
42
+
43
+ def child_relations
44
+ @child_relations ||= #{ schema.child_relations }
45
+ end
46
+ end
47
+ EOS
48
+ end
49
+
50
+ def field_attribute_methods
51
+ schema.field_names.each_with_object('') do |name, s|
52
+ s << <<~EOS
53
+ def #{name}
54
+ @#{name}
55
+ end
56
+
57
+ def #{name}=(value)
58
+ @#{name} = value
59
+ return if %i[Id LastModifiedDate IsDeleted SystemModstamp CreatedById CreatedDate LastModifiedById].include?(:#{name})
60
+
61
+ current_attributes[:#{name}] = value
62
+ if current_attributes[:#{name}] == original_attributes[:#{name}]
63
+ updated_attributes[:#{name}] = nil
64
+ else
65
+ updated_attributes[:#{name}] = (value.nil? ? :null : value)
66
+ end
67
+ end
68
+ EOS
69
+ end
70
+ end
71
+
72
+ def parent_relation_methods
73
+ schema.parent_relations.each_with_object('') do |r, s|
74
+ s << <<~EOS
75
+ def #{r[:name]}
76
+ @#{r[:name]}
77
+ end
78
+
79
+ def #{r[:name]}=(attributes)
80
+ @#{r[:name]} = attributes.nil? ? nil : #{r[:class_name]}.new(attributes)
81
+ end
82
+ EOS
83
+ end
84
+ end
85
+
86
+ def child_relation_methods
87
+ schema.child_relations.each_with_object('') do |r, s|
88
+ s << <<~EOS
89
+ def #{r[:name]}
90
+ @#{r[:name]}
91
+ end
92
+
93
+ def #{r[:name]}=(records)
94
+ @#{r[:name]} = records.map{|attributes| #{r[:class_name]}.new(attributes)}
95
+ end
96
+ EOS
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,37 @@
1
+ module SObjectModel
2
+ module DmlMethods
3
+ def self.included(c)
4
+ c.extend ClassMethods
5
+ end
6
+
7
+ def save
8
+ if new_record?
9
+ self.Id = self.class.connection.create(self.class.name.to_sym, current_attributes.reject{|_,v| v.nil?})
10
+ else
11
+ _updated_attributes =
12
+ updated_attributes
13
+ .reject{|_,v| v.nil?}
14
+ .each_with_object({}){ |(k,v),h| h[k] = (v == :null) ? nil : v }
15
+
16
+ self.class.connection.update(self.class.name.to_sym, self.Id, _updated_attributes)
17
+ end
18
+
19
+ @original_attributes = current_attributes.dup
20
+ @updated_attributes = {}
21
+
22
+ self.Id
23
+ end
24
+
25
+ def delete
26
+ return if self.Id.nil?
27
+
28
+ self.class.connection.delete(self.class.name.to_sym, self.Id)
29
+ end
30
+
31
+ module ClassMethods
32
+ def create(values = {})
33
+ connection.create(name.to_sym, values, Object.const_get(name.to_sym))
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,45 @@
1
+ require_relative './class_definition'
2
+
3
+ module SObjectModel
4
+ class Generator
5
+ attr_reader :connection
6
+
7
+ def initialize(connection)
8
+ @connection = connection
9
+ end
10
+
11
+ def connected?
12
+ connection.nil? == false
13
+ end
14
+
15
+ def generate(*object_types)
16
+ generated_types = []
17
+ object_types.each do |object_type|
18
+ next if generated? object_type
19
+
20
+ schema = describe(object_type)
21
+ class_definition = ClassDefinition.new(schema)
22
+
23
+ instance_eval "::#{object_type} = #{class_definition}"
24
+ klass = Object.const_get object_type.to_sym
25
+ klass.connection = connection
26
+ generated_types << object_type
27
+ end
28
+
29
+ generated_types
30
+ end
31
+
32
+ private
33
+
34
+ def describe(object_type)
35
+ connection.describe object_type
36
+ end
37
+
38
+ def generated?(object_type)
39
+ Object.const_get object_type.to_sym
40
+ true
41
+ rescue NameError
42
+ false
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,189 @@
1
+ require 'date'
2
+
3
+ module SObjectModel
4
+ module QueryMethods
5
+ class QueryCondition
6
+ attr_reader :connection,
7
+ :object_name,
8
+ :all_field_names,
9
+ :fields,
10
+ :conditions,
11
+ :limit_num,
12
+ :row_order,
13
+ :count_select,
14
+ :max_select_field,
15
+ :min_select_field
16
+
17
+ def initialize(connection, object_name, field_names)
18
+ @object_name = object_name
19
+ @all_field_names = field_names
20
+ @connection = connection
21
+ @fields = []
22
+ @conditions = []
23
+ @limit_num = nil
24
+ @row_order = nil
25
+ @count_select = false
26
+ @max_select_field = nil
27
+ @min_select_field = nil
28
+ end
29
+
30
+ def where(*expr)
31
+ return self unless valid_expr?(expr)
32
+
33
+ conditions.append to_string_expr(expr)
34
+ self
35
+ end
36
+
37
+ def not(*expr)
38
+ return self unless valid_expr?(expr)
39
+
40
+ conditions.append %|(NOT(#{to_string_expr(expr)}))|
41
+ self
42
+ end
43
+
44
+ def select(*expr)
45
+ return self if expr&.empty?
46
+
47
+ if expr.size > 1
48
+ @fields = self.fields + expr
49
+ else
50
+ self.fields << expr.first
51
+ end
52
+ return self
53
+ end
54
+
55
+ def limit(num)
56
+ @limit_num = num
57
+ return self
58
+ end
59
+
60
+ def order(*_fields)
61
+ return self if _fields&.empty?
62
+
63
+ @row_order = _fields
64
+ return self
65
+ end
66
+
67
+ def to_soql
68
+ base = 'SELECT %{select} FROM %{object}' % {select: select_fields, object: object_name}
69
+ where = conditions.size.zero? ? nil : 'WHERE %{where}' % {where: conditions.flatten.join(' AND ')}
70
+ _order = row_order.nil? ? nil : 'ORDER BY %{order}' % {order: row_order.join(', ')}
71
+ limit = limit_num.nil? ? nil : 'LIMIT %{limit}' % {limit: limit_num}
72
+
73
+ [base, where, _order, limit].compact.join(' ')
74
+ end
75
+
76
+ def all
77
+ connection.query(to_soql, Object.const_get(object_name.to_sym))
78
+ end
79
+
80
+ def pluck(field_name)
81
+ connection.query(to_soql, nil).map{|record| record[field_name.to_s]}
82
+ end
83
+
84
+ def take
85
+ limit(1).all.first
86
+ end
87
+
88
+ def count
89
+ @count_select = true
90
+ connection.query(to_soql, nil).first['expr0']
91
+ end
92
+
93
+ def max(field_name)
94
+ @max_select_field = field_name
95
+ connection.query(to_soql, nil).first['expr0']
96
+ end
97
+
98
+ def min(field_name)
99
+ @min_select_field = field_name
100
+ connection.query(to_soql, nil).first['expr0']
101
+ end
102
+
103
+ private
104
+
105
+ def select_fields
106
+ return 'COUNT(Id)' if count_select
107
+ return "MAX(#{max_select_field})" if max_select_field
108
+ return "MIN(#{min_select_field})" if min_select_field
109
+
110
+ (fields.empty? ? all_field_names : fields.push(:Id).uniq).join(', ')
111
+ end
112
+
113
+ def to_string_expr(expr)
114
+ return str_by_ternary_expr(expr) if expr.size > 1
115
+ return expr[0] if expr[0].instance_of? String
116
+
117
+ strs_by_hash_expr(expr)
118
+ end
119
+
120
+ def str_by_ternary_expr(expr)
121
+ return self if expr.size < 3
122
+
123
+ value = case expr[2].class.name.to_sym
124
+ when :String
125
+ %|'#{expr[2]}'|
126
+ when :Time
127
+ expr[2].to_datetime
128
+ when :NilClass
129
+ :null
130
+ when :Array
131
+ candidates = expr[2].map do |o|
132
+ case o.class.name.to_sym
133
+ when :String
134
+ %|'#{o}'|
135
+ when :Time
136
+ o.to_datetime
137
+ when :NilClass
138
+ :null
139
+ else
140
+ o
141
+ end
142
+ end
143
+ %|(#{candidates.join(', ')})|
144
+ else
145
+ expr[2]
146
+ end
147
+ %|#{expr[0]} #{expr[1]} #{value}|
148
+ end
149
+
150
+ def valid_expr?(expr)
151
+ return false if expr&.empty?
152
+ return false if expr.map{|o| (o == '' || o == {} || o == []) ? nil : o}.compact.size.zero?
153
+ return false unless [Hash, Symbol, String].any?{|klass| expr.first.instance_of? klass}
154
+
155
+ true
156
+ end
157
+
158
+ def strs_by_hash_expr(expr)
159
+ expr[0].map do |k,v|
160
+ case v.class.name.to_sym
161
+ when :String
162
+ %|#{k} = '#{v}'|
163
+ when :Time
164
+ %|#{k} = #{v.to_datetime}|
165
+ when :NilClass
166
+ %|#{k} = null|
167
+ when :Array
168
+ candidates = v.map do |o|
169
+ case o.class.name.to_sym
170
+ when :String
171
+ %|'#{o}'|
172
+ when :Time
173
+ %|#{o.to_datetime}|
174
+ when :NilClass
175
+ :null
176
+ else
177
+ o
178
+ end
179
+ end
180
+ %|#{k} IN (#{candidates.join(', ')})|
181
+ else
182
+ "#{k} = #{v}"
183
+ end
184
+ end
185
+ .join(' AND ')
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,78 @@
1
+ require_relative './query_condition'
2
+
3
+ module SObjectModel
4
+ module QueryMethods
5
+ def self.included(c)
6
+ c.extend ClassMethods
7
+ end
8
+
9
+ module ClassMethods
10
+ def where(*expr)
11
+ qc = QueryCondition.new(connection, self.name, self.field_names)
12
+ qc.where(*expr)
13
+ return qc
14
+ end
15
+
16
+ def select(*fields)
17
+ qc = QueryCondition.new(connection, self.name, self.field_names)
18
+ qc.select(*fields)
19
+ return qc
20
+ end
21
+
22
+ def limit(num)
23
+ qc = QueryCondition.new(connection, self.name, self.field_names)
24
+ qc.limit(num)
25
+ qc
26
+ end
27
+
28
+ def order(*field_names)
29
+ qc = QueryCondition.new(connection, self.name, self.field_names)
30
+ qc.order(*field_names)
31
+ qc
32
+ end
33
+ def find(id)
34
+ connection.find(name.to_sym, id, Object.const_get(name.to_sym))
35
+ end
36
+
37
+ def find_by(*find_condition)
38
+ qc = QueryCondition.new(connection, self.name, self.field_names)
39
+ qc.where(*find_condition).take
40
+ end
41
+
42
+ def all
43
+ qc = QueryCondition.new(connection, self.name, self.field_names)
44
+ qc.all
45
+ end
46
+
47
+ def to_csv
48
+ qc = QueryCondition.new(connection, self.name, self.field_names)
49
+ qc.to_csv
50
+ end
51
+
52
+ def pluck(field_name)
53
+ qc = QueryCondition.new(connection, self.name, self.field_names)
54
+ qc.pluck(field_name)
55
+ end
56
+
57
+ def take
58
+ qc = QueryCondition.new(connection, self.name, self.field_names)
59
+ qc.take
60
+ end
61
+
62
+ def count
63
+ qc = QueryCondition.new(connection, self.name, self.field_names)
64
+ qc.count
65
+ end
66
+
67
+ def max(field_name)
68
+ qc = QueryCondition.new(connection, self.name, self.field_names)
69
+ qc.max(field_name)
70
+ end
71
+
72
+ def min(field_name)
73
+ qc = QueryCondition.new(connection, self.name, self.field_names)
74
+ qc.min(field_name)
75
+ end
76
+ end
77
+ end
78
+ end