rmodel 0.4.0.dev → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +4 -0
- data/.travis.yml +4 -3
- data/README.md +119 -153
- data/Rakefile +1 -2
- data/examples/mongo_embedded.rb +27 -21
- data/examples/scopes.rb +28 -0
- data/examples/sql_repository.rb +14 -26
- data/examples/timestamps.rb +7 -10
- data/examples/webapp/models/task.rb +9 -0
- data/examples/webapp/repositories/task_repository.rb +14 -0
- data/examples/webapp/server.rb +25 -0
- data/examples/webapp/support/mappers.rb +11 -0
- data/examples/webapp/support/repositories.rb +12 -0
- data/examples/webapp/support/sources.rb +13 -0
- data/examples/webapp/views/index.erb +20 -0
- data/lib/rmodel.rb +11 -8
- data/lib/rmodel/array_mapper.rb +23 -0
- data/lib/rmodel/base_mapper.rb +56 -0
- data/lib/rmodel/dummy_mapper.rb +15 -0
- data/lib/rmodel/mongo/mapper.rb +11 -0
- data/lib/rmodel/mongo/source.rb +50 -0
- data/lib/rmodel/repository.rb +36 -0
- data/lib/rmodel/repository_ext/scopable.rb +44 -0
- data/lib/rmodel/repository_ext/sugarable.rb +35 -0
- data/lib/rmodel/repository_ext/timestampable.rb +29 -0
- data/lib/rmodel/scope.rb +31 -0
- data/lib/rmodel/sequel/mapper.rb +6 -0
- data/lib/rmodel/sequel/source.rb +43 -0
- data/lib/rmodel/uni_hash.rb +16 -0
- data/lib/rmodel/version.rb +1 -1
- data/rmodel.gemspec +9 -3
- data/spec/rmodel/array_mapper_spec.rb +43 -0
- data/spec/rmodel/mongo/mapper_spec.rb +154 -0
- data/spec/rmodel/mongo/repository_spec.rb +16 -37
- data/spec/rmodel/mongo/source_spec.rb +113 -0
- data/spec/rmodel/sequel/mapper_spec.rb +57 -0
- data/spec/rmodel/sequel/repository_spec.rb +27 -38
- data/spec/rmodel/sequel/source_spec.rb +121 -0
- data/spec/shared/base_mapper.rb +39 -0
- data/spec/shared/clean_moped.rb +6 -2
- data/spec/shared/clean_sequel.rb +1 -1
- data/spec/shared/{repository_ext → repository}/crud.rb +20 -14
- data/spec/shared/repository/initialization.rb +39 -0
- data/spec/shared/repository/scopable.rb +137 -0
- data/spec/shared/repository/sugarable.rb +67 -0
- data/spec/shared/repository/timestampable.rb +137 -0
- data/spec/spec_helper.rb +17 -18
- metadata +120 -54
- data/examples/advanced_creation_of_repository.rb +0 -15
- data/examples/user.rb +0 -48
- data/lib/rmodel/base/repository.rb +0 -12
- data/lib/rmodel/base/repository_ext/sugarable.rb +0 -17
- data/lib/rmodel/base/repository_ext/timestampable.rb +0 -19
- data/lib/rmodel/mongo/repository.rb +0 -62
- data/lib/rmodel/mongo/repository_ext/query.rb +0 -34
- data/lib/rmodel/mongo/repository_ext/queryable.rb +0 -34
- data/lib/rmodel/mongo/setup.rb +0 -17
- data/lib/rmodel/mongo/simple_factory.rb +0 -78
- data/lib/rmodel/sequel/repository.rb +0 -61
- data/lib/rmodel/sequel/repository_ext/query.rb +0 -34
- data/lib/rmodel/sequel/repository_ext/queryable.rb +0 -30
- data/lib/rmodel/sequel/setup.rb +0 -12
- data/lib/rmodel/sequel/simple_factory.rb +0 -28
- data/lib/rmodel/setup.rb +0 -34
- data/spec/rmodel/mongo/repository_ext/queryable_spec.rb +0 -103
- data/spec/rmodel/mongo/repository_initialize_spec.rb +0 -174
- data/spec/rmodel/mongo/simple_factory_spec.rb +0 -195
- data/spec/rmodel/sequel/repository_ext/queryable_spec.rb +0 -114
- data/spec/rmodel/sequel/repository_initialize_spec.rb +0 -118
- data/spec/rmodel/sequel/simple_factory_spec.rb +0 -55
- data/spec/rmodel/setup_spec.rb +0 -54
- data/spec/shared/repository_ext/sugarable.rb +0 -44
- data/spec/shared/repository_ext/timestampable.rb +0 -67
data/Rakefile
CHANGED
@@ -1,2 +1 @@
|
|
1
|
-
require
|
2
|
-
|
1
|
+
require 'bundler/gem_tasks'
|
data/examples/mongo_embedded.rb
CHANGED
@@ -1,33 +1,39 @@
|
|
1
1
|
require 'rmodel'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
end
|
3
|
+
DB = Mongo::Client.new(['localhost'], database: 'test')
|
4
|
+
source = Rmodel::Mongo::Source.new(DB, :flats)
|
6
5
|
|
7
|
-
Owner = Struct.new(:
|
8
|
-
|
6
|
+
Owner = Struct.new(:first_name, :last_name)
|
7
|
+
Bed = Struct.new(:type)
|
8
|
+
Room = Struct.new(:name, :square, :bed)
|
9
9
|
Flat = Struct.new(:id, :address, :rooms, :owner)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
10
|
+
|
11
|
+
owner_mapper = Rmodel::Mongo::Mapper.new(Owner)
|
12
|
+
.define_attributes(:first_name, :last_name)
|
13
|
+
|
14
|
+
bed_mapper = Rmodel::Mongo::Mapper.new(Bed).define_attribute(:type)
|
15
|
+
|
16
|
+
room_mapper = Rmodel::Mongo::Mapper.new(Room)
|
17
|
+
.define_attributes(:name, :square)
|
18
|
+
.define_attribute(:bed, bed_mapper)
|
19
|
+
|
20
|
+
rooms_mapper = Rmodel::ArrayMapper.new(room_mapper)
|
21
|
+
flat_mapper = Rmodel::Mongo::Mapper.new(Flat)
|
22
|
+
.define_attribute(:address)
|
23
|
+
.define_attribute(:rooms, rooms_mapper)
|
24
|
+
.define_attribute(:owner, owner_mapper)
|
25
|
+
|
26
|
+
repo = Rmodel::Repository.new(source, flat_mapper)
|
27
|
+
repo.delete_all
|
22
28
|
|
23
29
|
flat = Flat.new
|
24
30
|
flat.address = 'Googleplex, Mountain View, California, U.S'
|
25
31
|
flat.rooms = [
|
26
|
-
Room.new(
|
27
|
-
Room.new(
|
28
|
-
Room.new(
|
32
|
+
Room.new('dining room', 150),
|
33
|
+
Room.new('sleeping room #1', 50, Bed.new('single')),
|
34
|
+
Room.new('sleeping room #2', 20, Bed.new('king-size'))
|
29
35
|
]
|
30
|
-
flat.owner = Owner.new(
|
36
|
+
flat.owner = Owner.new('John', 'Doe')
|
31
37
|
|
32
38
|
repo.insert(flat)
|
33
39
|
p repo.find(flat.id)
|
data/examples/scopes.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rmodel'
|
2
|
+
|
3
|
+
DB = Mongo::Client.new(['localhost'], database: 'test')
|
4
|
+
source = Rmodel::Mongo::Source.new(DB, :users)
|
5
|
+
|
6
|
+
User = Struct.new(:id, :name, :email)
|
7
|
+
mapper = Rmodel::Mongo::Mapper.new(User).define_attributes(:name, :email)
|
8
|
+
|
9
|
+
class UserRepository < Rmodel::Repository
|
10
|
+
scope :have_email do
|
11
|
+
where(email: { '$exists' => true })
|
12
|
+
end
|
13
|
+
|
14
|
+
scope :start_with do |letter|
|
15
|
+
where(name: { '$regex' => "^#{letter}", '$options' => 'i' })
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
repo = UserRepository.new(source, mapper)
|
20
|
+
repo.delete_all
|
21
|
+
|
22
|
+
repo.insert(User.new(nil, 'John', 'john@example.com'))
|
23
|
+
repo.insert(User.new(nil, 'Bill', 'bill@example.com'))
|
24
|
+
repo.insert(User.new(nil, 'Bob'))
|
25
|
+
|
26
|
+
p repo.fetch.start_with('b').to_a
|
27
|
+
p repo.fetch.start_with('b').have_email.to_a
|
28
|
+
p repo.fetch.count
|
data/examples/sql_repository.rb
CHANGED
@@ -1,42 +1,30 @@
|
|
1
1
|
require 'rmodel'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
# http://sequel.jeremyevans.net/rdoc/files/doc/opening_databases_rdoc.html#label-Passing+a+block+to+either+method
|
6
|
-
client :default, { adapter: 'sqlite', database: 'rmodel_test.sqlite3' }
|
7
|
-
end
|
3
|
+
DB = Sequel.connect(adapter: 'sqlite', database: 'rmodel_test.sqlite3')
|
4
|
+
source = Rmodel::Sequel::Source.new(DB, :things)
|
8
5
|
|
9
|
-
|
10
|
-
|
11
|
-
client.create_table :things do
|
6
|
+
DB.drop_table? :things
|
7
|
+
DB.create_table :things do
|
12
8
|
primary_key :id
|
13
9
|
String :name
|
14
10
|
Float :price
|
15
11
|
end
|
16
12
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
def initialize(name = nil, price = nil)
|
21
|
-
self.name = name
|
22
|
-
self.price = price
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
class ThingRepository < Rmodel::Sequel::Repository
|
27
|
-
simple_factory Thing, :name, :price
|
13
|
+
Thing = Struct.new(:id, :name, :price)
|
14
|
+
mapper = Rmodel::Sequel::Mapper.new(Thing)
|
15
|
+
.define_attributes(:name, :price)
|
28
16
|
|
17
|
+
class ThingRepository < Rmodel::Repository
|
29
18
|
scope :worth_more_than do |amount|
|
30
19
|
# use Sequel dataset filtering http://sequel.jeremyevans.net/rdoc/files/doc/dataset_filtering_rdoc.html
|
31
20
|
where { price >= amount }
|
32
21
|
end
|
33
22
|
end
|
34
23
|
|
35
|
-
repo = ThingRepository.new
|
36
|
-
repo.insert Thing.new('iPod', 200)
|
37
|
-
repo.insert Thing.new('iPhone', 300)
|
38
|
-
repo.insert Thing.new('iPad', 500)
|
24
|
+
repo = ThingRepository.new(source, mapper)
|
25
|
+
repo.insert Thing.new(nil, 'iPod', 200)
|
26
|
+
repo.insert Thing.new(nil, 'iPhone', 300)
|
27
|
+
repo.insert Thing.new(nil, 'iPad', 500)
|
39
28
|
|
40
|
-
p repo.
|
41
|
-
p repo.
|
42
|
-
p repo.query.worth_more_than(400).to_sql
|
29
|
+
p repo.fetch.count # 3
|
30
|
+
p repo.fetch.worth_more_than(400).count # 1
|
data/examples/timestamps.rb
CHANGED
@@ -1,17 +1,14 @@
|
|
1
1
|
require 'rmodel'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
end
|
3
|
+
DB = Mongo::Client.new(['localhost'], database: 'test')
|
4
|
+
source = Rmodel::Mongo::Source.new(DB, :things)
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
Thing = Struct.new(:id, :name, :created_at, :updated_at)
|
7
|
+
mapper = Rmodel::Mongo::Mapper.new(Thing)
|
8
|
+
.define_attribute(:name)
|
9
|
+
.define_timestamps
|
10
10
|
|
11
|
-
|
12
|
-
simple_factory Thing, :name, :created_at, :updated_at
|
13
|
-
end
|
14
|
-
repo = ThingRepository.new
|
11
|
+
repo = Rmodel::Repository.new(source, mapper)
|
15
12
|
|
16
13
|
thing = Thing.new
|
17
14
|
thing.name = 'chair'
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class TaskRepository < Rmodel::Repository
|
2
|
+
scope(:by_title) { order_by(:title.asc) }
|
3
|
+
scope(:by_recency) { order_by(:created_at.desc) }
|
4
|
+
|
5
|
+
def sorted(sort_by)
|
6
|
+
if sort_by == 'title'
|
7
|
+
fetch.by_title
|
8
|
+
elsif sort_by == 'recency'
|
9
|
+
fetch.by_recency
|
10
|
+
else
|
11
|
+
fetch.by_title
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
require './lib/rmodel'
|
3
|
+
require './examples/webapp/models/task'
|
4
|
+
require './examples/webapp/repositories/task_repository'
|
5
|
+
require './examples/webapp/support/repositories'
|
6
|
+
|
7
|
+
include Repositories
|
8
|
+
|
9
|
+
get '/' do
|
10
|
+
tasks = task_repo.sorted(params[:sort_by])
|
11
|
+
erb :index, locals: { tasks: tasks }
|
12
|
+
end
|
13
|
+
|
14
|
+
post '/' do
|
15
|
+
task = Task.new
|
16
|
+
task.title = params[:title]
|
17
|
+
|
18
|
+
if task.valid?
|
19
|
+
task_repo.save(task)
|
20
|
+
redirect to "/?sort_by=#{params[:sort_by]}"
|
21
|
+
else
|
22
|
+
tasks = task_repo.sorted(params[:sort_by])
|
23
|
+
erb :index, locals: { tasks: tasks }
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require './examples/webapp/support/sources'
|
2
|
+
require './examples/webapp/support/mappers'
|
3
|
+
|
4
|
+
module Repositories
|
5
|
+
include LazyInjector
|
6
|
+
include Sources
|
7
|
+
include Mappers
|
8
|
+
|
9
|
+
register :task_repo do
|
10
|
+
TaskRepository.new(task_source, task_mapper)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<div>
|
2
|
+
sort by
|
3
|
+
<a href="/?sort_by=title">title</a>
|
4
|
+
<a href="/?sort_by=recency">recency</a>
|
5
|
+
</div>
|
6
|
+
|
7
|
+
<ol>
|
8
|
+
<% tasks.each do |task| %>
|
9
|
+
<li>
|
10
|
+
<%= task.title %>
|
11
|
+
(created at <%= task.created_at %>)
|
12
|
+
</li>
|
13
|
+
<% end %>
|
14
|
+
</ol>
|
15
|
+
|
16
|
+
<form action="/" method="post">
|
17
|
+
<input type="hidden" name="sort_by" value="<%= params[:sort_by] %>" />
|
18
|
+
<input type="text" placeholder="New Task" name="title" />
|
19
|
+
<input type="submit" />
|
20
|
+
</form>
|
data/lib/rmodel.rb
CHANGED
@@ -1,13 +1,16 @@
|
|
1
|
+
require 'active_support/inflector'
|
1
2
|
require 'rmodel/version'
|
2
|
-
require 'rmodel/setup'
|
3
3
|
require 'rmodel/errors'
|
4
|
-
require 'rmodel/
|
5
|
-
require 'rmodel/
|
6
|
-
require 'rmodel/
|
7
|
-
require 'rmodel/
|
8
|
-
require 'rmodel/
|
9
|
-
require 'rmodel/
|
4
|
+
require 'rmodel/scope'
|
5
|
+
require 'rmodel/repository'
|
6
|
+
require 'rmodel/uni_hash'
|
7
|
+
require 'rmodel/base_mapper'
|
8
|
+
require 'rmodel/array_mapper'
|
9
|
+
require 'rmodel/dummy_mapper'
|
10
|
+
require 'rmodel/mongo/source'
|
11
|
+
require 'rmodel/mongo/mapper'
|
12
|
+
require 'rmodel/sequel/source'
|
13
|
+
require 'rmodel/sequel/mapper'
|
10
14
|
|
11
15
|
module Rmodel
|
12
|
-
|
13
16
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Rmodel
|
2
|
+
class ArrayMapper
|
3
|
+
def initialize(mapper)
|
4
|
+
@mapper = mapper
|
5
|
+
end
|
6
|
+
|
7
|
+
def serialize(objects, id_included)
|
8
|
+
if objects.nil?
|
9
|
+
nil
|
10
|
+
else
|
11
|
+
objects.map { |object| @mapper.serialize(object, id_included) }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def deserialize(array)
|
16
|
+
if array.nil?
|
17
|
+
nil
|
18
|
+
else
|
19
|
+
array.map { |entry| @mapper.deserialize(entry) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Rmodel
|
2
|
+
class BaseMapper
|
3
|
+
def initialize(model)
|
4
|
+
@model = model
|
5
|
+
self.primary_key = :id
|
6
|
+
self.key_op = :to_sym
|
7
|
+
@attributes = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def define_attribute(attr, mapper = DummyMapper.instance)
|
11
|
+
@attributes[attr] = mapper
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def define_attributes(*attributes)
|
16
|
+
attributes.each { |attr| define_attribute(attr) }
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def define_timestamps
|
21
|
+
define_attributes :created_at, :updated_at
|
22
|
+
end
|
23
|
+
|
24
|
+
def deserialize(hash)
|
25
|
+
return nil if hash.nil?
|
26
|
+
|
27
|
+
uni_hash = UniHash.new(hash, key_op)
|
28
|
+
|
29
|
+
object = @model.new
|
30
|
+
object.id = uni_hash[primary_key] if object.respond_to?(:id)
|
31
|
+
@attributes.each do |attr, mapper|
|
32
|
+
deserialized = mapper.deserialize(uni_hash[attr])
|
33
|
+
object.public_send "#{attr}=", deserialized
|
34
|
+
end
|
35
|
+
object
|
36
|
+
end
|
37
|
+
|
38
|
+
def serialize(object, id_included)
|
39
|
+
return nil if object.nil?
|
40
|
+
|
41
|
+
uni_hash = UniHash.new({}, key_op)
|
42
|
+
@attributes.each do |attr, mapper|
|
43
|
+
serialized = mapper.serialize(object.public_send(attr), id_included)
|
44
|
+
uni_hash[attr] = serialized
|
45
|
+
end
|
46
|
+
if id_included && object.respond_to?(:id)
|
47
|
+
uni_hash[primary_key] = object.id
|
48
|
+
end
|
49
|
+
uni_hash.to_hash
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
attr_accessor :primary_key, :key_op
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'origin'
|
2
|
+
require 'mongo'
|
3
|
+
|
4
|
+
module Rmodel
|
5
|
+
module Mongo
|
6
|
+
class Source
|
7
|
+
class Query
|
8
|
+
include Origin::Queryable
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(connection, collection)
|
12
|
+
@connection = connection
|
13
|
+
raise ArgumentError, 'Connection is not setup' unless @connection
|
14
|
+
|
15
|
+
@collection = collection
|
16
|
+
raise ArgumentError, 'Collection can not be guessed' unless @collection
|
17
|
+
end
|
18
|
+
|
19
|
+
def find(id)
|
20
|
+
@connection[@collection].find('_id' => id).first
|
21
|
+
end
|
22
|
+
|
23
|
+
def insert(doc)
|
24
|
+
doc = doc.merge('_id' => BSON::ObjectId.new) if doc['_id'].nil?
|
25
|
+
@connection[@collection].insert_one(doc)
|
26
|
+
doc['_id']
|
27
|
+
end
|
28
|
+
|
29
|
+
def update(id, doc)
|
30
|
+
@connection[@collection].find(_id: id).update_one(doc)
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete(id)
|
34
|
+
@connection[@collection].find(_id: id).delete_one
|
35
|
+
end
|
36
|
+
|
37
|
+
def build_query
|
38
|
+
Query.new
|
39
|
+
end
|
40
|
+
|
41
|
+
def exec_query(query)
|
42
|
+
@connection[@collection].find(query.selector, query.options)
|
43
|
+
end
|
44
|
+
|
45
|
+
def delete_by_query(query)
|
46
|
+
exec_query(query).delete_many
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|