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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7f109dc67250882c4425341e1c12ae2abd8d9b80
4
- data.tar.gz: fce753b3c188ca2a2ae971e68af482e2bce202b3
3
+ metadata.gz: 6dde301579229402102122bbeeb2860a6841d575
4
+ data.tar.gz: 61f5d0549fb1afcd43ec1106d3ec34592b3f3e7b
5
5
  SHA512:
6
- metadata.gz: 8e3d5194b39f156b5035e671846f1714a1a4ad85b4b3ee921ebddb2c030aae715381dd24b0bb61aff0858dc09ab38fe04feaf669dc7f23d67a26fc25cd841c97
7
- data.tar.gz: 57fdedc6dbf69f9145c522b9c237ea328c84b845fc83efe87592c6f4e28264497986fd3450c5d4c7507acdddda83f26ac5993471128e65c5ca696a5b8a691283
6
+ metadata.gz: 8f5905101324c9b1d0d22a8af4039fda7746b6b105c34be0a16c3362b995fe474320ca7bada3d7082f276816da2958a7e4148b3a5d71305e3c15502f13882378
7
+ data.tar.gz: 6be556c640a7b4b5cedd1ffccb7a93f8dd5e94368476edd0a1a6def30951acb939369fe2caf3c5889593e1d61814022dd396abaafe0fc4eb6f74c529bab5a4b2
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # AtomicArrays
2
2
 
3
- TODO: Write a gem description
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/[my-github-username]/atomic_arrays/fork )
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`)
@@ -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 atomically updating PostgreSQL arrays.}
12
- spec.description = %q{AtomicArrays aims to assist ActiveRecord with updating Postgres arrays
13
- by offering a couple simple methods to change arrays in both the database
14
- and the instance it is called on. These methods are atomic in nature
15
- because they update the arrays in the database without relying on the current
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
 
@@ -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} WHERE #{related_table}.id IN (SELECT unnest(#{table}.#{field}) FROM #{table} WHERE #{table}.id = #{self.id}) LIMIT #{limit}})
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
 
@@ -1,3 +1,3 @@
1
1
  module AtomicArrays
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -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
- # load File.dirname(__FILE__) + '/migrations.rb'
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.0.0
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-25 00:00:00.000000000 Z
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 with updating Postgres arrays
85
- by offering a couple simple methods to change arrays in both the database
86
- and the instance it is called on. These methods are atomic in nature
87
- because they update the arrays in the database without relying on the current
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 atomically updating PostgreSQL arrays.
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