riak-record 0.1.0 → 0.2.0

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
  SHA1:
3
- metadata.gz: f9cefbb009666dc6e1e326b9e0f27add25fde982
4
- data.tar.gz: 16df6690a2751083ba1bfa157158dd648aeee2e8
3
+ metadata.gz: e51ff6ee221ebec773b72a371ad33d36b67bfd00
4
+ data.tar.gz: 5981fda818b55246e37830a9460c5efe12e66a63
5
5
  SHA512:
6
- metadata.gz: 9dda40575f7149e095a93ff445ac05870d906f4c364faf899fa0ab59fa27e79faf90367f8e8ec1e83989c3f01803ec928b052e791260d65ffa14d089560de061
7
- data.tar.gz: 90d8f57ddc8e236431c472f68d9468ea5f8b91317f9751a9cf22dc5b53dad363606716addd548772e834f8b7a40b4fa131cbccba13b9e8a3777f5a74983b5a99
6
+ metadata.gz: c7261d2c6f4abd75fc86475d3ed9b5b23232478dae3bd98d206e52615e315665e2fc9fffd71e645f1b1ae86f33caab532ac14fb9706cf9debd3aa209583953ee
7
+ data.tar.gz: 758c83cdd23fb36cc4a0f1ab39db09efcd8ddf17698c2a0b771c8a3e9696e3f635d6fdca2533c1d53591a590c5415a132c50324567bedf81ffb2017c74de64bd
data/README.md CHANGED
@@ -15,51 +15,44 @@ RiakRecord::Base.namespace = 'staging' # optional. Namespaces buckets
15
15
 
16
16
  class Post < RiakRecord::Base
17
17
  bucket_name :posts
18
- data_attributes :title, :body, :category
19
- belongs_to :author
20
- has_many :comments
18
+ data_attributes :title, :body
21
19
 
22
20
  index_int_attributes :author_id # author_id_int
23
21
  index_bin_attributes :category # catgory_bin
24
22
  end
25
23
 
26
- class Author < RiakRecord::Base
27
- bucket_name :authors
28
- data_attributes :name
29
- has_many :posts
30
- end
31
-
32
- class Comment < RiakRecord::Base
33
- bucket_name :comments
34
- data_attribute :comment
35
- belongs_to :post
36
-
37
- index_int_attributes :post_id
38
- end
24
+ Post.client #> instance of Riak::Client
25
+ Post.bucket #> instance of Riak::Bucket named "staging:posts"
39
26
 
40
- Author.client #> instance of Riak::Client
41
- Author.bucket #> instance of Riak::Bucket
42
-
43
- author = Author.new(99) # create a new record with id/key 99
44
- author.name = 'Robert' # set an attribute
45
- author.save # store in riak
46
-
47
- Post.find(["my-first-post","a-farewell-to-blogging"]) #> Array of ExampleRecords returned
48
-
49
- post = Post.find("my-first-post") #> Instance of ExampleRecord
27
+ Post.find(["my-first-post","a-farewell-to-blogging"]) #> Array of Posts returned
28
+ post = Post.find("my-first-post") #> Instance of Post
50
29
  post.riak_object #> directly access Riak::RObject
51
30
  post.data #> same as record.riak_object.data
52
31
 
53
32
  post.title = 'My First Post' #> record.riak_object.data['title']=
54
33
  post.title #> record.riak_object.data['title']
55
34
 
56
- post.author = 99 #> record.riak_object.indexes["author_id_int"] = [99]
35
+ post.author_id = 99 #> record.riak_object.indexes["author_id_int"] = [99]
57
36
  post.category = 'ruby' #> record.riak_object.indexes["category_bin"] = ["ruby"]
58
37
  post.save #> record.riak_object.store
38
+ post.new_record? #> false
59
39
  post.reload #> reload the underlying riak_object from the db discarding changes
60
40
 
61
- Author.find(99).posts #> [post]
41
+ ```
42
+
43
+ Callbacks are called in this order:
44
+ * before_save
45
+ * before_create or before_update
46
+ * ...save...
47
+ * after_save
48
+ * after_create or after_update
49
+
62
50
 
51
+ ### RiakRecord::Finder
52
+
53
+ RiakRecord::Finder provides find objects by indexes. Results are loaded in batches as needed.
54
+
55
+ ```ruby
63
56
  finder = Post.where(:category => 'ruby') #> Instance of RiakRecord::Finder
64
57
  finder.count #> 1
65
58
  finder.any? #> true
@@ -69,6 +62,54 @@ finder.each{|e| ... } #> supports all enumerable methods
69
62
  finder.count_by(:author_id) #> {"1" => 1}
70
63
  ```
71
64
 
65
+ ### RiakRecord::Associations
66
+
67
+ RiakRecord supports `has_many` and `belongs_to` associations which are light versions of what you'd expect with ActiveRecord.
68
+
69
+ ```ruby
70
+ class Author < RiakRecord::Base
71
+ bucket_name :authors
72
+ data_attributes :name
73
+ has_many :posts, :class_name => 'Post', :foreign_key => "post_id"
74
+ end
75
+
76
+ class Comment < RiakRecord::Base
77
+ bucket_name :comments
78
+ data_attribute :comment
79
+ belongs_to :post, :class_name => 'Post', :foreign_key => "post_id"
80
+
81
+ index_int_attributes :post_id
82
+ end
83
+
84
+ Author.find(99).posts #> RiakRecord::Finder [post]
85
+ Comment.find(12).author #> an instance of Author
86
+ ```
87
+
88
+ ### RiakRecord::Callbacks
89
+
90
+ RiakRecord supports before and after callbacks for save, create and update. You can prepend or append callbacks. And they can be strings (eval'd), Procs, Objects or Symbols.
91
+
92
+ ```ruby
93
+ class Blog < RiakRecord::Base
94
+ bucket :blogs
95
+ data_attribute :subdomain
96
+
97
+ class BlogCallbacks
98
+ def before_update
99
+ end
100
+ end
101
+
102
+ before_save Proc.new{|record| record.create_subdomain }
103
+ after_create :send_welcome_email
104
+ prepend_after_create "logger('new blog')"
105
+ after_update :send_change_confirmation
106
+ before_update BlogCallbacks.new
107
+
108
+ ...
109
+ end
110
+
111
+ ```
112
+
72
113
  ## Using RiakRecord::Associations in other classes
73
114
 
74
115
  If you're using another data store with Riak, it might be helpful to include Riak's associations in the other class.
data/TODO.md CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  * RiakRecord::Base
4
4
  * links_to should create a walkable link
5
+ * delete
6
+ * new(hash)
7
+ * id_generator
5
8
  * Document methods in classes
6
9
  * Validations support
7
10
  * Validate key is unique (bucket.get_or_new)
8
- * Autogen id/key (snowflake?)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
@@ -3,6 +3,7 @@ require 'riak'
3
3
  module RiakRecord
4
4
  class Base
5
5
  attr_reader :riak_object
6
+ include Callbacks
6
7
  include Associations
7
8
  class << self
8
9
  alias :has_many :has_many_riak
@@ -13,6 +14,7 @@ module RiakRecord
13
14
  unless r.is_a? Riak::RObject
14
15
  r = self.class.bucket.new(r.to_s)
15
16
  r.data = {}
17
+ r.content_type = 'application/json'
16
18
  end
17
19
  @riak_object = r
18
20
  end
@@ -26,10 +28,22 @@ module RiakRecord
26
28
  end
27
29
 
28
30
  def save
31
+ creating = new_record?
32
+
33
+ before_save!
34
+ creating ? before_create! : before_update!
29
35
  riak_object.store(:returnbody => false)
36
+ creating ? after_create! : after_update!
37
+ after_save!
38
+
39
+ @_stored = true
30
40
  self
31
41
  end
32
42
 
43
+ def new_record?
44
+ !(@_stored || riak_object.vclock)
45
+ end
46
+
33
47
  def id
34
48
  riak_object.key
35
49
  end
@@ -66,7 +80,7 @@ module RiakRecord
66
80
  index_names[method_name.to_sym] = "#{method_name}_int"
67
81
 
68
82
  define_method(method_name) do
69
- indexes["#{method_name}_int"]
83
+ indexes["#{method_name}_int"].to_a
70
84
  end
71
85
 
72
86
  define_method("#{method_name}=".to_sym) do |value|
@@ -80,7 +94,7 @@ module RiakRecord
80
94
  index_names[method_name.to_sym] = "#{method_name}_bin"
81
95
 
82
96
  define_method(method_name) do
83
- indexes["#{method_name}_bin"]
97
+ indexes["#{method_name}_bin"].to_a
84
98
  end
85
99
 
86
100
  define_method("#{method_name}=".to_sym) do |value|
@@ -126,7 +140,7 @@ module RiakRecord
126
140
  end
127
141
 
128
142
  def self.namespace_prefixed
129
- self.namespace + ":-:"
143
+ self.namespace + ":"
130
144
  end
131
145
 
132
146
  def self.all_buckets_in_namespace
@@ -0,0 +1,65 @@
1
+ module RiakRecord
2
+ module Callbacks
3
+
4
+ CALLBACK_TRIGGERS = [
5
+ :before_create, :after_create,
6
+ :before_save, :after_save,
7
+ :before_update, :after_update
8
+ ]
9
+
10
+ def call_callbacks!(trigger)
11
+ callbacks = self.class._callbacks(trigger)
12
+ callbacks.each do |callback|
13
+ if callback.is_a? Symbol
14
+ self.send(callback)
15
+ elsif callback.is_a? Proc
16
+ callback.call(self)
17
+ elsif callback.is_a? String
18
+ eval(callback)
19
+ else
20
+ callback.send(trigger, self)
21
+ end
22
+ end
23
+ end
24
+
25
+ CALLBACK_TRIGGERS.each do |trigger|
26
+ define_method("#{trigger}!") do
27
+ call_callbacks!(trigger)
28
+ end
29
+ end
30
+
31
+ module ClassMethods
32
+ def _callbacks(trigger)
33
+ @_callbacks ||= {}
34
+ @_callbacks[trigger] ||= []
35
+ end
36
+
37
+ CALLBACK_TRIGGERS.each do |trigger|
38
+
39
+ ruby = <<-END_OF_RUBY
40
+
41
+ def append_#{trigger}(*args)
42
+ _callbacks(:#{trigger}).concat(args)
43
+ end
44
+
45
+ def #{trigger}(*args)
46
+ append_#{trigger}(*args)
47
+ end
48
+
49
+ def prepend_#{trigger}(*args)
50
+ _callbacks(:#{trigger}).unshift(*args)
51
+ end
52
+
53
+ END_OF_RUBY
54
+
55
+ class_eval ruby
56
+ end
57
+
58
+ end
59
+
60
+ def self.included(base)
61
+ base.extend(ClassMethods)
62
+ end
63
+
64
+ end
65
+ end
@@ -27,7 +27,7 @@ module RiakRecord
27
27
  alias :to_a :all
28
28
 
29
29
  def each
30
- @loaded_objects.each{|o| yield o}
30
+ all.each{|o| yield o}
31
31
  end
32
32
 
33
33
  def count
data/lib/riak_record.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'riak_record/callbacks'
1
2
  require 'riak_record/associations'
2
3
  require 'riak_record/finder'
3
4
  require 'riak_record/base'
Binary file
data/riak-record.gemspec CHANGED
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: riak-record 0.1.0 ruby lib
5
+ # stub: riak-record 0.2.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "riak-record"
9
- s.version = "0.1.0"
9
+ s.version = "0.2.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Robert Graff"]
14
- s.date = "2014-09-23"
14
+ s.date = "2014-09-25"
15
15
  s.description = "RiakRecord is a thin and immature wrapper around riak-ruby-client. It creates a bucket for\n each class, provides a simple finder, and creates attribute reader."
16
16
  s.email = "robert_graff@yahoo.com"
17
17
  s.extra_rdoc_files = [
@@ -32,10 +32,13 @@ Gem::Specification.new do |s|
32
32
  "lib/riak_record.rb",
33
33
  "lib/riak_record/associations.rb",
34
34
  "lib/riak_record/base.rb",
35
+ "lib/riak_record/callbacks.rb",
35
36
  "lib/riak_record/finder.rb",
37
+ "riak-record-0.1.0.gem",
36
38
  "riak-record.gemspec",
37
39
  "spec/riak_record/associations_spec.rb",
38
40
  "spec/riak_record/base_spec.rb",
41
+ "spec/riak_record/callbacks_spec.rb",
39
42
  "spec/riak_record/finder_spec.rb",
40
43
  "spec/riak_record_spec.rb",
41
44
  "spec/spec_helper.rb"
@@ -16,8 +16,8 @@ end
16
16
 
17
17
  describe RiakRecord::Base do
18
18
  it "should set the bucket name on each instance of RiakRecord class" do
19
- expect(ExampleA.bucket_name).to eq(RiakRecord::Base.namespace+":-:example_a")
20
- expect(ExampleB.bucket_name).to eq(RiakRecord::Base.namespace+':-:example_b')
19
+ expect(ExampleA.bucket_name).to eq(RiakRecord::Base.namespace+":example_a")
20
+ expect(ExampleB.bucket_name).to eq(RiakRecord::Base.namespace+':example_b')
21
21
  end
22
22
 
23
23
  it "should share a client among all classes" do
@@ -45,13 +45,90 @@ describe RiakRecord::Base do
45
45
  end
46
46
  end
47
47
 
48
+ describe "new_record?" do
49
+ let(:record){ ExampleA.new("1") }
50
+ it "should be true for a new record" do
51
+ expect(record).to be_new_record
52
+ end
53
+
54
+ it "should be false for loaded new records" do
55
+ expect(record.save.reload).to_not be_new_record
56
+ end
57
+
58
+ it "should be a new_record if unstored" do
59
+ expect{
60
+ record.save
61
+ }.to change{ record.new_record? }.from(true).to(false)
62
+ end
63
+ end
64
+
48
65
  describe "save" do
49
- let(:record) { ExampleA.new("1234") }
66
+ after :each do
67
+ example_class.instance_variable_set("@_callbacks", nil)
68
+ end
69
+ let(:example_class){ ExampleA }
70
+ let(:record) { example_class.new("1234") }
71
+
50
72
  it "should store the object" do
51
73
  record.attribute1 = 'hello'
52
74
  record.save
53
75
  expect(record.reload.attribute1).to eq('hello')
54
76
  end
77
+
78
+ it "should call before_save callbacks" do
79
+ example_class.before_save(:a_callback)
80
+ expect(record).to receive(:a_callback)
81
+ record.save
82
+ end
83
+
84
+ it "should call after_save callbacks" do
85
+ example_class.after_save(:a_callback)
86
+ expect(record).to receive(:a_callback)
87
+ record.save
88
+ end
89
+
90
+ context "new records" do
91
+ before :each do
92
+ allow(record).to receive(:new_record?).and_return(true)
93
+ end
94
+ it "should call the before_create callbacks" do
95
+ example_class.before_create(:a_create_callback)
96
+ example_class.before_update(:an_update_callback)
97
+ expect(record).to receive(:a_create_callback).and_return(true)
98
+ expect(record).to_not receive(:an_update_callback)
99
+ record.save
100
+ end
101
+
102
+ it "should call the after_create callbacks" do
103
+ example_class.after_create(:a_create_callback)
104
+ example_class.after_update(:an_update_callback)
105
+ expect(record).to receive(:a_create_callback).and_return(true)
106
+ expect(record).to_not receive(:an_update_callback)
107
+ record.save
108
+ end
109
+ end
110
+
111
+ context "existing records" do
112
+ before :each do
113
+ allow(record).to receive(:new_record?).and_return(false)
114
+ end
115
+
116
+ it "should call the before_update callbacks" do
117
+ example_class.before_create(:a_create_callback)
118
+ example_class.before_update(:an_update_callback)
119
+ expect(record).to_not receive(:a_create_callback)
120
+ expect(record).to receive(:an_update_callback).and_return(true)
121
+ record.save
122
+ end
123
+
124
+ it "should call the after_update callbacks" do
125
+ example_class.after_create(:a_create_callback)
126
+ example_class.after_update(:an_update_callback)
127
+ expect(record).to_not receive(:a_create_callback)
128
+ expect(record).to receive(:an_update_callback).and_return(true)
129
+ record.save
130
+ end
131
+ end
55
132
  end
56
133
 
57
134
  describe "class methods" do
@@ -135,14 +212,24 @@ describe RiakRecord::Base do
135
212
  end
136
213
 
137
214
  describe "index_int_attributes" do
138
- let(:riak_object) { Riak::RObject.new("obj").tap{|r| r.indexes["index1_int"] = [1] } }
139
- let(:record) { ExampleA.new(riak_object) }
215
+ let(:record) { ExampleA.new("ob") }
216
+ before :each do
217
+ record.riak_object.indexes["index1_int"] = [1]
218
+ record.save
219
+ end
140
220
  it "should read and write each index" do
141
221
  expect{
142
222
  record.index1=[2]
143
223
  }.to change{record.riak_object.indexes["index1_int"]}.from([1]).to([2])
144
224
  end
145
225
 
226
+ it "should return arrays on reload" do
227
+ expect{
228
+ record.index1=2
229
+ record.save.reload
230
+ }.to change{record.riak_object.indexes["index1_int"]}.from([1]).to([2])
231
+ end
232
+
146
233
  it "should handle non arrays" do
147
234
  expect{
148
235
  record.index2=2
@@ -181,7 +268,7 @@ describe RiakRecord::Base do
181
268
  describe "namespacing buckets" do
182
269
  it "should prepend namespace to bucket name" do
183
270
  RiakRecord::Base.namespace = "namespace_test"
184
- expect(ExampleA.bucket_name).to eq("namespace_test:-:example_a")
271
+ expect(ExampleA.bucket_name).to eq("namespace_test:example_a")
185
272
  end
186
273
  end
187
274
 
@@ -0,0 +1,70 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ class Example
4
+ include RiakRecord::Callbacks
5
+ end
6
+
7
+ describe RiakRecord::Callbacks do
8
+
9
+ let(:example_class) { Example }
10
+ let(:example){ example_class.new }
11
+
12
+ before :each do
13
+ example_class.instance_variable_set("@_callbacks", nil)
14
+ example_class.before_save("nil")
15
+ end
16
+
17
+ describe "adding call backs" do
18
+ it "should add to the callbacks" do
19
+ example_class.after_save(:a_new_callback)
20
+ expect( example_class._callbacks(:after_save).last ).to eq(:a_new_callback)
21
+ end
22
+
23
+ it "should append to the callbacks" do
24
+ example_class.append_before_save(:a_late_callback)
25
+ expect( example_class._callbacks(:before_save).last ).to eq(:a_late_callback)
26
+ end
27
+
28
+ it "should prepend to the callbacks" do
29
+ example_class.prepend_before_save(:an_early_callback)
30
+ expect( example_class._callbacks(:before_save).first ).to eq(:an_early_callback)
31
+ end
32
+ end
33
+
34
+
35
+ describe "call_callbacks!" do
36
+ it "should call symbol callbacks" do
37
+ example_class.before_save(:a_symbol_callback)
38
+ expect( example ).to receive(:a_symbol_callback).and_return(nil)
39
+ example.before_save!
40
+ end
41
+
42
+ it "should eval string callbacks" do
43
+ class EvalProof < StandardError
44
+ end
45
+ example_class.before_save("raise EvalProof")
46
+ expect{
47
+ example.before_save!
48
+ }.to raise_error(EvalProof)
49
+ end
50
+
51
+ it "should call CallbackObjects" do
52
+ class CallbackObject
53
+ def before_save
54
+ end
55
+ end
56
+ callback_object = CallbackObject.new
57
+ example_class.before_save(callback_object)
58
+ expect(callback_object).to receive(:before_save)
59
+ example.before_save!
60
+ end
61
+
62
+ it "should call procs" do
63
+ a_proc = Proc.new{|a| a}
64
+ example_class.before_create(a_proc)
65
+ expect(a_proc).to receive(:call).with(example).and_return(nil)
66
+ example.call_callbacks!(:before_create)
67
+ end
68
+ end
69
+
70
+ end
@@ -64,7 +64,10 @@ describe RiakRecord::Finder do
64
64
 
65
65
  describe "enumberable methods" do
66
66
  it "should yield once per block" do
67
- expect( pop_finder.all?{|o| o.category == 'rock'} ).to eq(true)
67
+ expect( pop_finder.all?{|o| o.category == ['pop']} ).to eq(true)
68
+ end
69
+ it "should yield once per block" do
70
+ expect( pop_finder.map{|x| 1}.inject{|sum,x| sum = sum.to_i + x}).to eq(155)
68
71
  end
69
72
  end
70
73
 
data/spec/spec_helper.rb CHANGED
@@ -1,18 +1,18 @@
1
1
  require 'simplecov'
2
2
 
3
3
  module SimpleCov::Configuration
4
- def clean_filters
4
+ def cleans
5
5
  @filters = []
6
6
  end
7
7
  end
8
8
 
9
9
  SimpleCov.configure do
10
- clean_filters
10
+ cleans
11
11
  load_profile 'test_frameworks'
12
12
  end
13
13
 
14
14
  ENV["COVERAGE"] && SimpleCov.start do
15
- add_filter "/.rvm/"
15
+ add "/.rvm/"
16
16
  end
17
17
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
18
18
  $LOAD_PATH.unshift(File.dirname(__FILE__))
@@ -30,7 +30,7 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
30
30
  RSpec.configure do |config|
31
31
  config.before do
32
32
  RiakRecord::Base.namespace = 'test'
33
- RiakRecord::Base.all_buckets_in_namespace do |bucket|
33
+ RiakRecord::Base.all_buckets_in_namespace.each do |bucket|
34
34
  bucket.keys.each{|k| bucket.delete(k) }
35
35
  end
36
36
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: riak-record
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Graff
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-23 00:00:00.000000000 Z
11
+ date: 2014-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: riak-client
@@ -159,10 +159,13 @@ files:
159
159
  - lib/riak_record.rb
160
160
  - lib/riak_record/associations.rb
161
161
  - lib/riak_record/base.rb
162
+ - lib/riak_record/callbacks.rb
162
163
  - lib/riak_record/finder.rb
164
+ - riak-record-0.1.0.gem
163
165
  - riak-record.gemspec
164
166
  - spec/riak_record/associations_spec.rb
165
167
  - spec/riak_record/base_spec.rb
168
+ - spec/riak_record/callbacks_spec.rb
166
169
  - spec/riak_record/finder_spec.rb
167
170
  - spec/riak_record_spec.rb
168
171
  - spec/spec_helper.rb