rmodel 0.4.0.dev → 1.0.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.
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