active_shotgun 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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