clean_model 0.0.5 → 0.0.6
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.
- data/.gitignore +5 -5
- data/Gemfile +4 -4
- data/README.md +211 -211
- data/Rakefile +1 -1
- data/clean_model.gemspec +24 -25
- data/lib/clean_model/attribute.rb +55 -55
- data/lib/clean_model/base.rb +62 -62
- data/lib/clean_model/exceptions.rb +17 -29
- data/lib/clean_model/persistent.rb +75 -75
- data/lib/clean_model/remote.rb +75 -96
- data/lib/clean_model/version.rb +3 -3
- data/lib/clean_model.rb +11 -11
- data/spec/base_model_spec.rb +177 -177
- data/spec/persistent_model_spec.rb +111 -111
- data/spec/remote_models_spec.rb +147 -147
- data/spec/spec_helper.rb +7 -7
- data/spec/support/models/base_models.rb +36 -36
- data/spec/support/models/persistent_models.rb +9 -9
- data/spec/support/models/remote_models.rb +12 -12
- metadata +29 -20
data/.gitignore
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
*.gem
|
2
|
-
.bundle
|
3
|
-
Gemfile.lock
|
4
|
-
pkg/*
|
5
|
-
.idea
|
1
|
+
*.gem
|
2
|
+
.bundle
|
3
|
+
Gemfile.lock
|
4
|
+
pkg/*
|
5
|
+
.idea
|
data/Gemfile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
source "http://rubygems.org"
|
2
|
-
|
3
|
-
# Specify your gem's dependencies in clean_model.gemspec
|
4
|
-
gemspec
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in clean_model.gemspec
|
4
|
+
gemspec
|
data/README.md
CHANGED
@@ -1,211 +1,211 @@
|
|
1
|
-
# CleanModel
|
2
|
-
|
3
|
-
Extensions for ActiveModel to implement multiple types of models
|
4
|
-
|
5
|
-
## Installation
|
6
|
-
|
7
|
-
Add this line to your application's Gemfile:
|
8
|
-
|
9
|
-
gem 'clean_model'
|
10
|
-
|
11
|
-
And then execute:
|
12
|
-
|
13
|
-
$ bundle
|
14
|
-
|
15
|
-
Or install it yourself as:
|
16
|
-
|
17
|
-
$ gem install clean_model
|
18
|
-
|
19
|
-
## Basic models
|
20
|
-
|
21
|
-
### Class definitions
|
22
|
-
|
23
|
-
class Person
|
24
|
-
include CleanModel::Base
|
25
|
-
|
26
|
-
attribute :first_name
|
27
|
-
attribute :last_name
|
28
|
-
end
|
29
|
-
|
30
|
-
### Usage
|
31
|
-
|
32
|
-
person = Person.new first_name: 'John', last_name: 'Doe'
|
33
|
-
|
34
|
-
person.first_name -> 'John'
|
35
|
-
person.last_name -> 'Doe'
|
36
|
-
|
37
|
-
person.attributes -> {first_name: 'John', last_name: 'Doe'}
|
38
|
-
|
39
|
-
person.assign_attributes first_name: 'Jorge'
|
40
|
-
|
41
|
-
person.attributes -> {first_name: 'Jorge', last_name: 'Doe'}
|
42
|
-
|
43
|
-
### Defaults
|
44
|
-
|
45
|
-
class Person
|
46
|
-
include CleanModel::Base
|
47
|
-
|
48
|
-
attribute :first_name, default: 'John'
|
49
|
-
attribute :last_name
|
50
|
-
end
|
51
|
-
|
52
|
-
person = Person.new
|
53
|
-
person.first_name -> 'John'
|
54
|
-
person.last_name -> nil
|
55
|
-
|
56
|
-
### Active Model validations
|
57
|
-
|
58
|
-
class Person
|
59
|
-
include CleanModel::Base
|
60
|
-
|
61
|
-
attribute :first_name
|
62
|
-
attribute :last_name
|
63
|
-
|
64
|
-
validates_presence_of :first_name, :last_name
|
65
|
-
end
|
66
|
-
|
67
|
-
person = Person.new
|
68
|
-
person.valid? -> false
|
69
|
-
|
70
|
-
### Strong typing
|
71
|
-
|
72
|
-
class Engine
|
73
|
-
include CleanModel::Base
|
74
|
-
|
75
|
-
attribute :power, class_name: :numeric
|
76
|
-
attribute :cylinders, class_name: :integer
|
77
|
-
attribute :valves, class_name: 'Integer'
|
78
|
-
end
|
79
|
-
|
80
|
-
engine = Engine.new
|
81
|
-
engine.power = 130
|
82
|
-
engine.cylinders = 6.1 -> Raise error CleanModel::InvalidTypeAssignment
|
83
|
-
|
84
|
-
### Transformations
|
85
|
-
|
86
|
-
class Car
|
87
|
-
include CleanModel::Base
|
88
|
-
|
89
|
-
attribute :brand
|
90
|
-
attribute :model
|
91
|
-
attribute :engine, class_name: 'Engine'
|
92
|
-
attribute :comfort, transformation: lambda { |v| v.is_a?(String) ? v.split(',').map(&:strip) : v }
|
93
|
-
end
|
94
|
-
|
95
|
-
car = Car.new do |c|
|
96
|
-
c.engine = {power: 110, cylinders: 16, valves: 6}
|
97
|
-
end
|
98
|
-
car.engine -> <Engine @power=110, @cylinders=16, @valves=6>
|
99
|
-
|
100
|
-
car = Car.new do |c|
|
101
|
-
c.comfort = 'bluetooth, gps, electric pack'
|
102
|
-
end
|
103
|
-
car.comfort -> ['bluetooth', 'gps', 'electric pack']
|
104
|
-
|
105
|
-
### Collections
|
106
|
-
|
107
|
-
class Factory
|
108
|
-
include CleanModel::Base
|
109
|
-
|
110
|
-
attribute :cars, collection: 'Car'
|
111
|
-
end
|
112
|
-
|
113
|
-
factory = Factory.new do |f|
|
114
|
-
f.cars = [
|
115
|
-
{brand: 'Honda', model: 'Civic'},
|
116
|
-
{brand: 'Toyota', model: 'Corolla'},
|
117
|
-
]
|
118
|
-
end
|
119
|
-
|
120
|
-
factory.cars -> [<Car @brand=Honda, @model=Civic>, <Car @brand=Toyota, @model=Corolla>]
|
121
|
-
|
122
|
-
## Models with custom persistence
|
123
|
-
|
124
|
-
### Definition
|
125
|
-
|
126
|
-
class Post
|
127
|
-
include CleanModel::Persistent
|
128
|
-
|
129
|
-
attribute :subject
|
130
|
-
attribute :content
|
131
|
-
|
132
|
-
private
|
133
|
-
|
134
|
-
def create
|
135
|
-
...
|
136
|
-
end
|
137
|
-
|
138
|
-
def update
|
139
|
-
...
|
140
|
-
end
|
141
|
-
|
142
|
-
def delete
|
143
|
-
...
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
### Usage
|
148
|
-
|
149
|
-
Post.create(subject: 'Title', content: 'Some text')
|
150
|
-
or
|
151
|
-
post = Post.new subject: 'Title', content: 'Some text'
|
152
|
-
post.save
|
153
|
-
|
154
|
-
post.content = 'Another text'
|
155
|
-
post.save
|
156
|
-
|
157
|
-
post.update_attributes(title: 'Another title')
|
158
|
-
|
159
|
-
post.destroy
|
160
|
-
|
161
|
-
## Remote models (for REST APIs)
|
162
|
-
|
163
|
-
### Definition
|
164
|
-
|
165
|
-
class User
|
166
|
-
include CleanModel::Remote
|
167
|
-
|
168
|
-
connection host: 'localhost', port: 9999
|
169
|
-
|
170
|
-
attribute :first_name
|
171
|
-
attribute :last_name
|
172
|
-
attribute :email
|
173
|
-
|
174
|
-
def self.find(id)
|
175
|
-
|
176
|
-
new JSON.parse(response.body)
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
private
|
181
|
-
|
182
|
-
def create
|
183
|
-
|
184
|
-
end
|
185
|
-
|
186
|
-
def update
|
187
|
-
|
188
|
-
end
|
189
|
-
|
190
|
-
def delete
|
191
|
-
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
### Usage
|
196
|
-
|
197
|
-
User.create first_name: 'John', last_name: 'Doe'
|
198
|
-
|
199
|
-
user = User.find(1)
|
200
|
-
|
201
|
-
user.update_attributes(first_name: 'Jorge')
|
202
|
-
|
203
|
-
user.destroy
|
204
|
-
|
205
|
-
## Contributing
|
206
|
-
|
207
|
-
1. Fork it
|
208
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
209
|
-
3. Commit your changes (`git commit -am 'Added some feature'`)
|
210
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
211
|
-
5. Create new Pull Request
|
1
|
+
# CleanModel
|
2
|
+
|
3
|
+
Extensions for ActiveModel to implement multiple types of models
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'clean_model'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install clean_model
|
18
|
+
|
19
|
+
## Basic models
|
20
|
+
|
21
|
+
### Class definitions
|
22
|
+
|
23
|
+
class Person
|
24
|
+
include CleanModel::Base
|
25
|
+
|
26
|
+
attribute :first_name
|
27
|
+
attribute :last_name
|
28
|
+
end
|
29
|
+
|
30
|
+
### Usage
|
31
|
+
|
32
|
+
person = Person.new first_name: 'John', last_name: 'Doe'
|
33
|
+
|
34
|
+
person.first_name -> 'John'
|
35
|
+
person.last_name -> 'Doe'
|
36
|
+
|
37
|
+
person.attributes -> {first_name: 'John', last_name: 'Doe'}
|
38
|
+
|
39
|
+
person.assign_attributes first_name: 'Jorge'
|
40
|
+
|
41
|
+
person.attributes -> {first_name: 'Jorge', last_name: 'Doe'}
|
42
|
+
|
43
|
+
### Defaults
|
44
|
+
|
45
|
+
class Person
|
46
|
+
include CleanModel::Base
|
47
|
+
|
48
|
+
attribute :first_name, default: 'John'
|
49
|
+
attribute :last_name
|
50
|
+
end
|
51
|
+
|
52
|
+
person = Person.new
|
53
|
+
person.first_name -> 'John'
|
54
|
+
person.last_name -> nil
|
55
|
+
|
56
|
+
### Active Model validations
|
57
|
+
|
58
|
+
class Person
|
59
|
+
include CleanModel::Base
|
60
|
+
|
61
|
+
attribute :first_name
|
62
|
+
attribute :last_name
|
63
|
+
|
64
|
+
validates_presence_of :first_name, :last_name
|
65
|
+
end
|
66
|
+
|
67
|
+
person = Person.new
|
68
|
+
person.valid? -> false
|
69
|
+
|
70
|
+
### Strong typing
|
71
|
+
|
72
|
+
class Engine
|
73
|
+
include CleanModel::Base
|
74
|
+
|
75
|
+
attribute :power, class_name: :numeric
|
76
|
+
attribute :cylinders, class_name: :integer
|
77
|
+
attribute :valves, class_name: 'Integer'
|
78
|
+
end
|
79
|
+
|
80
|
+
engine = Engine.new
|
81
|
+
engine.power = 130
|
82
|
+
engine.cylinders = 6.1 -> Raise error CleanModel::InvalidTypeAssignment
|
83
|
+
|
84
|
+
### Transformations
|
85
|
+
|
86
|
+
class Car
|
87
|
+
include CleanModel::Base
|
88
|
+
|
89
|
+
attribute :brand
|
90
|
+
attribute :model
|
91
|
+
attribute :engine, class_name: 'Engine'
|
92
|
+
attribute :comfort, transformation: lambda { |v| v.is_a?(String) ? v.split(',').map(&:strip) : v }
|
93
|
+
end
|
94
|
+
|
95
|
+
car = Car.new do |c|
|
96
|
+
c.engine = {power: 110, cylinders: 16, valves: 6}
|
97
|
+
end
|
98
|
+
car.engine -> <Engine @power=110, @cylinders=16, @valves=6>
|
99
|
+
|
100
|
+
car = Car.new do |c|
|
101
|
+
c.comfort = 'bluetooth, gps, electric pack'
|
102
|
+
end
|
103
|
+
car.comfort -> ['bluetooth', 'gps', 'electric pack']
|
104
|
+
|
105
|
+
### Collections
|
106
|
+
|
107
|
+
class Factory
|
108
|
+
include CleanModel::Base
|
109
|
+
|
110
|
+
attribute :cars, collection: 'Car'
|
111
|
+
end
|
112
|
+
|
113
|
+
factory = Factory.new do |f|
|
114
|
+
f.cars = [
|
115
|
+
{brand: 'Honda', model: 'Civic'},
|
116
|
+
{brand: 'Toyota', model: 'Corolla'},
|
117
|
+
]
|
118
|
+
end
|
119
|
+
|
120
|
+
factory.cars -> [<Car @brand=Honda, @model=Civic>, <Car @brand=Toyota, @model=Corolla>]
|
121
|
+
|
122
|
+
## Models with custom persistence
|
123
|
+
|
124
|
+
### Definition
|
125
|
+
|
126
|
+
class Post
|
127
|
+
include CleanModel::Persistent
|
128
|
+
|
129
|
+
attribute :subject
|
130
|
+
attribute :content
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def create
|
135
|
+
...
|
136
|
+
end
|
137
|
+
|
138
|
+
def update
|
139
|
+
...
|
140
|
+
end
|
141
|
+
|
142
|
+
def delete
|
143
|
+
...
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
### Usage
|
148
|
+
|
149
|
+
Post.create(subject: 'Title', content: 'Some text')
|
150
|
+
or
|
151
|
+
post = Post.new subject: 'Title', content: 'Some text'
|
152
|
+
post.save
|
153
|
+
|
154
|
+
post.content = 'Another text'
|
155
|
+
post.save
|
156
|
+
|
157
|
+
post.update_attributes(title: 'Another title')
|
158
|
+
|
159
|
+
post.destroy
|
160
|
+
|
161
|
+
## Remote models (for REST APIs)
|
162
|
+
|
163
|
+
### Definition
|
164
|
+
|
165
|
+
class User
|
166
|
+
include CleanModel::Remote
|
167
|
+
|
168
|
+
connection host: 'localhost', port: 9999
|
169
|
+
|
170
|
+
attribute :first_name
|
171
|
+
attribute :last_name
|
172
|
+
attribute :email
|
173
|
+
|
174
|
+
def self.find(id)
|
175
|
+
connection.get "/users/#{id}.json" do |response|
|
176
|
+
new JSON.parse(response.body)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
def create
|
183
|
+
connection.post! '/users/create.json', wrapped_attributes
|
184
|
+
end
|
185
|
+
|
186
|
+
def update
|
187
|
+
connection.put! "/users/#{id}.json", wrapped_attributes(except: :id)
|
188
|
+
end
|
189
|
+
|
190
|
+
def delete
|
191
|
+
connection.delete!("/users/#{id}.json")
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
### Usage
|
196
|
+
|
197
|
+
User.create first_name: 'John', last_name: 'Doe'
|
198
|
+
|
199
|
+
user = User.find(1)
|
200
|
+
|
201
|
+
user.update_attributes(first_name: 'Jorge')
|
202
|
+
|
203
|
+
user.destroy
|
204
|
+
|
205
|
+
## Contributing
|
206
|
+
|
207
|
+
1. Fork it
|
208
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
209
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
210
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
211
|
+
5. Create new Pull Request
|
data/Rakefile
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require "bundler/gem_tasks"
|
1
|
+
require "bundler/gem_tasks"
|
data/clean_model.gemspec
CHANGED
@@ -1,25 +1,24 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
$:.push File.expand_path("../lib", __FILE__)
|
3
|
-
require 'clean_model/version'
|
4
|
-
|
5
|
-
Gem::Specification.new do |s|
|
6
|
-
s.name = 'clean_model'
|
7
|
-
s.version = CleanModel::VERSION
|
8
|
-
s.authors = ['Gabriel Naiman']
|
9
|
-
s.email = ['gabynaiman@gmail.com']
|
10
|
-
s.homepage = 'https://github.com/gabynaiman/clean_model'
|
11
|
-
s.summary = 'Extensions for ActiveModel to implement multiple types of models'
|
12
|
-
s.description = 'Extensions for ActiveModel to implement multiple types of models'
|
13
|
-
|
14
|
-
s.files = `git ls-files`.split("\n")
|
15
|
-
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
-
s.require_paths = ["lib"]
|
18
|
-
|
19
|
-
s.add_dependency '
|
20
|
-
s.add_dependency '
|
21
|
-
|
22
|
-
|
23
|
-
s.add_development_dependency '
|
24
|
-
|
25
|
-
end
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require 'clean_model/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'clean_model'
|
7
|
+
s.version = CleanModel::VERSION
|
8
|
+
s.authors = ['Gabriel Naiman']
|
9
|
+
s.email = ['gabynaiman@gmail.com']
|
10
|
+
s.homepage = 'https://github.com/gabynaiman/clean_model'
|
11
|
+
s.summary = 'Extensions for ActiveModel to implement multiple types of models'
|
12
|
+
s.description = 'Extensions for ActiveModel to implement multiple types of models'
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_dependency 'activemodel', '>= 3.0.0'
|
20
|
+
s.add_dependency 'web_client', '0.0.4'
|
21
|
+
|
22
|
+
s.add_development_dependency 'rspec'
|
23
|
+
s.add_development_dependency 'webmock'
|
24
|
+
end
|
@@ -1,56 +1,56 @@
|
|
1
|
-
module CleanModel
|
2
|
-
class Attribute
|
3
|
-
attr_reader :name, :options
|
4
|
-
|
5
|
-
def initialize(name, options={})
|
6
|
-
@name = symbolize(name)
|
7
|
-
@options = options
|
8
|
-
end
|
9
|
-
|
10
|
-
def validate!(value)
|
11
|
-
raise InvalidTypeAssignment.new(name, value) unless value.is_a? klass
|
12
|
-
end
|
13
|
-
|
14
|
-
def transform(value)
|
15
|
-
if @options[:transformation]
|
16
|
-
@options[:transformation].call(value)
|
17
|
-
elsif value.is_a?(Hash) && klass.new.respond_to?(:assign_attributes)
|
18
|
-
obj = klass.new
|
19
|
-
obj.assign_attributes value
|
20
|
-
obj
|
21
|
-
elsif value.is_a?(Array) && collection_class.instance_methods.include?(:assign_attributes)
|
22
|
-
value.map do |v|
|
23
|
-
if v.is_a? collection_class
|
24
|
-
v
|
25
|
-
else
|
26
|
-
obj = collection_class.new
|
27
|
-
obj.assign_attributes v
|
28
|
-
obj
|
29
|
-
end
|
30
|
-
end
|
31
|
-
else
|
32
|
-
value
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def assign_default(model)
|
37
|
-
default_value = @options[:default].is_a?(Proc) ? @options[:default].call : @options[:default]
|
38
|
-
model.send("#{@name}=", default_value) if default_value && model.respond_to?("#{@name}=")
|
39
|
-
end
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
def klass
|
44
|
-
@options[:class_name].to_s.classify.constantize
|
45
|
-
end
|
46
|
-
|
47
|
-
def collection_class
|
48
|
-
@options[:collection].to_s.classify.constantize
|
49
|
-
end
|
50
|
-
|
51
|
-
def symbolize(text)
|
52
|
-
text.is_a?(String) ? text.to_s.underscore.parameterize('_').to_sym : text
|
53
|
-
end
|
54
|
-
|
55
|
-
end
|
1
|
+
module CleanModel
|
2
|
+
class Attribute
|
3
|
+
attr_reader :name, :options
|
4
|
+
|
5
|
+
def initialize(name, options={})
|
6
|
+
@name = symbolize(name)
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def validate!(value)
|
11
|
+
raise InvalidTypeAssignment.new(name, value) unless value.is_a? klass
|
12
|
+
end
|
13
|
+
|
14
|
+
def transform(value)
|
15
|
+
if @options[:transformation]
|
16
|
+
@options[:transformation].call(value)
|
17
|
+
elsif value.is_a?(Hash) && klass.new.respond_to?(:assign_attributes)
|
18
|
+
obj = klass.new
|
19
|
+
obj.assign_attributes value
|
20
|
+
obj
|
21
|
+
elsif value.is_a?(Array) && collection_class.instance_methods.include?(:assign_attributes)
|
22
|
+
value.map do |v|
|
23
|
+
if v.is_a? collection_class
|
24
|
+
v
|
25
|
+
else
|
26
|
+
obj = collection_class.new
|
27
|
+
obj.assign_attributes v
|
28
|
+
obj
|
29
|
+
end
|
30
|
+
end
|
31
|
+
else
|
32
|
+
value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def assign_default(model)
|
37
|
+
default_value = @options[:default].is_a?(Proc) ? @options[:default].call : @options[:default]
|
38
|
+
model.send("#{@name}=", default_value) if default_value && model.respond_to?("#{@name}=")
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def klass
|
44
|
+
@options[:class_name].to_s.classify.constantize
|
45
|
+
end
|
46
|
+
|
47
|
+
def collection_class
|
48
|
+
@options[:collection].to_s.classify.constantize
|
49
|
+
end
|
50
|
+
|
51
|
+
def symbolize(text)
|
52
|
+
text.is_a?(String) ? text.to_s.underscore.parameterize('_').to_sym : text
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
56
|
end
|