passive_record 0.1.0
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.
- checksums.yaml +7 -0
- data/.document +3 -0
- data/.gitignore +6 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +4 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +20 -0
- data/README.md +81 -0
- data/Rakefile +31 -0
- data/bin/passive_record +14 -0
- data/features/.gitkeep +0 -0
- data/features/passive_record.feature +1 -0
- data/features/step_definitions/.gitkeep +0 -0
- data/features/step_definitions/passive_record_steps.rb +1 -0
- data/gemspec.yml +16 -0
- data/lib/passive_record.rb +111 -0
- data/lib/passive_record/associations.rb +42 -0
- data/lib/passive_record/associations/belongs_to.rb +27 -0
- data/lib/passive_record/associations/has_many.rb +19 -0
- data/lib/passive_record/associations/has_many_through.rb +24 -0
- data/lib/passive_record/associations/has_one.rb +38 -0
- data/lib/passive_record/core/identifier.rb +7 -0
- data/lib/passive_record/core/query.rb +25 -0
- data/lib/passive_record/hstruct.rb +38 -0
- data/lib/passive_record/version.rb +4 -0
- data/passive_record.gemspec +60 -0
- data/spec/passive_record_spec.rb +105 -0
- data/spec/spec_helper.rb +56 -0
- metadata +174 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: bae7d40e8ae674093246626a73f247c24cfdd442
|
4
|
+
data.tar.gz: d02d5675a5ec0857d1e15e24a7fb7149a880d362
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b0a66bb01e629e8b313aad8febe2085ad4b00a36af23e925393068501d01c988fe3b001d20822c640b4fcdbe7224b192bc93f23cacd78dbbbbbe743cb29d40c5
|
7
|
+
data.tar.gz: 23ccde9e6bc44c98080e854746b4078edf4585c4b59e53a278754c234e1c7d99add543e04dc7d5199ab699fd5e887d1f3e8a42a2025c19e416e3c63c9141f090
|
data/.document
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour --format documentation
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup markdown --title "passive_record Documentation" --protected
|
data/ChangeLog.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2016 Joseph Weissman
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# passive_record
|
2
|
+
|
3
|
+
* [Homepage](https://rubygems.org/gems/passive_record)
|
4
|
+
* [Documentation](http://rubydoc.info/gems/passive_record/frames)
|
5
|
+
* [Email](mailto:jweissman1986 at gmail.com)
|
6
|
+
|
7
|
+
[](https://codeclimate.com/github//passive_record)
|
8
|
+
|
9
|
+
## Description
|
10
|
+
|
11
|
+
PassiveRecord is an extremely lightweight in-memory pseudo-relational algebra.
|
12
|
+
|
13
|
+
We implement a simplified subset of AR's interface in pure Ruby.
|
14
|
+
|
15
|
+
## Why?
|
16
|
+
|
17
|
+
Do you need to track objects by ID and look them up again,
|
18
|
+
or look them up based on attributes, or even utilize some limited relational semantics,
|
19
|
+
but have no real need for persistence?
|
20
|
+
|
21
|
+
PassiveRecord may be right for you.
|
22
|
+
|
23
|
+
|
24
|
+
## Features
|
25
|
+
|
26
|
+
- Just 'include PassiveRecord' to activate a PORO in the system
|
27
|
+
- New objects are tracked and assigned IDs
|
28
|
+
- Query on attributes and simple relations (belongs_to, has_one, has_many)
|
29
|
+
- No database required!
|
30
|
+
|
31
|
+
## Examples
|
32
|
+
|
33
|
+
require 'passive_record'
|
34
|
+
|
35
|
+
class Model
|
36
|
+
include PassiveRecord
|
37
|
+
end
|
38
|
+
|
39
|
+
class Dog < Model
|
40
|
+
belongs_to :child
|
41
|
+
end
|
42
|
+
|
43
|
+
class Child < Model
|
44
|
+
has_one :dog
|
45
|
+
belongs_to :parent
|
46
|
+
end
|
47
|
+
|
48
|
+
class Parent < Model
|
49
|
+
has_many :children
|
50
|
+
has_many :dogs, :through => :children
|
51
|
+
end
|
52
|
+
|
53
|
+
# Let's create some models!
|
54
|
+
parent = Parent.create
|
55
|
+
|
56
|
+
child = parent.create_child
|
57
|
+
dog = child.create_dog
|
58
|
+
|
59
|
+
# inverse relationships
|
60
|
+
dog.child # ===> dog
|
61
|
+
|
62
|
+
Child.find_by(child_id: dog.child_id) # ===> dog
|
63
|
+
|
64
|
+
# has many thru
|
65
|
+
parent.dogs # ==> [dog]
|
66
|
+
|
67
|
+
## Requirements
|
68
|
+
|
69
|
+
## Install
|
70
|
+
|
71
|
+
$ gem install passive_record
|
72
|
+
|
73
|
+
## Synopsis
|
74
|
+
|
75
|
+
$ passive_record
|
76
|
+
|
77
|
+
## Copyright
|
78
|
+
|
79
|
+
Copyright (c) 2016 Joseph Weissman
|
80
|
+
|
81
|
+
See {file:LICENSE.txt} for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'bundler/setup'
|
7
|
+
rescue LoadError => e
|
8
|
+
abort e.message
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'rake'
|
12
|
+
|
13
|
+
|
14
|
+
require 'rubygems/tasks'
|
15
|
+
Gem::Tasks.new
|
16
|
+
|
17
|
+
require 'rspec/core/rake_task'
|
18
|
+
RSpec::Core::RakeTask.new
|
19
|
+
|
20
|
+
task :test => :spec
|
21
|
+
task :default => :spec
|
22
|
+
|
23
|
+
require 'yard'
|
24
|
+
YARD::Rake::YardocTask.new
|
25
|
+
task :doc => :yard
|
26
|
+
|
27
|
+
require 'cucumber/rake/task'
|
28
|
+
|
29
|
+
Cucumber::Rake::Task.new do |t|
|
30
|
+
t.cucumber_opts = %w[--format pretty]
|
31
|
+
end
|
data/bin/passive_record
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
root = File.expand_path(File.join(File.dirname(__FILE__),'..'))
|
4
|
+
if File.directory?(File.join(root,'.git'))
|
5
|
+
Dir.chdir(root) do
|
6
|
+
begin
|
7
|
+
require 'bundler/setup'
|
8
|
+
rescue LoadError => e
|
9
|
+
warn e.message
|
10
|
+
warn "Run `gem install bundler` to install Bundler"
|
11
|
+
exit -1
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/features/.gitkeep
ADDED
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
Feature: Blah blah blah
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
@wip
|
data/gemspec.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
name: passive_record
|
2
|
+
summary: "no-persistence relational algebra"
|
3
|
+
description: "lightweight in-memory simplified subset of AR"
|
4
|
+
license: MIT
|
5
|
+
authors: Joseph Weissman
|
6
|
+
email: jweissman1986@gmail.com
|
7
|
+
homepage: https://rubygems.org/gems/passive_record
|
8
|
+
|
9
|
+
development_dependencies:
|
10
|
+
bundler: ~> 1.10
|
11
|
+
codeclimate-test-reporter: ~> 0.1
|
12
|
+
cucumber: ~> 0.10.2
|
13
|
+
rake: ~> 10.0
|
14
|
+
rspec: ~> 3.0
|
15
|
+
rubygems-tasks: ~> 0.2
|
16
|
+
yard: ~> 0.8
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/core_ext/string/inflections'
|
3
|
+
|
4
|
+
require 'passive_record/version'
|
5
|
+
require 'passive_record/core/identifier'
|
6
|
+
require 'passive_record/core/query'
|
7
|
+
|
8
|
+
require 'passive_record/associations'
|
9
|
+
|
10
|
+
module PassiveRecord
|
11
|
+
def self.included(base)
|
12
|
+
base.send :include, InstanceMethods
|
13
|
+
base.extend(ClassMethods)
|
14
|
+
end
|
15
|
+
|
16
|
+
module InstanceMethods
|
17
|
+
def relationships
|
18
|
+
@relata ||= self.class.associations.map do |assn|
|
19
|
+
assn.to_relation(self)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(meth, *args, &blk)
|
24
|
+
matching_relation = relationships.detect do |relation| # matching relation...
|
25
|
+
meth == relation.association.target_name_symbol ||
|
26
|
+
meth.to_s == relation.association.target_name_symbol.to_s + "=" ||
|
27
|
+
meth.to_s == relation.association.target_name_symbol.to_s + "_id" ||
|
28
|
+
meth.to_s == relation.association.target_name_symbol.to_s + "_id=" ||
|
29
|
+
meth.to_s == "create_" + relation.association.target_name_symbol.to_s ||
|
30
|
+
meth.to_s == "create_" + (relation.association.target_name_symbol.to_s).singularize
|
31
|
+
end
|
32
|
+
|
33
|
+
if matching_relation
|
34
|
+
if meth.to_s.end_with?("_id")
|
35
|
+
matching_relation.parent_model_id
|
36
|
+
elsif meth.to_s.end_with?("_id=")
|
37
|
+
matching_relation.parent_model_id = args.first
|
38
|
+
elsif meth.to_s.end_with?("=")
|
39
|
+
matching_relation.parent_model_id = args.first.id
|
40
|
+
elsif meth.to_s.start_with?("create_")
|
41
|
+
matching_relation.create(*args)
|
42
|
+
else
|
43
|
+
# lookup the matching associated entities
|
44
|
+
matching_relation.lookup
|
45
|
+
end
|
46
|
+
else
|
47
|
+
super(meth,*args,&blk)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module ClassMethods
|
53
|
+
include PassiveRecord::Core
|
54
|
+
include PassiveRecord::Associations
|
55
|
+
|
56
|
+
include Enumerable
|
57
|
+
extend Forwardable
|
58
|
+
|
59
|
+
def all
|
60
|
+
instances_by_id.values
|
61
|
+
end
|
62
|
+
def_delegators :all, :each
|
63
|
+
|
64
|
+
def find_by(conditions)
|
65
|
+
if conditions.is_a?(Identifier)
|
66
|
+
find_by_id(conditions)
|
67
|
+
elsif conditions.is_a?(Array) && conditions.all? { |c| c.is_a?(Identifier) }
|
68
|
+
find_by_ids(conditions)
|
69
|
+
else
|
70
|
+
where(conditions).first
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def where(conditions)
|
75
|
+
Query.new(self, conditions)
|
76
|
+
end
|
77
|
+
|
78
|
+
def create(attrs={})
|
79
|
+
registrable = new #(*args)
|
80
|
+
|
81
|
+
registrable.singleton_class.class_eval { attr_accessor :id }
|
82
|
+
registrable.send(:"id=", Identifier.generate)
|
83
|
+
register(registrable)
|
84
|
+
|
85
|
+
attrs.each do |(k,v)|
|
86
|
+
registrable.send("#{k}=", v)
|
87
|
+
end
|
88
|
+
|
89
|
+
registrable
|
90
|
+
end
|
91
|
+
|
92
|
+
protected
|
93
|
+
def find_by_id(id)
|
94
|
+
instances_by_id[id]
|
95
|
+
end
|
96
|
+
|
97
|
+
def find_by_ids(ids)
|
98
|
+
instances_by_id.select { |id,_| ids.include?(id) }.values
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
def instances_by_id
|
103
|
+
@instances ||= {}
|
104
|
+
end
|
105
|
+
|
106
|
+
def register(model)
|
107
|
+
instances_by_id[model.id] = model
|
108
|
+
self
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'passive_record/associations/belongs_to'
|
2
|
+
require 'passive_record/associations/has_one'
|
3
|
+
require 'passive_record/associations/has_many'
|
4
|
+
require 'passive_record/associations/has_many_through'
|
5
|
+
|
6
|
+
module PassiveRecord
|
7
|
+
module Associations
|
8
|
+
def associations
|
9
|
+
@associations ||= []
|
10
|
+
end
|
11
|
+
|
12
|
+
def belongs_to(parent_name_sym, opts={})
|
13
|
+
target_class_name = opts.delete(:class_name) { (parent_name_sym.to_s).split('_').map(&:capitalize).join }
|
14
|
+
association = BelongsToAssociation.new(self, target_class_name, parent_name_sym)
|
15
|
+
associations.push(association)
|
16
|
+
end
|
17
|
+
|
18
|
+
def has_one(child_name_sym)
|
19
|
+
child_class_name = (child_name_sym.to_s).split('_').map(&:capitalize).join
|
20
|
+
association = HasOneAssociation.new(self, child_class_name, child_name_sym)
|
21
|
+
associations.push(association)
|
22
|
+
end
|
23
|
+
|
24
|
+
def has_many(collection_name_sym, opts={})
|
25
|
+
target_class_name = (collection_name_sym.to_s).split('_').map(&:capitalize).join
|
26
|
+
|
27
|
+
if opts.key?(:through)
|
28
|
+
through_class_collection_name = opts.delete(:through)
|
29
|
+
|
30
|
+
through_class_name = (through_class_collection_name.to_s).split('_').map(&:capitalize).join
|
31
|
+
base_association = associations.detect { |assn| assn.child_class_name == through_class_name }
|
32
|
+
|
33
|
+
association = HasManyThroughAssociation.new(self, target_class_name, collection_name_sym, through_class_collection_name, base_association)
|
34
|
+
|
35
|
+
associations.push(association)
|
36
|
+
else # simple has-many
|
37
|
+
association = HasManyAssociation.new(self, target_class_name, collection_name_sym)
|
38
|
+
associations.push(association)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module PassiveRecord
|
2
|
+
module Associations
|
3
|
+
class BelongsToAssociation < Struct.new(:child_class, :parent_class_name, :target_name_symbol)
|
4
|
+
def to_relation(child_model)
|
5
|
+
BelongsToRelation.new(self, child_model)
|
6
|
+
end
|
7
|
+
|
8
|
+
def parent_class
|
9
|
+
Object.const_get(parent_class_name)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class BelongsToRelation < Struct.new(:association, :child_model)
|
14
|
+
def lookup
|
15
|
+
association.parent_class.find_by(parent_model_id)
|
16
|
+
end
|
17
|
+
|
18
|
+
def parent_model_id
|
19
|
+
@parent_model_id ||= nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def parent_model_id=(id)
|
23
|
+
@parent_model_id = id
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module PassiveRecord
|
2
|
+
module Associations
|
3
|
+
class HasManyAssociation < Struct.new(:parent_class, :child_class_name, :children_name_sym)
|
4
|
+
def to_relation(parent_model)
|
5
|
+
HasManyRelation.new(self, parent_model)
|
6
|
+
end
|
7
|
+
|
8
|
+
def target_name_symbol
|
9
|
+
children_name_sym
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class HasManyRelation < HasOneRelation
|
14
|
+
def lookup
|
15
|
+
child_class.where(parent_model_id_field => parent_model.id).all
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module PassiveRecord
|
2
|
+
module Associations
|
3
|
+
class HasManyThroughAssociation < Struct.new(:parent_class, :child_class_name, :target_name_symbol, :through_class, :base_association)
|
4
|
+
def to_relation(parent_model)
|
5
|
+
HasManyThroughRelation.new(self, parent_model)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class HasManyThroughRelation < HasManyRelation
|
10
|
+
def lookup
|
11
|
+
association.base_association.
|
12
|
+
to_relation(parent_model).
|
13
|
+
lookup.
|
14
|
+
flat_map(&association.target_name_symbol.to_s.singularize.to_sym)
|
15
|
+
end
|
16
|
+
|
17
|
+
def create(attrs={})
|
18
|
+
# binding.pry
|
19
|
+
raise "missing intermediate relational key #{association.through_class}" unless attrs.key?(association.through_class)
|
20
|
+
super(attrs)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module PassiveRecord
|
2
|
+
module Associations
|
3
|
+
class HasOneAssociation < Struct.new(:parent_class, :child_class_name, :child_name_sym)
|
4
|
+
|
5
|
+
def to_relation(parent_model)
|
6
|
+
HasOneRelation.new(self, parent_model)
|
7
|
+
end
|
8
|
+
|
9
|
+
def target_name_symbol
|
10
|
+
child_name_sym
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class HasOneRelation < Struct.new(:association, :parent_model)
|
15
|
+
def lookup
|
16
|
+
child_class.find_by(parent_model_id_field => parent_model.id)
|
17
|
+
end
|
18
|
+
|
19
|
+
def create(*args)
|
20
|
+
model = child_class.create(*args)
|
21
|
+
model.send(parent_model_id_field + "=", parent_model.id)
|
22
|
+
model
|
23
|
+
end
|
24
|
+
|
25
|
+
def parent_model_id_field
|
26
|
+
parent_class_name + "_id"
|
27
|
+
end
|
28
|
+
|
29
|
+
def parent_class_name
|
30
|
+
association.parent_class.name.underscore
|
31
|
+
end
|
32
|
+
|
33
|
+
def child_class
|
34
|
+
Object.const_get(association.child_class_name.singularize)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module PassiveRecord
|
2
|
+
module Core
|
3
|
+
class Query < Struct.new(:klass, :conditions)
|
4
|
+
def all
|
5
|
+
klass.all.select do |instance|
|
6
|
+
conditions.all? do |(field,value)|
|
7
|
+
instance.send(field) == value
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def first
|
13
|
+
all.first
|
14
|
+
end
|
15
|
+
|
16
|
+
def create
|
17
|
+
klass.create(conditions)
|
18
|
+
end
|
19
|
+
|
20
|
+
def first_or_create
|
21
|
+
first || create
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#
|
2
|
+
# build a hash-initialized 'record' class
|
3
|
+
#
|
4
|
+
# singleton_class pattern taken from http://stackoverflow.com/questions/15256940/in-ruby-how-do-i-implement-a-class-whose-new-method-creates-subclasses-of-itsel
|
5
|
+
#
|
6
|
+
class HStruct
|
7
|
+
singleton_class.class_eval { alias :old_new :new }
|
8
|
+
|
9
|
+
def initialize(attributes={})
|
10
|
+
attributes.each do |k,v|
|
11
|
+
send("#{k}=",v)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch_values(*args)
|
16
|
+
to_h.fetch_values(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_h
|
20
|
+
attribute_names.inject({}) do |hsh,k|
|
21
|
+
hsh[k] = send("#{k}"); hsh
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.new(*attribute_names)
|
26
|
+
Class.new(self){
|
27
|
+
singleton_class.class_eval {
|
28
|
+
alias :new :old_new
|
29
|
+
}
|
30
|
+
|
31
|
+
attribute_names.each do |k|
|
32
|
+
attr_accessor k.to_sym
|
33
|
+
end
|
34
|
+
|
35
|
+
define_method(:attribute_names) { attribute_names }
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gemspec = YAML.load_file('gemspec.yml')
|
7
|
+
|
8
|
+
gem.name = gemspec.fetch('name')
|
9
|
+
gem.version = gemspec.fetch('version') do
|
10
|
+
lib_dir = File.join(File.dirname(__FILE__),'lib')
|
11
|
+
$LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
|
12
|
+
|
13
|
+
require 'passive_record/version'
|
14
|
+
PassiveRecord::VERSION
|
15
|
+
end
|
16
|
+
|
17
|
+
gem.summary = gemspec['summary']
|
18
|
+
gem.description = gemspec['description']
|
19
|
+
gem.licenses = Array(gemspec['license'])
|
20
|
+
gem.authors = Array(gemspec['authors'])
|
21
|
+
gem.email = gemspec['email']
|
22
|
+
gem.homepage = gemspec['homepage']
|
23
|
+
|
24
|
+
glob = lambda { |patterns| gem.files & Dir[*patterns] }
|
25
|
+
|
26
|
+
gem.files = `git ls-files`.split($/)
|
27
|
+
gem.files = glob[gemspec['files']] if gemspec['files']
|
28
|
+
|
29
|
+
gem.executables = gemspec.fetch('executables') do
|
30
|
+
glob['bin/*'].map { |path| File.basename(path) }
|
31
|
+
end
|
32
|
+
gem.default_executable = gem.executables.first if Gem::VERSION < '1.7.'
|
33
|
+
|
34
|
+
gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb']
|
35
|
+
gem.test_files = glob[gemspec['test_files'] || '{test/{**/}*_test.rb']
|
36
|
+
gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}']
|
37
|
+
|
38
|
+
gem.require_paths = Array(gemspec.fetch('require_paths') {
|
39
|
+
%w[ext lib].select { |dir| File.directory?(dir) }
|
40
|
+
})
|
41
|
+
|
42
|
+
gem.requirements = Array(gemspec['requirements'])
|
43
|
+
gem.required_ruby_version = gemspec['required_ruby_version']
|
44
|
+
gem.required_rubygems_version = gemspec['required_rubygems_version']
|
45
|
+
gem.post_install_message = gemspec['post_install_message']
|
46
|
+
|
47
|
+
split = lambda { |string| string.split(/,\s*/) }
|
48
|
+
|
49
|
+
if gemspec['dependencies']
|
50
|
+
gemspec['dependencies'].each do |name,versions|
|
51
|
+
gem.add_dependency(name,split[versions])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
if gemspec['development_dependencies']
|
56
|
+
gemspec['development_dependencies'].each do |name,versions|
|
57
|
+
gem.add_development_dependency(name,split[versions])
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Model do
|
4
|
+
describe "with a simple model including PR" do
|
5
|
+
let!(:model) { SimpleModel.create(foo: value) }
|
6
|
+
let(:value) { 'foo_value' }
|
7
|
+
|
8
|
+
describe "#id" do
|
9
|
+
it 'should be retrievable by id' do
|
10
|
+
expect(SimpleModel.find_by(model.id)).to eq(model)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#count" do
|
15
|
+
it 'should indicate the size of the models list' do
|
16
|
+
expect { SimpleModel.create }.to change { SimpleModel.count }.by(1)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#find_by" do
|
21
|
+
it 'should be retrievable by query' do
|
22
|
+
expect(SimpleModel.find_by(foo: 'foo_value')).to eq(model)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'one-to-one relationships' do
|
28
|
+
let(:child) { Child.create }
|
29
|
+
let(:another_child) { Child.create }
|
30
|
+
|
31
|
+
it 'should create children' do
|
32
|
+
expect { child.create_dog }.to change { Dog.count }.by(1)
|
33
|
+
expect(child.dog).to eq(Dog.first)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should have inverse relationships' do
|
37
|
+
dog = child.create_dog
|
38
|
+
expect(dog.child).to eq(child)
|
39
|
+
another_dog = another_child.create_dog
|
40
|
+
expect(another_dog.child).to eq(another_child)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'one-to-many relationships' do
|
45
|
+
let(:parent) { Parent.create }
|
46
|
+
|
47
|
+
it 'should create children' do
|
48
|
+
expect { parent.create_child }.to change{ Child.count }.by(1)
|
49
|
+
expect(parent.children).to all(be_a(Child))
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should create inverse relationships' do
|
53
|
+
child = parent.create_child
|
54
|
+
expect(child.parent).to eq(parent)
|
55
|
+
|
56
|
+
another_child = parent.create_child
|
57
|
+
expect(another_child.parent).to eq(parent)
|
58
|
+
|
59
|
+
expect(child.id).not_to eq(another_child.id)
|
60
|
+
expect(parent.children).to eq([child, another_child])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'one-to-many through relationships' do
|
65
|
+
let(:parent) { Parent.create }
|
66
|
+
let(:child) { parent.create_child }
|
67
|
+
subject(:dogs) { parent.dogs }
|
68
|
+
|
69
|
+
it 'should collect children of children' do
|
70
|
+
child.create_dog
|
71
|
+
expect(dogs).to all(be_a(Dog))
|
72
|
+
expect(dogs.first).to eq(child.dog)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'many-to-many' do
|
77
|
+
let(:patient) { Patient.create }
|
78
|
+
let(:doctor) { Doctor.create }
|
79
|
+
let!(:appointment) { Appointment.create(patient: patient, doctor: doctor) }
|
80
|
+
|
81
|
+
it 'should manage many-to-many relations' do
|
82
|
+
expect(appointment.doctor).to eq(doctor)
|
83
|
+
expect(appointment.patient).to eq(patient)
|
84
|
+
|
85
|
+
expect(patient.doctors).to eq([doctor])
|
86
|
+
expect(doctor.patients).to eq([patient])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'self-referential many-to-many' do
|
91
|
+
let!(:user_a) { User.create }
|
92
|
+
let!(:user_b) { User.create }
|
93
|
+
|
94
|
+
it 'should permit relations' do
|
95
|
+
expect(user_a.friends).to be_empty
|
96
|
+
|
97
|
+
# need to create bidirectional friendship
|
98
|
+
Friendship.create(user: user_a, friend: user_b)
|
99
|
+
Friendship.create(user: user_b, friend: user_a)
|
100
|
+
|
101
|
+
expect(user_a.friends).to eq([user_b])
|
102
|
+
expect(user_b.friends).to eq([user_a])
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'pry'
|
3
|
+
require 'passive_record'
|
4
|
+
|
5
|
+
class Model
|
6
|
+
include PassiveRecord
|
7
|
+
end
|
8
|
+
|
9
|
+
class SimpleModel < Struct.new(:foo)
|
10
|
+
include PassiveRecord
|
11
|
+
end
|
12
|
+
|
13
|
+
class Dog < Model
|
14
|
+
belongs_to :child
|
15
|
+
end
|
16
|
+
|
17
|
+
class Child < Model
|
18
|
+
has_one :dog
|
19
|
+
belongs_to :parent
|
20
|
+
end
|
21
|
+
|
22
|
+
class Parent < Model
|
23
|
+
has_many :children
|
24
|
+
has_many :dogs, :through => :children
|
25
|
+
end
|
26
|
+
|
27
|
+
###
|
28
|
+
|
29
|
+
class Patient < Model
|
30
|
+
has_many :appointments
|
31
|
+
has_many :doctors, :through => :appointments
|
32
|
+
end
|
33
|
+
|
34
|
+
class Appointment < Model
|
35
|
+
belongs_to :patient
|
36
|
+
belongs_to :doctor
|
37
|
+
end
|
38
|
+
|
39
|
+
class Doctor < Model
|
40
|
+
has_many :appointments
|
41
|
+
has_many :patients, :through => :appointments
|
42
|
+
end
|
43
|
+
|
44
|
+
###
|
45
|
+
#
|
46
|
+
# self-referential case
|
47
|
+
#
|
48
|
+
class Friendship < Model
|
49
|
+
belongs_to :user
|
50
|
+
belongs_to :friend, class_name: "User"
|
51
|
+
end
|
52
|
+
|
53
|
+
class User < Model
|
54
|
+
has_many :friendships
|
55
|
+
has_many :friends, :through => :friendships
|
56
|
+
end
|
metadata
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: passive_record
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joseph Weissman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-02-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: codeclimate-test-reporter
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.1'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: cucumber
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.10.2
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.10.2
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubygems-tasks
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.2'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.2'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: yard
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.8'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.8'
|
111
|
+
description: lightweight in-memory simplified subset of AR
|
112
|
+
email: jweissman1986@gmail.com
|
113
|
+
executables:
|
114
|
+
- passive_record
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files:
|
117
|
+
- ChangeLog.md
|
118
|
+
- LICENSE.txt
|
119
|
+
- README.md
|
120
|
+
files:
|
121
|
+
- ".document"
|
122
|
+
- ".gitignore"
|
123
|
+
- ".rspec"
|
124
|
+
- ".yardopts"
|
125
|
+
- ChangeLog.md
|
126
|
+
- Gemfile
|
127
|
+
- LICENSE.txt
|
128
|
+
- README.md
|
129
|
+
- Rakefile
|
130
|
+
- bin/passive_record
|
131
|
+
- features/.gitkeep
|
132
|
+
- features/passive_record.feature
|
133
|
+
- features/step_definitions/.gitkeep
|
134
|
+
- features/step_definitions/passive_record_steps.rb
|
135
|
+
- gemspec.yml
|
136
|
+
- lib/passive_record.rb
|
137
|
+
- lib/passive_record/associations.rb
|
138
|
+
- lib/passive_record/associations/belongs_to.rb
|
139
|
+
- lib/passive_record/associations/has_many.rb
|
140
|
+
- lib/passive_record/associations/has_many_through.rb
|
141
|
+
- lib/passive_record/associations/has_one.rb
|
142
|
+
- lib/passive_record/core/identifier.rb
|
143
|
+
- lib/passive_record/core/query.rb
|
144
|
+
- lib/passive_record/hstruct.rb
|
145
|
+
- lib/passive_record/version.rb
|
146
|
+
- passive_record.gemspec
|
147
|
+
- spec/passive_record_spec.rb
|
148
|
+
- spec/spec_helper.rb
|
149
|
+
homepage: https://rubygems.org/gems/passive_record
|
150
|
+
licenses:
|
151
|
+
- MIT
|
152
|
+
metadata: {}
|
153
|
+
post_install_message:
|
154
|
+
rdoc_options: []
|
155
|
+
require_paths:
|
156
|
+
- lib
|
157
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '0'
|
162
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
requirements: []
|
168
|
+
rubyforge_project:
|
169
|
+
rubygems_version: 2.4.5.1
|
170
|
+
signing_key:
|
171
|
+
specification_version: 4
|
172
|
+
summary: no-persistence relational algebra
|
173
|
+
test_files: []
|
174
|
+
has_rdoc:
|