riak-record 0.1.0 → 0.2.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: 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