active_repository 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -21,13 +21,9 @@ describe ActiveRepository::Base, "associations" do
21
21
  end
22
22
 
23
23
  class City < ActiveRepository::Base
24
- City.set_model_class(self)
25
- City.set_save_in_memory(true)
26
24
  end
27
25
 
28
26
  class Author < ActiveRepository::Base
29
- Author.set_model_class(self)
30
- Author.set_save_in_memory(true)
31
27
  end
32
28
 
33
29
  class Book < ActiveRecord::Base
@@ -261,14 +257,9 @@ describe ActiveRepository::Base, "associations" do
261
257
  describe "Multiple ORM" do
262
258
  before do
263
259
  Object.send :remove_const, :City
264
- # Object.send :remove_const, :Author
265
260
  Object.send :remove_const, :Country
266
- # Object.send :remove_const, :School
267
- # Object.send :remove_const, :Book
268
261
 
269
262
  class Country < ActiveRepository::Base
270
- Country.set_model_class(Country)
271
- Country.set_save_in_memory(true)
272
263
  has_many :states
273
264
  end
274
265
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_repository
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-26 00:00:00.000000000 Z
12
+ date: 2012-12-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: active_hash
@@ -80,17 +80,17 @@ dependencies:
80
80
  requirement: !ruby/object:Gem::Requirement
81
81
  none: false
82
82
  requirements:
83
- - - '='
83
+ - - ! '>='
84
84
  - !ruby/object:Gem::Version
85
- version: 3.0.11
85
+ version: '0'
86
86
  type: :development
87
87
  prerelease: false
88
88
  version_requirements: !ruby/object:Gem::Requirement
89
89
  none: false
90
90
  requirements:
91
- - - '='
91
+ - - ! '>='
92
92
  - !ruby/object:Gem::Version
93
- version: 3.0.11
93
+ version: '0'
94
94
  - !ruby/object:Gem::Dependency
95
95
  name: rake
96
96
  requirement: !ruby/object:Gem::Requirement
@@ -130,31 +130,17 @@ executables: []
130
130
  extensions: []
131
131
  extra_rdoc_files: []
132
132
  files:
133
- - .gitignore
134
- - .travis.yml
135
- - Gemfile
136
- - LICENSE
137
133
  - README.md
138
- - Rakefile
139
- - active_repository.gemspec
140
- - lib/active_repository.rb
141
- - lib/active_repository/associations.rb
142
- - lib/active_repository/base.rb
143
- - lib/active_repository/finders.rb
144
- - lib/active_repository/sql_query_executor.rb
145
- - lib/active_repository/uniqueness.rb
146
- - lib/active_repository/version.rb
147
- - lib/active_repository/write_support.rb
148
- - lib/active_repository/writers.rb
134
+ - LICENSE
149
135
  - spec/active_repository/associations_spec.rb
150
136
  - spec/active_repository/base_spec.rb
151
137
  - spec/active_repository/sql_query_executor_spec.rb
152
138
  - spec/spec_helper.rb
153
139
  - spec/support/shared_examples.rb
154
140
  - spec/support/sql_query_shared_examples.rb
155
- - support/mongoid.yml
156
141
  homepage: http://github.com/efreesen/active_repository
157
- licenses: []
142
+ licenses:
143
+ - MIT
158
144
  post_install_message:
159
145
  rdoc_options: []
160
146
  require_paths:
data/.gitignore DELETED
@@ -1,18 +0,0 @@
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
18
- .rbenv-version
data/.travis.yml DELETED
@@ -1,10 +0,0 @@
1
- rvm:
2
- - 1.9.3
3
- - jruby-19mode
4
-
5
- matrix:
6
- allow_failures:
7
- - rvm: jruby-19mode
8
-
9
- services:
10
- - mongodb
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in active_repository.gemspec
4
- gemspec
data/Rakefile DELETED
@@ -1,12 +0,0 @@
1
- #!/usr/bin/env rake
2
- require "bundler/gem_tasks"
3
-
4
- desc 'Default: run rspec tests.'
5
- task :default => [:travis]
6
-
7
- task :travis do
8
- cmd = "rspec spec"
9
- puts "Starting to run `#{cmd}`..."
10
- system("export DISPLAY=:99.0 && bundle exec rspec spec -c")
11
- raise "#{cmd} failed!" unless $?.exitstatus == 0
12
- end
@@ -1,27 +0,0 @@
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<activerecord>)
21
- gem.add_development_dependency(%q<mongoid>, ["= 3.0.11"])
22
- gem.add_development_dependency('rake')
23
- gem.add_development_dependency(%q<sqlite3>) unless RUBY_PLATFORM == 'java'
24
- gem.add_development_dependency(%q<jdbc-sqlite3>) if RUBY_PLATFORM == 'java'
25
- gem.add_development_dependency(%q<jruby-openssl>) if RUBY_PLATFORM == 'java'
26
- gem.add_development_dependency(%q<activerecord-jdbcsqlite3-adapter>) if RUBY_PLATFORM == 'java'
27
- end
@@ -1,15 +0,0 @@
1
- require 'active_repository'
2
-
3
- begin
4
- require 'active_model'
5
- require 'active_model/naming'
6
- rescue LoadError
7
- end
8
-
9
- begin
10
- require 'active_hash'
11
- require 'associations/associations'
12
- rescue LoadError
13
- end
14
-
15
- require 'active_repository/base'
@@ -1,97 +0,0 @@
1
- module ActiveRepository
2
- module Associations
3
-
4
- module ActiveRecordExtensions
5
-
6
- def belongs_to_active_repository(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
- define_method(association_id) do
37
- options = {
38
- :class_name => association_id.to_s.classify,
39
- :foreign_key => self.class.to_s.foreign_key
40
- }.merge(options)
41
-
42
- klass = options[:class_name].constantize
43
- objects = []
44
-
45
- if klass.respond_to?(:scoped)
46
- objects = klass.scoped(:conditions => {options[:foreign_key] => id})
47
- else
48
- objects = klass.send("find_all_by_#{options[:foreign_key]}", id)
49
- end
50
- end
51
- end
52
-
53
- def has_one(association_id, options = {})
54
- define_method(association_id) do
55
- options = {
56
- :class_name => association_id.to_s.classify,
57
- :foreign_key => self.class.to_s.foreign_key
58
- }.merge(options)
59
-
60
- scope = options[:class_name].constantize
61
-
62
- if scope.respond_to?(:scoped) && options[:conditions]
63
- scope = scope.scoped(:conditions => options[:conditions])
64
- end
65
- scope.send("find_by_#{options[:foreign_key]}", id)
66
- end
67
- end
68
- #
69
- def belongs_to(association_id, options = {})
70
-
71
- options = {
72
- :class_name => association_id.to_s.classify,
73
- :foreign_key => association_id.to_s.foreign_key
74
- }.merge(options)
75
-
76
- field options[:foreign_key].to_sym
77
-
78
- define_method(association_id) do
79
- klass = options[:class_name].constantize
80
- id = send(options[:foreign_key])
81
-
82
- if id.present?
83
- object = klass.find_by_id(id)
84
- else
85
- nil
86
- end
87
- end
88
-
89
- define_method("#{association_id}=") do |new_value|
90
- attributes[options[:foreign_key].to_sym] = new_value ? new_value.id : nil
91
- end
92
-
93
- end
94
- end
95
-
96
- end
97
- end
@@ -1,156 +0,0 @@
1
- require 'active_repository/associations'
2
- require 'active_repository/uniqueness'
3
- require 'active_repository/write_support'
4
- require 'active_repository/sql_query_executor'
5
- require 'active_repository/finders'
6
- require 'active_repository/writers'
7
-
8
- module ActiveRepository
9
-
10
- class Base < ActiveHash::Base
11
- extend ActiveModel::Callbacks
12
- extend ActiveRepository::Finders
13
- extend ActiveRepository::Writers
14
- include ActiveModel::Validations
15
- include ActiveModel::Validations::Callbacks
16
- include ActiveRepository::Associations
17
- include ActiveRepository::Writers::InstanceMethods
18
-
19
- class_attribute :model_class, :save_in_memory, :instance_writer => false
20
-
21
- before_validation :set_timestamps
22
-
23
- fields :created_at, :updated_at
24
-
25
- def reload
26
- serialize! self.class.get_model_class.find(self.id).attributes
27
- end
28
-
29
- def self.exists?(id)
30
- if self == get_model_class
31
- !find_by_id(id).nil?
32
- else
33
- if mongoid?
34
- find_by_id(id).present?
35
- else
36
- get_model_class.exists?(id)
37
- end
38
- end
39
- end
40
-
41
- def self.all
42
- self == get_model_class ? super : get_model_class.all.map { |object| serialize!(object.attributes) }
43
- end
44
-
45
- def self.delete_all
46
- self == get_model_class ? super : get_model_class.delete_all
47
- end
48
-
49
- def self.where(*args)
50
- raise ArgumentError.new("wrong number of arguments (0 for 1)") if args.empty?
51
- if self == get_model_class
52
- query = ActiveHash::SQLQueryExecutor.args_to_query(args)
53
- super(query)
54
- else
55
- objects = []
56
- args = args.first.is_a?(Hash) ? args.first : args
57
- get_model_class.where(args).each do |object|
58
- objects << self.serialize!(object.attributes)
59
- end
60
-
61
- objects
62
- end
63
- end
64
-
65
- def self.set_model_class(value)
66
- self.model_class = value if model_class.nil?
67
-
68
- field_names.each do |field_name|
69
- define_custom_find_by_field(field_name)
70
- define_custom_find_all_by_field(field_name)
71
- end
72
- end
73
-
74
- def self.set_save_in_memory(value)
75
- self.save_in_memory = value if save_in_memory.nil?
76
- end
77
-
78
- def persist
79
- if self.valid?
80
- save_in_memory? ? save : self.convert
81
- end
82
- end
83
-
84
- def convert(attribute="id")
85
- klass = self.class.get_model_class
86
- object = klass.where(attribute.to_sym => self.send(attribute)).first
87
-
88
- object ||= self.class.get_model_class.new
89
-
90
- attributes = self.attributes
91
-
92
- attributes.delete(:id)
93
-
94
- object.attributes = attributes
95
-
96
- object.save
97
-
98
- self.id = object.id
99
-
100
- object
101
- end
102
-
103
- def serialize!(attributes)
104
- unless attributes.nil?
105
- self.attributes = attributes
106
- end
107
-
108
- self
109
- end
110
-
111
- def self.serialize!(other)
112
- case other.class.to_s
113
- when "Hash" then self.new.serialize!(other)
114
- when "Array" then other.map { |o| serialize!(o.attributes) }
115
- when "Moped::BSON::Document" then self.new.serialize!(other)
116
- else self.new.serialize!(other.attributes)
117
- end
118
- end
119
-
120
- def self.serialized_attributes
121
- field_names.map &:to_s
122
- end
123
-
124
- def self.constantize
125
- self.to_s.constantize
126
- end
127
-
128
- def self.get_model_class
129
- return self if self.save_in_memory.nil?
130
- save_in_memory? ? self : self.model_class
131
- end
132
-
133
- def save_in_memory?
134
- self.save_in_memory.nil? ? true : save_in_memory
135
- end
136
-
137
- protected
138
- def model_class
139
- self.model_class
140
- end
141
-
142
- private
143
- def set_timestamps
144
- self.created_at = DateTime.now.utc if self.new_record?
145
- self.updated_at = DateTime.now.utc
146
- end
147
-
148
- def self.mongoid?
149
- get_model_class.included_modules.include?(Mongoid::Document)
150
- end
151
-
152
- def mongoid?
153
- self.class.mongoid?
154
- end
155
- end
156
- end
@@ -1,101 +0,0 @@
1
- module ActiveRepository
2
- module Finders
3
- def define_custom_find_by_field(field_name)
4
- method_name = :"find_all_by_#{field_name}"
5
- the_meta_class.instance_eval do
6
- define_method(method_name) do |*args|
7
- object = nil
8
-
9
- object = self.find_by_field(field_name.to_sym, args)
10
-
11
- object.nil? ? nil : serialize!(object.attributes)
12
- end
13
- end
14
- end
15
-
16
- def define_custom_find_all_by_field(field_name)
17
- method_name = :"find_all_by_#{field_name}"
18
- the_meta_class.instance_eval do
19
- define_method(method_name) do |*args|
20
- objects = []
21
-
22
- objects = self.find_all_by_field(field_name.to_sym, args)
23
-
24
- objects.empty? ? [] : objects.map{ |object| serialize!(object.attributes) }
25
- end
26
- end
27
- end
28
-
29
- def find_by_field(field_name, args)
30
- self.find_all_by_field(field_name, args).first
31
- end
32
-
33
- def find_all_by_field(field_name, args)
34
- objects = []
35
-
36
- if self == get_model_class
37
- objects = self.where(field_name.to_sym => args.first)
38
- else
39
- if mongoid?
40
- objects = get_model_class.where(field_name.to_sym => args.first)
41
- else
42
- method_name = :"find_all_by_#{field_name}"
43
- objects = get_model_class.send(method_name, args)
44
- end
45
- end
46
-
47
- objects
48
- end
49
-
50
- def find(id)
51
- begin
52
- if self == get_model_class
53
- super(id)
54
- else
55
- object = (id == :all) ? all : get_model_class.find(id)
56
-
57
- serialize!(object)
58
- end
59
- rescue Exception => e
60
- message = "Couldn't find #{self} with ID=#{id}"
61
- message = "Couldn't find all #{self} objects with IDs (#{id.join(', ')})" if id.is_a?(Array)
62
-
63
- raise ActiveHash::RecordNotFound.new(message)
64
- end
65
- end
66
-
67
- def find_by_id(id)
68
- if self == get_model_class
69
- super(id)
70
- else
71
- object = nil
72
-
73
- if mongoid?
74
- object = get_model_class.where(:id => id).entries.first
75
- else
76
- object = get_model_class.find_by_id(id)
77
- end
78
-
79
- object.nil? ? nil : serialize!(object.attributes)
80
- end
81
- end
82
-
83
- def first
84
- get("first")
85
- end
86
-
87
- def last
88
- get("last")
89
- end
90
-
91
- private
92
- def get(position)
93
- if self == get_model_class
94
- all.sort_by!{ |o| o.id }.send(position)
95
- else
96
- object = get_model_class.send(position)
97
- serialize! object.attributes
98
- end
99
- end
100
- end
101
- end
@@ -1,135 +0,0 @@
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
- execute_between(klass, sub_query)
79
- when "is"
80
- execute_is(klass, sub_query)
81
- else
82
- execute_operator(klass, sub_query)
83
- end
84
- end
85
-
86
- def execute_between(klass, sub_query)
87
- klass.all.select do |o|
88
- field, first_attr, second_attr = convert_attrs(o.send(sub_query.first), sub_query[2], sub_query[4])
89
-
90
- (field >= first_attr && field <= second_attr)
91
- end
92
- end
93
-
94
- def execute_is(klass, sub_query)
95
- klass.all.select do |o|
96
- field = o.send(sub_query.first).blank?
97
-
98
- sub_query.size == 3 ? field : !field
99
- end
100
- end
101
-
102
- def execute_operator(klass, sub_query)
103
- klass.all.select do |o|
104
- field, attribute = convert_attrs(o.send(sub_query.first), sub_query[2])
105
-
106
- field.blank? ? false : field.send(@operator, attribute)
107
- end
108
- end
109
-
110
- def convert_param(param)
111
- case param.class.name
112
- when "String"
113
- param = "'#{param}'"
114
- when "Date"
115
- param = "'#{param.strftime("%Y-%m-%d")}'"
116
- when "Time"
117
- param = "'#{param.strftime("%Y-%m-%d %H:%M:%S %z")}'"
118
- else
119
- param = param.to_s
120
- end
121
- end
122
-
123
- def convert_attrs(field, *attrs)
124
- attrs.each_with_index do |attribute, i|
125
- attribute = attribute.gsub("_", " ")
126
- attrs[i] = field.is_a?(Integer) ? attribute.to_i : attribute
127
- end
128
-
129
- field = field.is_a?(Integer) ? field : field.to_s
130
-
131
- [field, attrs].flatten
132
- end
133
- end
134
- end
135
- end
@@ -1,196 +0,0 @@
1
- module ActiveModel
2
- module Validations
3
- class UniquenessValidator < ActiveModel::EachValidator #:nodoc:
4
- def initialize(options)
5
- super(options.reverse_merge(:case_sensitive => true))
6
- end
7
-
8
- # Unfortunately, we have to tie Uniqueness validators to a class.
9
- def setup(klass)
10
- @klass = klass
11
- end
12
-
13
- def validate_each(record, attribute, value)
14
- finder_class = record.class.get_model_class
15
-
16
- finder_class.all.each do |object|
17
- if object.id != record.id && object.send(attribute) == record.send(attribute)
18
- record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope, :conditions).merge(:value => value))
19
- break
20
- end
21
- end
22
- end
23
-
24
- protected
25
-
26
- # The check for an existing value should be run from a class that
27
- # isn't abstract. This means working down from the current class
28
- # (self), to the first non-abstract class. Since classes don't know
29
- # their subclasses, we have to build the hierarchy between self and
30
- # the record's class.
31
- def find_finder_class_for(record) #:nodoc:
32
- class_hierarchy = [record.class]
33
-
34
- while class_hierarchy.first != @klass
35
- class_hierarchy.prepend(class_hierarchy.first.superclass)
36
- end
37
-
38
- class_hierarchy.detect { |klass| klass.respond_to?(:abstract_class?) ? !klass.abstract_class? : true }
39
- end
40
-
41
- def build_relation(klass, table, attribute, value) #:nodoc:
42
- reflection = klass.reflect_on_association(attribute)
43
- if reflection
44
- column = klass.columns_hash[reflection.foreign_key]
45
- attribute = reflection.foreign_key
46
- value = value.attributes[reflection.primary_key_column.name]
47
- else
48
- column = klass.columns_hash[attribute.to_s]
49
- end
50
- value = column.limit ? value.to_s[0, column.limit] : value.to_s if !value.nil? && column.text?
51
-
52
- if !options[:case_sensitive] && value && column.text?
53
- # will use SQL LOWER function before comparison, unless it detects a case insensitive collation
54
- relation = klass.connection.case_insensitive_comparison(table, attribute, column, value)
55
- else
56
- value = klass.connection.case_sensitive_modifier(value) unless value.nil?
57
- relation = table[attribute].eq(value)
58
- end
59
-
60
- relation
61
- end
62
- end
63
-
64
- module ClassMethods
65
- # Validates whether the value of the specified attributes are unique
66
- # across the system. Useful for making sure that only one user
67
- # can be named "davidhh".
68
- #
69
- # class Person < ActiveRecord::Base
70
- # validates_uniqueness_of :user_name
71
- # end
72
- #
73
- # It can also validate whether the value of the specified attributes are
74
- # unique based on a <tt>:scope</tt> parameter:
75
- #
76
- # class Person < ActiveRecord::Base
77
- # validates_uniqueness_of :user_name, scope: :account_id
78
- # end
79
- #
80
- # Or even multiple scope parameters. For example, making sure that a
81
- # teacher can only be on the schedule once per semester for a particular
82
- # class.
83
- #
84
- # class TeacherSchedule < ActiveRecord::Base
85
- # validates_uniqueness_of :teacher_id, scope: [:semester_id, :class_id]
86
- # end
87
- #
88
- # It is also possible to limit the uniqueness constraint to a set of
89
- # records matching certain conditions. In this example archived articles
90
- # are not being taken into consideration when validating uniqueness
91
- # of the title attribute:
92
- #
93
- # class Article < ActiveRecord::Base
94
- # validates_uniqueness_of :title, conditions: where('status != ?', 'archived')
95
- # end
96
- #
97
- # When the record is created, a check is performed to make sure that no
98
- # record exists in the database with the given value for the specified
99
- # attribute (that maps to a column). When the record is updated,
100
- # the same check is made but disregarding the record itself.
101
- #
102
- # Configuration options:
103
- #
104
- # * <tt>:message</tt> - Specifies a custom error message (default is:
105
- # "has already been taken").
106
- # * <tt>:scope</tt> - One or more columns by which to limit the scope of
107
- # the uniqueness constraint.
108
- # * <tt>:conditions</tt> - Specify the conditions to be included as a
109
- # <tt>WHERE</tt> SQL fragment to limit the uniqueness constraint lookup
110
- # (e.g. <tt>conditions: where('status = ?', 'active')</tt>).
111
- # * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
112
- # non-text columns (+true+ by default).
113
- # * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the
114
- # attribute is +nil+ (default is +false+).
115
- # * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the
116
- # attribute is blank (default is +false+).
117
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
118
- # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
119
- # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
120
- # proc or string should return or evaluate to a +true+ or +false+ value.
121
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to
122
- # determine if the validation should ot occur (e.g. <tt>unless: :skip_validation</tt>,
123
- # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
124
- # method, proc or string should return or evaluate to a +true+ or +false+
125
- # value.
126
- #
127
- # === Concurrency and integrity
128
- #
129
- # Using this validation method in conjunction with ActiveRecord::Base#save
130
- # does not guarantee the absence of duplicate record insertions, because
131
- # uniqueness checks on the application level are inherently prone to race
132
- # conditions. For example, suppose that two users try to post a Comment at
133
- # the same time, and a Comment's title must be unique. At the database-level,
134
- # the actions performed by these users could be interleaved in the following manner:
135
- #
136
- # User 1 | User 2
137
- # ------------------------------------+--------------------------------------
138
- # # User 1 checks whether there's |
139
- # # already a comment with the title |
140
- # # 'My Post'. This is not the case. |
141
- # SELECT * FROM comments |
142
- # WHERE title = 'My Post' |
143
- # |
144
- # | # User 2 does the same thing and also
145
- # | # infers that his title is unique.
146
- # | SELECT * FROM comments
147
- # | WHERE title = 'My Post'
148
- # |
149
- # # User 1 inserts his comment. |
150
- # INSERT INTO comments |
151
- # (title, content) VALUES |
152
- # ('My Post', 'hi!') |
153
- # |
154
- # | # User 2 does the same thing.
155
- # | INSERT INTO comments
156
- # | (title, content) VALUES
157
- # | ('My Post', 'hello!')
158
- # |
159
- # | # ^^^^^^
160
- # | # Boom! We now have a duplicate
161
- # | # title!
162
- #
163
- # This could even happen if you use transactions with the 'serializable'
164
- # isolation level. The best way to work around this problem is to add a unique
165
- # index to the database table using
166
- # ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the
167
- # rare case that a race condition occurs, the database will guarantee
168
- # the field's uniqueness.
169
- #
170
- # When the database catches such a duplicate insertion,
171
- # ActiveRecord::Base#save will raise an ActiveRecord::StatementInvalid
172
- # exception. You can either choose to let this error propagate (which
173
- # will result in the default Rails exception page being shown), or you
174
- # can catch it and restart the transaction (e.g. by telling the user
175
- # that the title already exists, and asking him to re-enter the title).
176
- # This technique is also known as optimistic concurrency control:
177
- # http://en.wikipedia.org/wiki/Optimistic_concurrency_control
178
- #
179
- # The bundled ActiveRecord::ConnectionAdapters distinguish unique index
180
- # constraint errors from other types of database errors by throwing an
181
- # ActiveRecord::RecordNotUnique exception. For other adapters you will
182
- # have to parse the (database-specific) exception message to detect such
183
- # a case.
184
- #
185
- # The following bundled adapters throw the ActiveRecord::RecordNotUnique exception:
186
- #
187
- # * ActiveRecord::ConnectionAdapters::MysqlAdapter
188
- # * ActiveRecord::ConnectionAdapters::Mysql2Adapter
189
- # * ActiveRecord::ConnectionAdapters::SQLite3Adapter
190
- # * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
191
- def validates_uniqueness_of(*attr_names)
192
- validates_with UniquenessValidator, _merge_attributes(attr_names)
193
- end
194
- end
195
- end
196
- end
@@ -1,3 +0,0 @@
1
- module ActiveRepository
2
- VERSION = "0.0.3"
3
- end
@@ -1,93 +0,0 @@
1
- require 'active_hash'
2
- require 'active_repository/sql_query_executor'
3
-
4
- begin
5
- klass = Module.const_get(ActiveRecord::Rollback)
6
- unless klass.is_a?(Class)
7
- raise "Not defined"
8
- end
9
- rescue
10
- module ActiveRecord
11
- class ActiveRecordError < StandardError
12
- end
13
- class Rollback < ActiveRecord::ActiveRecordError
14
- end
15
- end
16
- end
17
-
18
- module ActiveHash
19
- class Base
20
- def self.insert(record)
21
- record_id = record.id.to_s
22
- record_hash = record.hash
23
-
24
- if self.all.map(&:hash).include?(record_hash)
25
- record_index.delete(record_id)
26
- self.all.delete(record)
27
- end
28
-
29
- if record_index[record_id].nil? || !self.all.map(&:hash).include?(record_hash)
30
- insert_record(record)
31
- end
32
- end
33
-
34
- def self.where(query)
35
- if query.is_a?(String)
36
- return ActiveHash::SQLQueryExecutor.execute(self, query)
37
- else
38
- (@records || []).select do |record|
39
- query.all? { |col, match| record[col] == match }
40
- end
41
- end
42
- end
43
-
44
- def self.validate_unique_id(record)
45
- raise IdError.new("Duplicate Id found for record #{record.attributes}") if record_index.has_key?(record.id.to_s)
46
- end
47
-
48
- def update_attribute(key, value)
49
- self.send("#{key}=", value)
50
- self.save(:validate => false)
51
- end
52
-
53
- def readonly?
54
- false
55
- end
56
-
57
- def save(*args)
58
- record = self.class.find_by_id(self.id)
59
-
60
- self.class.insert(self) if record.nil? && record != self
61
- true
62
- end
63
-
64
- def to_param
65
- id.present? ? id.to_s : nil
66
- end
67
-
68
- def persisted?
69
- other = self.class.find_by_id(id)
70
- other.present?
71
- end
72
-
73
- def eql?(other)
74
- (other.instance_of?(self.class) || other.instance_of?(self.class.get_model_class)) && id.present? && (id == other.id) && (created_at == other.created_at)
75
- end
76
-
77
- alias == eql?
78
-
79
- private
80
- def self.insert_record(record)
81
- @records ||= []
82
- record.attributes[:id] ||= next_id
83
-
84
- validate_unique_id(record) if dirty
85
- mark_dirty
86
-
87
- if record.valid?
88
- add_to_record_index({ record.id.to_s => @records.length })
89
- @records << record
90
- end
91
- end
92
- end
93
- end
@@ -1,67 +0,0 @@
1
- module ActiveRepository
2
- module Writers
3
- def find_or_create(attributes)
4
- object = get_model_class.where(attributes).first
5
-
6
- object = model_class.create(attributes) if object.nil?
7
-
8
- serialize!(object.attributes)
9
- end
10
-
11
- def create(attributes={})
12
- object = get_model_class.new(attributes)
13
-
14
- object.id = nil if exists?(object.id)
15
-
16
- if get_model_class == self
17
- object.save
18
- else
19
- repository = serialize!(object.attributes)
20
- repository.valid? ? (object = get_model_class.create(attributes)) : false
21
- end
22
-
23
- serialize!(object.attributes) unless object.class.name == self
24
- end
25
-
26
- module InstanceMethods
27
- def update_attributes(attributes)
28
- object = nil
29
- if mongoid?
30
- object = self.class.get_model_class.find(self.id)
31
- else
32
- object = self.class.get_model_class.find(self.id)
33
- end
34
-
35
- attributes.each do |k,v|
36
- object.update_attribute("#{k.to_s}", v) unless k == :id
37
- end
38
-
39
- self.reload
40
- end
41
-
42
- def update_attribute(key, value)
43
- if self.class == self.class.get_model_class
44
- super(key,value)
45
- else
46
- object = self.class.get_model_class.find(self.id)
47
-
48
- if mongoid?
49
- super(key,value)
50
- key = key.to_s == 'id' ? '_id' : key.to_s
51
- end
52
-
53
- object.update_attribute(key, value)
54
- object.save
55
- end
56
-
57
- self.reload
58
- end
59
-
60
- def attributes=(new_attributes)
61
- new_attributes.each do |k,v|
62
- self.send("#{k.to_s == '_id' ? 'id' : k.to_s}=", v)
63
- end
64
- end
65
- end
66
- end
67
- end
data/support/mongoid.yml DELETED
@@ -1,6 +0,0 @@
1
- development:
2
- sessions:
3
- default:
4
- database: active_repository
5
- hosts:
6
- - localhost:27017