rrod 0.0.1 → 1.0.0.alpha.1

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/README.md +33 -18
  4. data/lib/rrod/all.rb +24 -1
  5. data/lib/rrod/caster/nested_model.rb +16 -0
  6. data/lib/rrod/caster.rb +34 -0
  7. data/lib/rrod/cli.rb +9 -2
  8. data/lib/rrod/configuration.rb +40 -0
  9. data/lib/rrod/model/attribute.rb +80 -0
  10. data/lib/rrod/model/attribute_methods.rb +85 -0
  11. data/lib/rrod/model/collection.rb +37 -0
  12. data/lib/rrod/model/finders.rb +59 -0
  13. data/lib/rrod/model/persistence.rb +35 -0
  14. data/lib/rrod/model/schema.rb +30 -0
  15. data/lib/rrod/model/serialization.rb +12 -0
  16. data/lib/rrod/model.rb +38 -0
  17. data/lib/rrod/query.rb +30 -0
  18. data/lib/rrod/test_server/rspec.rb +50 -0
  19. data/lib/rrod/test_server/runner.rb +56 -0
  20. data/lib/rrod/test_server.rb +77 -0
  21. data/lib/rrod/version.rb +2 -3
  22. data/rrod.gemspec +7 -1
  23. data/spec/rrod/caster_spec.rb +117 -0
  24. data/spec/rrod/configuration_spec.rb +55 -0
  25. data/spec/rrod/model/attribute_methods_spec.rb +64 -0
  26. data/spec/rrod/model/attribute_spec.rb +102 -0
  27. data/spec/rrod/model/collection_spec.rb +39 -0
  28. data/spec/rrod/model/finders_spec.rb +78 -0
  29. data/spec/rrod/model/persistence_spec.rb +43 -0
  30. data/spec/rrod/model/schema_spec.rb +59 -0
  31. data/spec/rrod/model/serialization_spec.rb +17 -0
  32. data/spec/rrod/model/validations_spec.rb +50 -0
  33. data/spec/rrod/model_spec.rb +50 -0
  34. data/spec/rrod/query_spec.rb +34 -0
  35. data/spec/rrod_spec.rb +4 -0
  36. data/spec/spec_helper.rb +4 -0
  37. data/spec/support/models/car.rb +3 -0
  38. data/spec/support/models/person.rb +41 -0
  39. data/spec/support/test_server.yml.example +17 -0
  40. metadata +106 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 483487fdba1ca64ce64a80010c2c9381ae9e471a
4
- data.tar.gz: b6be998e8cbbcd06e65ed0b89b7114465d00727c
3
+ metadata.gz: 54fca12a0dd242e700196f2689c7e0c10b20f238
4
+ data.tar.gz: a218efee402307b755b0e681d1872d231d619771
5
5
  SHA512:
6
- metadata.gz: f2a3a7e0d98d410a876392c8121f9b5a3ec5537b2a6a265ed159ebb88a438275f22b44296f5ec6c076b309683672641568461baf6ca07ff44a6de3adae3cb6b7
7
- data.tar.gz: c29f1826f9aaf76b048cbe286e2c3434c48f38b955f6ae3030735a7b2422c06d178a1145547be4f62cce952940c4efd8b4acfdc8a59d36ce08e3021557bfad92
6
+ metadata.gz: e4d07847ef76659034a92ca8dcc2711dd47af332dd8e525a411a226885df9fc6de90542fb155d289a941fd89236d6d14bf7466a852db9f5d347d0c8096584abc
7
+ data.tar.gz: 48e8aca8812277c91321f8120785d399a2c9d3a8c4cbca7732c24ad8a0cff4abab4a9a75af0f395b1414af69f813c7ebf2f1d88faf9ad46663e9fef84223b9e4
data/.gitignore CHANGED
@@ -4,6 +4,7 @@
4
4
  .config
5
5
  .ruby-gemset
6
6
  .ruby-version
7
+ .tmp
7
8
  .yardoc
8
9
  Gemfile.lock
9
10
  InstalledFiles
@@ -14,6 +15,7 @@ lib/bundler/man
14
15
  pkg
15
16
  rdoc
16
17
  spec/reports
18
+ spec/support/test_server.yml
17
19
  test/tmp
18
20
  test/version_tmp
19
21
  tmp
data/README.md CHANGED
@@ -1,36 +1,51 @@
1
+ <!--- TODO use consistent capitalization with Rrod and Riak as well as backticks-->
1
2
  # Rrod
2
3
  ### Riak ruby object database.
3
4
  Rrod lets you use the super awesome database, riak, to model and persist your
4
5
  ruby objects.
5
6
 
6
- # Riak
7
- First, you must make sure you have Riak installed. See:
8
- http://docs.basho.com/riak/latest/quickstart/#Install-Riak.
9
- These guys have extremely good docs
10
- and will usually answer your questions in IRC #riak.
7
+ ## Riak Installation
8
+ First, you must make sure you
9
+ [have Riak installed](http://docs.basho.com/riak/latest/quickstart/#Install-Riak).
10
+ These guys have extremely good docs and will usually answer your questions
11
+ in freenode IRC #riak. You must also make sure you have
12
+ [enabled search](http://docs.basho.com/riak/latest/ops/advanced/configs/search/)
13
+ for your Riak installation.
11
14
 
12
- ## Installation
15
+ ## Rrod Installation
13
16
  Add `rrod` to your Gemfile and run `bundle` or execute `gem install rrod`.
14
17
 
15
- # Usage
16
- Once you have riak and rrod installed, make sure you know the port of one of your
17
- riak nodes. Usually `8098`.
18
+ ## Usage
19
+ Once you have Riak and Rrod installed you can begin to play. `Rrod` will
20
+ connect to Riak on localhost using protocol buffers on port 8087. What you
21
+ say? Don't worry about it.
18
22
 
19
- In terminal, run:
23
+ Next, if you installed Rrod by adding it to your Gemfile, run
20
24
 
21
- $ rrod irb
25
+ ```
26
+ $ bundle exec rrod pry
27
+ ```
22
28
 
23
- This will set you up with an irb environment with rrod loaded. Then run the
24
- following commands.
29
+ Otherwise, if you installed using `gem install rrod`, run:
30
+
31
+ ```
32
+ $ rrod pry
33
+ ```
34
+
35
+ This will set you up with an pry environment with `Rrod` loaded.
36
+ Next you will wave your magic wand and enter the following incantations:
25
37
 
26
38
  ```ruby
27
- Car = Class.new(Rrod::Document)
28
- car = {wheels: 4, color: :black, make: 'Jeep'}
29
- Car.persist(car)
30
- @car = Car.first
31
- @car.color
39
+ Car = Class.new { include Rrod::Model }
40
+ car = Car.new(wheels: 5, color: 'black', make: 'Jeep')
41
+ car.save
42
+ @car = Car.find_first_by make: 'Jeep'
43
+ @car.wheels # => 5
32
44
  ```
33
45
 
46
+ `Rrod` lets you have arbitrary attributes on your ruby objects and persist and
47
+ retreive them from Riak.
48
+
34
49
  ## Contributing
35
50
 
36
51
  1. Fork it
data/lib/rrod/all.rb CHANGED
@@ -1 +1,24 @@
1
- require 'rrod/version'
1
+ require 'active_model'
2
+ require 'active_support/concern'
3
+ require 'active_support/core_ext/hash/indifferent_access'
4
+ require 'active_support/core_ext/hash/keys'
5
+ require 'active_support/core_ext/string/conversions'
6
+ require 'active_support/core_ext/string/inflections'
7
+ require 'active_support/core_ext/string/starts_ends_with'
8
+ require 'american_date'
9
+ require 'bigdecimal'
10
+ require 'riak'
11
+
12
+ require 'rrod/version' # first
13
+ require 'rrod/configuration'
14
+ require 'rrod/caster'
15
+ require 'rrod/caster/nested_model'
16
+ require 'rrod/model/attribute'
17
+ require 'rrod/model/attribute_methods'
18
+ require 'rrod/model/collection'
19
+ require 'rrod/model/finders'
20
+ require 'rrod/model/persistence'
21
+ require 'rrod/model/schema'
22
+ require 'rrod/model/serialization'
23
+ require 'rrod/model'
24
+ require 'rrod/query'
@@ -0,0 +1,16 @@
1
+ module Rrod
2
+ class Caster
3
+ module NestedModel
4
+
5
+ def rrod_cast(values)
6
+ return if values.nil?
7
+ Rrod::Model::Collection.new(values.map { |value| model.rrod_cast(value) })
8
+ end
9
+
10
+ def model
11
+ first
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,34 @@
1
+ module Rrod
2
+ class Caster
3
+
4
+ def initialize(&casting)
5
+ @casting = casting
6
+ end
7
+
8
+ def rrod_cast(value)
9
+ return if value.nil?
10
+ @casting.(value)
11
+ end
12
+
13
+ module Boolean
14
+ extend self
15
+ def rrod_cast(value)
16
+ [nil, false, 'false', 0, '0'].include?(value) ? false : true
17
+ end
18
+ end
19
+
20
+ BigDecimal = new { |value| ::BigDecimal.new(value.to_s) }
21
+ Date = new { |value| value.to_date }
22
+ DateTime = new { |value| value.to_datetime }
23
+ Float = new { |value| value.to_f }
24
+ Integer = new { |value| value.to_i }
25
+ Numeric = new { |value|
26
+ float_value = value.to_f
27
+ int_value = value.to_i
28
+ float_value == int_value ? int_value : float_value
29
+ }
30
+ String = new { |value| value.to_s }
31
+ Symbol = new { |value| value.to_sym }
32
+ Time = new { |value| value.to_time }
33
+ end
34
+ end
data/lib/rrod/cli.rb CHANGED
@@ -1,16 +1,23 @@
1
1
  require 'thor'
2
+ require 'rrod'
2
3
 
3
4
  module Rrod
4
5
  class Cli < Thor
5
6
  package_name "Rrod"
6
7
 
7
8
  map "-i" => :pry
9
+ map "-t" => :test
8
10
 
9
11
  desc "pry", "start an interactive ruby session with Rrod loaded"
10
12
  def pry
11
13
  require 'pry'
12
- require 'rrod'
13
- Class.new { pry }
14
+ TOPLEVEL_BINDING.pry
15
+ end
16
+
17
+ desc "test", "start the Rrod::TestServer"
18
+ def test
19
+ require 'rrod/test_server/runner'
20
+ Rrod::TestServer::Runner.run
14
21
  end
15
22
  end
16
23
  end
@@ -0,0 +1,40 @@
1
+ module Rrod
2
+ module Config
3
+ def configure
4
+ yield configuration if block_given?
5
+ end
6
+
7
+ def configuration
8
+ @configuration ||= Configuration.new
9
+ end
10
+ end
11
+
12
+ extend Config
13
+
14
+ class Configuration
15
+ attr_accessor :client, :http_port, :pb_port, :protocol,
16
+ :test_server_yml, :test_server_search_startup_timeout, :nodes
17
+
18
+ def initialize
19
+ @protocol = 'pbc'
20
+ @test_server_yml = File.expand_path('spec/support/test_server.yml', '.')
21
+ @test_server_search_startup_timeout = 40 # because it's a test
22
+ end
23
+
24
+ def client
25
+ @client ||= Riak::Client.new(client_options)
26
+ end
27
+
28
+ private
29
+
30
+ def client_options
31
+ attributes = %w[http_port pb_port protocol nodes]
32
+ attributes.inject({}) do |acc, method|
33
+ acc.tap { |hash|
34
+ value = public_send(method)
35
+ hash[method.to_sym] = value unless value.nil?
36
+ }
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,80 @@
1
+ module Rrod
2
+ module Model
3
+ class Attribute
4
+
5
+ def self.reader_definition(name)
6
+ -> { read_attribute name }
7
+ end
8
+
9
+ def self.writer_definition(name)
10
+ ->(value) { write_attribute name, value }
11
+ end
12
+
13
+ attr_accessor :model, :name, :type, :options, :default
14
+
15
+ # @param [Class] The class that is declaring the attribute
16
+ # @param [Symbol] The name of the attribute
17
+ # @param [Class] The type of the attribute for marshalling
18
+ # @param [Hash] Options for the attribute (they are optional).
19
+ def initialize(model, name, type, options={})
20
+ self.model = model
21
+ self.name = name.to_sym
22
+ self.type = type
23
+ self.default = options.delete(:default)
24
+ self.options = options
25
+ end
26
+
27
+ # @return [Object] default value or result of call on default value
28
+ def default
29
+ @default.respond_to?(:call) ? @default.call : @default
30
+ end
31
+
32
+ def define
33
+ define_reader
34
+ define_writer
35
+ apply_validators
36
+ self
37
+ end
38
+
39
+ def cast(value)
40
+ caster = type.respond_to?(:rrod_cast) ? type : rrod_caster
41
+ caster ? caster.rrod_cast(value) : value
42
+ end
43
+
44
+ private
45
+
46
+ def name=(value)
47
+ @name = value.to_sym
48
+ end
49
+
50
+ def type=(value)
51
+ @type = value
52
+ @type.extend(Rrod::Caster::NestedModel) if nested_model?
53
+ end
54
+
55
+ def nested_model?
56
+ type.is_a?(Array) and type.first.ancestors.include?(Rrod::Model)
57
+ end
58
+
59
+ def define_reader
60
+ model.send :define_method, name, self.class.reader_definition(name)
61
+ end
62
+
63
+ def define_writer
64
+ model.send :define_method, "#{name}=", self.class.writer_definition(name)
65
+ end
66
+
67
+ def apply_validators
68
+ model.validates name, options if options.present?
69
+ end
70
+
71
+ def rrod_caster
72
+ "Rrod::Caster::#{type}".constantize
73
+ rescue NameError
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ # stand-in for true/false attribute types
80
+ Boolean = Module.new unless defined? ::Boolean
@@ -0,0 +1,85 @@
1
+ module Rrod
2
+ module Model
3
+ module AttributeMethods
4
+
5
+ def initialize(attributes = {})
6
+ @attributes = {}
7
+ self.magic_methods = attributes.keys
8
+ self.attributes = attributes
9
+ end
10
+
11
+ def id
12
+ read_attribute :id
13
+ end
14
+
15
+ def id=(value)
16
+ write_attribute :id, value
17
+ end
18
+
19
+ # Returns a new hash with all of the object's attributes.
20
+ # @return [Hash] the object's attributes
21
+ def attributes
22
+ @attributes.keys.inject({}) { |acc, key|
23
+ acc.tap { |hash| hash[key] = respond_to?(key) ? public_send(key) : nil }
24
+ }
25
+ end
26
+
27
+ # Mass assign the attributes of the object.
28
+ # @param [Hash] the attributes to mass assign
29
+ def attributes=(attrs)
30
+ attrs.each do |key, value|
31
+ public_send "#{key}=", value
32
+ end
33
+ end
34
+
35
+ # Read a single attribute of the object.
36
+ # @param [Symbol, String] the key of the attribute to read
37
+ # @return the attribute at the given key
38
+ def read_attribute(key)
39
+ @attributes[key.to_s]
40
+ end
41
+ alias :[] :read_attribute
42
+
43
+ # Write a given value to the attributes for a given key.
44
+ # @param [Symbol, String] the key of the attribute to write
45
+ # @param the value to write to attributes
46
+ def write_attribute(key, value)
47
+ @attributes[key.to_s] = self.class.cast_attribute(key, value)
48
+ end
49
+ alias :[]= :write_attribute
50
+
51
+ private
52
+
53
+ def magic_methods=(keys)
54
+ return if self.class.schema? # classes with attributes don't get magic methods
55
+ @magic_methods = keys.inject([]) { |acc, k| acc << k.to_s << "#{k}=" }
56
+ end
57
+
58
+ def magic_methods
59
+ @magic_methods || []
60
+ end
61
+
62
+ def define_singleton_reader(attribute)
63
+ define_singleton_method attribute, Attribute.reader_definition(attribute)
64
+ end
65
+
66
+ def define_singleton_writer(attribute)
67
+ define_singleton_method "#{attribute}=", Attribute.writer_definition(attribute)
68
+ end
69
+
70
+ def method_missing(method_id, *args, &block)
71
+ method = method_id.to_s
72
+ return super unless magic_methods.include?(method)
73
+
74
+ accessor = method.ends_with?('=') ? :writer : :reader
75
+ send "define_singleton_#{accessor}", method.chomp('=')
76
+ send method_id, *args
77
+ end
78
+
79
+ def respond_to_missing?(*args)
80
+ magic_methods.include? args.first.to_s
81
+ end
82
+
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,37 @@
1
+ module Rrod
2
+ module Model
3
+ class Collection
4
+ include Enumerable
5
+
6
+ delegate(*%w[clear count each length size], to: :collection)
7
+
8
+ def initialize(collection=[])
9
+ self.collection = collection
10
+ end
11
+
12
+ def collection=(collection)
13
+ raise InvalidCollectionTypeError.new unless collection.respond_to?(:each)
14
+ collection.map { |member| push member }
15
+ rescue InvalidMemberTypeError => e
16
+ clear and raise e
17
+ end
18
+
19
+ def collection
20
+ @collection ||= []
21
+ end
22
+
23
+ def push(value)
24
+ raise InvalidMemberTypeError.new unless Rrod::Model === (value)
25
+ collection.push(value)
26
+ end
27
+ alias :<< :push
28
+
29
+ def serializable_hash(*)
30
+ collection.map(&:serializable_hash)
31
+ end
32
+
33
+ InvalidCollectionTypeError = Class.new(StandardError)
34
+ InvalidMemberTypeError = Class.new(StandardError)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,59 @@
1
+ module Rrod
2
+ module Model
3
+ module Finders
4
+
5
+ def find(id)
6
+ find_one(id).tap { |found| raise NotFound.new if found.nil? }
7
+ end
8
+
9
+ def find_one(id)
10
+ robject = bucket.get(id)
11
+ found(id, robject.data, robject)
12
+ rescue Riak::FailedRequest => e
13
+ raise e unless e.not_found?
14
+ end
15
+
16
+ def find_first_by(attributes)
17
+ query = Query.new(attributes)
18
+
19
+ if query.using_id?
20
+ find_one(query.id)
21
+ else
22
+ docs = search(query)
23
+ docs.any? ? found(nil, docs.first) : nil
24
+ end
25
+ end
26
+
27
+ def find_first_by!(attributes)
28
+ find_first_by(attributes).tap { |model| raise NotFound.new if model.nil? }
29
+ end
30
+
31
+ def find_all_by(attributes)
32
+ query = Query.new(attributes)
33
+ raise ArgumentError.new('Cannot pass id to find_all_by') if query.using_id?
34
+ search(query).map { |doc| found(nil, doc) }
35
+ end
36
+
37
+ def find_all_by!(attributes)
38
+ find_all_by(attributes).tap { |models| raise NotFound.new if models.empty? }
39
+ end
40
+
41
+ private
42
+
43
+ def search(query)
44
+ client.search(bucket_name, query.to_s)['docs']
45
+ end
46
+
47
+ def found(key, data, robject=nil)
48
+ new(data).tap { |instance|
49
+ instance.id = key
50
+ instance.robject = robject
51
+ instance.instance_variable_set(:@persisted, true)
52
+ }
53
+ end
54
+
55
+ end
56
+
57
+ NotFound = Class.new(StandardError)
58
+ end
59
+ end
@@ -0,0 +1,35 @@
1
+ module Rrod
2
+ module Model
3
+ module Persistence
4
+ attr_accessor :robject
5
+
6
+ def persisted?
7
+ @persisted
8
+ end
9
+
10
+ def new?
11
+ !persisted?
12
+ end
13
+ alias :new_record? :new?
14
+
15
+ def save(options={})
16
+ options.fetch(:validate, true) ?
17
+ (valid? and persist) : persist
18
+ end
19
+
20
+ def persist
21
+ bucket.enable_index!
22
+ robject.raw_data = to_json
23
+ robject.key = id unless id.nil?
24
+ robject.store
25
+ self.id = robject.key
26
+ @persisted = true
27
+ end
28
+
29
+ def robject
30
+ @robject ||= bucket.new
31
+ end
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,30 @@
1
+ module Rrod
2
+ module Model
3
+ module Schema
4
+
5
+ def attributes
6
+ @attributes ||= {}
7
+ end
8
+
9
+ def attribute(name, type, options={})
10
+ attributes[name.to_sym] = Attribute.new(self, name, type, options).define
11
+ end
12
+
13
+ def cast_attribute(key, value)
14
+ attribute = attributes[key.to_sym]
15
+ attribute ? attribute.cast(value) : value
16
+ end
17
+
18
+ def schema?
19
+ attributes.any?
20
+ end
21
+
22
+ def rrod_cast(value)
23
+ return if value.nil?
24
+ return value if value.is_a?(Rrod::Model)
25
+ found(nil, value)
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,12 @@
1
+ module Rrod
2
+ module Model
3
+ module Serialization
4
+
5
+ def read_attribute_for_serialization(key)
6
+ value = read_attribute(key)
7
+ value.respond_to?(:serializable_hash) ? value.serializable_hash : value
8
+ end
9
+
10
+ end
11
+ end
12
+ end
data/lib/rrod/model.rb ADDED
@@ -0,0 +1,38 @@
1
+ module Rrod
2
+ module Model
3
+ extend ActiveSupport::Concern
4
+
5
+ include AttributeMethods
6
+ include Persistence
7
+ include Serialization
8
+ include ActiveModel::Serializers::JSON
9
+ include ActiveModel::Serializers::Xml
10
+ include ActiveModel::Validations
11
+
12
+ module ClassMethods
13
+ include Finders
14
+ include Schema
15
+
16
+ def client
17
+ Rrod.configuration.client
18
+ end
19
+
20
+ def bucket
21
+ @bucket ||= client[bucket_name]
22
+ end
23
+
24
+ # TODO make class attribute
25
+ def bucket_name
26
+ name.tableize
27
+ end
28
+ end
29
+
30
+ def client
31
+ self.class.client
32
+ end
33
+
34
+ def bucket
35
+ self.class.bucket
36
+ end
37
+ end
38
+ end
data/lib/rrod/query.rb ADDED
@@ -0,0 +1,30 @@
1
+ module Rrod
2
+ class Query
3
+ attr_accessor :options
4
+
5
+ def initialize(options)
6
+ self.options = options
7
+ end
8
+
9
+ def id
10
+ options[:id]
11
+ end
12
+
13
+ def using_id?
14
+ id.present?
15
+ end
16
+
17
+ def to_s
18
+ options.map { |key, value| "#{key}:#{value}" }.join(" AND ")
19
+ end
20
+
21
+ def options=(options)
22
+ @options = options.with_indifferent_access
23
+ raise ArgumentError.new("no search options") if options.blank?
24
+ raise ArgumentError.new("cannot mix id with other options") if using_id? and options.keys.count > 1
25
+ rescue => e
26
+ @options.clear and raise e
27
+ end
28
+
29
+ end
30
+ end