active_repository 0.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,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in active_repository.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Caio Torres
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # ActiveRepository
2
+
3
+ ActiveRepository is designed so you can build your Business Models without depending on any ORM, it by default saves your data in memory using ActiveHash (https://github.com/zilkey/active_hash) gem. Then when you decides which ORM you want to use you only have to connect ActiveRepository with it. Actually it only works with ActiveRecord, we are working to give mongoid suppoort.
4
+
5
+ It also has the advantage of letting you test directly in memory, no need to save data in disk. This gives a great boost on your test suite speed.
6
+
7
+ ## Requirements
8
+
9
+ ### Ruby
10
+
11
+ ActiveRepository requires Ruby version **>= 1.9.2**.
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ gem 'active_repository'
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install active_repository
26
+
27
+ ## Usage
28
+
29
+ TODO: Write usage instructions here
30
+
31
+ ## Contributing
32
+
33
+ 1. Fork it
34
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
35
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
36
+ 4. Push to the branch (`git push origin my-new-feature`)
37
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/active_repository/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Caio Torres"]
6
+ gem.email = ["efreesen@gmail.com"]
7
+ gem.description = %q{An implementation of repository pattern that can connect with any ORM}
8
+ gem.summary = %q{An implementation of repository pattern that can connect with any ORM}
9
+ gem.homepage = "http://github.com/efreesen/active_repository"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.test_files = gem.files.grep(%r{^(spec)/})
13
+ gem.name = "active_repository"
14
+ gem.require_paths = ["lib"]
15
+ gem.version = ActiveRepository::VERSION
16
+
17
+ gem.add_runtime_dependency(%q<active_hash>, [">= 0.9.12"])
18
+ gem.add_runtime_dependency(%q<activemodel>, [">= 3.2.6"])
19
+ gem.add_development_dependency(%q<rspec>, ["~> 2.2.0"])
20
+ gem.add_development_dependency(%q<sqlite3>)
21
+ gem.add_development_dependency(%q<activerecord>)
22
+ gem.add_development_dependency(%q<mongoid>)
23
+ end
@@ -0,0 +1,102 @@
1
+ module ActiveRepository
2
+ module Associations
3
+
4
+ module ActiveRecordExtensions
5
+
6
+ def belongs_to_active_hash(association_id, options = {})
7
+ options = {
8
+ :class_name => association_id.to_s.classify,
9
+ :foreign_key => association_id.to_s.foreign_key
10
+ }.merge(options)
11
+
12
+ define_method(association_id) do
13
+ options[:class_name].constantize.find_by_id(send(options[:foreign_key]))
14
+ end
15
+
16
+ define_method("#{association_id}=") do |new_value|
17
+ send "#{options[:foreign_key]}=", new_value ? new_value.id : nil
18
+ end
19
+
20
+ create_reflection(
21
+ :belongs_to,
22
+ association_id.to_sym,
23
+ options,
24
+ options[:class_name].constantize
25
+ )
26
+ end
27
+
28
+ end
29
+
30
+ def self.included(base)
31
+ base.extend Methods
32
+ end
33
+
34
+ module Methods
35
+ def has_many(association_id, options = {})
36
+
37
+ define_method(association_id) do
38
+ options = {
39
+ :class_name => association_id.to_s.classify,
40
+ :foreign_key => self.class.to_s.foreign_key
41
+ }.merge(options)
42
+
43
+ klass = options[:class_name].constantize
44
+ objects = []
45
+
46
+ if klass.respond_to?(:scoped)
47
+ objects = klass.scoped(:conditions => {options[:foreign_key] => id})
48
+ else
49
+ objects = klass.send("find_all_by_#{options[:foreign_key]}", id)
50
+ end
51
+
52
+ objects.map{ |o| self.serialize!(o.attributes) }
53
+ end
54
+ end
55
+
56
+ def has_one(association_id, options = {})
57
+ define_method(association_id) do
58
+ options = {
59
+ :class_name => association_id.to_s.classify,
60
+ :foreign_key => self.class.to_s.foreign_key
61
+ }.merge(options)
62
+
63
+ scope = options[:class_name].constantize
64
+
65
+ if scope.respond_to?(:scoped) && options[:conditions]
66
+ scope = scope.scoped(:conditions => options[:conditions])
67
+ end
68
+ scope.send("find_by_#{options[:foreign_key]}", id)
69
+ end
70
+ end
71
+ #
72
+ def belongs_to(association_id, options = {})
73
+
74
+ options = {
75
+ :class_name => association_id.to_s.classify,
76
+ :foreign_key => association_id.to_s.foreign_key
77
+ }.merge(options)
78
+
79
+ field options[:foreign_key].to_sym
80
+
81
+ define_method(association_id) do
82
+ klass = self.class.get_model_class
83
+ id = send(options[:foreign_key])
84
+
85
+ if id.present?
86
+ object = klass.find_by_id(id)
87
+
88
+ object.nil? || object.class == self.class ? object : self.class.serialize!(object.attributes)
89
+ else
90
+ nil
91
+ end
92
+ end
93
+
94
+ define_method("#{association_id}=") do |new_value|
95
+ attributes[options[:foreign_key].to_sym] = new_value ? new_value.id : nil
96
+ end
97
+
98
+ end
99
+ end
100
+
101
+ end
102
+ end
@@ -0,0 +1,266 @@
1
+ require 'active_repository/associations'
2
+ require 'active_repository/uniqueness'
3
+ require 'active_repository/write_support'
4
+ require 'active_repository/sql_query_executor'
5
+
6
+ module ActiveRepository
7
+
8
+ class Base < ActiveHash::Base
9
+ extend ActiveModel::Callbacks
10
+ include ActiveModel::Validations
11
+ include ActiveModel::Validations::Callbacks
12
+ include ActiveRepository::Associations
13
+
14
+ # TODO: implement first, last,
15
+
16
+ class_attribute :model_class, :save_in_memory
17
+
18
+ before_validation :set_timestamps
19
+
20
+ fields :created_at, :updated_at
21
+
22
+ def self.define_custom_find_by_field(field_name)
23
+ method_name = :"find_by_#{field_name}"
24
+ unless has_singleton_method?(method_name)
25
+ the_meta_class.instance_eval do
26
+ define_method(method_name) do |*args|
27
+ object = get_model_class.send(method_name)
28
+ object.nil? ? nil : serialize!(object.attributes)
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ def self.define_custom_find_by_field(field_name)
35
+ method_name = :"find_by_#{field_name}"
36
+ the_meta_class.instance_eval do
37
+ define_method(method_name) do |*args|
38
+ object = nil
39
+
40
+ if self == get_model_class
41
+ object = self.where(field_name.to_sym => args.first).first
42
+ else
43
+ object = get_model_class.send(method_name, args)
44
+ end
45
+
46
+ object.nil? ? nil : serialize!(object.attributes)
47
+ end
48
+ end
49
+ end
50
+
51
+ def self.define_custom_find_all_by_field(field_name)
52
+ method_name = :"find_all_by_#{field_name}"
53
+ the_meta_class.instance_eval do
54
+ define_method(method_name) do |*args|
55
+ objects = []
56
+
57
+ if self == get_model_class
58
+ objects = self.where(field_name.to_sym => args.first)
59
+ else
60
+ objects = get_model_class.send(method_name, args)
61
+ end
62
+
63
+ objects.empty? ? [] : objects.map{ |object| serialize!(object.attributes) }
64
+ end
65
+ end
66
+ end
67
+
68
+ def self.find(id)
69
+ begin
70
+ if self == get_model_class
71
+ super(id)
72
+ else
73
+ object = get_model_class.find(id)
74
+
75
+ if object.is_a?(Array)
76
+ object.map { |o| serialize!(o.attributes) }
77
+ else
78
+ serialize!(object.attributes)
79
+ end
80
+ end
81
+ rescue Exception => e
82
+ message = ""
83
+
84
+ if id.is_a?(Array)
85
+ message = "Couldn't find all #{self} objects with IDs (#{id.join(', ')})"
86
+ else
87
+ message = "Couldn't find #{self} with ID=#{id}"
88
+ end
89
+
90
+ raise ActiveHash::RecordNotFound.new(message)
91
+ end
92
+ end
93
+
94
+ def reload
95
+ serialize! self.class.get_model_class.find(self.id).attributes
96
+ end
97
+
98
+ def self.exists?(id)
99
+ if self == get_model_class
100
+ !find_by_id(id).nil?
101
+ else
102
+ get_model_class.exists?(id)
103
+ end
104
+ end
105
+
106
+ def self.find_by_id(id)
107
+ if self == get_model_class
108
+ super(id)
109
+ else
110
+ get_model_class.find_by_id(id)
111
+ end
112
+ end
113
+
114
+ def self.find_or_create(attributes)
115
+ object = get_model_class.where(attributes).first
116
+
117
+ object = model_class.create(attributes) if object.nil?
118
+
119
+ serialize!(object.attributes)
120
+ end
121
+
122
+ def self.create(attributes={})
123
+ object = get_model_class.new(attributes)
124
+
125
+ object.id = nil if get_model_class.exists?(object.id)
126
+
127
+ object.save
128
+
129
+ serialize!(object.attributes) unless object.class.name == self
130
+ end
131
+
132
+ def update_attributes(attributes)
133
+ object = self.class.get_model_class.find(self.id)
134
+
135
+ attributes.each do |k,v|
136
+ object.send("#{k.to_s}=", v) unless k == :id
137
+ end
138
+
139
+ object.save
140
+
141
+ self.attributes = object.attributes
142
+ end
143
+
144
+ def self.all
145
+ self == get_model_class ? super : get_model_class.all.map { |object| serialize!(object.attributes) }
146
+ end
147
+
148
+ def self.delete_all
149
+ self == get_model_class ? super : get_model_class.delete_all
150
+ end
151
+
152
+ def self.where(*args)
153
+ raise ArgumentError.new("wrong number of arguments (0 for 1)") if args.empty?
154
+ if self == get_model_class
155
+ query = ActiveHash::SQLQueryExecutor.args_to_query(args)
156
+ super(query)
157
+ else
158
+ objects = []
159
+ args = args.first.is_a?(Hash) ? args.first : args
160
+ get_model_class.where(args).each do |object|
161
+ objects << self.serialize!(object.attributes)
162
+ end
163
+
164
+ objects
165
+ end
166
+ end
167
+
168
+ def self.set_model_class(value)
169
+ self.model_class = value if model_class.nil?
170
+
171
+ field_names.each do |field_name|
172
+ define_custom_find_by_field(field_name)
173
+ define_custom_find_all_by_field(field_name)
174
+ end
175
+ end
176
+
177
+ def self.set_save_in_memory(value)
178
+ self.save_in_memory = value if save_in_memory.nil?
179
+ end
180
+
181
+ def persist
182
+ if self.valid?
183
+ save_in_memory? ? save : self.convert
184
+ end
185
+ end
186
+
187
+ def self.first
188
+ get("first")
189
+ end
190
+
191
+ def self.last
192
+ get("last")
193
+ end
194
+
195
+ def self.get(position)
196
+ if self == get_model_class
197
+ id = get_model_class.all.map(&:id).sort.send(position)
198
+
199
+ self.find id
200
+ else
201
+ serialize! get_model_class.send(position).attributes
202
+ end
203
+ end
204
+
205
+ def convert(attribute="id")
206
+ object = self.class.get_model_class.send("find_by_#{attribute}", self.send(attribute))
207
+
208
+ object = self.class.get_model_class.new if object.nil?
209
+
210
+ self.attributes.each do |k,v|
211
+ object.send("#{k.to_s}=", v) unless k == :id
212
+ end
213
+
214
+ object.save
215
+
216
+ self.id = object.id
217
+
218
+ object
219
+ end
220
+
221
+ def attributes=(new_attributes)
222
+ new_attributes.each do |k,v|
223
+ self.send("#{k.to_s}=", v)
224
+ end
225
+ end
226
+
227
+ def serialize!(attributes)
228
+ unless attributes.nil?
229
+ attributes.each do |k,v|
230
+ self.send("#{k.to_s}=", v)
231
+ end
232
+ end
233
+
234
+ self
235
+ end
236
+
237
+ def self.serialize!(attributes)
238
+ object = self.new
239
+
240
+ object.serialize!(attributes)
241
+ end
242
+
243
+ def self.serialized_attributes
244
+ field_names.map &:to_s
245
+ end
246
+
247
+ def self.constantize
248
+ self.to_s.constantize
249
+ end
250
+
251
+ def self.get_model_class
252
+ save_in_memory? ? self : self.model_class
253
+ end
254
+
255
+ protected
256
+ def model_class
257
+ self.model_class
258
+ end
259
+
260
+ private
261
+ def set_timestamps
262
+ self.created_at = DateTime.now.utc if self.new_record?
263
+ self.updated_at = DateTime.now.utc
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,123 @@
1
+ module ActiveHash
2
+ class SQLQueryExecutor
3
+ class << self
4
+ def execute(klass, query)
5
+ @operator, @sub_query, @objects = process_first(klass, query, query.split(" ")[1])
6
+
7
+ @operator.nil? ? @objects : @objects.send(@operator, execute(klass, @sub_query)).sort_by{ |o| o.id }
8
+ end
9
+
10
+ def args_to_query(args)
11
+ return args.first if args.size == 1
12
+
13
+ query = args.first
14
+ param = args.delete(args[1])
15
+
16
+ param = convert_param(param)
17
+
18
+ args[0] = query.sub("?", param)
19
+
20
+ args_to_query(args)
21
+ end
22
+
23
+ private
24
+ def process_first(klass, query, operator)
25
+ @operator = operator == "=" ? "==" : operator
26
+ @query = sanitize_query(query)
27
+ sub_query = divide_query
28
+
29
+ binding_operator = get_operator(sub_query)
30
+
31
+ objects = execute_sub_query(klass, sub_query)
32
+
33
+ sub_query = query.gsub(sub_query.join(" "), "")
34
+
35
+ [binding_operator, sub_query, objects]
36
+ end
37
+
38
+ def sanitize_query(query)
39
+ new_query = query
40
+ params = query.scan(/([\"'])(.*?)\1/)
41
+
42
+ params.each do |quote, param|
43
+ new_query = new_query.gsub(quote,"").gsub(param, param.gsub(" ", "_"))
44
+ end
45
+
46
+ new_query
47
+ end
48
+
49
+ def divide_query
50
+ array = @query.split(" ")
51
+ case @operator
52
+ when "between"
53
+ array[0..5]
54
+ when "is"
55
+ size = array[2].downcase == "not" ? 4 : 3
56
+ array[0..size]
57
+ else
58
+ array[0..3]
59
+ end
60
+ end
61
+
62
+ def get_operator(attributes)
63
+ operator = attributes.size >= 4 ? attributes.last : nil
64
+
65
+ case operator
66
+ when "or"
67
+ "+"
68
+ when "and"
69
+ "&"
70
+ else
71
+ nil
72
+ end
73
+ end
74
+
75
+ def execute_sub_query(klass, sub_query)
76
+ case @operator
77
+ when "between"
78
+ klass.all.select do |o|
79
+ field, first_attr, second_attr = convert_attrs(o.send(sub_query.first), sub_query[2], sub_query[4])
80
+
81
+ (field >= first_attr && field <= second_attr)
82
+ end
83
+ when "is"
84
+ klass.all.select do |o|
85
+ field = o.send(sub_query.first).blank?
86
+
87
+ sub_query.size == 3 ? field : !field
88
+ end
89
+ else
90
+ klass.all.select do |o|
91
+ field, attribute = convert_attrs(o.send(sub_query.first), sub_query[2])
92
+
93
+ field.blank? ? false : field.send(@operator, attribute)
94
+ end
95
+ end
96
+ end
97
+
98
+ def convert_param(param)
99
+ case param.class.name
100
+ when "String"
101
+ param = "'#{param}'"
102
+ when "Date"
103
+ param = "'#{param.strftime("%Y-%m-%d")}'"
104
+ when "Time"
105
+ param = "'#{param.strftime("%Y-%m-%d %H:%M:%S %z")}'"
106
+ else
107
+ param = param.to_s
108
+ end
109
+ end
110
+
111
+ def convert_attrs(field, *attrs)
112
+ attrs.each_with_index do |attribute, i|
113
+ attribute = attribute.gsub("_", " ")
114
+ attrs[i] = field.is_a?(Integer) ? attribute.to_i : attribute
115
+ end
116
+
117
+ field = field.is_a?(Integer) ? field : field.to_s
118
+
119
+ [field, attrs].flatten
120
+ end
121
+ end
122
+ end
123
+ end