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.
@@ -0,0 +1,191 @@
1
+ module Jason
2
+
3
+ module Persistence
4
+
5
+ extend self
6
+
7
+ def included(base)
8
+ base.class_eval do
9
+ include InstanceMethods
10
+ extend ClassMethods
11
+ end
12
+ end
13
+
14
+ module ClassMethods
15
+
16
+ attr_accessor :new_record
17
+
18
+ define_method(:respond_to_missing?) do |meth_name, include_private|
19
+ if meth_name.to_s.match(/find_by_/)
20
+ conditions = meth_name.to_s.split("find_by_").last
21
+ return defined_attributes_include?(conditions.to_sym)
22
+ else
23
+ super(meth_name,include_private)
24
+ end
25
+ end
26
+
27
+ def method_missing(meth_name,*args,&block)
28
+ if meth_name.to_s.match(/find_by_/)
29
+ conditions = meth_name.to_s.split("find_by_").last
30
+ if defined_attributes_include?(conditions.to_sym)
31
+ options = {:klass => self}
32
+ options[conditions.to_sym] = args.pop
33
+ return Encoding::PersistenceHandler::restore(:with_conditions, options)
34
+ else
35
+ super
36
+ end
37
+ else
38
+ super
39
+ end
40
+ end
41
+
42
+ def defined_attributes_include?(symbol)
43
+ defined_attributes.map{|item| item[:name]}.include?(symbol)
44
+ end
45
+
46
+ def data_type_for_attribute(attribute)
47
+ defined_attributes.detect{|item| item[:name] == attribute}[:attribute_type]
48
+ end
49
+
50
+ def defined_attributes
51
+ @defined_attributes ||= []
52
+ end
53
+
54
+ def find(id)
55
+ #with_id id do
56
+ # klass_to_restore self
57
+ #end
58
+ Encoding::PersistenceHandler::restore(:by_id,{:id => id,:klass=>self})
59
+ end
60
+
61
+ def all
62
+ Encoding::PersistenceHandler::restore(:all,{:klass => self})
63
+ end
64
+
65
+ def with_id(id,&block)
66
+ p id
67
+ block.call
68
+ end
69
+
70
+ # PUBLIC Add attribute to persistence layer for this model.
71
+ #
72
+ # Defines getter and setter methods for given attribute
73
+ #
74
+ # The Setter methods converts the attribute into the given
75
+ # data type.
76
+ #
77
+ # args - List of arguments:
78
+ #
79
+ # * first argument - attribute name as symbol
80
+ # * second argument - data type of attribute
81
+ #
82
+ # Currently three data types are supported:
83
+ #
84
+ # * String
85
+ # * Integer
86
+ # * Date
87
+ def attribute(*args)
88
+ attribute_name,attribute_type = args[0], args[1]
89
+
90
+ unless DATA_TYPES.keys.include?("#{attribute_type}".to_sym)
91
+ raise Errors::NotSupportedDataTypeError.new("This Kind of type is not supported or missing!")
92
+ end
93
+
94
+ cast_to = DATA_TYPES["#{attribute_type}".to_sym]#eval "Jason::#{attribute_type}"
95
+
96
+ define_method attribute_name do
97
+ instance_variable_get("@#{attribute_name}")
98
+ end
99
+
100
+ define_method "#{attribute_name}=" do |attribute|
101
+ instance_variable_set("@#{attribute_name}", attribute.send(cast_to))
102
+ end
103
+
104
+ defined_attributes << {:name => attribute_name, :type => attribute_type} unless defined_attributes.include?(attribute_name)
105
+
106
+ unless defined_attributes_include?(:id)
107
+ define_method :id do
108
+ instance_variable_get("@id")
109
+ end
110
+
111
+ define_method "id=" do |val|
112
+ instance_variable_set("@id", val)
113
+ end
114
+ defined_attributes << {:name => :id, :type => String}
115
+ end
116
+ end
117
+
118
+ end
119
+
120
+
121
+ module InstanceMethods
122
+
123
+ def initialize(attrs=nil)
124
+ @attributes = attrs || {}
125
+ @new_record = true
126
+ process_attributes(attrs) unless attrs.nil?
127
+ yield(self) if block_given?
128
+ end
129
+
130
+ def attributes
131
+ @attributes.merge!(:id => self.id)
132
+ end
133
+
134
+ def save
135
+ saved = Encoding::PersistenceHandler.persist(self, new_record? ? {} : {:update => true})
136
+ @new_record = saved ? false : true
137
+ return saved
138
+ end
139
+
140
+ def delete
141
+ raise Jason::Errors::UndeletableError.new "Could not delete an unpersisted object!" if new_record?
142
+ Encoding::PersistenceHandler.delete(self)
143
+ end
144
+
145
+ def update_attributes(attributes={})
146
+ process_attributes(attributes.merge(:id => self.id), :reload => true)
147
+ updated = Encoding::PersistenceHandler.persist(self,:update => true)
148
+ return updated
149
+ end
150
+
151
+ def to_hsh
152
+ #p self.attributes
153
+ self.attributes
154
+ end
155
+ alias_method :to_hash, :to_hsh
156
+
157
+ def as_json
158
+ jsonable = {}
159
+ jsonable[Jason::singularize_key(self.class)] = self.to_hsh
160
+ return jsonable
161
+ end
162
+
163
+ def new_record?
164
+ @new_record
165
+ end
166
+ alias_method :persisted?, :new_record?
167
+
168
+ def reload_attributes
169
+ self.class.defined_attributes.each do |attribute|
170
+ called_attribute = self.send(attribute[:name])
171
+ @attributes[attribute[:name]] = called_attribute if called_attribute
172
+ end
173
+ end
174
+
175
+ private
176
+
177
+ def process_attributes(attrs={}, options = {})
178
+ reload = options.fetch(:reload, false)
179
+ attrs.each_pair{ |key,value| self.send("#{key}=",value) }
180
+ unless attrs.has_key?(:id)
181
+ # generate a new id
182
+ @id = Encryptors::Document.process_document_id
183
+ end
184
+ reload_attributes if reload
185
+ end
186
+
187
+ end
188
+
189
+ end
190
+
191
+ end
@@ -0,0 +1,19 @@
1
+ module Jason
2
+
3
+ module Reflection
4
+
5
+ class Base
6
+
7
+ attr_accessor :name,:class_name, :type
8
+
9
+ def initialize(attrs={})
10
+ attrs.each_pair do |key,value|
11
+ self.send("#{key}=", value)
12
+ end
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,127 @@
1
+ module Jason
2
+
3
+ module Relation
4
+
5
+ def self.included(base)
6
+ base.class_eval do
7
+ include InstanceMethods
8
+ extend ClassMethods
9
+ end
10
+ end
11
+
12
+ module InstanceMethods
13
+
14
+ end
15
+
16
+ module ClassMethods
17
+
18
+ def reflections
19
+ @reflections ||= []
20
+ end
21
+
22
+ def reflect_on_relation(relation_name)
23
+ reflections.detect{|reflection| reflection.name.eql?(relation_name.to_s)}
24
+ end
25
+
26
+ def has_many(*args)
27
+ relation_name = args.shift.to_s
28
+ options = args.empty? ? {} : args.shift
29
+
30
+ class_name = options.fetch(:class, relation_name.downcase.singularize).capitalize
31
+
32
+ reflections << Reflection::Base.new(:name => relation_name,
33
+ :class_name => class_name,
34
+ :type => "has_many")
35
+
36
+ ivar_name = "#{relation_name}_ids".to_sym
37
+
38
+ attribute ivar_name, String
39
+
40
+ method_definition_getter = <<-RUBY
41
+
42
+ def #{relation_name}
43
+ # find all children in *.json if included.
44
+ # Otherwise return empty array.
45
+ relation_objects = self.instance_variable_get("@#{relation_name}")
46
+ finder = ->() do
47
+ objects = []
48
+ object_ids = self.send("#{ivar_name}")
49
+ unless object_ids.nil? or object_ids.empty?
50
+ object_ids.split(Jason::has_many_separator).each do |object_id|
51
+ begin
52
+ klass = Module.const_get("#{class_name}".to_sym)
53
+ objects << klass.find(object_id)
54
+ rescue
55
+ next
56
+ end
57
+ end
58
+ end
59
+ self.instance_variable_set("@#{relation_name}", objects)
60
+ return objects
61
+ end
62
+ relation_objects ||= finder.call
63
+ end
64
+
65
+ RUBY
66
+
67
+ method_definition_setter = <<-RUBY
68
+
69
+ def #{relation_name}=(objects)
70
+ self.send("#{ivar_name}=", objects.map(&:id).join(Jason::has_many_separator))
71
+ self.instance_variable_set("@#{relation_name}",objects)
72
+ self.reload_attributes
73
+ end
74
+
75
+ RUBY
76
+
77
+ class_eval method_definition_getter
78
+ class_eval method_definition_setter
79
+
80
+ end
81
+
82
+ def belongs_to(*args)
83
+ relation_name = args.shift.to_s
84
+ options = args.empty? ? {} : args.shift
85
+
86
+ class_name = options.fetch(:class, relation_name).capitalize
87
+
88
+ reflections << Reflection::Base.new(:name => relation_name,
89
+ :class_name => class_name,
90
+ :type => "belongs_to")
91
+
92
+ ivar_name = "#{relation_name}_id".to_sym
93
+ attribute ivar_name, String
94
+
95
+ method_definition_setter = <<-RUBY
96
+ def #{relation_name}=(obj)
97
+ self.send("#{ivar_name}=", obj.id)
98
+ self.instance_variable_set("@#{relation_name}",obj)
99
+ self.reload_attributes
100
+ end
101
+
102
+ RUBY
103
+
104
+ method_definition_getter = <<-RUBY
105
+ def #{relation_name}
106
+ relation_obj = self.instance_variable_get("@#{relation_name}")
107
+ finder = ->() do
108
+ klass = Module.const_get("#{class_name}".to_sym)
109
+ relation_obj = klass.find(self.send("#{ivar_name}"))
110
+ self.instance_variable_set("@#{relation_name}", relation_obj)
111
+ return relation_obj
112
+ end
113
+ relation_obj ||= finder.call
114
+ end
115
+ RUBY
116
+
117
+ class_eval method_definition_setter, "relation.rb", 63
118
+ class_eval method_definition_getter, "relation.rb", 72
119
+ end
120
+
121
+
122
+ end
123
+
124
+ end
125
+
126
+
127
+ end
data/lib/jason.rb ADDED
@@ -0,0 +1,71 @@
1
+ require 'bundler/setup'
2
+
3
+ begin
4
+ Bundler.setup
5
+ rescue
6
+ raise RuntimeError, "Bundler couldn't find some gems." +
7
+ "Did you run 'bundle install'?"
8
+ end
9
+
10
+ require 'active_support/core_ext/module/qualified_const'
11
+ #require 'active_support/core_ext/string/inflections'
12
+ require 'active_support/inflector'
13
+ require 'active_support/json'
14
+
15
+ module Jason
16
+
17
+ require 'jason/core_ext/string'
18
+
19
+ extend self
20
+
21
+ module Encryptors
22
+ autoload :Document, 'jason/crypt/document_id'
23
+ end
24
+
25
+ module Operations
26
+ autoload :File, 'jason/operations/file'
27
+ end
28
+
29
+ module Encoding
30
+ autoload :PersistenceHandler, 'jason/encoding/persistence_handler'
31
+ end
32
+
33
+ module Reflection
34
+ autoload :Base, 'jason/reflection/base'
35
+ end
36
+
37
+ autoload :Errors, 'jason/errors'
38
+
39
+ autoload :Relation, 'jason/relation'
40
+ autoload :Persistence, 'jason/persistence'
41
+
42
+ DATA_TYPES = {
43
+ :Integer => :to_i,
44
+ :String => :to_s,
45
+ :Date => :to_date
46
+ }
47
+
48
+ #Integer = :to_i
49
+ #String = :to_s
50
+ #Date = :to_date
51
+
52
+ def setup(&block)
53
+ yield(self)
54
+ end
55
+
56
+ mattr_accessor :persistence_path
57
+ @@persistence_path = File.expand_path(File.join(File.dirname( __FILE__)), 'json')
58
+
59
+ mattr_accessor :restore_app
60
+ @@restore_app = Encoding::PersistenceHandler::Restorable
61
+
62
+ mattr_accessor :has_many_separator
63
+ @@has_many_separator = ","
64
+
65
+ def singularize_key(key)
66
+ key.name.downcase.singularize if key.respond_to?(:name)
67
+ end
68
+
69
+
70
+
71
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Jason
2
+ VERSION = "0.1"
3
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe String do
4
+
5
+ subject{String.new}
6
+
7
+ it{should respond_to(:to_date)}
8
+
9
+ context "#to_date" do
10
+
11
+ it "converts string to Date object" do
12
+ date_string = "2012-10-09"
13
+ eql_date = Chronic.parse(date_string)
14
+ date_string.to_date.should eq eql_date.to_date
15
+ end
16
+
17
+ end
18
+
19
+
20
+ end
21
+
22
+
@@ -0,0 +1,259 @@
1
+ require 'spec_helper'
2
+
3
+ class Person
4
+
5
+ include Jason::Persistence
6
+
7
+ attribute :firstname, String
8
+ attribute :lastname, String
9
+ attribute :date_of_birth, Date
10
+ attribute :age, Integer
11
+
12
+ end
13
+
14
+ describe "Jason::Persistence" do
15
+
16
+ before(:all) do
17
+ FileUtils.rm_rf(File.join(fixtures_path, 'people.json'))
18
+ end
19
+
20
+ after(:all) do
21
+ FileUtils.rm_rf(File.join(fixtures_path, 'people.json'))
22
+ end
23
+
24
+ context "class methods" do
25
+
26
+ subject{Person}
27
+
28
+ it{should respond_to :attribute}
29
+ it{should respond_to :find}
30
+ it{should respond_to :all}
31
+
32
+ context "#find" do
33
+
34
+ context "when document found" do
35
+
36
+ let(:person) do
37
+ Person.new(:lastname => "Hauptmann", :firstname => "Rene", :age => 12,
38
+ :date_of_birth => "2000/09/09")
39
+ end
40
+
41
+ before do
42
+ person.save
43
+ end
44
+
45
+ subject{Person.find(person.id)}
46
+
47
+ it "returns single object" do
48
+ subject.should be_instance_of Person
49
+ end
50
+
51
+ it "id equals the person id" do
52
+ subject.id.should eq person.id
53
+ end
54
+
55
+ it "lastname equals person lastname" do
56
+ subject.lastname.should eq person.lastname
57
+ end
58
+
59
+ it "firstname equals person firstname" do
60
+ subject.firstname.should eq person.firstname
61
+ end
62
+
63
+ it "date_of_birth is a date and equals person date" do
64
+ subject.date_of_birth.should be_instance_of Date
65
+ subject.date_of_birth.day.should eq 9
66
+ subject.date_of_birth.month.should eq 9
67
+ subject.date_of_birth.year.should eq 2000
68
+ end
69
+
70
+ it "is not a new record" do
71
+ subject.new_record?.should be false
72
+ end
73
+
74
+ it "age is a fixnum" do
75
+ subject.age.should be_instance_of Fixnum
76
+ end
77
+
78
+ end
79
+
80
+ context "when document not found" do
81
+
82
+ it "raises an Jason::DocumentNotFoundError" do
83
+ expect{Person.find('123456')}.to raise_error Jason::Errors::DocumentNotFoundError
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+
90
+ context "#all" do
91
+
92
+ it "returns an array of documents" do
93
+ result = Person.all
94
+ result.should be_instance_of Array
95
+ result.each do |instance|
96
+ instance.should be_instance_of Person
97
+ end
98
+ result.should_not be_empty
99
+ end
100
+
101
+ end
102
+
103
+ context "magic finders" do
104
+
105
+ before(:all) do
106
+ FileUtils.rm_rf(File.join(fixtures_path, 'people.json'))
107
+ ["Max", "Werner", "Claudia", "Werner"].each do |name|
108
+ person = Person.new(:firstname => name,:lastname => "Winter")
109
+ person.save
110
+ end
111
+ end
112
+
113
+ it{should respond_to(:find_by_firstname)}
114
+ it{should respond_to(:find_by_lastname)}
115
+ it{should respond_to(:find_by_date_of_birth)}
116
+
117
+ it "find by firstname" do
118
+ result = Person.find_by_firstname("Werner")
119
+ result.should be_instance_of Array
120
+ result.should have(2).items
121
+ end
122
+
123
+ it "find by lastname" do
124
+ result = Person.find_by_lastname("Winter")
125
+ result.should have(4).items
126
+ end
127
+ end
128
+
129
+ end
130
+
131
+ context "instance methods" do
132
+
133
+ let(:person) do
134
+ Person.new(:firstname => "Bill",
135
+ :lastname => "Cosby")
136
+ end
137
+ subject{person}
138
+
139
+ it{should respond_to(:attributes)}
140
+ it{should respond_to(:to_hsh)}
141
+ it{should respond_to(:update_attributes)}
142
+ it{should respond_to(:save)}
143
+ it{should respond_to(:new_record?)}
144
+ it{should respond_to(:as_json)}
145
+
146
+ context "defining attributes" do
147
+
148
+ context "get access to getters" do
149
+
150
+ it{should respond_to(:firstname)}
151
+ it{should respond_to(:lastname)}
152
+ it{should respond_to(:date_of_birth)}
153
+ it{should respond_to(:id)}
154
+
155
+ end
156
+
157
+ context "get access to setters" do
158
+
159
+ it{should respond_to(:firstname=)}
160
+ it{should respond_to(:lastname=)}
161
+ it{should respond_to(:date_of_birth=)}
162
+ it{should respond_to(:id=)}
163
+
164
+ end
165
+
166
+ context "setting attributes and getting their values" do
167
+
168
+ it "first name should be Bill" do
169
+ person.firstname.should eq "Bill"
170
+ end
171
+
172
+ it "last name should be Cosby" do
173
+ person.lastname.should eq "Cosby"
174
+ end
175
+
176
+ context "when using the constructor" do
177
+
178
+ it "an id is generated" do
179
+ person.id.should_not be_nil
180
+ end
181
+
182
+ end
183
+
184
+
185
+ context "#attributes" do
186
+
187
+ subject{person.attributes}
188
+
189
+ it "is a Hash" do
190
+ subject.should be_instance_of Hash
191
+ end
192
+
193
+ it "includes the defined attributes as keys" do
194
+ keys = subject.keys
195
+ keys.should include(:firstname)
196
+ keys.should include(:lastname)
197
+ end
198
+
199
+ it "and its values as value of the key" do
200
+ values = subject.values
201
+ values.should include("Bill")
202
+ values.should include("Cosby")
203
+ end
204
+
205
+ end
206
+
207
+ end
208
+ end
209
+
210
+ context "#save" do
211
+
212
+ it "returns true if successful" do
213
+ person.new_record?.should be true
214
+ person.save.should be true
215
+ end
216
+
217
+ it "sets new_record to false" do
218
+ person.save
219
+ person.new_record?.should be false
220
+ end
221
+
222
+ end
223
+
224
+ context "#update_attributes" do
225
+
226
+ let(:another_person) do
227
+ Person.new(:firstname => "max", :lastname => "Mustermann")
228
+ end
229
+
230
+ it "returns true if successful" do
231
+ another_person.save
232
+ result = another_person.update_attributes(:firstname => "William")
233
+ result.should be true
234
+ another_person.new_record?.should be false
235
+ end
236
+
237
+ end
238
+
239
+ context "#delete" do
240
+
241
+ let(:another_person) do
242
+ Person.new(:firstname => "max", :lastname => "Mustermann")
243
+ end
244
+
245
+ it "raise UndeletableError if it is a new record" do
246
+ expect{another_person.delete}.to raise_error Jason::Errors::UndeletableError
247
+ end
248
+
249
+ it "returns true if successful" do
250
+ another_person.save
251
+ person = Person.find(another_person.id)
252
+ person.delete.should be true
253
+ expect{Person.find(person.id)}.to raise_error Jason::Errors::DocumentNotFoundError
254
+ end
255
+
256
+ end
257
+ end
258
+
259
+ end