active_shotgun 0.0.3 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b1e2345b2533af34895ef3e5ba10ab8c161f24ad36edb318d4e101747dddeb9e
4
- data.tar.gz: 39e87e5dfc26b9697094d2b826518ac9fa9a6021e24dcab684b56f08989720b1
3
+ metadata.gz: 611b243c8abdc485ee5fe5b8b1bf86fa393a0d782a568081e78991c5bd42546f
4
+ data.tar.gz: b121ffffa365bcc386d0009722692f5a9cb5162bc874c229b154e1b92bb4ddb4
5
5
  SHA512:
6
- metadata.gz: c37bf9627795d8a13f32755a0120fbe28b521b35d242e99727f3e15ceb9150b80928361f699ac4481d3f7424dea5418864e1852b957dc7285d6402d4f6f2139d
7
- data.tar.gz: 2e949a1793fcd76a7cbf16fa894b9d23f5fe07eae3feaa0a035cddde61e38da69d102076df29935fd4b9b64dbc3859241e60f66a8e14cce3476424eb00e51190
6
+ metadata.gz: f6e8be7c026a4d694a17fb825b4c24a0ff91c9a5f6eb958b30dd6b5f8ecc2f6f94cdf4fd6d6a6fbfb4892b68bc8ce926f64730d17d04013d0b14e063b64275d1
7
+ data.tar.gz: 78a61e5073d3e913e5382086c095320ca3efe4cf3ff6cd79ea6a9ffd7116ead4a8e793e563302c3165b8190b47e031e3be4f72a852247534f15c66b223d77765
@@ -17,6 +17,9 @@ AllCops:
17
17
  Naming/FileName:
18
18
  Enabled: false
19
19
 
20
+ Naming/PredicateName:
21
+ Enabled: false
22
+
20
23
  Gemspec/RequiredRubyVersion:
21
24
  Enabled: false
22
25
 
@@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.0.4] - 2020-12-17
10
+ ### Added
11
+ - count and size
12
+ - support for belongs_to and has_many
13
+
9
14
  ## [0.0.3] - 2020-12-16
10
15
  ### Added
11
16
  - First version of AR complient queries. Supports:
@@ -42,7 +47,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
42
47
  ### Added
43
48
  - Gem init
44
49
 
45
- [Unreleased]: https://github.com/zaratan/active_shotgun/compare/v0.0.3...HEAD
50
+ [Unreleased]: https://github.com/zaratan/active_shotgun/compare/v0.0.4...HEAD
51
+ [0.0.4]: https://github.com/zaratan/active_shotgun/releases/tag/v0.0.4
46
52
  [0.0.3]: https://github.com/zaratan/active_shotgun/releases/tag/v0.0.3
47
53
  [0.0.2.1]: https://github.com/zaratan/active_shotgun/releases/tag/v0.0.2.1
48
54
  [0.0.2]: https://github.com/zaratan/active_shotgun/releases/tag/v0.0.2
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- active_shotgun (0.0.3)
4
+ active_shotgun (0.0.4)
5
5
  activemodel (>= 5.2)
6
6
  shotgun_api_ruby (>= 0.0.8.2)
7
7
  zeitwerk (~> 2)
@@ -100,7 +100,7 @@ GEM
100
100
  simplecov (>= 0.4.1)
101
101
  simplecov_json_formatter (0.1.2)
102
102
  thor (1.0.1)
103
- tzinfo (2.0.3)
103
+ tzinfo (2.0.4)
104
104
  concurrent-ruby (~> 1.0)
105
105
  unicode-display_width (1.7.0)
106
106
  yard (0.9.25)
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveShotgun
4
+ class AssociationsProxy
5
+ def initialize(base_class:, base_id:, field_name:, possible_types: {})
6
+ @base_class = base_class
7
+ @base_id = base_id
8
+ @field_name = field_name
9
+ @possible_types = possible_types
10
+ @queries =
11
+ possible_types.map do |type, klass|
12
+ Query.new(type: type, klass: klass)
13
+ end
14
+ @global_limit = 1000
15
+ @global_offset = 0
16
+ @global_orders = nil
17
+ @global_fields = nil
18
+ end
19
+
20
+ def push(model)
21
+ already_here =
22
+ @queries.reduce([]) do |result, query|
23
+ result + query.dup.limit(1000).pluck(:id).map{ |id|
24
+ { type: query.instance_variable_get(:@type), id: id }
25
+ }
26
+ end
27
+ Client.shotgun.entities(@base_class.to_s).update(
28
+ @base_id,
29
+ @field_name => already_here.push(
30
+ {
31
+ type: model.class.shotgun_type,
32
+ id: model.id,
33
+ }
34
+ ).uniq
35
+ )
36
+ @array = nil
37
+ true
38
+ end
39
+ alias_method :<<, :push
40
+
41
+ def delete(id, type = nil)
42
+ raise "Many types possible. Please specify." if !type && has_many_types?
43
+
44
+ type ||= @possible_types.keys.first
45
+ already_here =
46
+ @queries.reduce([]) do |result, query|
47
+ result + query.dup.limit(1000).pluck(:id).map{ |item_id|
48
+ { type: query.instance_variable_get(:@type), id: item_id }
49
+ }
50
+ end
51
+ new_items = already_here.reject{ |item| item[:type] == type && item[:id] == id }
52
+
53
+ Client.shotgun.entities(@base_class.to_s).update(
54
+ @base_id,
55
+ @field_name => new_items.uniq
56
+ )
57
+ @array = nil
58
+ true
59
+ end
60
+
61
+ def has_many_types?
62
+ @has_many_types ||= @queries.size > 1
63
+ end
64
+
65
+ def all
66
+ @array ||= resolve_all_queries # rubocop:disable Naming/MemoizedInstanceVariableName
67
+ end
68
+ alias_method :to_a, :all
69
+
70
+ def first(number = 1)
71
+ results = limit(number).all
72
+ if @global_limit == 1
73
+ results.first
74
+ else
75
+ results
76
+ end
77
+ end
78
+
79
+ def limit(number)
80
+ @global_limit = number
81
+ @queries =
82
+ @queries.map do |query|
83
+ has_many_types? ? query.limit(1000) : query.limit(number)
84
+ end
85
+ self
86
+ end
87
+
88
+ def offset(number)
89
+ @global_offset = number
90
+ @queries =
91
+ @queries.map do |query|
92
+ has_many_types? ? query : query.offset(number)
93
+ end
94
+ @array = nil
95
+ self
96
+ end
97
+
98
+ def where(conditions)
99
+ @queries =
100
+ @queries.map do |query|
101
+ query.where(conditions)
102
+ end
103
+ @array = nil
104
+ self
105
+ end
106
+
107
+ def find_by(conditions)
108
+ where(conditions).first
109
+ end
110
+
111
+ def orders(new_orders)
112
+ @global_orders = new_orders
113
+ @queries =
114
+ @queries.map do |query|
115
+ query.orders(new_orders)
116
+ end
117
+ @array = nil
118
+ self
119
+ end
120
+
121
+ def select(*fields)
122
+ @queries =
123
+ @queries.map do |query|
124
+ query.select(fields)
125
+ end
126
+ @array = nil
127
+ self
128
+ end
129
+
130
+ def pluck(*fields)
131
+ fields.flatten!
132
+ result = select(fields).map{ |e| fields.map{ |field| e.public_send(field) } }
133
+ fields.size == 1 ? result.flatten : result
134
+ end
135
+
136
+ extend Forwardable
137
+ def_delegators(
138
+ :all,
139
+ :each,
140
+ :size,
141
+ :count
142
+ )
143
+ include Enumerable
144
+
145
+ private
146
+
147
+ def global_orders_to_h
148
+ case @global_orders
149
+ when Hash # rubocop:disable Lint/EmptyWhen
150
+ when Array
151
+ @global_orders.map do |str|
152
+ [str.gsub(/^[-+]/, '').to_sym, str.start_with?('-') ? :desc : :asc]
153
+ end
154
+ when String
155
+ @global_orders = @global_orders.split(/\s*,\s*/)
156
+ global_orders_to_h
157
+ else
158
+ raise "Unknown order type"
159
+ end
160
+ end
161
+
162
+ def sort_results(results)
163
+ orders = global_orders_to_h
164
+ results.sort do |a, b|
165
+ orders.reduce(0) do |res, order|
166
+ if res == 0
167
+ field = order.first
168
+ if order.last == :asc
169
+ a.public_send(field) <=> b.public_send(field)
170
+ else
171
+ b.public_send(field) <=> a.public_send(field)
172
+ end
173
+ else
174
+ res
175
+ end
176
+ end
177
+ end
178
+ end
179
+
180
+ def resolve_all_queries
181
+ if has_many_types?
182
+ results = @queries.flat_map(&:to_a)
183
+ results = sort_results(results)
184
+ results.shift(@global_offset).first(@global_limit)
185
+ else
186
+ @queries.flat_map(&:to_a)
187
+ end
188
+ end
189
+ end
190
+ end
@@ -58,6 +58,8 @@ module ActiveShotgun
58
58
  base_class.extend(Delete::ClassMethods)
59
59
  base_class.prepend(Validations)
60
60
  base_class.prepend(Callbacks)
61
+ base_class.include(Associations)
62
+ base_class.extend(Associations::ClassMethods)
61
63
  end
62
64
  end
63
65
  end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveShotgun
4
+ module Model
5
+ module Associations
6
+ def self.included(base_class)
7
+ base_class.const_set(:BELONG_ASSOC, [])
8
+ base_class.const_set(:MANY_ASSOC, [])
9
+ end
10
+
11
+ module ClassMethods
12
+ def belongs_to(assoc_name, klass: nil, types: nil, type: nil)
13
+ klass ||= assoc_name.to_s.camelize
14
+ types ||= [type].flatten if type
15
+ types ||= [assoc_name.to_s.camelize]
16
+ types = [types].flatten.map(&:to_s)
17
+ # define name_id
18
+ # define name that read and populate the class
19
+ instance_eval do
20
+ # Register the association
21
+ self::BELONG_ASSOC.push(assoc_name)
22
+
23
+ # Define the id reader
24
+ attr_reader "#{assoc_name}_id"
25
+
26
+ # Define the id writter
27
+ define_attribute_methods("#{assoc_name}_id")
28
+ define_method("#{assoc_name}_id=") do |value, new_type = nil|
29
+ send("#{assoc_name}_id_will_change!")
30
+ if new_type
31
+ unless types.include?(new_type)
32
+ raise "Invalid Type #{new_type}. Valid types are: [#{types.join(', ')}]"
33
+ end
34
+
35
+ instance_variable_set("@#{assoc_name}_type", new_type)
36
+ elsif types.size == 1
37
+ instance_variable_set("@#{assoc_name}_type", types.first)
38
+ else
39
+ unless public_send("#{assoc_name}_type")
40
+ raise "Multiple types possible. You must specify a type from [#{types.join(', ')}]"
41
+ end
42
+ end
43
+ instance_variable_set("@#{assoc_name}_id", value)
44
+ end
45
+
46
+ # Define the type reader
47
+ attr_reader "#{assoc_name}_type"
48
+
49
+ # Define the assoc reader
50
+ define_method(assoc_name) do
51
+ instance_variable_get("@#{assoc_name}") ||
52
+ instance_variable_set(
53
+ "@#{assoc_name}",
54
+ (klass.is_a?(String) ? klass.constantize : klass).
55
+ parse_shotgun_results(
56
+ Client.
57
+ shotgun.
58
+ entities(public_send("#{assoc_name}_type")).
59
+ find(public_send("#{assoc_name}_id"))
60
+ )
61
+ )
62
+ end
63
+
64
+ define_method("#{assoc_name}=") do |assoc_item|
65
+ public_send("#{assoc_name}_id=", assoc_item.id, assoc_item.class.shotgun_type)
66
+ instance_variable_set("@#{assoc_name}", assoc_item)
67
+ end
68
+ end
69
+ end
70
+
71
+ def has_many(assoc_name_plural, possible_types: nil)
72
+ assoc_name = assoc_name_plural.to_s.singularize
73
+ possible_types ||= {
74
+ assoc_name.camelize => assoc_name.camelize,
75
+ }
76
+ if possible_types.is_a?(Array)
77
+ possible_types.map do |type|
78
+ [
79
+ type.camelize,
80
+ type.camelize,
81
+ ]
82
+ end.to_h
83
+ end
84
+ # define name that read and populate an array of (a query ?)
85
+
86
+ instance_eval do
87
+ # Register the association
88
+ self::MANY_ASSOC.push(assoc_name_plural)
89
+
90
+ # Define reader must return an AssociationQuery which override push/<</delete with instant remove/add
91
+ define_method(assoc_name_plural) do
92
+ instance_variable_get("@#{assoc_name_plural}") ||
93
+ instance_variable_set(
94
+ "@#{assoc_name_plural}",
95
+ AssociationsProxy.new(
96
+ possible_types: possible_types,
97
+ base_class: self.class,
98
+ base_id: id,
99
+ field_name: assoc_name_plural
100
+ ).where(
101
+ [
102
+ self.class.shotgun_type.downcase.to_s.pluralize,
103
+ "is",
104
+ {
105
+ type: self.class.shotgun_type.camelize,
106
+ id: id,
107
+ },
108
+ ]
109
+ )
110
+ )
111
+ end
112
+
113
+ # Define writer
114
+ define_attribute_methods(assoc_name_plural)
115
+ define_method("#{assoc_name_plural}=") do |array|
116
+ send("#{assoc_name_plural}_will_change!")
117
+ instance_variable_set("@#{assoc_name_plural}", array)
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -15,10 +15,17 @@ module ActiveShotgun
15
15
  end.to_h
16
16
  end
17
17
 
18
- def initialize(new_attributes = {})
18
+ def initialize(new_attributes = {}, new_relations = {})
19
19
  new_attributes.slice(*self.class.shotgun_readable_fetched_attributes).each do |attribute, value|
20
20
  instance_variable_set("@#{attribute}", value)
21
21
  end
22
+ self.class::BELONG_ASSOC.each do |assoc|
23
+ next unless relation = new_relations[assoc]
24
+
25
+ instance_variable_set("@#{assoc}_type", relation["type"])
26
+ instance_variable_set("@#{assoc}_id", relation["id"])
27
+ instance_variable_set("@#{assoc}", nil)
28
+ end
22
29
  end
23
30
 
24
31
  def reload
@@ -60,7 +67,22 @@ module ActiveShotgun
60
67
 
61
68
  def find(id)
62
69
  sg_result = shotgun_client.find(id)
63
- new(sg_result.attributes.to_h.merge(id: sg_result.id))
70
+ parse_shotgun_results(sg_result)
71
+ end
72
+
73
+ def count
74
+ prepare_new_query.count
75
+ end
76
+
77
+ def size
78
+ prepare_new_query.size
79
+ end
80
+
81
+ def parse_shotgun_results(sg_result)
82
+ new(
83
+ sg_result.attributes.to_h.merge(id: sg_result.id),
84
+ sg_result.relationships.transform_values{ |v| v["data"] }.with_indifferent_access
85
+ )
64
86
  end
65
87
  end
66
88
  end
@@ -12,23 +12,35 @@ module ActiveShotgun
12
12
 
13
13
  sg_result =
14
14
  if persisted?
15
- shotgun_client.update(id, changes.transform_values(&:last))
15
+ shotgun_client.update(id, changes_with_relations)
16
16
  else
17
- shotgun_client.create(changes.transform_values(&:last))
17
+ shotgun_client.create(changes_with_relations)
18
18
  end
19
- override_attributes!(sg_result.attributes.to_h.merge(id: sg_result.id))
19
+ override_attributes!(sg_result)
20
20
  changes_applied
21
21
  true
22
22
  end
23
23
  alias_method :save!, :save
24
24
 
25
25
  def mass_assign(assigned_attributes)
26
- assigned_attributes.
27
- transform_keys(&:to_sym).
26
+ sym_assigned_attributes = assigned_attributes.transform_keys(&:to_sym)
27
+ sym_assigned_attributes.
28
28
  slice(*self.class.shotgun_writable_fetched_attributes).
29
29
  each do |k, v|
30
30
  public_send("#{k}=", v)
31
31
  end
32
+
33
+ sym_assigned_attributes.
34
+ slice(*self.class::BELONG_ASSOC.map{ |assoc| "#{assoc}_id".to_sym }).
35
+ each do |k, v|
36
+ public_send("#{k}=", v, sym_assigned_attributes["#{k.to_s.gsub(/_id$/, '')}_type".to_sym])
37
+ end
38
+
39
+ sym_assigned_attributes.
40
+ slice(*self.class::BELONG_ASSOC.map(&:to_sym)).
41
+ each do |k, v|
42
+ public_send("#{k}=", v)
43
+ end
32
44
  end
33
45
 
34
46
  def update(updated_attributes)
@@ -46,24 +58,49 @@ module ActiveShotgun
46
58
  new_entity = new
47
59
  new_entity.mass_assign(create_attributes)
48
60
  new_entity.save
61
+ new_entity
49
62
  end
50
63
 
51
64
  def create!(create_attributes)
52
65
  new_entity = new
53
66
  new_entity.mass_assign(create_attributes)
54
67
  new_entity.save!
68
+ new_entity
55
69
  end
56
70
  end
57
71
 
58
72
  private
59
73
 
60
- def override_attributes!(new_attributes)
74
+ def changes_with_relations
75
+ attribute_changes = changes.slice(*self.class.shotgun_writable_fetched_attributes).transform_values(&:last)
76
+
77
+ belongs_to_changes = changes.slice(*self.class::BELONG_ASSOC.map{ |assoc| "#{assoc}_id" }).map do |k, _v|
78
+ raw = k.gsub(/_id$/, '')
79
+ [
80
+ raw,
81
+ { id: public_send(k), type: public_send("#{raw}_type") },
82
+ ]
83
+ end.to_h
84
+ attribute_changes.merge(belongs_to_changes)
85
+ end
86
+
87
+ def override_attributes!(sg_result)
88
+ new_attributes = sg_result.attributes.to_h.merge(id: sg_result.id)
61
89
  new_attributes.
62
90
  transform_keys(&:to_sym).
63
91
  slice(*self.class.shotgun_readable_fetched_attributes).
64
92
  each do |k, v|
65
93
  instance_variable_set("@#{k}", v)
66
94
  end
95
+
96
+ new_relations = sg_result.relationships.transform_values{ |v| v["data"] }.with_indifferent_access
97
+ self.class::BELONG_ASSOC.each do |assoc|
98
+ next unless relation = new_relations[assoc]
99
+
100
+ instance_variable_set("@#{assoc}_type", relation["type"])
101
+ instance_variable_set("@#{assoc}_id", relation["id"])
102
+ instance_variable_set("@#{assoc}", nil)
103
+ end
67
104
  end
68
105
  end
69
106
  end
@@ -4,7 +4,7 @@ module ActiveShotgun
4
4
  class Query
5
5
  def initialize(type:, klass:)
6
6
  @type = type
7
- @klass = klass
7
+ @klass = klass.is_a?(String) ? klass.constantize : klass
8
8
  @conditions = nil
9
9
  @limit = 100
10
10
  @offset = 0
@@ -15,7 +15,9 @@ module ActiveShotgun
15
15
  extend Forwardable
16
16
  def_delegators(
17
17
  :to_a,
18
- :each
18
+ :each,
19
+ :size,
20
+ :count
19
21
  )
20
22
  include Enumerable
21
23
 
@@ -31,7 +33,7 @@ module ActiveShotgun
31
33
  results.pop(page_calc[:end_trim])
32
34
  results.shift(page_calc[:start_trim])
33
35
 
34
- results.map{ |result| @klass.new(result.attributes.to_h.merge(id: result.id)) }
36
+ results.map{ |result| @klass.parse_shotgun_results(result) }
35
37
  end
36
38
  alias_method :to_a, :all
37
39
 
@@ -55,7 +57,40 @@ module ActiveShotgun
55
57
  end
56
58
 
57
59
  def where(conditions)
58
- @conditions = (@conditions || {}).merge(conditions)
60
+ @conditions ||= {}
61
+ @conditions =
62
+ case conditions
63
+ when Hash
64
+ case @conditions
65
+ when Hash
66
+ @conditions.merge(conditions)
67
+ when Array
68
+ @conditions + translate_hash_contitions_to_array(conditions)
69
+ else
70
+ raise "Unknow type. Please use Hash or Array conditions"
71
+ end
72
+ when Array
73
+ case @conditions
74
+ when Hash
75
+ translate_hash_contitions_to_array(@conditions) +
76
+ if conditions.first.is_a? Array
77
+ conditions
78
+ else
79
+ [conditions]
80
+ end
81
+ when Array
82
+ @conditions +
83
+ if conditions.first.is_a? Array
84
+ conditions
85
+ else
86
+ [conditions]
87
+ end
88
+ else
89
+ raise "Unknow type. Please use Hash or Array conditions"
90
+ end
91
+ else
92
+ @conditions
93
+ end
59
94
  self
60
95
  end
61
96
 
@@ -81,6 +116,12 @@ module ActiveShotgun
81
116
 
82
117
  private
83
118
 
119
+ def translate_hash_contitions_to_array(hash_conditions)
120
+ hash_conditions.map do |k, v|
121
+ [k, "is", v]
122
+ end
123
+ end
124
+
84
125
  def format_page_from_limit_and_offset(limit, offset)
85
126
  min = (offset + 1).to_f
86
127
  max = (offset + limit).to_f
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveShotgun
4
- VERSION = "0.0.3"
4
+ VERSION = "0.0.4"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_shotgun
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis <Zaratan> Pasin
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-12-16 00:00:00.000000000 Z
11
+ date: 2020-12-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -263,9 +263,11 @@ files:
263
263
  - bin/console
264
264
  - bin/setup
265
265
  - lib/active_shotgun.rb
266
+ - lib/active_shotgun/associations_proxy.rb
266
267
  - lib/active_shotgun/client.rb
267
268
  - lib/active_shotgun/config.rb
268
269
  - lib/active_shotgun/model.rb
270
+ - lib/active_shotgun/model/associations.rb
269
271
  - lib/active_shotgun/model/callbacks.rb
270
272
  - lib/active_shotgun/model/delete.rb
271
273
  - lib/active_shotgun/model/read.rb