tyrion 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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