activeitem 0.0.22 → 0.0.24
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 +4 -4
- data/CHANGELOG.md +41 -0
- data/README.md +44 -0
- data/lib/active_item/base.rb +81 -7
- data/lib/active_item/begins_with.rb +18 -0
- data/lib/active_item/query_helpers.rb +22 -0
- data/lib/active_item/relation.rb +23 -50
- data/lib/active_item/version.rb +1 -1
- data/lib/activeitem.rb +6 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 296b80f6ec0f16823c99df6d3a041fe16d55eab4ce59118d95081566f13e01d4
|
|
4
|
+
data.tar.gz: 8ec0e73b20698346c67f96075c0ccc3070f2f6232c1e1b3607a61424c1a19f8b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8b5d8087d0cffede8296535868978d6dd24cddc70d1f6a1ba0741db31d5fc46caaea9635791f8984166aa8de92bfb50db000ecc935df4fe6498ecc2456fc1029
|
|
7
|
+
data.tar.gz: ef5ca8b2bb01a83a5673d0181d13627d5eb0b8bb01612f6e6ef146c5e4eb669cfd2ef0936c1aa09b15e6a5eed2367d7b1adb3c844c7e8aa4c72e7dc600201624
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.0.24
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`ActiveItem.begins_with` for prefix queries** — Pass it as a `where` value. On an index's sort key it goes into the key condition (`begins_with(#sk, :sk_val)`), so DynamoDB reads only the matching items instead of scanning. On any other attribute it becomes a `begins_with` filter, and `where.not` gives `NOT begins_with(...)`. Works with `limit`, `order`, `count`, `explain`, and array (fan-out) partition keys.
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
# GSI PrefixIndex: partition = first 2 letters, sort = whole name (both lowercased)
|
|
11
|
+
Username.where(search_prefix: 'ab', search_name: ActiveItem.begins_with('abe')).limit(10)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- The equality / Range / prefix sort-key condition is now built in one place (`build_sort_key_condition`) instead of five copies in `Relation`.
|
|
17
|
+
|
|
18
|
+
## 0.0.23
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **`read_attribute` / `write_attribute`** — ActiveItem now exposes the same attribute primitives as ActiveRecord. `write_attribute(:name, value)` records the change for dirty tracking; `read_attribute(:name)` returns the current value. Custom readers/writers can route through these instead of touching the ivar directly.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **Custom attribute writers now persist on update, Rails-style** — Overriding a generated writer to coerce or normalize a value no longer silently drops the change. Generated readers/writers are now defined in an included module (like Rails' `GeneratedAttributeMethods`), so a hand-written `def foo=` can call `super` and get dirty tracking for free. Previously, a custom writer that assigned `@foo` directly left the attribute out of the changeset — `save` returned `true` with no error, but the update wrote nothing. Both `super` and `write_attribute` are now supported.
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
class Article < ActiveItem::Base
|
|
30
|
+
attr_accessor :tags, :payload
|
|
31
|
+
|
|
32
|
+
# Coerce to an Array, Rails-style: override and call super.
|
|
33
|
+
def tags=(value)
|
|
34
|
+
super(Array(value))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Or be explicit with write_attribute.
|
|
38
|
+
def payload=(value)
|
|
39
|
+
write_attribute(:payload, value.is_a?(String) ? value : JSON.generate(value))
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
3
44
|
## 0.0.22
|
|
4
45
|
|
|
5
46
|
### Changed
|
data/README.md
CHANGED
|
@@ -75,6 +75,18 @@ User.count
|
|
|
75
75
|
User.exists?('user-123')
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
+
Sort-key conditions on an index go into the key condition, not a filter:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
Event.where(customer_id: id, created_at: 1.week.ago..) # >=, <=, BETWEEN via Range
|
|
82
|
+
Username.where(search_prefix: 'ab', search_name: ActiveItem.begins_with('abe')) # prefix
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
For prefix search (autocomplete), add a GSI whose partition key is the first few
|
|
86
|
+
lowercased characters and whose sort key is the whole lowercased value, then
|
|
87
|
+
`begins_with` on the sort key. `begins_with` on a non-key attribute works too, but
|
|
88
|
+
it's a filter, so DynamoDB still reads every item in the query or scan.
|
|
89
|
+
|
|
78
90
|
### Associations
|
|
79
91
|
|
|
80
92
|
```ruby
|
|
@@ -114,6 +126,38 @@ class Customer < ActiveItem::Base
|
|
|
114
126
|
end
|
|
115
127
|
```
|
|
116
128
|
|
|
129
|
+
### Custom Attribute Writers
|
|
130
|
+
|
|
131
|
+
Like ActiveRecord, you can override a generated writer to coerce or normalize a
|
|
132
|
+
value on assignment. Just route the assignment back through the generated
|
|
133
|
+
writer — call `super`, or use `write_attribute`. Both record the change for
|
|
134
|
+
dirty tracking, so `save` (and `update`) persist it. Assigning `@ivar` directly
|
|
135
|
+
bypasses dirty tracking and the change is silently dropped on update.
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
class Article < ActiveItem::Base
|
|
139
|
+
attr_accessor :tags, :payload
|
|
140
|
+
|
|
141
|
+
# Coerce to an Array, Rails-style: override and call super.
|
|
142
|
+
def tags=(value)
|
|
143
|
+
super(Array(value))
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Or be explicit with write_attribute.
|
|
147
|
+
def payload=(value)
|
|
148
|
+
write_attribute(:payload, value.is_a?(String) ? value : JSON.generate(value))
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`read_attribute(:name)` is available too, for custom readers:
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
def display_name
|
|
157
|
+
read_attribute(:name).to_s.strip
|
|
158
|
+
end
|
|
159
|
+
```
|
|
160
|
+
|
|
117
161
|
## License
|
|
118
162
|
|
|
119
163
|
MIT
|
data/lib/active_item/base.rb
CHANGED
|
@@ -100,15 +100,33 @@ module ActiveItem
|
|
|
100
100
|
|
|
101
101
|
define_attribute_methods attr_name
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
# Define the generated reader/writer in a dedicated module that is
|
|
104
|
+
# included into the model, rather than directly on the class. This
|
|
105
|
+
# mirrors Rails' GeneratedAttributeMethods: a hand-written
|
|
106
|
+
# `def foo=` on the model sits *above* the generated writer in the
|
|
107
|
+
# ancestor chain, so a custom writer can call `super` (or
|
|
108
|
+
# `write_attribute`) to get dirty tracking for free.
|
|
109
|
+
generated_attribute_methods.module_eval do
|
|
110
|
+
define_method(attr_name) do
|
|
111
|
+
read_attribute(attr_name)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
define_method("#{attr_name}=") do |value|
|
|
115
|
+
write_attribute(attr_name, value)
|
|
116
|
+
end
|
|
105
117
|
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
106
120
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
121
|
+
# Per-class module that holds the generated attribute reader/writer
|
|
122
|
+
# methods. Included once, ahead of ActiveModel in the ancestor chain but
|
|
123
|
+
# behind the model class itself, so user-defined setters can `super`.
|
|
124
|
+
def generated_attribute_methods
|
|
125
|
+
@generated_attribute_methods ||= begin
|
|
126
|
+
mod = Module.new
|
|
127
|
+
include mod
|
|
128
|
+
|
|
129
|
+
mod
|
|
112
130
|
end
|
|
113
131
|
end
|
|
114
132
|
|
|
@@ -490,6 +508,50 @@ module ActiveItem
|
|
|
490
508
|
end
|
|
491
509
|
end
|
|
492
510
|
|
|
511
|
+
# Read the current value of an attribute by name.
|
|
512
|
+
#
|
|
513
|
+
# This is the Rails-like primitive that generated readers delegate to.
|
|
514
|
+
# Custom readers can call it (or `super`) instead of poking at the ivar
|
|
515
|
+
# directly.
|
|
516
|
+
#
|
|
517
|
+
# def display_name
|
|
518
|
+
# read_attribute(:name).to_s.strip
|
|
519
|
+
# end
|
|
520
|
+
#
|
|
521
|
+
# @param attr_name [String, Symbol] the attribute name
|
|
522
|
+
# @return the attribute's current value
|
|
523
|
+
def read_attribute(attr_name)
|
|
524
|
+
instance_variable_get("@#{attr_name}")
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
# Write an attribute value, recording it for dirty tracking so `save`
|
|
528
|
+
# actually persists it.
|
|
529
|
+
#
|
|
530
|
+
# This is the single place dirty tracking happens for attributes, mirroring
|
|
531
|
+
# Rails' `write_attribute`. Generated writers delegate here, which means a
|
|
532
|
+
# custom writer gets dirty tracking for free by going through either
|
|
533
|
+
# `super` or `write_attribute` directly:
|
|
534
|
+
#
|
|
535
|
+
# # coerce/normalize on assignment, Rails-style
|
|
536
|
+
# def tags=(value)
|
|
537
|
+
# super(Array(value)) # -> generated writer -> write_attribute
|
|
538
|
+
# end
|
|
539
|
+
#
|
|
540
|
+
# # or be explicit
|
|
541
|
+
# def payload=(value)
|
|
542
|
+
# write_attribute(:payload, value.is_a?(String) ? value : JSON.generate(value))
|
|
543
|
+
# end
|
|
544
|
+
#
|
|
545
|
+
# @param attr_name [String, Symbol] the attribute name
|
|
546
|
+
# @param value the value to assign
|
|
547
|
+
# @return the assigned value
|
|
548
|
+
def write_attribute(attr_name, value)
|
|
549
|
+
attr_name = attr_name.to_s
|
|
550
|
+
old_value = instance_variable_get("@#{attr_name}")
|
|
551
|
+
mark_attribute_will_change(attr_name, old_value, value)
|
|
552
|
+
instance_variable_set("@#{attr_name}", value)
|
|
553
|
+
end
|
|
554
|
+
|
|
493
555
|
def attribute_changed?(attr_name)
|
|
494
556
|
super(attr_name.to_s)
|
|
495
557
|
end
|
|
@@ -511,6 +573,18 @@ module ActiveItem
|
|
|
511
573
|
|
|
512
574
|
private
|
|
513
575
|
|
|
576
|
+
# Record a pending change for dirty tracking. Guarded with `respond_to?`
|
|
577
|
+
# so it degrades gracefully for attributes that weren't declared via
|
|
578
|
+
# `attr_accessor` (and under test harnesses that stub the ORM), and skips
|
|
579
|
+
# re-recording an original value that's already been captured.
|
|
580
|
+
def mark_attribute_will_change(attr_name, old_value, new_value)
|
|
581
|
+
return if old_value == new_value
|
|
582
|
+
return if changed_attributes.key?(attr_name)
|
|
583
|
+
return unless respond_to?("#{attr_name}_will_change!", true)
|
|
584
|
+
|
|
585
|
+
send("#{attr_name}_will_change!")
|
|
586
|
+
end
|
|
587
|
+
|
|
514
588
|
# Enroll this record's save operation in the current transaction.
|
|
515
589
|
# Called when save is invoked inside a transaction block.
|
|
516
590
|
def enroll_in_transaction
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveItem
|
|
4
|
+
# A prefix match for `where`, built with ActiveItem.begins_with.
|
|
5
|
+
#
|
|
6
|
+
# On an index's sort key it becomes part of the key condition, so DynamoDB
|
|
7
|
+
# reads only the matching items. On any other attribute it is a filter.
|
|
8
|
+
#
|
|
9
|
+
# @example Prefix search on a GSI (partition = first letters, sort = whole name)
|
|
10
|
+
# Username.where(search_prefix: 'ab', search_name: ActiveItem.begins_with('abc')).limit(10)
|
|
11
|
+
BeginsWith = Data.define(:prefix) do
|
|
12
|
+
def initialize(prefix:)
|
|
13
|
+
raise ArgumentError, 'begins_with needs a non-empty prefix' if prefix.to_s.empty?
|
|
14
|
+
|
|
15
|
+
super(prefix: prefix.to_s)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -411,6 +411,7 @@ module ActiveItem
|
|
|
411
411
|
# - Range values (BETWEEN): { date: Date.today..Date.today + 7 }
|
|
412
412
|
# - Beginless range (<=): { date: ..Date.today }
|
|
413
413
|
# - Endless range (>=): { date: Date.today.. }
|
|
414
|
+
# - Prefix match: { name: ActiveItem.begins_with('ab') }
|
|
414
415
|
# - Model objects: { parent_container: container } -> uses container.id
|
|
415
416
|
#
|
|
416
417
|
# Note: Attribute names are converted from Ruby snake_case to DynamoDB camelCase
|
|
@@ -452,6 +453,9 @@ module ActiveItem
|
|
|
452
453
|
# Handle Range values (BETWEEN, >=, <=)
|
|
453
454
|
return build_range_condition(dynamo_attr, val, idx) if val.is_a?(Range)
|
|
454
455
|
|
|
456
|
+
# Handle prefix match (begins_with)
|
|
457
|
+
return ["begins_with(#attr#{idx}, :val#{idx})", { "#attr#{idx}" => dynamo_attr }, { ":val#{idx}" => val.prefix }] if val.is_a?(ActiveItem::BeginsWith)
|
|
458
|
+
|
|
455
459
|
# Handle array values (IN clause)
|
|
456
460
|
return build_in_condition(dynamo_attr, val, idx) if val.is_a?(Array)
|
|
457
461
|
|
|
@@ -596,6 +600,24 @@ module ActiveItem
|
|
|
596
600
|
end
|
|
597
601
|
end
|
|
598
602
|
|
|
603
|
+
# Build the sort key part of a GSI key condition: equality, a Range
|
|
604
|
+
# (BETWEEN, >=, <=), or ActiveItem.begins_with.
|
|
605
|
+
def build_sort_key_condition(sort_key, value, placeholder_prefix = 'sk')
|
|
606
|
+
return build_sort_key_range_condition(sort_key, value, placeholder_prefix) if value.is_a?(Range)
|
|
607
|
+
|
|
608
|
+
expression = if value.is_a?(ActiveItem::BeginsWith)
|
|
609
|
+
value = value.prefix
|
|
610
|
+
"begins_with(##{placeholder_prefix}, :#{placeholder_prefix}_val)"
|
|
611
|
+
else
|
|
612
|
+
"##{placeholder_prefix} = :#{placeholder_prefix}_val"
|
|
613
|
+
end
|
|
614
|
+
{
|
|
615
|
+
expression: expression,
|
|
616
|
+
names: { "##{placeholder_prefix}" => sort_key },
|
|
617
|
+
values: { ":#{placeholder_prefix}_val" => value }
|
|
618
|
+
}
|
|
619
|
+
end
|
|
620
|
+
|
|
599
621
|
# Build sort key condition for Range values in GSI queries
|
|
600
622
|
# Returns the key condition expression part and values for sort key
|
|
601
623
|
def build_sort_key_range_condition(sort_key, range, placeholder_prefix = 'sk')
|
data/lib/active_item/relation.rb
CHANGED
|
@@ -660,16 +660,10 @@ module ActiveItem
|
|
|
660
660
|
resolved_model.to_dynamo_key(k.to_s) == sort_key || k.to_s == sort_key
|
|
661
661
|
end
|
|
662
662
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
params[:expression_attribute_values].merge!(range_condition[:values])
|
|
668
|
-
else
|
|
669
|
-
params[:key_condition_expression] += ' AND #sk = :sk_val'
|
|
670
|
-
params[:expression_attribute_names]['#sk'] = sort_key
|
|
671
|
-
params[:expression_attribute_values][':sk_val'] = sort_value
|
|
672
|
-
end
|
|
663
|
+
key_condition = resolved_model.send(:build_sort_key_condition, sort_key, sort_value)
|
|
664
|
+
params[:key_condition_expression] += " AND #{key_condition[:expression]}"
|
|
665
|
+
params[:expression_attribute_names].merge!(key_condition[:names])
|
|
666
|
+
params[:expression_attribute_values].merge!(key_condition[:values])
|
|
673
667
|
end
|
|
674
668
|
end
|
|
675
669
|
|
|
@@ -906,16 +900,10 @@ module ActiveItem
|
|
|
906
900
|
resolved_model.to_dynamo_key(k.to_s) == sort_key || k.to_s == sort_key
|
|
907
901
|
end
|
|
908
902
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
params[:expression_attribute_values].merge!(range_condition[:values])
|
|
914
|
-
else
|
|
915
|
-
params[:key_condition_expression] += ' AND #sk = :sk_val'
|
|
916
|
-
params[:expression_attribute_names]['#sk'] = sort_key
|
|
917
|
-
params[:expression_attribute_values][':sk_val'] = sort_value
|
|
918
|
-
end
|
|
903
|
+
key_condition = resolved_model.send(:build_sort_key_condition, sort_key, sort_value)
|
|
904
|
+
params[:key_condition_expression] += " AND #{key_condition[:expression]}"
|
|
905
|
+
params[:expression_attribute_names].merge!(key_condition[:names])
|
|
906
|
+
params[:expression_attribute_values].merge!(key_condition[:values])
|
|
919
907
|
end
|
|
920
908
|
end
|
|
921
909
|
|
|
@@ -1020,16 +1008,10 @@ module ActiveItem
|
|
|
1020
1008
|
resolved_model.to_dynamo_key(k.to_s) == sort_key || k.to_s == sort_key
|
|
1021
1009
|
end
|
|
1022
1010
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
params[:expression_attribute_values].merge!(range_condition[:values])
|
|
1028
|
-
else
|
|
1029
|
-
params[:key_condition_expression] += ' AND #sk = :sk_val'
|
|
1030
|
-
params[:expression_attribute_names]['#sk'] = sort_key
|
|
1031
|
-
params[:expression_attribute_values][':sk_val'] = sort_value
|
|
1032
|
-
end
|
|
1011
|
+
key_condition = resolved_model.send(:build_sort_key_condition, sort_key, sort_value)
|
|
1012
|
+
params[:key_condition_expression] += " AND #{key_condition[:expression]}"
|
|
1013
|
+
params[:expression_attribute_names].merge!(key_condition[:names])
|
|
1014
|
+
params[:expression_attribute_values].merge!(key_condition[:values])
|
|
1033
1015
|
end
|
|
1034
1016
|
end
|
|
1035
1017
|
|
|
@@ -1186,16 +1168,10 @@ module ActiveItem
|
|
|
1186
1168
|
resolved_model.to_dynamo_key(k.to_s) == sort_key || k.to_s == sort_key
|
|
1187
1169
|
end
|
|
1188
1170
|
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
params[:expression_attribute_values].merge!(range_condition[:values])
|
|
1194
|
-
else
|
|
1195
|
-
params[:key_condition_expression] += ' AND #sk = :sk_val'
|
|
1196
|
-
params[:expression_attribute_names]['#sk'] = sort_key
|
|
1197
|
-
params[:expression_attribute_values][':sk_val'] = sort_value
|
|
1198
|
-
end
|
|
1171
|
+
key_condition = resolved_model.send(:build_sort_key_condition, sort_key, sort_value)
|
|
1172
|
+
params[:key_condition_expression] += " AND #{key_condition[:expression]}"
|
|
1173
|
+
params[:expression_attribute_names].merge!(key_condition[:names])
|
|
1174
|
+
params[:expression_attribute_values].merge!(key_condition[:values])
|
|
1199
1175
|
end
|
|
1200
1176
|
end
|
|
1201
1177
|
|
|
@@ -1416,16 +1392,10 @@ module ActiveItem
|
|
|
1416
1392
|
return unless sort_condition
|
|
1417
1393
|
|
|
1418
1394
|
_, sort_value = sort_condition
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
params[:expression_attribute_values].merge!(range_condition[:values])
|
|
1424
|
-
else
|
|
1425
|
-
params[:key_condition_expression] += ' AND #sk = :sk_val'
|
|
1426
|
-
params[:expression_attribute_names]['#sk'] = sort_key
|
|
1427
|
-
params[:expression_attribute_values][':sk_val'] = sort_value
|
|
1428
|
-
end
|
|
1395
|
+
key_condition = resolved_model.send(:build_sort_key_condition, sort_key, sort_value)
|
|
1396
|
+
params[:key_condition_expression] += " AND #{key_condition[:expression]}"
|
|
1397
|
+
params[:expression_attribute_names].merge!(key_condition[:names])
|
|
1398
|
+
params[:expression_attribute_values].merge!(key_condition[:values])
|
|
1429
1399
|
end
|
|
1430
1400
|
|
|
1431
1401
|
def sort_fanout_results(items, sort_key)
|
|
@@ -1616,6 +1586,9 @@ module ActiveItem
|
|
|
1616
1586
|
return ["NOT (#attr#{idx} IN (#{placeholders.join(', ')}))", { "#attr#{idx}" => dynamo_attr }, values]
|
|
1617
1587
|
end
|
|
1618
1588
|
|
|
1589
|
+
# NOT begins_with
|
|
1590
|
+
return ["NOT begins_with(#attr#{idx}, :val#{idx})", { "#attr#{idx}" => dynamo_attr }, { ":val#{idx}" => val.prefix }] if val.is_a?(ActiveItem::BeginsWith)
|
|
1591
|
+
|
|
1619
1592
|
# Simple not equal
|
|
1620
1593
|
["#attr#{idx} <> :val#{idx}", { "#attr#{idx}" => dynamo_attr }, { ":val#{idx}" => val }]
|
|
1621
1594
|
end
|
data/lib/active_item/version.rb
CHANGED
data/lib/activeitem.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative 'active_item/configuration'
|
|
|
5
5
|
require_relative 'active_item/logging'
|
|
6
6
|
require_relative 'active_item/errors'
|
|
7
7
|
require_relative 'active_item/database_helpers'
|
|
8
|
+
require_relative 'active_item/begins_with'
|
|
8
9
|
require_relative 'active_item/query_helpers'
|
|
9
10
|
require_relative 'active_item/relation'
|
|
10
11
|
require_relative 'active_item/embedded_collection'
|
|
@@ -32,5 +33,10 @@ module ActiveItem
|
|
|
32
33
|
def logger
|
|
33
34
|
configuration.logger
|
|
34
35
|
end
|
|
36
|
+
|
|
37
|
+
# Prefix match for `where`; see ActiveItem::BeginsWith.
|
|
38
|
+
def begins_with(prefix)
|
|
39
|
+
BeginsWith.new(prefix)
|
|
40
|
+
end
|
|
35
41
|
end
|
|
36
42
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activeitem
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.24
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andy Davis
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2026-
|
|
12
|
+
date: 2026-10-30 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: activemodel
|
|
@@ -157,6 +157,7 @@ files:
|
|
|
157
157
|
- README.md
|
|
158
158
|
- lib/active_item/associations.rb
|
|
159
159
|
- lib/active_item/base.rb
|
|
160
|
+
- lib/active_item/begins_with.rb
|
|
160
161
|
- lib/active_item/composed_of.rb
|
|
161
162
|
- lib/active_item/configuration.rb
|
|
162
163
|
- lib/active_item/database_helpers.rb
|