active_mapper 0.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.
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