jason-orm 0.1

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,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