atomic_arrays 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +111 -3
- data/atomic_arrays.gemspec +6 -6
- data/lib/atomic_arrays.rb +2 -2
- data/lib/atomic_arrays/version.rb +1 -1
- data/spec/spec_helper.rb +7 -1
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6dde301579229402102122bbeeb2860a6841d575
|
4
|
+
data.tar.gz: 61f5d0549fb1afcd43ec1106d3ec34592b3f3e7b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8f5905101324c9b1d0d22a8af4039fda7746b6b105c34be0a16c3362b995fe474320ca7bada3d7082f276816da2958a7e4148b3a5d71305e3c15502f13882378
|
7
|
+
data.tar.gz: 6be556c640a7b4b5cedd1ffccb7a93f8dd5e94368476edd0a1a6def30951acb939369fe2caf3c5889593e1d61814022dd396abaafe0fc4eb6f74c529bab5a4b2
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# AtomicArrays
|
2
2
|
|
3
|
-
|
3
|
+
AtomicArrays is a lightweight gem that aims to assist ActiveRecord with PostgreSQL array operations by offering a couple simple methods to update arrays in the database and the instance that it is called on. These methods are atomic in nature because they update the arrays in the database without relying on the current object's instantiated arrays.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -17,12 +17,120 @@ Or install it yourself as:
|
|
17
17
|
$ gem install atomic_arrays
|
18
18
|
|
19
19
|
## Usage
|
20
|
+
This gem is very simple to use. After bundling the gem, include it in your ActiveRecord-descended class. Example:
|
21
|
+
```ruby
|
22
|
+
class User < ActiveRecord::Base
|
23
|
+
include AtomicArrays
|
24
|
+
end
|
25
|
+
```
|
26
|
+
Make sure that you have specified the array field in your migrations. Example:
|
27
|
+
```ruby
|
28
|
+
class CreateUsers < ActiveRecord::Migration
|
29
|
+
def change
|
30
|
+
create_table :users, force: true do |t|
|
31
|
+
t.string :name
|
32
|
+
t.text :hobbies, array: true, default: [] # This is an array of strings
|
33
|
+
t.integer :comment_ids, array: true, default: [] # This is an array of ints
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
This will give you a couple of instance methods used in updating and relating arrays. The first argument of each method is the targeted array column and the second is the value/values.
|
40
|
+
|
41
|
+
### Appending
|
42
|
+
Method `atomic_append(array_column, value)` will take a single value to append it on to the end of the specified PG array. Example:
|
43
|
+
```ruby
|
44
|
+
user = User.find(1)
|
45
|
+
# => <#User id: 1, hobbies: ["Basketball", "Racing"]>
|
46
|
+
user.atomic_append(:hobbies, "Eating")
|
47
|
+
# => <#User id: 1, hobbies: ["Basketball", "Racing", "Eating"]> # "Eating" was appended to the array in the db.
|
48
|
+
```
|
49
|
+
|
50
|
+
### Removing
|
51
|
+
Method `atomic_remove(array_column, value)` will remove a single value from the specified PG array. It should be noted that the PG array "remove" function removes ALL occurences of that value, therefore this method does as well. Example:
|
52
|
+
```ruby
|
53
|
+
user = User.find(2)
|
54
|
+
# => <#User id: 2, friend_ids: [12, 34, 89]>
|
55
|
+
user.atomic_remove(:friend_ids, 12)
|
56
|
+
# => <#User id: 2, friend_ids: [34, 89]> # 12 was removed from the array in the db.
|
57
|
+
```
|
58
|
+
|
59
|
+
### Concatenation
|
60
|
+
Method `atomic_cat(array_column, value_array)` will concatenate an array of values with the specified PG array. Example:
|
61
|
+
```ruby
|
62
|
+
user = User.find(2)
|
63
|
+
# => <#User id: 2, friend_ids: [34, 89]>
|
64
|
+
user.atomic_cat(:friend_ids, [34, 30, 56, 90])
|
65
|
+
# => <#User id: 2, friend_ids: [34, 89, 34, 30, 56, 90]> # All four values were concatenated with the array in the db.
|
66
|
+
```
|
67
|
+
|
68
|
+
### Relating
|
69
|
+
Method `atomic_relate(array_column, related_class, limit=100)` is a little odd and unorthodox with a relational db. It assists with querying a denormalized database that uses arrays. Let's say your `users` table has an array column called `blog_ids` and you also have a `blogs` table with each row having an id, like normal. Every time a `User` creates a blog, you could append that blog's id to your user's `blog_ids` column. When relating your user to his/her blogs (`one->many`), rather than scanning the `blogs`.`user_id` column for your user's id, you could potentially just use this method to grab all of his/her blogs in a single query, without scanning a table. First, make sure `AtomicArrays` is included in both classes, then it'll be ready to go! Example:
|
70
|
+
```ruby
|
71
|
+
user = User.find(2)
|
72
|
+
# => <#User id: 2, blog_ids: [4, 16, 74]>
|
73
|
+
user.atomic_relate(:blog_ids, Blog)
|
74
|
+
# => [
|
75
|
+
# <#Blog id: 4, body: "This is my blog!">,
|
76
|
+
# <#Blog id: 16, body: "This is my other blog!">,
|
77
|
+
# <#Blog id: 74, body: "This is my third blog!">
|
78
|
+
# ]
|
79
|
+
```
|
80
|
+
This method is extremely performant, especially with large tables because it uses a subquery to grab all of the user's `blog_ids` then immediately `unnests` the ids `IN` the primary id key of the `blogs` table. The subquery that this method employs has nearly zero overhead on performance. The power of this method really reveals itself with (`many->many`) relationships. For instance, let's say each `Blog` has many authors and each `User` authors many blogs. Instead of having a `blog_users` join table, you can potentially just store all of the blogs' `user_ids` in one of its columns and the users' `blog_ids` on one of their columns. Then you could relate them by using `atomic_relate`.
|
81
|
+
|
82
|
+
While denormalizing using arrays may sound like an excellent performance prospect, there are a couple downsides. For instance, with the aformentioned (`many->many`) relationship, you will not be able to store any other columns normally associated with a join table, such as an `updated_at` timestamp. Another downside is that arrays are much harder to query than a join table, even with a GIN index. It should also be noted that PostgreSQL still lacks many features involving arrays, including foreign ids. Arrays should NOT be seen as a direct replacement for (`x->many`) tables/keys, but rather a very performant solution if your database NEEDS to be denormalized.
|
83
|
+
|
84
|
+
|
85
|
+
## Expound on this gem's assistance with atomicity.
|
86
|
+
So be it! All methods in this gem share the same first argument. When you pass the array column name as the first argument, such as `user.atomic_append(:sports, "Golf")`, it doesn't call the instance's attribute with that name, but rather ignores it, updates the array in the database, then updates the instance's array with the returned columns. What does this mean?
|
87
|
+
|
88
|
+
Here's an example of nonatomic arrays. Pretend the code on the left and right are happening at the same time:
|
89
|
+
```ruby
|
90
|
+
user = User.find(2) | user = User.find(2)
|
91
|
+
# => <#User id: 2, blog_ids: [4, 16]> | # => <#User id: 2, blog_ids: [4, 16]>
|
92
|
+
user.update({blog_ids: user.blog_ids+=[20]}) | ...
|
93
|
+
# => <#User id: 2, blog_ids: [4, 16, 20]> | ...
|
94
|
+
... | user.update({blog_ids: user.blog_ids+=[35]})
|
95
|
+
... | # => <#User id: 2, blog_ids: [4, 16, 35]>
|
96
|
+
```
|
97
|
+
The same user was being updated on both the left and right, and because the instance on the right side was updated last, it over-wrote the left side's added `blog_id` of `20` with its own `blog_id` update of `35`.
|
98
|
+
|
99
|
+
Here's how this gem works in the same situation.
|
100
|
+
```ruby
|
101
|
+
user = User.find(2) | user = User.find(2)
|
102
|
+
# => <#User id: 2, blog_ids: [4, 16]> | # => <#User id: 2, blog_ids: [4, 16]>
|
103
|
+
user.atomic_append(:blog_ids, 20) | ...
|
104
|
+
# => <#User id: 2, blog_ids: [4, 16, 20]> | ...
|
105
|
+
... | user.atomic_append(:blog_ids, 35)
|
106
|
+
... | # => <#User id: 2, blog_ids: [4, 16, 20, 35]>
|
107
|
+
```
|
108
|
+
The user's `blog_ids` will now include both `20` and `35` because this gem's methods append the value to the raw data array in the db first, then return the rows and re-hydrate the instance.
|
109
|
+
|
110
|
+
## Releases
|
111
|
+
|
112
|
+
`1.0.0` - Initial release.
|
113
|
+
|
114
|
+
`1.1.0` - Replaced `IN` with `JOIN` clause for `atomic_relate`, providing much better performance with large arrays.
|
115
|
+
|
116
|
+
|
117
|
+
## Etcetera
|
118
|
+
|
119
|
+
Apologies for any syntax highlighting or grammar issues above.
|
120
|
+
|
121
|
+
There is also a class method that this gem uses internally called `execute_and_wrap`. It was heavily influenced by `find_by_sql` in ActiveRecord, so thank you to the Rails guys.
|
122
|
+
|
123
|
+
This gem is focused on being both lightweight and performance-oriented. The entire gem is only about fifty lines of actual code. I tried to make the API as simple and predictable as possible. It was tested against Ruby-2.1.0, ActiveRecord 4.0.x, and Postgres 9.3. If you are looking to use the JRuby-AR adapter, this gem is very easy to replicate and modify to fit with the JRuby-AR adapter. I tried it with an earlier iteration of this gem and had no problems adapting it, but I have not tested this version of the gem with JRuby.
|
124
|
+
|
125
|
+
This gem is especially powerful if your favorite animal is either a Unicorn or a Puma.
|
126
|
+
|
127
|
+
If you find any issues or have any suggestions to improve this gem, open an issue!
|
128
|
+
|
20
129
|
|
21
|
-
TODO: Write usage instructions here
|
22
130
|
|
23
131
|
## Contributing
|
24
132
|
|
25
|
-
1. Fork it ( https://github.com/
|
133
|
+
1. Fork it ( https://github.com/twincharged/atomic_arrays/fork )
|
26
134
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
135
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
136
|
4. Push to the branch (`git push origin my-new-feature`)
|
data/atomic_arrays.gemspec
CHANGED
@@ -8,12 +8,12 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = AtomicArrays::VERSION
|
9
9
|
spec.authors = ["Joseph"]
|
10
10
|
spec.email = ["joseph.cs.ritchie@gmail.com"]
|
11
|
-
spec.summary = %q{ActiveRecord extension for
|
12
|
-
spec.description = %q{AtomicArrays aims to assist ActiveRecord
|
13
|
-
by offering a couple simple methods
|
14
|
-
and the instance it is called on.
|
15
|
-
|
16
|
-
object's instantiated arrays.}
|
11
|
+
spec.summary = %q{ActiveRecord extension for atomic operations on PostgreSQL arrays.}
|
12
|
+
spec.description = %q{AtomicArrays is a lightweight gem that aims to assist ActiveRecord
|
13
|
+
with PostgreSQL array operations by offering a couple simple methods
|
14
|
+
to update arrays in the database and the instance that it is called on.
|
15
|
+
These methods are atomic in nature because they update the arrays in
|
16
|
+
the database without relying on the current object's instantiated arrays.}
|
17
17
|
spec.homepage = "https://github.com/twincharged/atomic_arrays"
|
18
18
|
spec.license = "MIT"
|
19
19
|
|
data/lib/atomic_arrays.rb
CHANGED
@@ -30,7 +30,7 @@ module AtomicArrays
|
|
30
30
|
raise "Relates to a class, not a string or integer." if (related_class.is_a?(Integer) || related_class.is_a?(String))
|
31
31
|
(table, field) = self.prepare_array_query(field)
|
32
32
|
related_table = related_class.table_name.inspect
|
33
|
-
return result = related_class.execute_and_wrap(%Q{SELECT #{related_table}.* FROM #{related_table}
|
33
|
+
return result = related_class.execute_and_wrap(%Q{SELECT #{related_table}.* FROM #{related_table} JOIN (SELECT unnest(#{table}.#{field}) AS id FROM #{table} WHERE #{table}.id = #{self.id}) u USING (id) LIMIT #{limit}})
|
34
34
|
end
|
35
35
|
|
36
36
|
def execute_array_query(field, value, array_method)
|
@@ -47,7 +47,7 @@ module AtomicArrays
|
|
47
47
|
|
48
48
|
def prepare_array_vals(value)
|
49
49
|
prep_array = []
|
50
|
-
[*value].map {|val| val = "\'#{val}\'" if val.class == String; prep_array.push(val)}
|
50
|
+
[*value].map {|val| val = "\'#{val}\'" if val.class == String; prep_array.push(val)}
|
51
51
|
return prep_array.join(", ")
|
52
52
|
end
|
53
53
|
|
data/spec/spec_helper.rb
CHANGED
@@ -4,7 +4,11 @@ require "rspec"
|
|
4
4
|
require "yaml"
|
5
5
|
|
6
6
|
ActiveRecord::Base.establish_connection(YAML.load_file(File.expand_path("../db/database.yml", __FILE__))["test"])
|
7
|
-
|
7
|
+
|
8
|
+
RSpec.configure do |config|
|
9
|
+
config.color = true
|
10
|
+
config.tty = true
|
11
|
+
end
|
8
12
|
|
9
13
|
|
10
14
|
class User < ActiveRecord::Base
|
@@ -16,6 +20,8 @@ class Comment < ActiveRecord::Base
|
|
16
20
|
include AtomicArrays
|
17
21
|
end
|
18
22
|
|
23
|
+
# Seed
|
24
|
+
|
19
25
|
Comment.create({id: 1, user_id: 1, body: "from user 1!!!", tags: ["#fun"]})
|
20
26
|
Comment.create({id: 2, user_id: 2, body: "from user 2!!!", liker_ids: [4,5]})
|
21
27
|
Comment.create({id: 3, user_id: 3, body: "from user 2!!!", tags: ["#fun", "#coolpostbro"]})
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: atomic_arrays
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joseph
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-07-
|
11
|
+
date: 2014-07-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -81,11 +81,11 @@ dependencies:
|
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '4.0'
|
83
83
|
description: |-
|
84
|
-
AtomicArrays aims to assist ActiveRecord
|
85
|
-
by offering a couple simple methods
|
86
|
-
and the instance it is called on.
|
87
|
-
|
88
|
-
object's instantiated arrays.
|
84
|
+
AtomicArrays is a lightweight gem that aims to assist ActiveRecord
|
85
|
+
with PostgreSQL array operations by offering a couple simple methods
|
86
|
+
to update arrays in the database and the instance that it is called on.
|
87
|
+
These methods are atomic in nature because they update the arrays in
|
88
|
+
the database without relying on the current object's instantiated arrays.
|
89
89
|
email:
|
90
90
|
- joseph.cs.ritchie@gmail.com
|
91
91
|
executables: []
|
@@ -128,7 +128,7 @@ rubyforge_project:
|
|
128
128
|
rubygems_version: 2.2.2
|
129
129
|
signing_key:
|
130
130
|
specification_version: 4
|
131
|
-
summary: ActiveRecord extension for
|
131
|
+
summary: ActiveRecord extension for atomic operations on PostgreSQL arrays.
|
132
132
|
test_files:
|
133
133
|
- spec/atomic_arrays_spec.rb
|
134
134
|
- spec/db/001_create_users.rb
|