tyrion 0.2.0 → 0.3.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.
@@ -16,6 +16,8 @@ Each file is an array of homogeneous documents (much like collections).
16
16
  ```
17
17
 
18
18
  ### Document
19
+ It supports validations from ActiveModel::Validations.
20
+
19
21
  ``` ruby
20
22
  class Post
21
23
  include Tyrion::Document
@@ -23,18 +25,19 @@ Each file is an array of homogeneous documents (much like collections).
23
25
  field :title
24
26
  field :body
25
27
  field :rank
28
+
29
+ validates_presence_of :title, :body, :rank
26
30
  end
27
31
  ```
28
32
 
29
33
  ### Persistence
30
34
  #### Save
31
35
  ``` ruby
32
- post = Post.create :title => "Hello", :body => "Hi there, ..."
36
+ post = Post.new :title => "Hello", :body => "Hi there, ..."
33
37
  post.save
34
38
  ```
35
39
  ``` ruby
36
- # Insta-save with !
37
- Post.create! :title => "Hello", :body => "Hi there, ..."
40
+ Post.create :title => "Hello", :body => "Hi there, ..."
38
41
  ```
39
42
 
40
43
  #### Delete
@@ -43,22 +46,57 @@ Each file is an array of homogeneous documents (much like collections).
43
46
  post.delete
44
47
  ```
45
48
  ``` ruby
46
- Post.delete_all # You get the idea
49
+ Post.delete_all
47
50
  ```
51
+
52
+ ### Querying
53
+ Chainable querying is allowed.
54
+ First called is first executed and an enumerable is returned.
55
+
56
+ `where`: all matching documents
57
+
48
58
  ``` ruby
49
- Post.delete :title => /^Hello/
59
+ Post.where :title => 'Hello', :rank => 3
50
60
  ```
51
61
 
52
- ### Querying
53
- `find_by_attribute`: just the first match
62
+ `limit`: limits returned documents
54
63
 
55
64
  ``` ruby
56
- Post.find_by_title "Hello"
57
- Post.find_by_body /^Hi there/i
65
+ Post.limit(5)
58
66
  ```
59
67
 
60
- `where`: all matching documents
68
+ `skip`: offsets returned documents
69
+
70
+ ``` ruby
71
+ Post.skip(5)
72
+ ```
73
+
74
+ `asc`: sorts ascendingly according to passed keys
61
75
 
62
76
  ``` ruby
63
- Post.where :title => /^Hello/, :rank => 3
64
- ```
77
+ Post.asc(:rank, :title)
78
+ ```
79
+
80
+ `desc`: sorts discendingly according to passed keys
81
+
82
+ ``` ruby
83
+ Post.desc(:rank, :title)
84
+ ```
85
+
86
+ #### Fancy chains
87
+ ``` ruby
88
+ Post.where(:rank => 5).desc(:title, :body).skip(5).limit(5)
89
+ ```
90
+ And since it delegates to an enumerable...
91
+
92
+ ``` ruby
93
+ Post.where(:rank => 5).count
94
+ Post.where(:rank => 10).each{ |doc| ... }
95
+ ```
96
+
97
+ ## ToDos
98
+
99
+ * Modifiers on criterias (delete, update, ...)
100
+ * Default values for attributes
101
+ * Embedded documents
102
+ * Keys (_id?)
@@ -6,9 +6,9 @@ require 'tyrion/connection'
6
6
 
7
7
  require 'tyrion/attributes'
8
8
  require 'tyrion/persistence'
9
+ require 'tyrion/criteria'
9
10
  require 'tyrion/querying'
10
11
  require 'tyrion/storage'
11
- require 'tyrion/components'
12
12
  require 'tyrion/validations'
13
13
 
14
14
  require 'tyrion/document'
@@ -1,42 +1,54 @@
1
1
  module Tyrion
2
- module Attributes
2
+ module Attributes
3
3
  def self.included(receiver)
4
4
  receiver.extend ClassMethods
5
5
  receiver.send :include, InstanceMethods
6
6
  end
7
-
7
+
8
8
  module ClassMethods
9
9
  def field(name, type = String)
10
10
  name = name.to_s
11
-
11
+
12
12
  define_method("#{name}"){ attributes[name] }
13
13
  define_method("#{name}="){ |value| attributes[name] = value }
14
14
  end
15
15
  end
16
-
16
+
17
17
  module InstanceMethods
18
18
  attr_reader :attributes
19
-
19
+
20
+ def initialize(attrs = {})
21
+ @attributes = attrs.stringify_keys
22
+ end
23
+
20
24
  def method_missing(name, *args)
21
25
  attr = name.to_s
22
26
  return super unless attributes.has_key? attr.gsub("=", "")
23
-
27
+
24
28
  if attr =~ /=$/
25
29
  write_attribute(attr[0..-2], args.shift || raise("BOOM"))
26
30
  else
27
31
  read_attribute(attr)
28
32
  end
29
33
  end
30
-
31
- protected
32
-
34
+
35
+ # Checks for equality.
36
+ # @example Compare two objects
37
+ # object == other
38
+ # @param [Object] The other object to compare with
39
+ # @return [true, false] True if objects' attributes are the same, false otherwise.
40
+ def == other
41
+ return false unless other.is_a?(Tyrion::Document)
42
+ other.attributes == attributes
43
+ end
44
+
33
45
  def read_attribute(name)
34
46
  attributes[name.to_s]
35
47
  end
36
-
48
+
37
49
  def write_attribute(name, arg)
38
50
  attributes[name.to_s] = arg
39
51
  end
40
52
  end
41
53
  end
42
- end
54
+ end
@@ -1,11 +1,11 @@
1
1
  module Tyrion
2
2
  module Connection
3
3
  extend self
4
-
4
+
5
5
  def path= folder_path
6
6
  @folder_path = folder_path
7
7
  end
8
-
8
+
9
9
  def path
10
10
  @folder_path || raise("No path set!")
11
11
  end
@@ -0,0 +1,94 @@
1
+ module Tyrion
2
+ class Criteria
3
+ include Enumerable
4
+
5
+ attr_reader :klass
6
+
7
+ def initialize(storage, klass)
8
+ @storage = storage
9
+ @klass = klass
10
+ @constraints = []
11
+ end
12
+
13
+ def all
14
+ self
15
+ end
16
+
17
+ def where(params = {})
18
+ @constraints << [:where, params]
19
+ self
20
+ end
21
+
22
+ def limit(n)
23
+ @constraints << [:limit, n]
24
+ self
25
+ end
26
+
27
+ def skip(n)
28
+ @constraints << [:skip, n]
29
+ self
30
+ end
31
+
32
+ def asc(*keys)
33
+ @constraints << [:sort, [keys, true]]
34
+ self
35
+ end
36
+
37
+ def desc(*keys)
38
+ @constraints << [:sort, [keys, false]]
39
+ self
40
+ end
41
+
42
+ def inspect
43
+ "<Criteria:0x#{__id__.to_s(16)}\n" +
44
+ " Model:#{@klass.capitalize},\n" +
45
+ " Constraints: " + @constraints.inspect[1..-2] + ">"
46
+ end
47
+
48
+ def each(&block)
49
+ fetch_documents if not @data
50
+ @data.each{ |c| block.call(c) }
51
+ end
52
+
53
+ def last
54
+ to_a.last
55
+ end
56
+
57
+ private
58
+
59
+ def fetch_documents
60
+ @data = @storage.dup
61
+
62
+ @constraints.each do |con|
63
+ type, args = con
64
+
65
+ case type
66
+ when :where
67
+ @data = @data.select do |doc|
68
+ args.each_pair.map do |k, v|
69
+ doc.read_attribute(k) == v
70
+ end.inject(&:&)
71
+ end
72
+ when :limit
73
+ @data = @data.take(args)
74
+ when :skip
75
+ @data = Array(@data[args..-1])
76
+ when :sort
77
+ keys, dir = args
78
+ if @data.respond_to? :sort_by!
79
+ @data.sort_by! do |doc|
80
+ keys.map{|s| doc.read_attribute(s) }
81
+ end
82
+ else
83
+ @data.sort! do |a, b|
84
+ first = keys.map{|s| a.read_attribute(s) }
85
+ second = keys.map{|s| b.read_attribute(s) }
86
+ first <=> second
87
+ end
88
+ end
89
+ @data.reverse! unless dir
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -1,42 +1,31 @@
1
1
  module Tyrion
2
-
2
+
3
3
  # Base module to persist objects.
4
4
  module Document
5
5
  def self.included(receiver)
6
6
  receiver.extend ClassMethods
7
-
7
+
8
8
  receiver.class_eval do
9
- include Tyrion::Components
9
+ include Tyrion::Attributes
10
+ include Tyrion::Querying
11
+ include Tyrion::Validations
12
+ include Tyrion::Persistence
13
+ include Tyrion::Storage
10
14
  include InstanceMethods
11
15
  end
12
16
  end
13
-
17
+
14
18
  module ClassMethods
15
- protected
16
-
19
+ private
20
+
17
21
  def klass_name
18
22
  to_s.downcase
19
23
  end
20
24
  end
21
-
25
+
22
26
  module InstanceMethods
23
-
24
- # Checks for equality.
25
- # @example Compare two objects
26
- # object == other
27
- # @param [Object] The other object to compare with
28
- # @return [true, false] True if objects' attributes are the same, false otherwise.
29
- def == other
30
- other.attributes == attributes
31
- end
32
-
33
- def initialize(attrs = {})
34
- @attributes = attrs.stringify_keys
35
- @new_document = true
36
- end
37
-
38
27
  private
39
-
28
+
40
29
  def klass_name
41
30
  self.class.to_s.downcase
42
31
  end
@@ -4,47 +4,43 @@ module Tyrion
4
4
  receiver.extend ClassMethods
5
5
  receiver.send :include, InstanceMethods
6
6
  end
7
-
7
+
8
8
  module ClassMethods
9
- def create attributes = {}
10
- new(attributes).tap do |n|
11
- attributes.each_pair{ |k, v| n.send(:write_attribute, k.to_s, v) }
12
- end
13
- end
14
-
15
- def create! attributes = {}
16
- create(attributes).tap{ |doc| doc.save }
9
+ def create(*args)
10
+ new(*args).tap{ |doc| doc.save }
17
11
  end
18
-
12
+
19
13
  def delete_all
20
14
  storage[klass_name].clear
21
- end
22
-
23
- def delete attributes = {}
24
- where(attributes).each(&:delete)
15
+ save_storage(klass_name)
25
16
  end
26
17
  end
27
-
28
- module InstanceMethods
18
+
19
+ module InstanceMethods
20
+ def initialize(*args)
21
+ super(*args)
22
+ @persisted = false
23
+ end
24
+
29
25
  def save
30
26
  if valid?
31
27
  self.class.storage[klass_name] << self
32
28
  self.class.save_storage klass_name
33
- @new_document = false
34
-
29
+ @persisted = true
30
+
35
31
  true
36
32
  else
37
33
  false
38
34
  end
39
35
  end
40
-
36
+
41
37
  def delete
42
38
  self.class.storage[klass_name].delete_if{ |doc| self == doc }
43
- self.class.save_storage klass_name
39
+ self.class.save_storage(klass_name)
44
40
  end
45
-
46
- def new_document?
47
- @new_document
41
+
42
+ def persisted?
43
+ @persisted
48
44
  end
49
45
  end
50
46
  end
@@ -1,50 +1,26 @@
1
+ require 'forwardable'
2
+
1
3
  module Tyrion
2
4
  module Querying
3
5
  def self.included(receiver)
4
6
  receiver.extend ClassMethods
5
7
  end
6
-
8
+
7
9
  module ClassMethods
8
- def all
9
- storage[klass_name].dup
10
+ extend Forwardable
11
+
12
+ def_delegators :new_criteria, :all, :where, :first, :last, :limit,
13
+ :skip, :asc, :desc
14
+
15
+ def count
16
+ storage[klass_name].count
10
17
  end
11
-
12
- def where attributes = {}
13
- all.map do |doc|
14
- match = attributes.each_pair.map do |k, v|
15
- operator = if v.is_a? Regexp
16
- :=~
17
- else
18
- :==
19
- end
20
-
21
- true if doc.send(:read_attribute, k.to_s).send(operator, v)
22
- end.compact
23
-
24
- doc if match.count == attributes.count
25
- end.compact
26
- end
27
-
28
- def method_missing(method, *args)
29
- if method.to_s =~ /^find_by_(.+)$/
30
- attr = $1
31
- arg = args.shift || raise("Provide at least one argument!")
32
-
33
- all.each do |doc|
34
- operator = if arg.is_a? Regexp
35
- :=~
36
- else
37
- :==
38
- end
39
-
40
- return doc if doc.attributes[attr].send(operator, arg)
41
- end
42
-
43
- nil
44
- else
45
- super
46
- end
18
+
19
+ private
20
+
21
+ def new_criteria
22
+ Criteria.new(storage[klass_name], klass_name)
47
23
  end
48
24
  end
49
25
  end
50
- end
26
+ end
@@ -1,38 +1,38 @@
1
1
  module Tyrion
2
- module Storage
2
+ module Storage
3
3
  def self.included(receiver)
4
4
  receiver.extend ClassMethods
5
-
6
- receiver.reload unless receiver == Tyrion::Components
5
+
6
+ receiver.reload unless receiver == Tyrion::Document
7
7
  end
8
-
8
+
9
9
  module ClassMethods
10
10
  def storage
11
11
  @storage ||= {}
12
12
  end
13
-
13
+
14
14
  def reload
15
15
  klass_name = to_s.downcase
16
16
  path = klass_filepath klass_name
17
-
17
+
18
18
  if File.exists?(path)
19
19
  raw_file = File.read(path)
20
- storage[klass_name] = MultiJson.decode(raw_file).map{ |doc| create doc }
20
+ storage[klass_name] = MultiJson.decode(raw_file).map{ |doc| new(doc) }
21
21
  else
22
22
  storage[klass_name] = []
23
23
  end
24
24
  end
25
-
25
+
26
26
  def save_storage klass_name
27
27
  path = klass_filepath klass_name
28
-
28
+
29
29
  File.open(path, 'w') do |f|
30
30
  f.puts MultiJson.encode(storage[klass_name].map &:attributes)
31
31
  end
32
32
  end
33
-
33
+
34
34
  protected
35
-
35
+
36
36
  def klass_filepath klass_name
37
37
  File.join(Connection.path, klass_name + ".json")
38
38
  end
@@ -1,3 +1,3 @@
1
1
  module Tyrion #:nodoc:
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tyrion
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-12-29 00:00:00.000000000 Z
12
+ date: 2012-01-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: multi_json
16
- requirement: &70281257801860 !ruby/object:Gem::Requirement
16
+ requirement: &70231390612120 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70281257801860
24
+ version_requirements: *70231390612120
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: json_pure
27
- requirement: &70281257801040 !ruby/object:Gem::Requirement
27
+ requirement: &70231390611420 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70281257801040
35
+ version_requirements: *70231390611420
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: activemodel
38
- requirement: &70281257816640 !ruby/object:Gem::Requirement
38
+ requirement: &70231390610660 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 3.1.0
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70281257816640
46
+ version_requirements: *70231390610660
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rspec
49
- requirement: &70281257815820 !ruby/object:Gem::Requirement
49
+ requirement: &70231390626300 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,21 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70281257815820
57
+ version_requirements: *70231390626300
58
+ - !ruby/object:Gem::Dependency
59
+ name: pry
60
+ requirement: &70231390625480 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70231390625480
58
69
  - !ruby/object:Gem::Dependency
59
70
  name: simplecov
60
- requirement: &70281257814980 !ruby/object:Gem::Requirement
71
+ requirement: &70231390624840 !ruby/object:Gem::Requirement
61
72
  none: false
62
73
  requirements:
63
74
  - - ! '>='
@@ -65,7 +76,7 @@ dependencies:
65
76
  version: '0'
66
77
  type: :development
67
78
  prerelease: false
68
- version_requirements: *70281257814980
79
+ version_requirements: *70231390624840
69
80
  description: Tyrion's goal is to provide a fast (as in _easy to setup_) and dirty
70
81
  unstructured document store.
71
82
  email:
@@ -75,8 +86,8 @@ extensions: []
75
86
  extra_rdoc_files: []
76
87
  files:
77
88
  - lib/tyrion/attributes.rb
78
- - lib/tyrion/components.rb
79
89
  - lib/tyrion/connection.rb
90
+ - lib/tyrion/criteria.rb
80
91
  - lib/tyrion/document.rb
81
92
  - lib/tyrion/persistence.rb
82
93
  - lib/tyrion/querying.rb
@@ -1,13 +0,0 @@
1
- module Tyrion
2
- module Components
3
- def self.included(receiver)
4
- receiver.class_eval do
5
- include Tyrion::Attributes
6
- include Tyrion::Validations
7
- include Tyrion::Persistence
8
- include Tyrion::Querying
9
- include Tyrion::Storage
10
- end
11
- end
12
- end
13
- end