rrod 1.0.0.alpha.1 → 1.0.0.alpha.2

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: 54fca12a0dd242e700196f2689c7e0c10b20f238
4
- data.tar.gz: a218efee402307b755b0e681d1872d231d619771
3
+ metadata.gz: 95ae98b20d5fcbe473c9a350fad0b168d599c4e0
4
+ data.tar.gz: 454c37cfe3682918c89e72c69e586c30e261dd9c
5
5
  SHA512:
6
- metadata.gz: e4d07847ef76659034a92ca8dcc2711dd47af332dd8e525a411a226885df9fc6de90542fb155d289a941fd89236d6d14bf7466a852db9f5d347d0c8096584abc
7
- data.tar.gz: 48e8aca8812277c91321f8120785d399a2c9d3a8c4cbca7732c24ad8a0cff4abab4a9a75af0f395b1414af69f813c7ebf2f1d88faf9ad46663e9fef84223b9e4
6
+ metadata.gz: 01f3c763fb67f2b88bb8ca2f1d1d1aa2c6ba240fb1b64f0e419bdc2fac9bf2e60778dff971152e6730dba2f68e40ebe0e851704249bc734944978e4d4fe5c781
7
+ data.tar.gz: 919a75a3101a49e1bcebc69a822925ce59b39fe5da96c8785665d9a1b9072bbc1d21e58d8da2a122fb210b752da37059a43c19f8c6371bb6f0fee4f7984249c6
data/Gemfile CHANGED
@@ -2,3 +2,8 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in rrod.gemspec
4
4
  gemspec
5
+
6
+ platforms :mri do
7
+ gem 'oj'
8
+ end
9
+
data/README.md CHANGED
@@ -9,7 +9,7 @@ First, you must make sure you
9
9
  [have Riak installed](http://docs.basho.com/riak/latest/quickstart/#Install-Riak).
10
10
  These guys have extremely good docs and will usually answer your questions
11
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/)
12
+ [enabled Secondary Indexing](http://docs.basho.com/riak/latest/ops/advanced/configs/secondary-index/)
13
13
  for your Riak installation.
14
14
 
15
15
  ## Rrod Installation
@@ -36,11 +36,16 @@ This will set you up with an pry environment with `Rrod` loaded.
36
36
  Next you will wave your magic wand and enter the following incantations:
37
37
 
38
38
  ```ruby
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
39
+ class Person
40
+ attribute :name, String, index: true
41
+ attribute :age, Integer, index: true
42
+ end
43
+
44
+ hank = Person.new(name: "Hank", age: 40)
45
+ hank.save
46
+
47
+ @hank = Person.find_by name: "Hank"
48
+ @hank.age # => 40
44
49
  ```
45
50
 
46
51
  `Rrod` lets you have arbitrary attributes on your ruby objects and persist and
data/lib/rrod/all.rb CHANGED
@@ -15,10 +15,14 @@ require 'rrod/caster'
15
15
  require 'rrod/caster/nested_model'
16
16
  require 'rrod/model/attribute'
17
17
  require 'rrod/model/attribute_methods'
18
+ require 'rrod/model/callbacks'
18
19
  require 'rrod/model/collection'
19
20
  require 'rrod/model/finders'
20
21
  require 'rrod/model/persistence'
21
22
  require 'rrod/model/schema'
22
23
  require 'rrod/model/serialization'
24
+ require 'rrod/model/timestamps'
25
+ require 'rrod/model/validations'
26
+ require 'rrod/model/validations/associated_validator'
23
27
  require 'rrod/model'
24
28
  require 'rrod/query'
@@ -4,7 +4,7 @@ module Rrod
4
4
 
5
5
  def rrod_cast(values)
6
6
  return if values.nil?
7
- Rrod::Model::Collection.new(values.map { |value| model.rrod_cast(value) })
7
+ Rrod::Model::Collection.new(model, values)
8
8
  end
9
9
 
10
10
  def model
@@ -38,3 +38,5 @@ module Rrod
38
38
  end
39
39
  end
40
40
  end
41
+
42
+ I18n.enforce_available_locales = true
data/lib/rrod/model.rb CHANGED
@@ -5,9 +5,11 @@ module Rrod
5
5
  include AttributeMethods
6
6
  include Persistence
7
7
  include Serialization
8
- include ActiveModel::Serializers::JSON
9
- include ActiveModel::Serializers::Xml
10
- include ActiveModel::Validations
8
+
9
+ include Validations
10
+
11
+ include Callbacks
12
+ include Timestamps
11
13
 
12
14
  module ClassMethods
13
15
  include Finders
@@ -10,7 +10,7 @@ module Rrod
10
10
  ->(value) { write_attribute name, value }
11
11
  end
12
12
 
13
- attr_accessor :model, :name, :type, :options, :default
13
+ attr_accessor :model, :name, :type, :options, :default, :index
14
14
 
15
15
  # @param [Class] The class that is declaring the attribute
16
16
  # @param [Symbol] The name of the attribute
@@ -22,11 +22,12 @@ module Rrod
22
22
  self.type = type
23
23
  self.default = options.delete(:default)
24
24
  self.options = options
25
+ set_index if options.delete(:index)
25
26
  end
26
27
 
27
28
  # @return [Object] default value or result of call on default value
28
- def default
29
- @default.respond_to?(:call) ? @default.call : @default
29
+ def default(instance)
30
+ @default.respond_to?(:call) ? instance.instance_exec(&@default) : @default
30
31
  end
31
32
 
32
33
  def define
@@ -63,6 +64,10 @@ module Rrod
63
64
  def define_writer
64
65
  model.send :define_method, "#{name}=", self.class.writer_definition(name)
65
66
  end
67
+
68
+ def set_index
69
+ @index = Index.new(self)
70
+ end
66
71
 
67
72
  def apply_validators
68
73
  model.validates name, options if options.present?
@@ -1,6 +1,9 @@
1
1
  module Rrod
2
2
  module Model
3
3
  module AttributeMethods
4
+ extend ActiveSupport::Concern
5
+
6
+ attr_accessor :_parent
4
7
 
5
8
  def initialize(attributes = {})
6
9
  @attributes = {}
@@ -36,7 +39,8 @@ module Rrod
36
39
  # @param [Symbol, String] the key of the attribute to read
37
40
  # @return the attribute at the given key
38
41
  def read_attribute(key)
39
- @attributes[key.to_s]
42
+ @attributes[key.to_s] ||
43
+ write_attribute(key, self.class.attributes[key.to_sym].try(:default, self))
40
44
  end
41
45
  alias :[] :read_attribute
42
46
 
@@ -44,7 +48,9 @@ module Rrod
44
48
  # @param [Symbol, String] the key of the attribute to write
45
49
  # @param the value to write to attributes
46
50
  def write_attribute(key, value)
47
- @attributes[key.to_s] = self.class.cast_attribute(key, value)
51
+ @attributes[key.to_s] = self.class.cast_attribute(key, value).tap { |attr|
52
+ attr._parent = self if attr.respond_to?(:_parent=)
53
+ }
48
54
  end
49
55
  alias :[]= :write_attribute
50
56
 
@@ -70,7 +76,6 @@ module Rrod
70
76
  def method_missing(method_id, *args, &block)
71
77
  method = method_id.to_s
72
78
  return super unless magic_methods.include?(method)
73
-
74
79
  accessor = method.ends_with?('=') ? :writer : :reader
75
80
  send "define_singleton_#{accessor}", method.chomp('=')
76
81
  send method_id, *args
@@ -0,0 +1,26 @@
1
+ module Rrod
2
+ module Model
3
+ module Callbacks
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ extend ActiveModel::Callbacks
8
+ define_model_callbacks :assignment, :validation, :save, :create, :update
9
+ end
10
+
11
+ def attributes=(*)
12
+ run_callbacks(:assignment) { super }
13
+ end
14
+
15
+ def valid?
16
+ run_callbacks(:validation) { super }
17
+ end
18
+
19
+ def save(*)
20
+ run_callbacks(:create) if new?
21
+ run_callbacks(:update) if persisted?
22
+ run_callbacks(:save) { super }
23
+ end
24
+ end
25
+ end
26
+ end
@@ -2,13 +2,22 @@ module Rrod
2
2
  module Model
3
3
  class Collection
4
4
  include Enumerable
5
+ attr_accessor :model
5
6
 
6
- delegate(*%w[clear count each length size], to: :collection)
7
+ # explicitly declare the public interface to the underlying collection
8
+ COLLECTION_INTERFACE = %w[clear count each length size [] first last]
7
9
 
8
- def initialize(collection=[])
10
+ delegate(*COLLECTION_INTERFACE, to: :collection)
11
+
12
+ def initialize(model, collection=[])
13
+ self.model = model
9
14
  self.collection = collection
10
15
  end
11
16
 
17
+ def _parent=(value)
18
+ each { |member| member._parent = value }
19
+ end
20
+
12
21
  def collection=(collection)
13
22
  raise InvalidCollectionTypeError.new unless collection.respond_to?(:each)
14
23
  collection.map { |member| push member }
@@ -20,15 +29,24 @@ module Rrod
20
29
  @collection ||= []
21
30
  end
22
31
 
32
+ def build(attributes={})
33
+ push attributes
34
+ end
35
+
23
36
  def push(value)
24
- raise InvalidMemberTypeError.new unless Rrod::Model === (value)
25
- collection.push(value)
37
+ instance = model.rrod_cast(value)
38
+ raise InvalidMemberTypeError.new unless model === instance
39
+ collection.push(instance)
26
40
  end
27
41
  alias :<< :push
28
42
 
29
43
  def serializable_hash(*)
30
44
  collection.map(&:serializable_hash)
31
45
  end
46
+
47
+ def valid?
48
+ collection.all?(&:valid?)
49
+ end
32
50
 
33
51
  InvalidCollectionTypeError = Class.new(StandardError)
34
52
  InvalidMemberTypeError = Class.new(StandardError)
@@ -2,8 +2,12 @@ module Rrod
2
2
  module Model
3
3
  module Finders
4
4
 
5
- def find(id)
6
- find_one(id).tap { |found| raise NotFound.new if found.nil? }
5
+ def find(*ids)
6
+ if ids.length == 1
7
+ find_one(ids.first).tap { |found| raise NotFound.new if found.nil? }
8
+ else
9
+ find_many(ids)
10
+ end
7
11
  end
8
12
 
9
13
  def find_one(id)
@@ -13,45 +17,52 @@ module Rrod
13
17
  raise e unless e.not_found?
14
18
  end
15
19
 
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
20
+ def find_by(query)
21
+ find_by!(query)
22
+ rescue Rrod::Model::NotFound
23
+ nil
25
24
  end
26
-
27
- def find_first_by!(attributes)
28
- find_first_by(attributes).tap { |model| raise NotFound.new if model.nil? }
25
+
26
+ def find_by!(query)
27
+ return find(query[:id]) if query[:id]
28
+ find_all_by!(query).first
29
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) }
30
+
31
+ def find_all_by(query)
32
+ find_all_by!(query)
33
+ rescue Rrod::Model::NotFound
34
+ []
35
35
  end
36
36
 
37
37
  def find_all_by!(attributes)
38
- find_all_by(attributes).tap { |models| raise NotFound.new if models.empty? }
38
+ query = Query.new(attributes)
39
+ raise ArgumentError.new("Cannot search by id") if query.using_id?
40
+ search(query).map{ |doc| found(nil, doc) }
39
41
  end
40
-
41
- private
42
-
42
+
43
+ private
44
+
43
45
  def search(query)
44
- client.search(bucket_name, query.to_s)['docs']
46
+ records = client.search(bucket_name, query.to_s)['docs']
47
+ raise ArgumentError.new("NO RES ULTS") unless records.any?
48
+ records
49
+ end
50
+
51
+ def find_many(ids)
52
+ bucket.get_many(ids).map{|doc| found(doc.first, doc.last.data) }
45
53
  end
46
54
 
47
55
  def found(key, data, robject=nil)
48
- new(data).tap { |instance|
49
- instance.id = key
56
+ instantiate(key, data).tap { |instance|
50
57
  instance.robject = robject
51
58
  instance.instance_variable_set(:@persisted, true)
52
59
  }
53
60
  end
54
61
 
62
+ def instantiate(key, data)
63
+ new(data).tap { |instance| instance.id = key if key }
64
+ end
65
+
55
66
  end
56
67
 
57
68
  NotFound = Class.new(StandardError)
@@ -1,6 +1,8 @@
1
1
  module Rrod
2
2
  module Model
3
3
  module Persistence
4
+ extend ActiveSupport::Concern # so we can override save
5
+
4
6
  attr_accessor :robject
5
7
 
6
8
  def persisted?
@@ -12,11 +14,10 @@ module Rrod
12
14
  end
13
15
  alias :new_record? :new?
14
16
 
15
- def save(options={})
16
- options.fetch(:validate, true) ?
17
- (valid? and persist) : persist
17
+ def save
18
+ persist
18
19
  end
19
-
20
+
20
21
  def persist
21
22
  bucket.enable_index!
22
23
  robject.raw_data = to_json
@@ -5,7 +5,7 @@ module Rrod
5
5
  def attributes
6
6
  @attributes ||= {}
7
7
  end
8
-
8
+
9
9
  def attribute(name, type, options={})
10
10
  attributes[name.to_sym] = Attribute.new(self, name, type, options).define
11
11
  end
@@ -15,6 +15,10 @@ module Rrod
15
15
  attribute ? attribute.cast(value) : value
16
16
  end
17
17
 
18
+ def nested_in(parent)
19
+ define_method(parent) { @_parent }
20
+ end
21
+
18
22
  def schema?
19
23
  attributes.any?
20
24
  end
@@ -22,9 +26,11 @@ module Rrod
22
26
  def rrod_cast(value)
23
27
  return if value.nil?
24
28
  return value if value.is_a?(Rrod::Model)
25
- found(nil, value)
29
+ raise UncastableObjectError.new("#{value.inspect} cannot be rrod_cast") unless Hash === value
30
+ instantiate(nil, value)
26
31
  end
27
32
 
28
- end
33
+ end
34
+ UncastableObjectError = Class.new(StandardError)
29
35
  end
30
36
  end
@@ -2,10 +2,19 @@ module Rrod
2
2
  module Model
3
3
  module Serialization
4
4
 
5
+ def self.included(base)
6
+ base.send :include, ActiveModel::Serializers::JSON
7
+ base.send :include, ActiveModel::Serializers::Xml
8
+ end
9
+
5
10
  def read_attribute_for_serialization(key)
6
11
  value = read_attribute(key)
7
12
  value.respond_to?(:serializable_hash) ? value.serializable_hash : value
8
13
  end
14
+
15
+ def to_json(options={})
16
+ MultiJson.dump serializable_hash(options)
17
+ end
9
18
 
10
19
  end
11
20
  end
@@ -0,0 +1,19 @@
1
+ module Rrod
2
+ module Model
3
+ module Timestamps
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def timestamps!
8
+ attribute :created_at, Time, default: -> { Time.now }
9
+ attribute :updated_at, Time, default: -> { Time.now }
10
+ before_save :touch
11
+ end
12
+ end
13
+
14
+ def touch
15
+ tap { self.updated_at = Time.now }
16
+ end
17
+ end
18
+ end
19
+ end