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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +4 -0
  4. data/.travis.yml +4 -3
  5. data/README.md +119 -153
  6. data/Rakefile +1 -2
  7. data/examples/mongo_embedded.rb +27 -21
  8. data/examples/scopes.rb +28 -0
  9. data/examples/sql_repository.rb +14 -26
  10. data/examples/timestamps.rb +7 -10
  11. data/examples/webapp/models/task.rb +9 -0
  12. data/examples/webapp/repositories/task_repository.rb +14 -0
  13. data/examples/webapp/server.rb +25 -0
  14. data/examples/webapp/support/mappers.rb +11 -0
  15. data/examples/webapp/support/repositories.rb +12 -0
  16. data/examples/webapp/support/sources.rb +13 -0
  17. data/examples/webapp/views/index.erb +20 -0
  18. data/lib/rmodel.rb +11 -8
  19. data/lib/rmodel/array_mapper.rb +23 -0
  20. data/lib/rmodel/base_mapper.rb +56 -0
  21. data/lib/rmodel/dummy_mapper.rb +15 -0
  22. data/lib/rmodel/mongo/mapper.rb +11 -0
  23. data/lib/rmodel/mongo/source.rb +50 -0
  24. data/lib/rmodel/repository.rb +36 -0
  25. data/lib/rmodel/repository_ext/scopable.rb +44 -0
  26. data/lib/rmodel/repository_ext/sugarable.rb +35 -0
  27. data/lib/rmodel/repository_ext/timestampable.rb +29 -0
  28. data/lib/rmodel/scope.rb +31 -0
  29. data/lib/rmodel/sequel/mapper.rb +6 -0
  30. data/lib/rmodel/sequel/source.rb +43 -0
  31. data/lib/rmodel/uni_hash.rb +16 -0
  32. data/lib/rmodel/version.rb +1 -1
  33. data/rmodel.gemspec +9 -3
  34. data/spec/rmodel/array_mapper_spec.rb +43 -0
  35. data/spec/rmodel/mongo/mapper_spec.rb +154 -0
  36. data/spec/rmodel/mongo/repository_spec.rb +16 -37
  37. data/spec/rmodel/mongo/source_spec.rb +113 -0
  38. data/spec/rmodel/sequel/mapper_spec.rb +57 -0
  39. data/spec/rmodel/sequel/repository_spec.rb +27 -38
  40. data/spec/rmodel/sequel/source_spec.rb +121 -0
  41. data/spec/shared/base_mapper.rb +39 -0
  42. data/spec/shared/clean_moped.rb +6 -2
  43. data/spec/shared/clean_sequel.rb +1 -1
  44. data/spec/shared/{repository_ext → repository}/crud.rb +20 -14
  45. data/spec/shared/repository/initialization.rb +39 -0
  46. data/spec/shared/repository/scopable.rb +137 -0
  47. data/spec/shared/repository/sugarable.rb +67 -0
  48. data/spec/shared/repository/timestampable.rb +137 -0
  49. data/spec/spec_helper.rb +17 -18
  50. metadata +120 -54
  51. data/examples/advanced_creation_of_repository.rb +0 -15
  52. data/examples/user.rb +0 -48
  53. data/lib/rmodel/base/repository.rb +0 -12
  54. data/lib/rmodel/base/repository_ext/sugarable.rb +0 -17
  55. data/lib/rmodel/base/repository_ext/timestampable.rb +0 -19
  56. data/lib/rmodel/mongo/repository.rb +0 -62
  57. data/lib/rmodel/mongo/repository_ext/query.rb +0 -34
  58. data/lib/rmodel/mongo/repository_ext/queryable.rb +0 -34
  59. data/lib/rmodel/mongo/setup.rb +0 -17
  60. data/lib/rmodel/mongo/simple_factory.rb +0 -78
  61. data/lib/rmodel/sequel/repository.rb +0 -61
  62. data/lib/rmodel/sequel/repository_ext/query.rb +0 -34
  63. data/lib/rmodel/sequel/repository_ext/queryable.rb +0 -30
  64. data/lib/rmodel/sequel/setup.rb +0 -12
  65. data/lib/rmodel/sequel/simple_factory.rb +0 -28
  66. data/lib/rmodel/setup.rb +0 -34
  67. data/spec/rmodel/mongo/repository_ext/queryable_spec.rb +0 -103
  68. data/spec/rmodel/mongo/repository_initialize_spec.rb +0 -174
  69. data/spec/rmodel/mongo/simple_factory_spec.rb +0 -195
  70. data/spec/rmodel/sequel/repository_ext/queryable_spec.rb +0 -114
  71. data/spec/rmodel/sequel/repository_initialize_spec.rb +0 -118
  72. data/spec/rmodel/sequel/simple_factory_spec.rb +0 -55
  73. data/spec/rmodel/setup_spec.rb +0 -54
  74. data/spec/shared/repository_ext/sugarable.rb +0 -44
  75. data/spec/shared/repository_ext/timestampable.rb +0 -67
data/Rakefile CHANGED
@@ -1,2 +1 @@
1
- require "bundler/gem_tasks"
2
-
1
+ require 'bundler/gem_tasks'
@@ -1,33 +1,39 @@
1
1
  require 'rmodel'
2
2
 
3
- Rmodel.setup do
4
- client :default, { hosts: [ 'localhost'], database: 'test' }
5
- end
3
+ DB = Mongo::Client.new(['localhost'], database: 'test')
4
+ source = Rmodel::Mongo::Source.new(DB, :flats)
6
5
 
7
- Owner = Struct.new(:id, :first_name, :last_name)
8
- Room = Struct.new(:id, :name, :square, :bed)
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
- Bed = Struct.new(:id, :type)
11
-
12
- class FlatRepository < Rmodel::Mongo::Repository
13
- simple_factory Flat, :address do
14
- embeds_many :rooms, simple_factory(Room, :name, :square) do
15
- embeds_one :bed, simple_factory(Bed, :type)
16
- end
17
- embeds_one :owner, simple_factory(Owner, :first_name, :last_name)
18
- end
19
- end
20
- repo = FlatRepository.new
21
- repo.query.remove
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(nil, 'dining room', 150),
27
- Room.new(nil, 'sleeping room #1', 50, Bed.new(nil, 'single')),
28
- Room.new(nil, 'sleeping room #2', 20, Bed.new(nil, 'king-size'))
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(nil, 'John', 'Doe')
36
+ flat.owner = Owner.new('John', 'Doe')
31
37
 
32
38
  repo.insert(flat)
33
39
  p repo.find(flat.id)
@@ -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
@@ -1,42 +1,30 @@
1
1
  require 'rmodel'
2
2
 
3
- Rmodel.setup do
4
- # see more examples of connection options
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
- client = Rmodel.setup.establish_sequel_client(:default)
10
- client.drop_table? :things
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
- class Thing
18
- attr_accessor :id, :name, :price
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.query.count # 3
41
- p repo.query.worth_more_than(400).count # 1
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
@@ -1,17 +1,14 @@
1
1
  require 'rmodel'
2
2
 
3
- Rmodel.setup do
4
- client :default, { hosts: [ 'localhost' ], database: 'test' }
5
- end
3
+ DB = Mongo::Client.new(['localhost'], database: 'test')
4
+ source = Rmodel::Mongo::Source.new(DB, :things)
6
5
 
7
- class Thing
8
- attr_accessor :id, :name, :created_at, :updated_at
9
- end
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
- class ThingRepository < Rmodel::Mongo::Repository
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,9 @@
1
+ require 'active_model'
2
+
3
+ class Task
4
+ include ActiveModel::Validations
5
+
6
+ attr_accessor :id, :title, :created_at, :updated_at
7
+
8
+ validates :title, presence: true
9
+ end
@@ -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,11 @@
1
+ require 'lazy_injector'
2
+
3
+ module Mappers
4
+ include LazyInjector
5
+
6
+ register :task_mapper do
7
+ Rmodel::Mongo::Mapper.new(Task)
8
+ .define_attribute(:title)
9
+ .define_timestamps
10
+ end
11
+ 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,13 @@
1
+ require 'lazy_injector'
2
+
3
+ module Sources
4
+ include LazyInjector
5
+
6
+ register :db do
7
+ Mongo::Client.new(['localhost'], database: 'test')
8
+ end
9
+
10
+ register :task_source do
11
+ Rmodel::Mongo::Source.new(db, :tasks)
12
+ end
13
+ 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/mongo/setup'
5
- require 'rmodel/mongo/simple_factory'
6
- require 'rmodel/mongo/repository'
7
- require 'rmodel/sequel/setup'
8
- require 'rmodel/sequel/simple_factory'
9
- require 'rmodel/sequel/repository'
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,15 @@
1
+ require 'singleton'
2
+
3
+ module Rmodel
4
+ class DummyMapper
5
+ include Singleton
6
+
7
+ def serialize(arg, _id_included)
8
+ arg
9
+ end
10
+
11
+ def deserialize(arg)
12
+ arg
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module Rmodel
2
+ module Mongo
3
+ class Mapper < Rmodel::BaseMapper
4
+ def initialize(model)
5
+ super
6
+ self.primary_key = '_id'
7
+ self.key_op = :to_s
8
+ end
9
+ end
10
+ end
11
+ 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