jason-orm 0.1

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