tyrion 0.1.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.
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ .DS_Store?
2
+ .DS_Store
3
+ ehthumbs.db
4
+ Icon?
5
+ Thumbs.db
6
+
7
+ # Rails
8
+ coverage
9
+ rdoc
10
+ tmp
11
+ pkg
12
+ *.gem
13
+ *.rbc
14
+ lib/bundler/man
15
+ spec/reports
16
+ .config
17
+ .bundle
18
+
19
+ # YARD artifacts
20
+ .yardoc
21
+ _yardoc
22
+ doc/
23
+
24
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - ruby-head
6
+ - ree
7
+ - rbx
8
+ - rbx-2.0
9
+ - jruby
10
+
11
+ script: 'bundle exec rspec spec'
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in tyrion.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Federico Ravasio
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,64 @@
1
+ # Tyrion [![Build Status](http://travis-ci.org/razielgn/tyrion.png)](http://travis-ci.org/razielgn/tyrion)
2
+
3
+ A small JSON ODM.
4
+
5
+ ## Goal
6
+
7
+ Tyrion's goal is to provide a fast (as in _easy to setup_) and dirty unstructured document store.
8
+
9
+ ## Usage
10
+ ### Connection
11
+ Tyrion uses a folder to store JSON files, one for each Tyrion document defined.
12
+ Each file is an array of homogeneous documents (much like collections).
13
+
14
+ ``` ruby
15
+ Tyrion::Connection.path = "/a/folder"
16
+ ```
17
+
18
+ ### Document
19
+ ``` ruby
20
+ class Post
21
+ include Tyrion::Document
22
+
23
+ field :title
24
+ field :body
25
+ field :rank
26
+ end
27
+ ```
28
+
29
+ ### Persistence
30
+ #### Save
31
+ ``` ruby
32
+ post = Post.create :title => "Hello", :body => "Hi there, ..."
33
+ post.save
34
+ ```
35
+ ``` ruby
36
+ # Insta-save with !
37
+ Post.create! :title => "Hello", :body => "Hi there, ..."
38
+ ```
39
+
40
+ #### Delete
41
+ ``` ruby
42
+ post = Post.create :title => "Hello", :body => "Hi there, ..."
43
+ post.delete
44
+ ```
45
+ ``` ruby
46
+ Post.delete_all # You get the idea
47
+ ```
48
+ ``` ruby
49
+ Post.delete :title => /^Hello/
50
+ ```
51
+
52
+ ### Querying
53
+ `find_by_attribute`: just the first match
54
+
55
+ ``` ruby
56
+ Post.find_by_title "Hello"
57
+ Post.find_by_body /^Hi there/i
58
+ ```
59
+
60
+ `where`: all matching documents
61
+
62
+ ``` ruby
63
+ Post.where :title => /^Hello/, :rank => 3
64
+ ```
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'yard'
5
+
6
+ desc 'Generate documentation'
7
+ YARD::Rake::YardocTask.new do |t|
8
+ t.files = ['lib/**/*.rb', '-', 'LICENSE']
9
+ t.options = ['--main', 'README.markdown', '--no-private']
10
+ end
@@ -0,0 +1,44 @@
1
+ module Tyrion
2
+ module Attributes
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def field(name, type = String)
7
+ name = name.to_s
8
+
9
+ define_method("#{name}"){ attributes[name] }
10
+ define_method("#{name}="){ |value| attributes[name] = value }
11
+ end
12
+ end
13
+
14
+ module InstanceMethods
15
+ attr_reader :attributes
16
+
17
+ def initialize
18
+ super
19
+ @attributes = {}
20
+ end
21
+
22
+ def method_missing(name, *args)
23
+ attr = name.to_s
24
+ return super unless attributes.has_key? attr.gsub("=", "")
25
+
26
+ if attr =~ /=$/
27
+ write_attribute(attr[0..-2], args.shift || raise("BOOM"))
28
+ else
29
+ read_attribute(attr)
30
+ end
31
+ end
32
+
33
+ protected
34
+
35
+ def read_attribute(name)
36
+ attributes[name.to_s]
37
+ end
38
+
39
+ def write_attribute(name, arg)
40
+ attributes[name.to_s] = arg
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,10 @@
1
+ module Tyrion
2
+ module Components
3
+ extend ActiveSupport::Concern
4
+
5
+ include Tyrion::Attributes
6
+ include Tyrion::Persistence
7
+ include Tyrion::Querying
8
+ include Tyrion::Storage
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ module Tyrion
2
+ module Connection
3
+ extend self
4
+
5
+ def path= folder_path
6
+ @folder_path = folder_path
7
+ end
8
+
9
+ def path
10
+ @folder_path || raise("No path set!")
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,34 @@
1
+ module Tyrion
2
+
3
+ # Base module to persist objects.
4
+ module Document
5
+ extend ActiveSupport::Concern
6
+ include Tyrion::Components
7
+
8
+ module ClassMethods
9
+ protected
10
+
11
+ def klass_name
12
+ to_s.downcase
13
+ end
14
+ end
15
+
16
+ module InstanceMethods
17
+
18
+ # Checks for equality.
19
+ # @example Compare two objects
20
+ # object == other
21
+ # @param [Object] The other object to compare with
22
+ # @return [true, false] True if objects' attributes are the same, false otherwise.
23
+ def == other
24
+ other.attributes == attributes
25
+ end
26
+
27
+ private
28
+
29
+ def klass_name
30
+ self.class.to_s.downcase
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,37 @@
1
+ module Tyrion
2
+ module Persistence
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def create attributes = {}
7
+ new.tap do |n|
8
+ attributes.each_pair{ |k, v| n.send(:write_attribute, k.to_s, v) }
9
+ end
10
+ end
11
+
12
+ def create! attributes = {}
13
+ create(attributes).tap{ |doc| doc.save }
14
+ end
15
+
16
+ def delete_all
17
+ storage[klass_name].clear
18
+ end
19
+
20
+ def delete attributes = {}
21
+ where(attributes).each(&:delete)
22
+ end
23
+ end
24
+
25
+ module InstanceMethods
26
+ def save
27
+ self.class.storage[klass_name] << self
28
+ self.class.save_storage klass_name
29
+ end
30
+
31
+ def delete
32
+ self.class.storage[klass_name].delete_if{ |doc| self == doc }
33
+ self.class.save_storage klass_name
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,51 @@
1
+ module Tyrion
2
+ module Querying
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def all
7
+ storage[klass_name].dup
8
+ end
9
+
10
+ def where attributes = {}
11
+ all.map do |doc|
12
+ match = attributes.each_pair.map do |k, v|
13
+ operator = if v.is_a? Regexp
14
+ :=~
15
+ else
16
+ :==
17
+ end
18
+
19
+ true if doc.send(:read_attribute, k.to_s).send(operator, v)
20
+ end.compact
21
+
22
+ doc if match.count == attributes.count
23
+ end.compact
24
+ end
25
+
26
+ def method_missing(method, *args)
27
+ if method.to_s =~ /^find_by_(.+)$/
28
+ attr = $1
29
+ arg = args.shift || raise("Provide at least one argument!")
30
+
31
+ all.each do |doc|
32
+ operator = if arg.is_a? Regexp
33
+ :=~
34
+ else
35
+ :==
36
+ end
37
+
38
+ return doc if doc.attributes[attr].send(operator, arg)
39
+ end
40
+
41
+ nil
42
+ else
43
+ super
44
+ end
45
+ end
46
+ end
47
+
48
+ module InstanceMethods
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,46 @@
1
+ module Tyrion
2
+ module Storage
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ reload unless self == Tyrion::Components
7
+ end
8
+
9
+ module ClassMethods
10
+ def storage
11
+ @storage ||= {}
12
+ end
13
+
14
+ def reload
15
+ klass_name = to_s.downcase
16
+ path = klass_filepath klass_name
17
+
18
+ if File.exists?(path)
19
+ raw_file = File.read(path)
20
+ storage[klass_name] = MultiJson.decode(raw_file).map{ |doc| create doc }
21
+ else
22
+ storage[klass_name] = []
23
+ end
24
+ end
25
+
26
+ def save_storage klass_name
27
+ path = klass_filepath klass_name
28
+
29
+ File.open(path, 'w') do |f|
30
+ f.puts MultiJson.encode(storage[klass_name].map &:attributes)
31
+ end
32
+
33
+ true
34
+ end
35
+
36
+ protected
37
+
38
+ def klass_filepath klass_name
39
+ File.join(Connection.path, klass_name + ".json")
40
+ end
41
+ end
42
+
43
+ module InstanceMethods
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ module Tyrion #:nodoc:
2
+ VERSION = '0.1.0'
3
+ end
data/lib/tyrion.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'multi_json'
2
+ require 'active_support'
3
+
4
+ require 'tyrion/version'
5
+ require 'tyrion/connection'
6
+
7
+ require 'tyrion/attributes'
8
+ require 'tyrion/persistence'
9
+ require 'tyrion/querying'
10
+ require 'tyrion/storage'
11
+ require 'tyrion/components'
12
+
13
+ require 'tyrion/document'
@@ -0,0 +1,22 @@
1
+ require 'test_helper'
2
+
3
+ describe Tyrion::Connection do
4
+ before{ @original_path = subject.path }
5
+ after{ subject.path = @original_path }
6
+
7
+ describe '.path' do
8
+ it 'should raise an exception if no path is set' do
9
+ expect do
10
+ subject.path = nil
11
+ subject.path
12
+ end.to raise_exception
13
+ end
14
+ end
15
+
16
+ describe '.path=' do
17
+ it 'should set the path correctly' do
18
+ subject.path = ENV['HOME']
19
+ subject.path.should == ENV['HOME']
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,133 @@
1
+ require 'test_helper'
2
+ require 'examples/post'
3
+
4
+ describe Tyrion::Document do
5
+
6
+ let! :yml_posts do
7
+ YAML.load_file File.join(File.dirname(__FILE__), 'fixtures', 'posts.yml')
8
+ end
9
+
10
+ before :each do
11
+ Post.reload
12
+
13
+ yml_posts.each do |p|
14
+ Post.create! p
15
+ end
16
+ end
17
+
18
+ it 'should survive a reload' do
19
+ Post.reload
20
+ Post.all.count.should == yml_posts.count
21
+ end
22
+
23
+ describe ".create" do
24
+ it 'should create a new instance with given attributes' do
25
+ post = Post.create :title => "Title", :body => "Body"
26
+ post.title.should == "Title"
27
+ post.body.should == "Body"
28
+ end
29
+ end
30
+
31
+ describe ".all" do
32
+ it 'should find three posts' do
33
+ Post.all.count.should == yml_posts.count
34
+ end
35
+ end
36
+
37
+ describe ".find_by_" do
38
+ context "when performing the search without a regexp" do
39
+ it "should return a single value" do
40
+ search = Post.find_by_title("Hello")
41
+ search.should_not be_nil
42
+ search.should_not be_a(Array)
43
+ end
44
+
45
+ it 'should return the first document found' do
46
+ search = Post.find_by_rank(2)
47
+ search.title.should == "Testing"
48
+ end
49
+
50
+ it "should return nil if nothing is found" do
51
+ Post.find_by_title(5).should be_nil
52
+ end
53
+ end
54
+
55
+ context "when performing the search with a regexp" do
56
+ it "should return a single value" do
57
+ search = Post.find_by_title(/Hello/).should_not be_nil
58
+ search.should_not be_nil
59
+ search.should_not be_a(Array)
60
+ end
61
+
62
+ it "should return nil if nothing is found" do
63
+ Post.find_by_title(/^u/).should be_nil
64
+ end
65
+ end
66
+
67
+ it 'should raise an exception if the search is performed without arguments' do
68
+ expect do
69
+ Post.find_by_body
70
+ end.to raise_exception
71
+ end
72
+
73
+ it 'should raise an exception if the search is performed on an unknown attribute' do
74
+ expect do
75
+ Post.find_by_id
76
+ end.to raise_exception
77
+ end
78
+ end
79
+
80
+ describe ".remove_all" do
81
+ it 'should delete every document' do
82
+ Post.delete_all
83
+ Post.all.count.should == 0
84
+ end
85
+ end
86
+
87
+ describe ".remove" do
88
+ it "should remove all matching documents" do
89
+ criteria = { :rank => 2, :body => /!$/ }
90
+ Post.delete(criteria)
91
+ Post.where(criteria).should be_empty
92
+ end
93
+ end
94
+
95
+ describe ".where" do
96
+ it 'should return an array on matching documents' do
97
+ search = Post.where(:rank => 2, :body => /!$/)
98
+ search.should_not be_nil
99
+ search.should be_a(Array)
100
+ search.count.should == 2
101
+ end
102
+
103
+ it 'shoudl return an empty array if no documents are matched' do
104
+ Post.where(:title => /e/, :body => /^A/, :rank => 9000).should be_empty
105
+ end
106
+ end
107
+
108
+ describe "#save" do
109
+ it 'should save a new document' do
110
+ post = Post.new
111
+ post.title = "Title"
112
+ post.body = "Body"
113
+ post.save.should be_true
114
+ end
115
+ end
116
+
117
+ describe "#method_missing" do
118
+ it 'should allow to read and update new attributes' do
119
+ post = Post.new
120
+ post.send :write_attribute, :comments, 1
121
+ post.comments.should == 1
122
+ post.comments = 2
123
+ post.comments.should == 2
124
+ end
125
+ end
126
+
127
+ describe "#delete" do
128
+ it 'should remove the document' do
129
+ Post.find_by_title("Hello").delete
130
+ Post.find_by_title("Hello").should be_nil
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,7 @@
1
+ class Post
2
+ include Tyrion::Document
3
+
4
+ field :rank
5
+ field :title
6
+ field :body
7
+ end
@@ -0,0 +1,10 @@
1
+ ---
2
+ - title: Hello
3
+ body: "Hi there, this is my new blog!"
4
+ rank: 1
5
+ - title: Testing
6
+ body: "Just testing some stuff around!"
7
+ rank: 2
8
+ - title: An idea
9
+ body: "I've got an idea, lemme try something!"
10
+ rank: 2
@@ -0,0 +1,24 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
4
+ require 'tmpdir'
5
+ require 'fileutils'
6
+
7
+ require 'tyrion'
8
+
9
+ tmpdir = Dir.mktmpdir
10
+ Tyrion::Connection.path = tmpdir
11
+
12
+ RSpec.configure do |config|
13
+ config.before(:each) do
14
+ Tyrion::Connection.path = Dir.mktmpdir
15
+ end
16
+
17
+ config.after(:each) do
18
+ FileUtils.rm_rf Tyrion::Connection.path
19
+ end
20
+
21
+ config.after(:suite) do
22
+ FileUtils.rm_rf tmpdir
23
+ end
24
+ end
data/tyrion.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "tyrion/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "tyrion"
7
+ s.version = Tyrion::VERSION
8
+ s.authors = ["Federico Ravasio"]
9
+ s.email = ["ravasio.federico@gmail.com"]
10
+ s.homepage = "http://github.com/"
11
+ s.summary = %q{Tyrion is a small JSON ODM}
12
+ s.description = %q{Tyrion's goal is to provide a fast (as in _easy to setup_) and dirty unstructured document store.}
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ["lib"]
18
+
19
+ s.add_dependency 'multi_json'
20
+ s.add_dependency 'json_pure'
21
+ s.add_dependency 'active_support'
22
+
23
+ s.add_development_dependency 'rspec'
24
+ s.add_development_dependency 'simplecov'
25
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tyrion
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Federico Ravasio
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-09-22 00:00:00.000000000 +02:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: multi_json
17
+ requirement: &70236571329080 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *70236571329080
26
+ - !ruby/object:Gem::Dependency
27
+ name: json_pure
28
+ requirement: &70236571328660 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *70236571328660
37
+ - !ruby/object:Gem::Dependency
38
+ name: active_support
39
+ requirement: &70236573373400 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :runtime
46
+ prerelease: false
47
+ version_requirements: *70236573373400
48
+ - !ruby/object:Gem::Dependency
49
+ name: rspec
50
+ requirement: &70236573372980 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *70236573372980
59
+ - !ruby/object:Gem::Dependency
60
+ name: simplecov
61
+ requirement: &70236573372560 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: *70236573372560
70
+ description: Tyrion's goal is to provide a fast (as in _easy to setup_) and dirty
71
+ unstructured document store.
72
+ email:
73
+ - ravasio.federico@gmail.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - .gitignore
79
+ - .rspec
80
+ - .travis.yml
81
+ - Gemfile
82
+ - LICENSE
83
+ - README.markdown
84
+ - Rakefile
85
+ - lib/tyrion.rb
86
+ - lib/tyrion/attributes.rb
87
+ - lib/tyrion/components.rb
88
+ - lib/tyrion/connection.rb
89
+ - lib/tyrion/document.rb
90
+ - lib/tyrion/persistence.rb
91
+ - lib/tyrion/querying.rb
92
+ - lib/tyrion/storage.rb
93
+ - lib/tyrion/version.rb
94
+ - spec/connection_spec.rb
95
+ - spec/document_spec.rb
96
+ - spec/examples/post.rb
97
+ - spec/fixtures/posts.yml
98
+ - spec/test_helper.rb
99
+ - tyrion.gemspec
100
+ has_rdoc: true
101
+ homepage: http://github.com/
102
+ licenses: []
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ! '>='
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubyforge_project:
121
+ rubygems_version: 1.6.2
122
+ signing_key:
123
+ specification_version: 3
124
+ summary: Tyrion is a small JSON ODM
125
+ test_files:
126
+ - spec/connection_spec.rb
127
+ - spec/document_spec.rb
128
+ - spec/examples/post.rb
129
+ - spec/fixtures/posts.yml
130
+ - spec/test_helper.rb