mini_model 0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a8e0f797ba49f5f53cd9f8951740f3be6cfd0106
4
+ data.tar.gz: 60127c7c9cf2997c7ffcc33b0eb99a8ac69293ba
5
+ SHA512:
6
+ metadata.gz: b28d1b7ea7b1d3d3fb356eca45294c528beaab5f6d96addc8ceb633a76a003e2437c3160e24182b500f0c0d455c5c576d6250f0dbd8aecde05c13001256d7d74
7
+ data.tar.gz: 9ce5cc6657617da3953f50897cdc2e71432efe0f419821c652a4bf26bb2b850ac4fb0e49a54fdb80deffad8849ed7c14f1b26002bf28a29d6f05dddb7dc133c8
data/.gems ADDED
@@ -0,0 +1,3 @@
1
+ cutest -v 1.2.3
2
+ sequel -v 5.1.0
3
+ sqlite3 -v 1.3.13
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ /.gs
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Michel Martens and Damian Janowski
2
+ Copyright (c) 2017 Steven Weiss
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in
12
+ all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,238 @@
1
+ # MiniModel
2
+
3
+ MiniModel is an alternative active record implementation to Sequel::Model.
4
+ It is designed to work with Sequel::Dataset, but aims to be a much smaller api
5
+ than Sequel::Model. The api is largely based on the lovely [Ohm][ohm] gem,
6
+ an ORM in Ruby for Redis.
7
+
8
+ ## Installation
9
+
10
+ `$ gem install mini_model`
11
+
12
+ You must also install [Sequel][sequel] and the database of your choice.
13
+
14
+ ## Usage
15
+
16
+ Checkout the tests and `./examples` for more advanced use cases,
17
+ but the basics look something like:
18
+
19
+ ```ruby
20
+ require 'mini_model'
21
+ require 'sequel'
22
+ require 'sqlite' # Or any Sequel supported database.
23
+
24
+ DB = Sequel.sqlite
25
+
26
+ class Model
27
+ include MiniModel
28
+
29
+ # ...
30
+ end
31
+
32
+ Model.dataset = DB[:models]
33
+ ```
34
+
35
+ ## API
36
+
37
+ ### Class Methods
38
+
39
+ `::dataset=` Sets the dataset for the given model.
40
+
41
+ `::dataset` Gets the assigned dataset.
42
+
43
+ `::attribute` Macro for generating an accessor for the attributes hash.
44
+
45
+ `::build` Builds a new instance of the model when given attributes.
46
+
47
+ `::create` Convenience for `new(attributes).create`.
48
+
49
+ `::[]` Fetches a record for the given id via Sequel::Datase#first.
50
+
51
+ `::first` Fetches the first record for the given args, see Sequel::Dataset#first.
52
+
53
+ `::all` Fetch all records for the current dataset, see Sequel::Dataset#all.
54
+
55
+ `::where` Fetch records for the given conditions, see Sequel::Dataset#where.
56
+
57
+ `::to_foreign_key` Contains the covention for converting class names into foreign keys.
58
+ `Person` becomes `person_id`.
59
+
60
+ `::children` Association macro for "1 to n". See the section on associations below.
61
+
62
+ `::child` Association macro for "1 to 1" where the foreign key is on the other table.
63
+
64
+ `::parent` Association macro for "1 to 1/n" where the foreign key is on our table.
65
+
66
+ ### Instance Methods
67
+
68
+ `#initialize` Sets the given attributes on the model instance, note that if the id
69
+ is in the attributes hash it will also be assigned.
70
+
71
+ `#dataset` Delegates to the class' dataset.
72
+
73
+ `#id` The current id value, if it exists, or raises an error.
74
+ Raising the error is to stop anything right away that may depend on the id.
75
+ We don't want to be assigning nil all over our associations and what have you.
76
+
77
+ `#id=` Assigns the id.
78
+
79
+ `#attributes` Gets the attributes hash.
80
+
81
+ `#attributes=` For each key/value in the given hash, sends the writer to self.
82
+ This means it'll be a missing method if you have not created the writer via the
83
+ `::attribute` macro, or manually. This is not safe to use with user input.
84
+
85
+ `#==` Compares two models to see if they are equals. It ensures that the class,
86
+ id, and attributes of each are the same.
87
+
88
+ `#persisted?` Used to check if a model has an id or not. It is assumed if
89
+ it has an id it's in the database.
90
+
91
+ `#save` Delegates to the proper persistence method.
92
+
93
+ `#create` Inserts attributes in the database. Returns self on success, nil
94
+ on failure.
95
+
96
+ `#update` Updates attributes in the database for id. Returns self on success,
97
+ nil on failure.
98
+
99
+ `#delete` Deletes itself from the database and unassigns the id. If you want
100
+ to do anything with the id after deletion, copy it before calling delete.
101
+
102
+ ## Attributes
103
+
104
+ Attributes in MiniModel are all stored internally inside the `@attributes` hash.
105
+ The `::attribute` macro is really just an easy way of defining accessors
106
+ similar to `attr_accessor`, but getting and setting on that hash.
107
+
108
+ There is plans to implement a more robust attribute api,
109
+ but right now it is not implemented.
110
+
111
+ ## Associations
112
+
113
+ Associations in MiniModel are largely inspired by [Ohm][ohm].
114
+ They are pretty much the same, but where Ohm uses a collection/reference metaphor,
115
+ MiniModel uses parent/child(ren).
116
+
117
+ Note that associations finders are not cached at this time, a small caching
118
+ layer will be added in the future.
119
+
120
+ ### Parent
121
+
122
+ Lets take a look at the `::parent` macro.
123
+
124
+ ```ruby
125
+ class Photo
126
+ include MiniModel
127
+
128
+ # It turns this...
129
+
130
+ parent :user, :User
131
+
132
+ # Into something like this...
133
+
134
+ def user_id
135
+ @attributes[:user_id]
136
+ end
137
+
138
+ def user_id=(user_id)
139
+ @attributes[:user_id] = user_id
140
+ end
141
+
142
+ def user
143
+ User[user_id]
144
+ end
145
+
146
+ def user=(user)
147
+ self.user_id = user.id
148
+ end
149
+ end
150
+ ```
151
+
152
+ This means that when we say `parent :user, :User` user is our parent, and the foreign
153
+ key is on our table. You can customize the foreign key by passing a third symbol argument,
154
+ but with this convention the primary key on the parent must be id. You can always
155
+ skip the parent macro and implement things manually if you're using a different
156
+ primary key.
157
+
158
+ ### Children
159
+
160
+ Children are even simpler than parents, as it's just a finder.
161
+
162
+ ```ruby
163
+ class User
164
+ include MiniModel
165
+
166
+ children :photos, :Photo
167
+
168
+ # Roughly becomes...
169
+
170
+ def photos
171
+ Photo.where(user_id: id)
172
+ end
173
+ end
174
+ ```
175
+
176
+ Once again you can customize the foreign key (user_id) with a third argument,
177
+ but not that it's referring to the id on ourself. These macros are just
178
+ conveniences for the 90% use case, unique situations are easy to implement
179
+ yourself like...
180
+
181
+ ```ruby
182
+ def photos
183
+ Photo.where(user_email: email)
184
+ end
185
+ ```
186
+
187
+ An **important** thing to note when dealing with associations is that MiniModel
188
+ only provides the association writer on the "child" side of the relationship. That
189
+ means the parent must be saved to start assign associations, but also you can't do
190
+ something like `user.photos << photo` and have it persist. Another note is the relationship
191
+ will not be persisted until calling save (or create/update) on the child.
192
+
193
+ On our User model, if we want to work from that side of the association, you could
194
+ create delegation methods like so:
195
+
196
+ ```
197
+ def add_photo(photo)
198
+ photo.user_id = id
199
+ end
200
+ ```
201
+
202
+ ### Child
203
+
204
+ The final association macro is `child`. It works the same exact way as
205
+ `children` though uses the `.first` finder to get a single record.
206
+
207
+ ```ruby
208
+ class User
209
+ include MiniModel
210
+
211
+ child :profile, :Profile
212
+
213
+ # This roughly expands to...
214
+
215
+ def profile
216
+ Profile.first(user_id: id)
217
+ end
218
+ end
219
+ ```
220
+
221
+ ## Non-SQL Databases
222
+
223
+ MiniModel is designed to work with Sequel (and SQL Databases),
224
+ though it will work with anything that implements a subset of Sequel::DataSet.
225
+
226
+ If you would like your write your own adapter/dataset for a non SQL database,
227
+ MiniMapper depends on the following Sequel::DataSet methods:
228
+
229
+ `Sequel::Dataset#all`
230
+ `Sequel::Dataset#delete`
231
+ `Sequel::Dataset#first`
232
+ `Sequel::Dataset#insert`
233
+ `Sequel::Dataset#update`
234
+ `Sequel::Dataset#where`
235
+
236
+ [ohm]: http://github.com/soveran/ohm
237
+ [sequel]: https://github.com/jeremyevans/sequel
238
+
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ desc 'open irb in gs context'
2
+ task :console do
3
+ sh 'gs irb'
4
+ end
5
+
6
+ desc 'installs gems'
7
+ task :install do
8
+ sh 'mkdir -p .gs & gs dep install'
9
+ end
10
+
11
+ desc 'tests the given [test].rb'
12
+ task :test, :name do |t, args|
13
+ name = args[:name] || '*'
14
+
15
+ sh "gs cutest -r ./test/#{name}.rb"
16
+ end
@@ -0,0 +1,61 @@
1
+ class User
2
+ include MiniModel
3
+
4
+ attribute :email
5
+
6
+ child :profile, :Profile
7
+
8
+ children :photos, :Photo
9
+
10
+ children :photo_comments, :PhotoComment
11
+
12
+ children :videos, :Video
13
+
14
+ children :video_comments, :VideoComment
15
+
16
+ def comments
17
+ photo_comments + video_comments
18
+ end
19
+ end
20
+
21
+ class Profile
22
+ include MiniModel
23
+
24
+ parent :user, :User
25
+ end
26
+
27
+ class Photo
28
+ include MiniModel
29
+
30
+ attribute :url
31
+
32
+ parent :user, :User
33
+
34
+ children :comments, :PhotoComment
35
+ end
36
+
37
+ class Video
38
+ include MiniModel
39
+
40
+ attribute :url
41
+
42
+ parent :user, :User
43
+
44
+ children :comments, :VideoComment
45
+ end
46
+
47
+ class Comment
48
+ include MiniModel
49
+
50
+ attribute :body
51
+
52
+ parent :user, :User
53
+ end
54
+
55
+ class PhotoComment < Comment
56
+ parent :photo, :Photo
57
+ end
58
+
59
+ class VideoComment < Comment
60
+ parent :video, :Video
61
+ end
@@ -0,0 +1,39 @@
1
+ class Model
2
+ include MiniModel
3
+
4
+ attribute :created_at
5
+
6
+ attribute :updated_at
7
+
8
+ def before_create
9
+ self.created_at = Time.noew
10
+ end
11
+
12
+ def after_create; end
13
+
14
+ def create
15
+ before_create
16
+
17
+ ret = super
18
+
19
+ after_create
20
+
21
+ ret
22
+ end
23
+
24
+ def before_update
25
+ self.updated_at = Time.noew
26
+ end
27
+
28
+ def after_update; end
29
+
30
+ def update
31
+ before_update
32
+
33
+ ret = super
34
+
35
+ after_update
36
+
37
+ ret
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ db1 = Sequel.sqlite
2
+
3
+ db2 = Sequel.sqlite # sorry ibm
4
+
5
+ class User
6
+ include MiniModel
7
+ end
8
+
9
+ User.dataset = db1[:users]
10
+
11
+ class Post
12
+ include MiniModel
13
+ end
14
+
15
+ Post.dataset = db2[:posts]
@@ -0,0 +1,13 @@
1
+ db = Sequel.sqlite
2
+
3
+ class User
4
+ include MiniModel
5
+ end
6
+
7
+ User.dataset = db[:users]
8
+
9
+ class Post
10
+ include MiniModel
11
+ end
12
+
13
+ Post.dataset = db[:posts]
@@ -0,0 +1,15 @@
1
+ class Model
2
+ include MiniModel
3
+
4
+ attribute :deleted_at
5
+
6
+ def deleted?
7
+ !!deleted_at
8
+ end
9
+
10
+ def delete
11
+ self.deleted_at = Time.now
12
+
13
+ update
14
+ end
15
+ end
data/lib/mini_model.rb ADDED
@@ -0,0 +1,237 @@
1
+ module MiniModel
2
+ VERSION = '0.0.0'
3
+
4
+ class Error < StandardError; end
5
+
6
+ class MissingId < Error; end
7
+
8
+ def self.included(model)
9
+ model.extend(ClassMethods)
10
+ end
11
+
12
+ module ClassMethods
13
+ def dataset
14
+ @dataset
15
+ end
16
+
17
+ def dataset=(dataset)
18
+ @dataset = dataset
19
+ end
20
+
21
+ # Defines an accessor for the attributes hash. The whole point
22
+ # of the attributes hash vs. instance variables is for easily
23
+ # passing a hash to the dataset for persistence. Maybe this is a bad
24
+ # idea and we should use plain ol' attr_accessor and build the hash
25
+ # when needed.
26
+ def attribute(key, type = nil)
27
+ reader = :"#{key}"
28
+ writer = :"#{key}="
29
+
30
+ define_method(reader) do
31
+ self.attributes[reader]
32
+ end
33
+
34
+ define_method(writer) do |value|
35
+ self.attributes[reader] = value
36
+ end
37
+ end
38
+
39
+ def build(attributes)
40
+ if attributes
41
+ new(attributes)
42
+ else
43
+ nil
44
+ end
45
+ end
46
+
47
+ # Convenience for initializin and persisting a
48
+ # new model instance.
49
+ def create(attributes = {})
50
+ new(attributes).create
51
+ end
52
+
53
+ def [](id)
54
+ build(dataset.first(id: id))
55
+ end
56
+
57
+ def first(*args, &block)
58
+ build(dataset.first(*args, &block))
59
+ end
60
+
61
+ def all(&block)
62
+ dataset.all(&block).map { |each| build(each) }
63
+ end
64
+
65
+ def where(*args, &block)
66
+ dataset.where(*args, &block).map { |each| build(each) }
67
+ end
68
+
69
+ def to_foreign_key
70
+ name.
71
+ to_s.
72
+ match(/^(?:.*::)*(.*)$/)[1].
73
+ gsub(/([a-z\d])([A-Z])/, '\1_\2').
74
+ downcase.
75
+ concat('_id').
76
+ to_sym
77
+ end
78
+
79
+ def children(association, model_name, foreign_key = to_foreign_key)
80
+ define_method(association) do
81
+ model = self.class.const_get(model_name)
82
+
83
+ model.where(foreign_key => id)
84
+ end
85
+ end
86
+
87
+ def child(association, model_name, foreign_key = to_foreign_key)
88
+ define_method(association) do
89
+ model = self.class.const_get(model_name)
90
+
91
+ model.first(foreign_key => id)
92
+ end
93
+ end
94
+
95
+ def parent(association, model_name, foreign_key = :"#{association}_id")
96
+ reader = foreign_key
97
+ writer = :"#{foreign_key}="
98
+
99
+ define_method(reader) do
100
+ self.attributes[reader]
101
+ end
102
+
103
+ define_method(writer) do |value|
104
+ self.attributes[reader] = value
105
+ end
106
+
107
+ define_method(association) do
108
+ model = self.class.const_get(model_name)
109
+
110
+ model[send(foreign_key)]
111
+ end
112
+
113
+ define_method(:"#{association}=") do |value|
114
+ if value
115
+ send(writer, value.id)
116
+ else
117
+ send(writer, value)
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ def initialize(attributes = {})
124
+ self.attributes = attributes # Will set the id if it exists.
125
+ end
126
+
127
+ def dataset
128
+ self.class.dataset
129
+ end
130
+
131
+ def id
132
+ if @id
133
+ @id
134
+ else
135
+ # If our model does not have an id, raise at the first occurence
136
+ # of anyone expecting it. This prevents us from assigning associations
137
+ # and other logical paths for things that do not exist in the db.
138
+ raise MissingId
139
+ end
140
+ end
141
+
142
+ def id=(id)
143
+ @id = id
144
+ end
145
+
146
+ def attributes
147
+ @attributes
148
+ end
149
+
150
+ # #attributes= is vulnerable to mass assignment attacks if used
151
+ # directly with user input. Some sort of filter must be in place
152
+ # before setting attributes or initializing a new model. Sending
153
+ # a key in the hash argument that doesn't have an accessor raises an error.
154
+ def attributes=(attributes)
155
+ @attributes = {}
156
+
157
+ attributes.each do |key, value|
158
+ send(:"#{key}=", value)
159
+ end
160
+ end
161
+
162
+ # Strap in, the is probably the most complicated method
163
+ # in the entire library.
164
+ def ==(other)
165
+ # If the classes don't match, they cannot possibly be equal.
166
+ if self.class != other.class
167
+ return false
168
+ end
169
+
170
+ # If the persisted state doesn't match, they also can never be equal.
171
+ if persisted? != other.persisted?
172
+ return false
173
+ end
174
+
175
+ # When persisted, check the other's id to see if it's the same,
176
+ # cannot possible be equals if they have different ids.
177
+ if persisted? && id != other.id
178
+ return false
179
+ end
180
+
181
+ # Finally, compare the attributes hash. If all key/values match,
182
+ # they are considered equal.
183
+ attributes == other.attributes
184
+ end
185
+
186
+ def persisted?
187
+ !!@id
188
+ end
189
+
190
+ # Use #save to write generic persistence code in things like form objects
191
+ # so you don't have to reach inside the model to determine the proper
192
+ # method to call.
193
+ def save
194
+ if persisted?
195
+ update
196
+ else
197
+ create
198
+ end
199
+ end
200
+
201
+ # #create (as well as #update, and #delete) return self on
202
+ # success and nil on failure. This lets us use these actions
203
+ # as if conditions which is convenience though dangerous.
204
+ def create
205
+ id = dataset.insert(attributes)
206
+
207
+ if id
208
+ self.id = id
209
+
210
+ self
211
+ else
212
+ nil
213
+ end
214
+ end
215
+
216
+ def update
217
+ count = dataset.where(id: id).update(attributes)
218
+
219
+ if count.to_i > 0
220
+ self
221
+ else
222
+ nil
223
+ end
224
+ end
225
+
226
+ def delete
227
+ count = dataset.where(id: id).delete
228
+
229
+ if count.to_i > 0
230
+ self.id = nil
231
+
232
+ self
233
+ else
234
+ nil
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,16 @@
1
+ require_relative "./lib/mini_model"
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "mini_model"
5
+ s.summary = "MiniModel"
6
+ s.version = MiniModel::VERSION
7
+ s.authors = ["Steve Weiss"]
8
+ s.email = ["weissst@mail.gvsu.edu"]
9
+ s.homepage = "https://github.com/sirscriptalot/mini_model"
10
+ s.license = "MIT"
11
+ s.files = `git ls-files`.split("\n")
12
+
13
+ s.add_development_dependency "cutest", "~> 1.2"
14
+ s.add_development_dependency "sequel", "~> 5.1"
15
+ s.add_development_dependency "sqlite3", "~> 1.3"
16
+ end
data/test/all.rb ADDED
@@ -0,0 +1,467 @@
1
+ require 'sequel'
2
+ require 'sqlite3'
3
+ require_relative '../lib/mini_model'
4
+
5
+ db = Sequel.sqlite
6
+
7
+ db.create_table :users do
8
+ primary_key :id
9
+
10
+ String :email, size: 255, unique: true, not_null: true
11
+ end
12
+
13
+ db.create_table :profiles do
14
+ primary_key :id
15
+
16
+ foreign_key :user_id, :users, on_delete: :cascade
17
+ end
18
+
19
+
20
+ db.create_table :photos do
21
+ primary_key :id
22
+
23
+ foreign_key :user_id, :users, on_delete: :cascade
24
+
25
+ String :url, unique: true, not_null: true
26
+ end
27
+
28
+ db.create_table :videos do
29
+ primary_key :id
30
+
31
+ foreign_key :user_id, :users, on_delete: :cascade
32
+
33
+ String :url, unique: true, not_null: true
34
+ end
35
+
36
+ db.create_table :photo_comments do
37
+ primary_key :id
38
+
39
+ foreign_key :user_id, :users, on_delete: :cascade
40
+
41
+ foreign_key :photo_id, :photos, on_delete: :cascade
42
+
43
+ # Make sure we can spam user comments to Twitter, we need attention.
44
+ String :body, size: 140, nut_null: true
45
+ end
46
+
47
+ db.create_table :video_comments do
48
+ primary_key :id
49
+
50
+ foreign_key :user_id, :users, on_delete: :cascade
51
+
52
+ foreign_key :video_id, :videos, on_delete: :cascade
53
+
54
+ # Make sure we can spam user comments to Twitter, we need attention.
55
+ String :body, size: 140, nut_null: true
56
+ end
57
+
58
+ require_relative '../examples/associations'
59
+
60
+ User.dataset = db[:users]
61
+
62
+ Photo.dataset = db[:photos]
63
+
64
+ Video.dataset = db[:videos]
65
+
66
+ PhotoComment.dataset = db[:photo_comments]
67
+
68
+ VideoComment.dataset = db[:video_comments]
69
+
70
+ prepare do
71
+ # Ensure each test runs with a clean database.
72
+ [User, Photo, Video, PhotoComment, VideoComment].each do |model|
73
+ model.dataset.delete
74
+ end
75
+ end
76
+
77
+ test '::dataset= sets dataset' do
78
+ original = User.dataset
79
+
80
+ User.dataset = :fake
81
+
82
+ assert_equal User.dataset, :fake
83
+
84
+ User.dataset = original
85
+ end
86
+
87
+ test '::dataset gets dataset' do
88
+ assert_equal User.dataset, db[:users]
89
+ end
90
+
91
+ test '::attribute defines accessor methods' do
92
+ user = User.new
93
+
94
+ assert user.respond_to?(:email)
95
+ assert user.respond_to?(:email=)
96
+ end
97
+
98
+ test '::build returns an instance when given a hash' do
99
+ user = User.build({})
100
+
101
+ assert user.is_a?(User)
102
+ end
103
+
104
+ test '::build returns nil when given a falsey value' do
105
+ user = User.build(false)
106
+
107
+ assert user.nil?
108
+ end
109
+
110
+ test '::create creates an instance and persists it in the database' do
111
+ user = User.create(email: 'name@example.com')
112
+
113
+ assert user.is_a?(User)
114
+ assert user.persisted?
115
+ end
116
+
117
+ test '::[] fetchs record for a given id' do
118
+ left = User.create(email: 'name@example.com')
119
+
120
+ right = User[left.id]
121
+
122
+ assert_equal left, right
123
+ end
124
+
125
+ test '::first fetchs one record for for the give conditions' do
126
+ email = 'name@example.com'
127
+
128
+ user = User.create(email: email)
129
+
130
+ assert_equal User.first(email: email), user
131
+ end
132
+
133
+ test '::all fetchs all records in the dataset' do
134
+ one = User.create(email: 'one@example.com')
135
+
136
+ two = User.create(email: 'two@example.com')
137
+
138
+ all = User.all
139
+
140
+ assert all.include?(one)
141
+ assert all.include?(two)
142
+ end
143
+
144
+ test '::where fetch all records in the dataset matching condition' do
145
+ email = 'one@example.com'
146
+
147
+ one = User.create(email: email)
148
+
149
+ two = User.create(email: 'two@example.com')
150
+
151
+ assert User.where(email: email).include?(one)
152
+ end
153
+
154
+ test '::to_foreign_key snake cases and appends _id to model name' do
155
+ assert_equal User.to_foreign_key, :user_id
156
+ assert_equal PhotoComment.to_foreign_key, :photo_comment_id
157
+ end
158
+
159
+ test '::children defines getter' do
160
+ user = User.new
161
+
162
+ assert user.respond_to?(:photos)
163
+ assert user.respond_to?(:photo_comments)
164
+ end
165
+
166
+ test '::child defines getter' do
167
+ user = User.new
168
+
169
+ assert user.respond_to?(:profile)
170
+ end
171
+
172
+ test '::parent defines getters and setters' do
173
+ profile = Profile.new
174
+
175
+ assert profile.respond_to?(:user)
176
+ assert profile.respond_to?(:user=)
177
+ assert profile.respond_to?(:user_id)
178
+ assert profile.respond_to?(:user_id=)
179
+ end
180
+
181
+ test '#dataset is delegates to class' do
182
+ user = User.new
183
+
184
+ assert_equal user.dataset, User.dataset
185
+ end
186
+
187
+ test '#id returns id when it exists' do
188
+ id = 1
189
+
190
+ user = User.new(id: id)
191
+
192
+ assert_equal user.id, id
193
+ end
194
+
195
+ test '#id raises when missing an id' do
196
+ user = User.new
197
+
198
+ assert_raise MiniModel::MissingId do
199
+ user.id
200
+ end
201
+ end
202
+
203
+ test '#id= sets the id' do
204
+ id = 2
205
+
206
+ user = User.new
207
+
208
+ user.id = id
209
+
210
+ assert_equal user.id, id
211
+ end
212
+
213
+ test '#attributes returns a hash' do
214
+ user = User.new
215
+
216
+ assert user.attributes.is_a?(Hash)
217
+ end
218
+
219
+ test '#attributes= sets key/value in attributes hash that exist' do
220
+ id = 1
221
+
222
+ email = 'name@example.com'
223
+
224
+ user = User.new
225
+
226
+ user.attributes = { id: id, email: email }
227
+
228
+ assert_equal user.id, id
229
+ assert_equal user.email, email
230
+ assert_equal user.email, user.attributes[:email]
231
+ end
232
+
233
+ test '#attributes= resets all attributes, but not id' do
234
+ id = 1
235
+
236
+ email = 'name@example.com'
237
+
238
+ user = User.new
239
+
240
+ user.attributes = { id: id, email: email }
241
+
242
+ assert_equal user.id, id
243
+ assert_equal user.email, email
244
+ assert_equal user.email, user.attributes[:email]
245
+
246
+ user.attributes = {}
247
+
248
+ assert_equal user.id, id
249
+ assert_equal user.email, nil
250
+ assert_equal user.email, user.attributes[:email]
251
+ end
252
+
253
+ test '#attributes= raises when there is no accessor for a key' do
254
+ user = User.new
255
+
256
+ assert_raise do
257
+ user.attributes = { foo: :bar }
258
+ end
259
+ end
260
+
261
+ setup do
262
+ one = User.new(id: 1, email: 'one')
263
+ two = User.new(id: 1, email: 'one')
264
+
265
+ [one, two]
266
+ end
267
+
268
+ test '#== must have class the same' do |one, two|
269
+ assert_equal one.class, two.class
270
+ assert_equal one, two
271
+ end
272
+
273
+ test '#== must have id the same' do |one, two|
274
+ assert_equal one.id, two.id
275
+ assert_equal one, two
276
+
277
+ two.id = 2
278
+
279
+ assert one != two
280
+ end
281
+
282
+ test '#== must have attributes the same' do |one, two|
283
+ assert_equal one.attributes, two.attributes
284
+ assert_equal one, two
285
+
286
+ two.attributes = { email: 'two' }
287
+
288
+ assert one != two
289
+ end
290
+
291
+ test '#persisted? checks if the model has an id or not' do
292
+ user = User.new
293
+
294
+ assert !user.persisted?
295
+
296
+ user.id = 1
297
+
298
+ assert user.persisted?
299
+ end
300
+
301
+ test '#save delegates to the proper persistence method' do
302
+ klass = Class.new(User) do
303
+ def created?
304
+ @did_create
305
+ end
306
+
307
+ def create
308
+ @did_create = true
309
+ end
310
+
311
+ def updated?
312
+ @did_update
313
+ end
314
+
315
+ def update
316
+ @did_update = true
317
+ end
318
+ end
319
+
320
+ to_create = klass.new
321
+
322
+ to_create.save
323
+
324
+ assert to_create.created? && !to_create.updated?
325
+
326
+ to_update = klass.new
327
+
328
+ to_update.id = 1
329
+
330
+ to_update.save
331
+
332
+ assert !to_update.created? && to_update.updated?
333
+ end
334
+
335
+ class SuccessDataset
336
+ def insert(*args)
337
+ 9
338
+ end
339
+
340
+ def where(*args)
341
+ self
342
+ end
343
+
344
+ def update(*args)
345
+ 1
346
+ end
347
+
348
+ def delete(*args)
349
+ 1
350
+ end
351
+ end
352
+
353
+ class FailureDataset
354
+ def insert(*args)
355
+ nil
356
+ end
357
+
358
+ def where(*args)
359
+ self
360
+ end
361
+
362
+ def update(*args)
363
+ 0
364
+ end
365
+
366
+ def delete(*args)
367
+ 0
368
+ end
369
+ end
370
+
371
+ setup do
372
+ klass = Class.new(User)
373
+
374
+ klass.dataset = SuccessDataset.new
375
+
376
+ klass.new
377
+ end
378
+
379
+ test '#create assigns id on success' do |instance|
380
+ instance.create
381
+
382
+ assert instance.id
383
+ end
384
+
385
+ test '#create returns self on success' do |instance|
386
+ ret = instance.create
387
+
388
+ assert_equal ret, instance
389
+ end
390
+
391
+ setup do
392
+ klass = Class.new(User)
393
+
394
+ klass.dataset = FailureDataset.new
395
+
396
+ klass.new
397
+ end
398
+
399
+ test '#create returns nil on failure' do |instance|
400
+ ret = instance.create
401
+
402
+ assert ret.nil?
403
+ end
404
+
405
+ setup do
406
+ klass = Class.new(User)
407
+
408
+ klass.dataset = SuccessDataset.new
409
+
410
+ klass.new(id: 1)
411
+ end
412
+
413
+ test '#update returns self on success' do |instance|
414
+ ret = instance.update
415
+
416
+ assert_equal ret, instance
417
+ end
418
+
419
+ setup do
420
+ klass = Class.new(User)
421
+
422
+ klass.dataset = FailureDataset.new
423
+
424
+ klass.new(id: 1)
425
+ end
426
+
427
+ test '#update returns nil on failure' do |instance|
428
+ ret = instance.update
429
+
430
+ assert ret.nil?
431
+ end
432
+
433
+ setup do
434
+ klass = Class.new(User)
435
+
436
+ klass.dataset = SuccessDataset.new
437
+
438
+ klass.new(id: 1)
439
+ end
440
+
441
+ test '#delete unassigns id on success' do |instance|
442
+ assert instance.persisted?
443
+
444
+ instance.delete
445
+
446
+ assert !instance.persisted?
447
+ end
448
+
449
+ test '#delete returns self on success' do |instance|
450
+ ret = instance.delete
451
+
452
+ assert_equal ret, instance
453
+ end
454
+
455
+ setup do
456
+ klass = Class.new(User)
457
+
458
+ klass.dataset = FailureDataset.new
459
+
460
+ klass.new(id: 1)
461
+ end
462
+
463
+ test '#delete returns nil on failure' do |instance|
464
+ ret = instance.delete
465
+
466
+ assert ret.nil?
467
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mini_model
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Steve Weiss
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-10-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: cutest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.2'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sequel
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.1'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sqlite3
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ description:
56
+ email:
57
+ - weissst@mail.gvsu.edu
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gems"
63
+ - ".gitignore"
64
+ - LICENSE
65
+ - README.md
66
+ - Rakefile
67
+ - examples/associations.rb
68
+ - examples/callbacks.rb
69
+ - examples/multi_database.rb
70
+ - examples/single_database.rb
71
+ - examples/soft_deletes.rb
72
+ - lib/mini_model.rb
73
+ - mini_model.gemspec
74
+ - test/all.rb
75
+ homepage: https://github.com/sirscriptalot/mini_model
76
+ licenses:
77
+ - MIT
78
+ metadata: {}
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 2.6.11
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: MiniModel
99
+ test_files: []