active_mapper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +62 -0
  9. data/Rakefile +1 -0
  10. data/active_mapper.gemspec +26 -0
  11. data/lib/active_mapper/adapter/active_record/order/attribute.rb +34 -0
  12. data/lib/active_mapper/adapter/active_record/order.rb +30 -0
  13. data/lib/active_mapper/adapter/active_record/query/attribute.rb +63 -0
  14. data/lib/active_mapper/adapter/active_record/query/expression.rb +63 -0
  15. data/lib/active_mapper/adapter/active_record/query.rb +23 -0
  16. data/lib/active_mapper/adapter/active_record.rb +96 -0
  17. data/lib/active_mapper/adapter/memory/order/attribute.rb +40 -0
  18. data/lib/active_mapper/adapter/memory/order.rb +31 -0
  19. data/lib/active_mapper/adapter/memory/query/attribute.rb +61 -0
  20. data/lib/active_mapper/adapter/memory/query/expression.rb +81 -0
  21. data/lib/active_mapper/adapter/memory/query.rb +22 -0
  22. data/lib/active_mapper/adapter/memory.rb +80 -0
  23. data/lib/active_mapper/adapter.rb +7 -0
  24. data/lib/active_mapper/mapper.rb +107 -0
  25. data/lib/active_mapper/relation.rb +133 -0
  26. data/lib/active_mapper/version.rb +3 -0
  27. data/lib/active_mapper.rb +24 -0
  28. data/spec/active_mapper/adapter/active_record/order_spec.rb +11 -0
  29. data/spec/active_mapper/adapter/active_record_spec.rb +164 -0
  30. data/spec/active_mapper/adapter/memory_spec.rb +158 -0
  31. data/spec/active_mapper/mapper_spec.rb +195 -0
  32. data/spec/active_mapper/relation_spec.rb +190 -0
  33. data/spec/active_mapper_spec.rb +25 -0
  34. data/spec/features/active_record_spec.rb +7 -0
  35. data/spec/features/memory_spec.rb +5 -0
  36. data/spec/spec_helper.rb +30 -0
  37. data/spec/support/models/user.rb +20 -0
  38. data/spec/support/shared/active_mapper_integration.rb +129 -0
  39. metadata +162 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c6a0962e9a48619f43dcd83bd2c1c537d86d431d
4
+ data.tar.gz: ce7a305b11b716922e48dd4eee63ea31791c7c27
5
+ SHA512:
6
+ metadata.gz: f7dd85906718afeb6de5c2422e2508ff8449b649a842552df2869fa3aaf3df6120f3b120c2958d68ad2bab818c452cde1bf7f137785ac11b1ca389825194aeee
7
+ data.tar.gz: bbfe65e3421d429cebad52d4fc04e59578619dfe856f18cd695a8819ec8d422fd0a478a18b46e72ed18c532b54a82c8802be20460f8e6a3af05817e3bef39c2b
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/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ active_mapper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0.0-p247
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in active_mapper.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Artin Boghosian
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,62 @@
1
+ # ActiveMapper
2
+
3
+ [![Code Climate](https://codeclimate.com/github/artinboghosian/active_mapper.png)](https://codeclimate.com/github/artinboghosian/active_mapper)
4
+
5
+ ActiveMapper is a data-mapper using ActiveRecord/Arel as a backend. It allows you to create models using PORO and comes with a Memory adapter which you can use during testing to make your tests faster and not reliant on a database. The only requirements are that your models have an id attribute (auto-incrementing int) and respond to a valid? and .model_name ( ActiveSupport::Name). These can easily be injected into your models by including ActiveModel::Model.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'active_mapper'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install active_mapper
20
+
21
+ ## Usage
22
+
23
+ class Post
24
+ include ActiveModel::Model
25
+
26
+ attr_accessor :id, :title, :content, :status, :created_at, :updated_at
27
+
28
+ def persisted?
29
+ id
30
+ end
31
+ end
32
+
33
+ post = Post.new(title: 'Post', content: 'My First Post')
34
+
35
+ # ActiveMapper[Class] will automatically create and wire up the mapper
36
+ # if it does not exist. The only requirement is that the table exists
37
+ # with the proper columns (e.g. use ActiveRecord migrations).
38
+ ActiveMapper[Post].save(post)
39
+
40
+ record = ActiveMapper[Post].find(post.id)
41
+ ### Querying
42
+ mapper = ActiveMapper[Post]
43
+
44
+ # some methods return an ActiveMapper::Relation which will not execute
45
+ # any queries until #to_a, #each or #map are called.
46
+
47
+ mapper.select { |post| post.title.starts_with('Post') }
48
+ mapper.reject { |post| post.status == 'draft' }
49
+
50
+ # some methods actually execute the query immediately
51
+
52
+ mapper.first { |post| !(post.status == 'draft') }
53
+ mapper.last { |post| (post.status == 'published') & (post.created_at > 5.days.ago) }
54
+ mapper.all { |post| (post.content.contains('monkey')) & (post.status == 'published) }
55
+
56
+ ## Contributing
57
+
58
+ 1. Fork it ( http://github.com/<my-github-username>/active_mapper/fork )
59
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
60
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
61
+ 4. Push to the branch (`git push origin my-new-feature`)
62
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'active_mapper/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "active_mapper"
8
+ spec.version = ActiveMapper::VERSION
9
+ spec.authors = ["Artin Boghosian"]
10
+ spec.email = ["artinboghosian@gmail.com"]
11
+ spec.summary = %q{Data mapper using ActiveRecord for data access}
12
+ spec.description = %q{Data mapper using ActiveRecord for data access}
13
+ spec.homepage = "https://github.com/artinboghosian/active_mapper"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "activerecord"
22
+ spec.add_development_dependency "bundler", "~> 1.5"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "rspec"
25
+ spec.add_development_dependency "sqlite3"
26
+ end
@@ -0,0 +1,34 @@
1
+ module ActiveMapper
2
+ module Adapter
3
+ class ActiveRecord
4
+ class Order
5
+ class Attribute
6
+ def initialize(name)
7
+ @name = name
8
+ @direction = :asc
9
+ end
10
+
11
+ def -@
12
+ @direction = :desc
13
+ self
14
+ end
15
+
16
+ def reverse
17
+ @direction = asc? ? :desc : :asc
18
+ self
19
+ end
20
+
21
+ def to_sql
22
+ { @name => @direction }
23
+ end
24
+
25
+ private
26
+
27
+ def asc?
28
+ @direction == :asc
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,30 @@
1
+ require 'active_mapper/adapter/active_record/order/attribute'
2
+
3
+ module ActiveMapper
4
+ module Adapter
5
+ class ActiveRecord
6
+ class Order
7
+ def initialize(&block)
8
+ @block = block
9
+ end
10
+
11
+ def to_sql
12
+ [attributes].flatten.inject({}) do |memo, attribute|
13
+ memo = memo.merge(attribute.to_sql)
14
+ memo
15
+ end
16
+ end
17
+
18
+ def method_missing(name, *args, &block)
19
+ Attribute.new(name)
20
+ end
21
+
22
+ private
23
+
24
+ def attributes
25
+ @block ? @block.call(self) : []
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,63 @@
1
+ module ActiveMapper
2
+ module Adapter
3
+ class ActiveRecord
4
+ class Query
5
+ class Attribute
6
+ def initialize(attribute)
7
+ @attribute = attribute
8
+ end
9
+
10
+ def in(*collection)
11
+ Expression.new(@attribute, :in, collection)
12
+ end
13
+
14
+ def not_in(*collection)
15
+ Expression.new(@attribute, :not_in, collection)
16
+ end
17
+
18
+ def starts_with(value)
19
+ matches("#{value}%")
20
+ end
21
+
22
+ def contains(value)
23
+ matches("%#{value}%")
24
+ end
25
+
26
+ def ends_with(value)
27
+ matches("%#{value}")
28
+ end
29
+
30
+ def ==(value)
31
+ Expression.new(@attribute, :eq, value)
32
+ end
33
+
34
+ def !=(value)
35
+ Expression.new(@attribute, :not_eq, value)
36
+ end
37
+
38
+ def >(value)
39
+ Expression.new(@attribute, :gt, value)
40
+ end
41
+
42
+ def >=(value)
43
+ Expression.new(@attribute, :gteq, value)
44
+ end
45
+
46
+ def <(value)
47
+ Expression.new(@attribute, :lt, value)
48
+ end
49
+
50
+ def <=(value)
51
+ Expression.new(@attribute, :lteq, value)
52
+ end
53
+
54
+ private
55
+
56
+ def matches(value)
57
+ Expression.new(@attribute, :matches, value)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,63 @@
1
+ module ActiveMapper
2
+ module Adapter
3
+ class ActiveRecord
4
+ class Query
5
+ class Expression
6
+ def initialize(attribute, comparator, value)
7
+ @attribute = attribute
8
+ @comparator = comparator
9
+ @value = value
10
+ end
11
+
12
+ def to_sql
13
+ @attribute.send(@comparator, @value)
14
+ end
15
+
16
+ def !
17
+ NotExpression.new(self)
18
+ end
19
+
20
+ def &(expression)
21
+ AndExpression.new(self, expression)
22
+ end
23
+
24
+ def |(expression)
25
+ OrExpression.new(self, expression)
26
+ end
27
+ end
28
+
29
+ class NotExpression < Expression
30
+ def initialize(expression)
31
+ @expression = expression
32
+ end
33
+
34
+ def to_sql
35
+ @expression.to_sql.not
36
+ end
37
+ end
38
+
39
+ class AndExpression < Expression
40
+ def initialize(left, right)
41
+ @left = left
42
+ @right = right
43
+ end
44
+
45
+ def to_sql
46
+ @left.to_sql.and(@right.to_sql)
47
+ end
48
+ end
49
+
50
+ class OrExpression < Expression
51
+ def initialize(left, right)
52
+ @left = left
53
+ @right = right
54
+ end
55
+
56
+ def to_sql
57
+ @left.to_sql.or(@right.to_sql)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,23 @@
1
+ require 'active_mapper/adapter/active_record/query/attribute'
2
+ require 'active_mapper/adapter/active_record/query/expression'
3
+
4
+ module ActiveMapper
5
+ module Adapter
6
+ class ActiveRecord
7
+ class Query
8
+ def initialize(table, &block)
9
+ @table = table
10
+ @block = block
11
+ end
12
+
13
+ def to_sql
14
+ @block ? @block.call(self).to_sql : {}
15
+ end
16
+
17
+ def method_missing(name, *args, &block)
18
+ Attribute.new(@table[name])
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,96 @@
1
+ require 'active_record'
2
+ require 'active_mapper/adapter/active_record/query'
3
+ require 'active_mapper/adapter/active_record/order'
4
+
5
+ module ActiveMapper
6
+ module Adapter
7
+ class ActiveRecord
8
+ def initialize
9
+ @collection = {}
10
+ end
11
+
12
+ def find(klass, id)
13
+ collection(klass).find_by(id: id)
14
+ end
15
+
16
+ def where(klass, options = {}, &block)
17
+ active_record = collection(klass)
18
+ query = Query.new(active_record.arel_table, &block)
19
+ order = Order.new(&options[:order])
20
+
21
+ records = active_record.where(query.to_sql)
22
+ records = records.limit(options[:limit]) if options[:limit]
23
+ records = records.offset(options[:offset]) if options[:offset]
24
+ records = records.order(order.to_sql)
25
+
26
+ records
27
+ end
28
+
29
+ def count(klass, &block)
30
+ where(klass, &block).count
31
+ end
32
+
33
+ def minimum(klass, attribute, &block)
34
+ calculate(:minimum, klass, attribute, &block)
35
+ end
36
+
37
+ def maximum(klass, attribute, &block)
38
+ calculate(:maximum, klass, attribute, &block)
39
+ end
40
+
41
+ def average(klass, attribute, &block)
42
+ calculate(:average, klass, attribute, &block)
43
+ end
44
+
45
+ def sum(klass, attribute, &block)
46
+ calculate(:sum, klass, attribute, &block)
47
+ end
48
+
49
+ def insert(klass, object)
50
+ active_record = collection(klass)
51
+ attributes = serialize(klass, object)
52
+
53
+ record = active_record.new(attributes)
54
+ record.save
55
+ record.id
56
+ end
57
+
58
+ def update(klass, object)
59
+ find(klass, object.id).update_columns(serialize(klass, object))
60
+ end
61
+
62
+ def serialize(klass, object)
63
+ collection(klass).column_names.dup.inject({}) do |memo, attribute|
64
+ memo[attribute] = object.send(attribute)
65
+ memo
66
+ end
67
+ end
68
+
69
+ def unserialize(klass, object)
70
+ klass.new(serialize(klass, object))
71
+ end
72
+
73
+ def delete(klass, object)
74
+ find(klass, object.id).delete
75
+ end
76
+
77
+ def delete_all(klass, &block)
78
+ where(klass, &block).delete_all
79
+ end
80
+
81
+ private
82
+
83
+ def collection(klass)
84
+ @collection[klass] ||= begin
85
+ active_record = Class.new(::ActiveRecord::Base)
86
+ active_record.table_name = klass.model_name.plural
87
+ active_record
88
+ end
89
+ end
90
+
91
+ def calculate(operation, klass, attribute, &block)
92
+ where(klass, &block).send(operation, attribute)
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,40 @@
1
+ module ActiveMapper
2
+ module Adapter
3
+ class Memory
4
+ class Order
5
+ class Attribute
6
+ def initialize(name)
7
+ @name = name
8
+ @direction = :asc
9
+ end
10
+
11
+ def -@
12
+ @direction = :desc
13
+ self
14
+ end
15
+
16
+ def reverse
17
+ @direction = asc? ? :desc : :asc
18
+ self
19
+ end
20
+
21
+ def to_proc
22
+ proc do |x,y|
23
+ if asc?
24
+ x.send(@name) <=> y.send(@name)
25
+ else
26
+ y.send(@name) <=> x.send(@name)
27
+ end
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def asc?
34
+ @direction == :asc
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,31 @@
1
+ require 'active_mapper/adapter/memory/order/attribute'
2
+
3
+ module ActiveMapper
4
+ module Adapter
5
+ class Memory
6
+ class Order
7
+ def initialize(&block)
8
+ @block = block
9
+ end
10
+
11
+ def to_proc
12
+ proc do |x,y|
13
+ [attributes].flatten.sum do |attribute|
14
+ attribute.to_proc.call(x,y)
15
+ end
16
+ end
17
+ end
18
+
19
+ def method_missing(name, *args, &block)
20
+ Attribute.new(name)
21
+ end
22
+
23
+ private
24
+
25
+ def attributes
26
+ @block ? @block.call(self) : [id]
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,61 @@
1
+ module ActiveMapper
2
+ module Adapter
3
+ class Memory
4
+ class Attribute
5
+ def initialize(attribute)
6
+ @attribute = attribute
7
+ end
8
+
9
+ def in(*collection)
10
+ InvertedExpression.new(collection, :include?, @attribute)
11
+ end
12
+
13
+ def not_in(*collection)
14
+ !(self.in(*collection))
15
+ end
16
+
17
+ def starts_with(value)
18
+ matches(/^#{value}/i)
19
+ end
20
+
21
+ def contains(value)
22
+ matches(/#{value}/i)
23
+ end
24
+
25
+ def ends_with(value)
26
+ matches(/#{value}$/i)
27
+ end
28
+
29
+ def ==(value)
30
+ Expression.new(@attribute, :==, value)
31
+ end
32
+
33
+ def !=(value)
34
+ Expression.new(@attribute, :!=, value)
35
+ end
36
+
37
+ def >(value)
38
+ Expression.new(@attribute, :>, value)
39
+ end
40
+
41
+ def >=(value)
42
+ Expression.new(@attribute, :>=, value)
43
+ end
44
+
45
+ def <(value)
46
+ Expression.new(@attribute, :<, value)
47
+ end
48
+
49
+ def <=(value)
50
+ Expression.new(@attribute, :<=, value)
51
+ end
52
+
53
+ private
54
+
55
+ def matches(regexp)
56
+ Expression.new(@attribute, :match, regexp)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,81 @@
1
+ module ActiveMapper
2
+ module Adapter
3
+ class Memory
4
+ class Expression
5
+ def initialize(attribute, comparator, value)
6
+ @attribute = attribute
7
+ @comparator = comparator
8
+ @value = value
9
+ end
10
+
11
+ def to_proc
12
+ proc do |object|
13
+ object.send(@attribute).send(@comparator, @value)
14
+ end
15
+ end
16
+
17
+ def !
18
+ NotExpression.new(self)
19
+ end
20
+
21
+ def &(expression)
22
+ AndExpression.new(self, expression)
23
+ end
24
+
25
+ def |(expression)
26
+ OrExpression.new(self, expression)
27
+ end
28
+ end
29
+
30
+ class InvertedExpression < Expression
31
+ def initialize(value, comparator, attribute)
32
+ super(attribute, comparator, value)
33
+ end
34
+
35
+ def to_proc
36
+ proc do |object|
37
+ @value.send(@comparator, object.send(@attribute))
38
+ end
39
+ end
40
+ end
41
+
42
+ class NotExpression < Expression
43
+ def initialize(expression)
44
+ @expression = expression
45
+ end
46
+
47
+ def to_proc
48
+ proc do |object|
49
+ !@expression.to_proc.call(object)
50
+ end
51
+ end
52
+ end
53
+
54
+ class AndExpression < Expression
55
+ def initialize(left, right)
56
+ @left = left
57
+ @right = right
58
+ end
59
+
60
+ def to_proc
61
+ proc do |object|
62
+ @left.to_proc.call(object) && @right.to_proc.call(object)
63
+ end
64
+ end
65
+ end
66
+
67
+ class OrExpression < Expression
68
+ def initialize(left, right)
69
+ @left = left
70
+ @right = right
71
+ end
72
+
73
+ def to_proc
74
+ proc do |object|
75
+ @left.to_proc.call(object) || @right.to_proc.call(object)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end