jason-orm 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ spec/fixtures/*.json
2
+ *.gem
3
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,41 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ jason (0.1)
5
+ activesupport
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ activesupport (3.2.6)
11
+ i18n (~> 0.6)
12
+ multi_json (~> 1.0)
13
+ chronic (0.6.7)
14
+ diff-lcs (1.1.3)
15
+ fuubar (1.0.0)
16
+ rspec (~> 2.0)
17
+ rspec-instafail (~> 0.2.0)
18
+ ruby-progressbar (~> 0.0.10)
19
+ i18n (0.6.0)
20
+ multi_json (1.3.6)
21
+ rake (0.9.2.2)
22
+ rspec (2.11.0)
23
+ rspec-core (~> 2.11.0)
24
+ rspec-expectations (~> 2.11.0)
25
+ rspec-mocks (~> 2.11.0)
26
+ rspec-core (2.11.0)
27
+ rspec-expectations (2.11.1)
28
+ diff-lcs (~> 1.1.3)
29
+ rspec-instafail (0.2.4)
30
+ rspec-mocks (2.11.1)
31
+ ruby-progressbar (0.0.10)
32
+
33
+ PLATFORMS
34
+ ruby
35
+
36
+ DEPENDENCIES
37
+ chronic
38
+ fuubar
39
+ jason!
40
+ rake
41
+ rspec
data/Readme.md ADDED
@@ -0,0 +1,130 @@
1
+ ## Jason (Json Persistence Framework)
2
+
3
+ ### Introduction
4
+
5
+ It's only for demonstration purposes. If you want to use it in your application - feel free.
6
+
7
+ It uses *.json files for persistence. One for each model (imagine as *row*).
8
+
9
+ ### Features
10
+
11
+ By now, Jason supports:
12
+
13
+ * a persistence_layer
14
+ * a simple relation (association) mapper
15
+
16
+ It's customizable:
17
+
18
+ ```ruby
19
+
20
+ Jason.setup do |config|
21
+
22
+ config.persistence_path = "/Users/bob/jsons"
23
+
24
+ config.restore_app = MyOwn::RestoreApp
25
+
26
+ end
27
+
28
+ ```
29
+
30
+ <code>config.restore_app</code> allows to use an own Class which handles restoring from *.json* files.
31
+ If this is set, it replaces the integrated <code>Encoding::PersistenceHandler::Restorable</code> class.
32
+
33
+
34
+ **The persistence layer**
35
+
36
+ Usage of the persistence layer is widly known from other libraries.
37
+
38
+ By now, it supports the following datatypes:
39
+
40
+ * Integer
41
+ * String
42
+ * Date
43
+
44
+ ```ruby
45
+ class Person
46
+
47
+ include Jason::Persistence
48
+
49
+ attribute :first_name, String
50
+ attribute :last_name, String
51
+
52
+ end
53
+
54
+ person = Person.new(:first_name => "Michail", :last_name => "Bulgakov")
55
+ person.save
56
+
57
+ person.update_attributes(:first_name => "Sascha")
58
+
59
+ person.delete
60
+
61
+ Person.find("1223wer43")
62
+ Person.find_by_id("1223wer43")
63
+ Person.find_by_last_name("Bulgakov") #=> returns Array, it's kind of 'where'
64
+ Person.find_by_first_name("Michael")
65
+
66
+ ```
67
+
68
+ **The relation mapper**
69
+
70
+ Usage of the relation mapper is also widly known from other libraries.
71
+
72
+ By now it supports:
73
+
74
+ * belongs_to
75
+ * has_many (work in progress)
76
+
77
+ ```ruby
78
+ class Person
79
+
80
+ include Jason::Persistence
81
+ include Jason::Relation
82
+
83
+ attribute :first_name, String
84
+ attribute :last_name, String
85
+ attribute :age, Integer
86
+
87
+ belongs_to :wife
88
+
89
+ end
90
+
91
+ class Wife
92
+
93
+ include Jason::Persistence
94
+ include Jason::Relation
95
+
96
+ attribute :name, String
97
+ end
98
+
99
+ person = Person.new(:first_name => "Michail", :last_name => "Bulgakov")
100
+
101
+ woman = Wife.new(:name => "Natascha Rutskovskaja")
102
+
103
+ person.wife = woman
104
+ person.save
105
+ ```
106
+
107
+ <code>belongs_to</code> takes also a second parameter which is a hash:
108
+
109
+ ```ruby
110
+
111
+ belongs_to :wife, :class => "Woman"
112
+
113
+ ```
114
+
115
+ so different class (names) are assignable to the relation name.
116
+
117
+ ### TODO
118
+
119
+ | Feature | Status |
120
+ |:--------------------------------|:------------------|
121
+ | Date | DONE |
122
+ | has_many | DONE |
123
+ | Custom data types | TODO |
124
+ | deletable only if already saved | DONE |
125
+ | code documentation | Partly & TODO |
126
+ | Gemspec | DONE |
127
+
128
+ ### Last words
129
+
130
+ Author: Daniel Schmidt, 15/16/17. July 2012
data/jason.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "jason-orm"
7
+ s.version = Jason::VERSION
8
+ s.authors = ["Daniel Schmidt"]
9
+ s.email = ["dsci@code79.net"]
10
+ s.homepage = ""
11
+ s.summary = %q{JSON persistence framework}
12
+ s.description = %q{A persistence framework based on json files.}
13
+
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ # specify any dependencies here; for example:
21
+
22
+ s.add_dependency "activesupport"
23
+
24
+ s.add_development_dependency "rake"
25
+ s.add_development_dependency "rspec"
26
+ s.add_development_dependency "fuubar"
27
+ s.add_development_dependency "chronic"
28
+ end
@@ -0,0 +1,7 @@
1
+ class String
2
+
3
+ def to_date
4
+ Date.parse(self)
5
+ end
6
+
7
+ end
@@ -0,0 +1,15 @@
1
+ module Jason
2
+
3
+ module Encryptors
4
+
5
+ class Document
6
+
7
+ def self.process_document_id(length=21)
8
+ rand(36**length).to_s(36)
9
+ end
10
+
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,30 @@
1
+ module Jason
2
+
3
+ module Encoding
4
+
5
+ module PersistenceHandler
6
+
7
+ class Deletable < PersistenceObject
8
+
9
+ include Jason::Operations::File
10
+
11
+ def delete
12
+ deleted = true
13
+ persisted_file_content = load_from_file(where_to_persist)
14
+ r_objects = ActiveSupport::JSON.decode(persisted_file_content)
15
+ r_objects.delete_if{|obj| obj[@root]["id"] == @persistable_obj.send(:id)}
16
+ save_to_file(ActiveSupport::JSON.encode(r_objects))
17
+ rescue
18
+ deleted = false
19
+
20
+ ensure
21
+ return deleted
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,102 @@
1
+ module Jason
2
+ module Encoding
3
+ module PersistenceHandler
4
+
5
+ class Persistable < PersistenceObject
6
+
7
+ include Jason::Operations::File
8
+
9
+ def initialize(obj,options={})
10
+ @update = options.fetch(:update, false)
11
+ super(obj)
12
+ end
13
+
14
+
15
+ def process_persistence
16
+ persisted = true
17
+ begin
18
+ persisted_file_content = load_from_file(where_to_persist)
19
+ r_objects = ActiveSupport::JSON.decode(persisted_file_content)
20
+ rescue MultiJson::DecodeError => de
21
+ r_objects = []
22
+ ensure
23
+ # There was something persisted before. Search for id and replace.
24
+ # (Only if update is true)
25
+ # If not updated, append to objects array.
26
+ unless @update
27
+ as_json = @persistable_obj.send(:as_json)
28
+
29
+ # persist with relations if given.
30
+ # persist belongs_to
31
+
32
+ if @persistable_obj.class.ancestors.map(&:to_s).include?("Jason::Relation")
33
+ as_json[@root].each_pair do |key,value|
34
+ next unless key.to_s.include?("_id")
35
+ relation = key.to_s.split("_id").first.to_sym
36
+ reflection = @persistable_obj.class.reflect_on_relation(relation)
37
+ if reflection
38
+ process_method = instance_method("process_#{reflection.type}")
39
+ process_method.bind(self).call(reflection,value)
40
+ end
41
+ end
42
+ end
43
+ r_objects << as_json
44
+ else
45
+ r_objects.each do |object|
46
+ if object[@root]["id"] == @persistable_obj.send(:id)
47
+ # persist with relations if given.
48
+ object[@root] = @persistable_obj.send(:as_json)[@root]
49
+ break
50
+ end
51
+ end
52
+ end
53
+ persisted = save_to_file(ActiveSupport::JSON.encode(r_objects))
54
+ end
55
+ return persisted
56
+ end
57
+
58
+ private
59
+
60
+ def process_belongs_to(reflection,value)
61
+ persist = instance_method(:run_relation_persistence)
62
+ action = lambda do |relation_name,relation_class,value|
63
+ begin
64
+ already_persisted = relation_class.find(value)
65
+ rescue Jason::Errors::DocumentNotFoundError
66
+ relation_obj = @persistable_obj.send(relation_name)
67
+ relation_obj.save
68
+ end
69
+ end
70
+ persist.bind(self).call(reflection,value,action)
71
+ end
72
+
73
+ def process_has_many(reflection,value)
74
+ persist = instance_method(:run_relation_persistence)
75
+ action = lambda do |relation_name,relation_class,value|
76
+ ids = value.split(Jason::has_many_separator)
77
+ ids.each do |id|
78
+ begin
79
+ relation_class.find(id)
80
+ rescue Jason::Errors::DocumentNotFoundError
81
+ relation_obj = @persistable_obj.send(relation_name).detect{|obj| obj.id == id}
82
+ relation_obj.save unless relation_obj.nil?
83
+ end
84
+ end
85
+ end
86
+ persist.bind(self).call(reflection,value,action)
87
+ end
88
+
89
+ def run_relation_persistence(reflection,value,block)
90
+ relation_name = reflection.name
91
+ relation_class = Module.const_get(reflection.class_name.to_sym)
92
+ block.call(relation_name,relation_class,value)
93
+ end
94
+
95
+ def r_objects
96
+ @r_objects ||= []
97
+ end
98
+
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,33 @@
1
+ require 'active_support/hash_with_indifferent_access'
2
+ require_relative 'persistence_object'
3
+ require_relative 'deletable'
4
+ require_relative 'persistable'
5
+ require_relative 'restorable'
6
+
7
+
8
+ module Jason
9
+
10
+ module Encoding
11
+
12
+ module PersistenceHandler
13
+
14
+ extend self
15
+
16
+ def persist(obj, options={})
17
+ return Persistable.new(obj,options).process_persistence
18
+ end
19
+
20
+ def delete(obj)
21
+ return Deletable.new(obj).delete
22
+ end
23
+
24
+ def restore(*args)
25
+ action = args.shift
26
+ Jason.restore_app.new.send(action,*args)
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,26 @@
1
+ module Jason
2
+ module Encoding
3
+ module PersistenceHandler
4
+
5
+ class PersistenceObject
6
+ attr_accessor :persistable_obj
7
+
8
+ def eigenclass
9
+ class << self;
10
+ self;
11
+ end
12
+ end
13
+
14
+ def initialize(obj)
15
+ @persistable_obj = obj
16
+ @root = Jason.singularize_key(obj.class)
17
+ end
18
+
19
+ def instance_method(method_name)
20
+ eigenclass.instance_method(method_name)
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,83 @@
1
+ module Jason
2
+ module Encoding
3
+ module PersistenceHandler
4
+
5
+ class Restorable
6
+
7
+ include Jason::Operations::File
8
+
9
+ def by_id(*args)
10
+ options = args.shift
11
+
12
+ @id = options[:id]
13
+ @klass = options[:klass]
14
+ key = Jason::singularize_key(@klass)
15
+ persisted_file_content = load_from_file(where_to_persist(@klass.name))
16
+ r_objects = ActiveSupport::JSON.decode(persisted_file_content)
17
+ obj = r_objects.detect{|obj| obj[key]["id"] == @id}
18
+ raise Jason::Errors::DocumentNotFoundError, "Document not found with id #{@id}." if obj.nil?
19
+
20
+ return restore_with_cast(obj[key])
21
+ rescue MultiJson::DecodeError => de
22
+ raise Jason::Errors::DocumentNotFoundError, "Document not found with id #{@id}."
23
+ end
24
+
25
+ def all(*args)
26
+ options = args.shift
27
+
28
+ @klass = options[:klass]
29
+ key = Jason::singularize_key(@klass)
30
+ persisted_file_content = load_from_file(where_to_persist(@klass.name))
31
+ r_objects = ActiveSupport::JSON.decode(persisted_file_content)
32
+ r_objects.map do |obj|
33
+ restore_with_cast(obj[key])
34
+ end
35
+ end
36
+
37
+ def with_conditions(*args)
38
+ options = args.pop
39
+
40
+ @klass = options[:klass]
41
+ options.delete(:klass)
42
+ key = Jason::singularize_key(@klass)
43
+
44
+ persisted_file_content = load_from_file(where_to_persist(@klass.name))
45
+ r_objects = ActiveSupport::JSON.decode(persisted_file_content)
46
+ found_objects = []
47
+
48
+ r_objects.each do |obj|
49
+ if obj[key][options.keys.join.to_s] == options.values.join
50
+ found_objects << restore_with_cast(obj[key])
51
+ end
52
+ end
53
+ found_objects
54
+ end
55
+
56
+ private
57
+
58
+ def restore_with_cast(attributes)
59
+ instance = @klass.new(HashWithIndifferentAccess.new(attributes))
60
+
61
+ instance.instance_eval do
62
+ @new_record = false
63
+ # parse in given datatypes
64
+ @attributes.each_pair do |key,value|
65
+ begin
66
+ attribute_definition = self.class.defined_attributes.detect do |item|
67
+ item[:name] == key.to_sym
68
+ end[:type]
69
+
70
+ cast_to = Jason::DATA_TYPES["#{attribute_definition.to_sym}"]
71
+ instance_variable_set("@#{key}",value.send(cast_to))
72
+ rescue
73
+ next
74
+ end
75
+ end
76
+ end
77
+ return instance
78
+ end
79
+ end
80
+
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,13 @@
1
+ module Jason
2
+
3
+ module Errors
4
+
5
+ class NotSupportedDataTypeError < StandardError;end
6
+
7
+ class DocumentNotFoundError < StandardError;end
8
+
9
+ class UndeletableError < StandardError; end
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,41 @@
1
+ module Jason
2
+
3
+ module Operations
4
+
5
+ module File
6
+
7
+ private
8
+
9
+ def where_to_persist(obj_name=nil)
10
+ if obj_name.nil? and self.respond_to?(:persistable_obj)
11
+ file_prefix = persistable_obj.class.name.tableize
12
+ else
13
+ file_prefix = obj_name.tableize
14
+ end
15
+ ::File.join(Jason.persistence_path,"#{file_prefix}.json")
16
+ end
17
+
18
+ def save_to_file(json)
19
+ persisted = true
20
+ begin
21
+ open(where_to_persist, 'w') do |file|
22
+ file.puts json
23
+ end
24
+ rescue => e
25
+ puts e.message
26
+ persisted = false
27
+ end
28
+ return persisted
29
+ end
30
+
31
+ def load_from_file(file_name)
32
+ ::File.open(file_name).read rescue ""
33
+ end
34
+
35
+
36
+ end
37
+
38
+
39
+ end
40
+
41
+ end