sequel_dm 0.0.2

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 ADDED
@@ -0,0 +1,5 @@
1
+ vendor/
2
+ tags
3
+ .bundle
4
+ .DS_Store
5
+ pkg
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ rvm:
2
+ - 2.0.0
3
+ script: "bundle exec rspec spec/"
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in aggregate_builder.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'rspec'
8
+ gem 'debugger'
9
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,51 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ sequel_dm (0.0.2)
5
+ activesupport
6
+ sequel (~> 4.6)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activesupport (4.0.2)
12
+ i18n (~> 0.6, >= 0.6.4)
13
+ minitest (~> 4.2)
14
+ multi_json (~> 1.3)
15
+ thread_safe (~> 0.1)
16
+ tzinfo (~> 0.3.37)
17
+ atomic (1.1.14)
18
+ columnize (0.3.6)
19
+ debugger (1.6.5)
20
+ columnize (>= 0.3.1)
21
+ debugger-linecache (~> 1.2.0)
22
+ debugger-ruby_core_source (~> 1.3.1)
23
+ debugger-linecache (1.2.0)
24
+ debugger-ruby_core_source (1.3.1)
25
+ diff-lcs (1.2.5)
26
+ i18n (0.6.9)
27
+ minitest (4.7.5)
28
+ multi_json (1.8.4)
29
+ rake (10.1.1)
30
+ rspec (2.14.1)
31
+ rspec-core (~> 2.14.0)
32
+ rspec-expectations (~> 2.14.0)
33
+ rspec-mocks (~> 2.14.0)
34
+ rspec-core (2.14.7)
35
+ rspec-expectations (2.14.4)
36
+ diff-lcs (>= 1.1.3, < 2.0)
37
+ rspec-mocks (2.14.4)
38
+ sequel (4.6.0)
39
+ thread_safe (0.1.3)
40
+ atomic
41
+ tzinfo (0.3.38)
42
+
43
+ PLATFORMS
44
+ ruby
45
+
46
+ DEPENDENCIES
47
+ bundler (~> 1.3)
48
+ debugger
49
+ rake
50
+ rspec
51
+ sequel_dm!
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Albert Gazizov
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # SequelDM [![Build Status](https://travis-ci.org/AlbertGazizov/sequel_dm.png)](https://travis-ci.org/AlbertGazizov/sequel_dm) [![Code Climate](https://codeclimate.com/github/AlbertGazizov/sequel_dm.png)](https://codeclimate.com/github/AlbertGazizov/sequel_dm)
2
+
3
+
4
+
5
+ SequelDM is a Sequel based Data Mapper pattern implementation
6
+
7
+ NOTE: This gem is not production ready, it needs a lot of work!
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'sequel_dm', github: 'AlbertGazizov/sequel_dm'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ ## Contributing
20
+
21
+ 1. Fork it
22
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
23
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
24
+ 4. Push to the branch (`git push origin my-new-feature`)
25
+ 5. Create new Pull Request
26
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,76 @@
1
+ # Helper class for arguments validation
2
+ module SequelDM::ArgsValidator
3
+ class << self
4
+
5
+ # Checks that specifid +obj+ is a symbol
6
+ # @param obj some object
7
+ # @param obj_name object's name, used to clarify error causer in exception
8
+ def is_symbol!(obj, obj_name)
9
+ unless obj.is_a?(Symbol)
10
+ raise ArgumentError, "#{obj_name} should be a Symbol"
11
+ end
12
+ end
13
+
14
+ # Checks that specifid +obj+ is an Array
15
+ # @param obj some object
16
+ # @param obj_name object's name, used to clarify error causer in exception
17
+ def is_array!(obj, obj_name)
18
+ unless obj.is_a?(Array)
19
+ raise ArgumentError, "#{obj_name} should be an Array"
20
+ end
21
+ end
22
+
23
+ # Checks that specifid +obj+ is a Hash
24
+ # @param obj some object
25
+ # @param obj_name object's name, used to clarify error causer in exception
26
+ def is_hash!(obj, obj_name)
27
+ unless obj.is_a?(Hash)
28
+ raise ArgumentError, "#{obj_name} should be a Hash"
29
+ end
30
+ end
31
+
32
+ # Checks that specifid +obj+ is a Class
33
+ # @param obj some object
34
+ # @param obj_name object's name, used to clarify error causer in exception
35
+ def is_class!(obj, obj_name)
36
+ unless obj.is_a?(Class)
37
+ raise ArgumentError, "#{obj_name} should be a Class"
38
+ end
39
+ end
40
+
41
+ # Checks that specifid +obj+ is a Proc
42
+ # @param obj some object
43
+ # @param obj_name object's name, used to clarify error causer in exception
44
+ def is_proc!(obj, obj_name)
45
+ unless obj.is_a?(Proc)
46
+ raise ArgumentError, "#{obj_name} should be a Proc"
47
+ end
48
+ end
49
+
50
+ # Checks that specifid +obj+ is a symbol or Class
51
+ # @param obj some object
52
+ # @param obj_name object's name, used to clarify error causer in exception
53
+ def is_symbol_or_class!(obj, obj_name)
54
+ if !obj.is_a?(Symbol) && !obj.is_a?(Class)
55
+ raise ArgumentError, "#{obj_name} should be a Symbol or Class"
56
+ end
57
+ end
58
+
59
+ # Checks that specifid +hash+ has a specified +key+
60
+ # @param hash some hash
61
+ # @param key hash's key
62
+ def has_key!(hash, key)
63
+ unless hash.has_key?(key)
64
+ raise ArgumentError, "#{hash} should has #{key} key"
65
+ end
66
+ end
67
+
68
+ # Checks that specified +block+ is given
69
+ # @param block some block
70
+ def block_given!(block)
71
+ unless block
72
+ raise ArgumentError, "Block should be given"
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,353 @@
1
+ require 'sequel_dm/extensions/select_fields'
2
+
3
+ SequelDM::DAO = Class.new(Sequel::Model)
4
+ module SequelDM
5
+ def self.DAO(source)
6
+ Class.new(SequelDM::DAO).set_dataset(source)
7
+ end
8
+
9
+ class DAO
10
+ class_attribute :mapper
11
+
12
+ dataset_module do
13
+ include SequelDM::Extensions::SelectFields
14
+ end
15
+
16
+ class << self
17
+ def def_one_to_many(opts)
18
+ one_to_one = opts[:type] == :one_to_one
19
+ name = opts[:name]
20
+ model = self
21
+ key = (opts[:key] ||= opts.default_key)
22
+ km = opts[:key_method] ||= opts[:key]
23
+ cks = opts[:keys] = Array(key)
24
+ opts[:key_methods] = Array(opts[:key_method])
25
+ primary_key = (opts[:primary_key] ||= self.primary_key)
26
+ opts[:eager_loader_key] = primary_key unless opts.has_key?(:eager_loader_key)
27
+ cpks = opts[:primary_keys] = Array(primary_key)
28
+ pkc = opts[:primary_key_column] ||= primary_key
29
+ pkcs = opts[:primary_key_columns] ||= Array(pkc)
30
+ raise(Error, "mismatched number of keys: #{cks.inspect} vs #{cpks.inspect}") unless cks.length == cpks.length
31
+ uses_cks = opts[:uses_composite_keys] = cks.length > 1
32
+ slice_range = opts.slice_range
33
+ opts[:dataset] ||= proc do
34
+ opts.associated_dataset.where(opts.predicate_keys.zip(cpks.map{|k| send(k)}))
35
+ end
36
+ opts[:eager_loader] = proc do |eo|
37
+ h = eo[:id_map]
38
+ rows = eo[:rows]
39
+ reciprocal = opts.reciprocal
40
+ klass = opts.associated_class
41
+ filter_keys = opts.predicate_key
42
+ ds = model.eager_loading_dataset(opts, klass.where(filter_keys=>h.keys), nil, eo[:associations], eo)
43
+ assign_singular = true if one_to_one
44
+ case opts.eager_limit_strategy
45
+ when :distinct_on
46
+ ds = ds.distinct(*filter_keys).order_prepend(*filter_keys)
47
+ when :window_function
48
+ delete_rn = true
49
+ rn = ds.row_number_column
50
+ ds = apply_window_function_eager_limit_strategy(ds, opts)
51
+ when :ruby
52
+ assign_singular = false if one_to_one && slice_range
53
+ end
54
+ ds.all do |assoc_record|
55
+ assoc_record.values.delete(rn) if delete_rn
56
+ hash_key = uses_cks ? km.map{|k| assoc_record.send(k)} : assoc_record.send(km)
57
+ next unless objects = h[hash_key]
58
+ if assign_singular
59
+ objects.each do |object|
60
+ unless object.send(name)
61
+ # TODO: add persistance_associations update here
62
+ object.send("#{name}=", assoc_record)
63
+ assoc_record.send("#{reciprocal}=", object) if reciprocal
64
+ end
65
+ end
66
+ else
67
+ objects.each do |object|
68
+ add_to_associations_state(object, name, assoc_record)
69
+ object.send(name).push(assoc_record)
70
+ assoc_record.send("#{reciprocal}=", object) if reciprocal
71
+ end
72
+ end
73
+ end
74
+ if opts.eager_limit_strategy == :ruby
75
+ if one_to_one
76
+ if slice_range
77
+ rows.each{|o| o.associations[name] = o.associations[name][slice_range.begin]}
78
+ end
79
+ else
80
+ rows.each{|o| o.associations[name] = o.associations[name][slice_range] || []}
81
+ end
82
+ end
83
+ end
84
+ super
85
+ end
86
+
87
+ def set_dataset_row_proc(ds)
88
+ ds.row_proc = Proc.new do |raw|
89
+ raise StandardError, "Mapper should be specified" if !self.mapper
90
+ entity = self.mapper.to_entity(raw)
91
+ save_state(entity, raw)
92
+ entity
93
+ end
94
+ end
95
+
96
+ def set_mapper(mapper)
97
+ SequelDM::ArgsValidator.is_class!(mapper, :mapper)
98
+ self.mapper = mapper
99
+ end
100
+
101
+ # Database methods
102
+
103
+ def insert(entity, root = nil)
104
+ raw = mapper.to_hash(entity, root)
105
+ key = dataset.insert(raw)
106
+ set_entity_primary_key(entity, raw, key)
107
+ save_state(entity, raw)
108
+ insert_associations(entity)
109
+ entity
110
+ end
111
+
112
+ def insert_all(entities, root = nil)
113
+ entities.each do |entity|
114
+ insert(entity, root)
115
+ end
116
+ end
117
+
118
+ def update(entity, root = nil)
119
+ raw = mapper.to_hash(entity, root)
120
+ raw = select_only_changed_values(entity, raw)
121
+
122
+ unless raw.empty?
123
+ update_state(entity, raw)
124
+
125
+ key_condition = prepare_key_condition_from_entity(entity)
126
+ dataset.where(key_condition).update(raw)
127
+ end
128
+
129
+ insert_or_update_associations(entity)
130
+ entity
131
+ end
132
+
133
+ def update_all(entities, root = nil)
134
+ entities.each do |entity|
135
+ update(entity, root)
136
+ end
137
+ end
138
+
139
+ def save(entity, root = nil)
140
+ if has_persistance_state?(entity)
141
+ update(entity, root)
142
+ else
143
+ insert(entity, root)
144
+ end
145
+ end
146
+
147
+ def save_all(entities, root = nil)
148
+ entities.each do |entity|
149
+ save(entity, root)
150
+ end
151
+ end
152
+
153
+ def delete(entity)
154
+ key_condition = prepare_key_condition_from_entity(entity)
155
+ dataset.where(key_condition).delete
156
+ delete_associations(entity)
157
+ end
158
+
159
+ # TODO: refactor
160
+ def delete_all(entities)
161
+ entity_ids = entities.map(&:id)
162
+ dataset.where(id: entity_ids).delete
163
+ unless association_reflections.empty?
164
+ association_reflections.each do |association, options|
165
+ association_dao = options[:class]
166
+ conditions = (options[:conditions] || {}).merge(options[:key] => entity_ids)
167
+ association_dao.where(conditions).delete
168
+ end
169
+ end
170
+ end
171
+
172
+ private
173
+
174
+ def select_only_changed_values(entity, hash)
175
+ changes = {}
176
+ return hash unless entity.instance_variable_defined?(:@persistance_state)
177
+
178
+ persistance_state = entity.instance_variable_get(:@persistance_state)
179
+ hash.each do |column, value|
180
+ previous_column_value = persistance_state[column]
181
+ if persistance_state.has_key?(column) && column_value_changed?(previous_column_value, value)
182
+ changes[column] = value
183
+ end
184
+ end
185
+ changes
186
+ end
187
+
188
+ def column_value_changed?(previous_value, new_value)
189
+ previous_value != new_value
190
+ end
191
+
192
+ def save_state(entity, raw)
193
+ if !entity.is_a?(Integer) && !entity.is_a?(Symbol)
194
+ entity.instance_variable_set(:@persistance_state, raw)
195
+ end
196
+ end
197
+
198
+ def update_state(entity, raw)
199
+ persistance_state = entity.instance_variable_get(:@persistance_state)
200
+ if persistance_state
201
+ persistance_state.merge!(raw)
202
+ end
203
+ end
204
+
205
+ def has_persistance_state?(entity)
206
+ !!entity.instance_variable_get(:@persistance_state)
207
+ end
208
+
209
+ def set_associations_state(entity, association_name, associations)
210
+ persistance_associations = entity.instance_variable_get(:@persistance_associations) || {}
211
+ persistance_associations[association_name] ||= []
212
+ persistance_associations[association_name] |= associations
213
+ entity.instance_variable_set(:@persistance_associations, persistance_associations)
214
+ end
215
+
216
+ def add_to_associations_state(entity, association_name, association)
217
+ persistance_associations = entity.instance_variable_get(:@persistance_associations) || {}
218
+ persistance_associations[association_name] ||= []
219
+ persistance_associations[association_name] << association
220
+ entity.instance_variable_set(:@persistance_associations, persistance_associations)
221
+ end
222
+
223
+ def prepare_key_condition_from_entity(entity)
224
+ key_condition = {}
225
+ if primary_key.is_a?(Array)
226
+ primary_key.each do |key_part|
227
+ key_part_value = entity.send(key_part)
228
+ raise ArgumentError, "entity's primary key can't be nil, got nil for #{key_part}" unless key_part_value
229
+ key_condition[key_part] = key_part_value
230
+ end
231
+ elsif primary_key.is_a?(Symbol)
232
+ key_value = entity.send(primary_key)
233
+ raise ArgumentError, "entity's primary key can't be nil, got nil for #{primary_key}" unless key_value
234
+ key_condition[primary_key] = key_value
235
+ else
236
+ raise StandardError, "primary key should be array or symbol"
237
+ end
238
+ key_condition
239
+ end
240
+
241
+ def set_entity_primary_key(entity, raw, key)
242
+ if key && !primary_key.is_a?(Array)
243
+ entity.instance_variable_set("@#{primary_key}", key)
244
+ raw[primary_key] = key
245
+ end
246
+ end
247
+
248
+ def insert_associations(entity)
249
+ unless association_reflections.empty?
250
+ association_reflections.each do |association_name, options|
251
+ association_dao = options[:class]
252
+ if entity.respond_to?(association_name)
253
+ children = association_dao.insert_all(entity.send(association_name), entity)
254
+ set_associations_state(entity, association_name, children)
255
+ end
256
+ end
257
+ end
258
+ end
259
+
260
+ def insert_or_update_associations(entity)
261
+ unless association_reflections.empty?
262
+ association_reflections.each do |association_name, options|
263
+ association_dao = options[:class]
264
+ raise ArgumentError, "class option should be specified for #{association_name}" unless association_dao
265
+
266
+ delete_dissapeared_children(entity, association_name, options)
267
+
268
+ children = entity.send(association_name)
269
+ association_dao.save_all(children, entity)
270
+ set_associations_state(entity, association_name, children)
271
+ end
272
+ end
273
+ end
274
+
275
+ def delete_associations(entity)
276
+ unless association_reflections.empty?
277
+ association_reflections.each do |association, options|
278
+ if options[:delete]
279
+ association_dao = options[:class]
280
+ conditions = (options[:conditions] || {}).merge(options[:key] => entity.send(primary_key))
281
+ association_dao.where(conditions).delete
282
+ end
283
+ end
284
+ end
285
+ end
286
+
287
+ def delete_dissapeared_children(entity, association, options)
288
+ association_dao = options[:class]
289
+ unless options[:key]
290
+ raise ArgumentError, "key option should be specified for #{association}"
291
+ end
292
+ if options[:key].is_a?(Symbol)
293
+ conditions = (options[:conditions] || {}).merge(options[:key] => entity.send(primary_key))
294
+ elsif options[:key].is_a?(Array)
295
+ conditions = options[:key].inject(options[:conditions] || {}) { |result, key| result[key] = entity.send(key); result }
296
+ else
297
+ raise ArgumentError, "key should be symbol or array"
298
+ end
299
+
300
+ # get ids of removed children
301
+ association_objects = get_association_objects(entity, association)
302
+ dissapeared_objects = association_objects - entity.send(association)
303
+
304
+ scope_key = options[:scope_key] || association_dao.primary_key
305
+ if scope_key.is_a?(Symbol)
306
+ child_keys = { scope_key => [] }
307
+ dissapeared_objects.each do |child_object|
308
+ key = child_object.send(scope_key)
309
+ child_keys[scope_key] << key
310
+ end
311
+
312
+ if !child_keys[scope_key].empty?
313
+ association_dao.where(conditions).where(child_keys).delete
314
+ end
315
+ elsif scope_key.is_a?(Array)
316
+ child_keys = []
317
+ dissapeared_objects.each do |child_object|
318
+ child_keys << scope_key.inject({}) do |condition, key|
319
+ condition[key] = child_object.send(key)
320
+ condition
321
+ end
322
+ end
323
+ if !child_keys.empty?
324
+ child_keys.each { |keys| keys.merge!(conditions) }
325
+ association_dao.where(Sequel.|(*child_keys)).delete
326
+ end
327
+ elsif scope_key.is_a?(Proc)
328
+ child_keys = []
329
+ dissapeared_objects.each do |child_object|
330
+ child_keys << scope_key.call(child_object)
331
+ end
332
+ if !child_keys.empty?
333
+ child_keys.each { |keys| keys.merge!(conditions) }
334
+ association_dao.where(Sequel.|(*child_keys)).delete
335
+ end
336
+ else
337
+ raise StandardError, "scope key should be array or symbol"
338
+ end
339
+ end
340
+
341
+ def get_association_objects(entity, association)
342
+ persistance_associations = entity.instance_variable_get(:@persistance_associations)
343
+ if persistance_associations
344
+ persistance_associations[association] || []
345
+ else
346
+ []
347
+ end
348
+ end
349
+
350
+ end
351
+ end
352
+
353
+ end
@@ -0,0 +1,47 @@
1
+ module SequelDM
2
+ module Extensions
3
+ module SelectFields
4
+ def select_fields(fields)
5
+ if fields.empty?
6
+ if !model.association_reflections.empty?
7
+ eager(model.association_reflections.keys)
8
+ else
9
+ self
10
+ end
11
+ else
12
+ eager_associations = {}
13
+ fields.each do |association, columns|
14
+ next if association == :fields
15
+ if columns && !columns.is_a?(Array)
16
+ columns = get_columns_from_mapper(association)
17
+ end
18
+ if columns
19
+ table_name = model.association_reflections[association][:class].table_name
20
+ columns = columns.map { |column| :"#{table_name}__#{column}___#{column}" }
21
+ eager_associations[association] = proc{|ds| ds.select(*columns) }
22
+ end
23
+ end
24
+
25
+ if fields[:fields].is_a?(Array)
26
+ columns = fields[:fields]
27
+ else
28
+ columns = model.mapper.mappings.keys
29
+ end
30
+ columns = columns.map { |column| :"#{model.table_name}__#{column}___#{column}" }
31
+ eager(eager_associations).select(*columns)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def get_columns_from_mapper(association)
38
+ reflection = model.association_reflections[association]
39
+ raise ArgumentError, "association with name #{association} is not defined in dao" unless reflection
40
+ association_dao = reflection[:class]
41
+ raise ArgumentError, "association #{association} should have class option" unless association_dao
42
+ association_dao.mapper.mappings.keys
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,54 @@
1
+ require 'sequel_dm/args_validator'
2
+ require 'sequel_dm/mappings_dsl'
3
+
4
+ module SequelDM::Mapper
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute :entity_class, :mappings
9
+ end
10
+
11
+ module ClassMethods
12
+ def map(entity_class, &mappings_proc)
13
+ SequelDM::ArgsValidator.is_class!(entity_class, :entity_class)
14
+ self.entity_class = entity_class
15
+ self.mappings = SequelDM::MappingsDSL.new(&mappings_proc).mappings
16
+ end
17
+
18
+ def to_entity(hash)
19
+ attributes = {}
20
+ entity = self.entity_class.new
21
+ hash.each do |key, value|
22
+ if mapping = self.mappings[key]
23
+ entity.instance_variable_set(:"@#{mapping.entity_field}", to_attribute(hash, value, mapping)) if mapping.set_field?
24
+ end
25
+ end
26
+ entity
27
+ end
28
+
29
+ def to_hash(entity, *args)
30
+ hash = {}
31
+
32
+ entity_mappings = self.mappings
33
+ entity_mappings.each do |column, mapping|
34
+ value = to_column(entity, mapping, *args)
35
+ hash[column] = value if mapping.set_column?
36
+ end
37
+
38
+ hash
39
+ end
40
+
41
+ private
42
+
43
+ def to_attribute(hash, value, mapping)
44
+ mapping.load? ? mapping.load(hash) : value
45
+ end
46
+
47
+ def to_column(entity, mapping, *args)
48
+ mapping.dump? ? mapping.dump(entity, *args) : entity.send(mapping.entity_field)
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+
@@ -0,0 +1,69 @@
1
+ class SequelDM::MappingsDSL
2
+ attr_reader :mappings
3
+ def initialize(&dsl_block)
4
+ @mappings = {}
5
+ instance_exec(&dsl_block)
6
+ end
7
+
8
+ def column(column_name, options = {})
9
+ SequelDM::ArgsValidator.is_symbol!(column_name, :column_name)
10
+ SequelDM::ArgsValidator.is_hash!(options, :column_options)
11
+ SequelDM::ArgsValidator.is_symbol!(options[:to], :to) if options[:to]
12
+ SequelDM::ArgsValidator.is_proc!(options[:load], :load) if options[:load]
13
+ SequelDM::ArgsValidator.is_proc!(options[:dump], :dump) if options[:dump]
14
+
15
+ set_field = options[:set_field] == false ? false : true
16
+ set_column = options[:set_column] == false ? false : true
17
+ mappings[column_name] = Mapping.new(
18
+ column_name,
19
+ options[:to] || column_name,
20
+ options[:load],
21
+ options[:dump],
22
+ set_field,
23
+ set_column,
24
+ )
25
+ end
26
+
27
+ def columns(*column_names)
28
+ SequelDM::ArgsValidator.is_array!(column_names, :column_names)
29
+ column_names.each { |column_name| column(column_name) }
30
+ end
31
+
32
+ class Mapping
33
+ attr_accessor :column_name, :entity_field, :load, :dump
34
+
35
+ def initialize(column_name, entity_field, load = nil, dump = nil, set_field = true, set_column = true)
36
+ @column_name = column_name
37
+ @entity_field = entity_field
38
+ @load = load
39
+ @dump = dump
40
+ @set_field = set_field
41
+ @set_column = set_column
42
+ end
43
+
44
+ def set_field?
45
+ @set_field
46
+ end
47
+
48
+ def set_column?
49
+ @set_column
50
+ end
51
+
52
+ def load?
53
+ !!@load
54
+ end
55
+
56
+ def dump?
57
+ !!@dump
58
+ end
59
+
60
+ def load(value)
61
+ @load.call(value)
62
+ end
63
+
64
+ def dump(value, *args)
65
+ @dump.call(value, *args)
66
+ end
67
+ end
68
+ end
69
+
@@ -0,0 +1,3 @@
1
+ module SequelDM
2
+ VERSION = "0.0.2"
3
+ end
data/lib/sequel_dm.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/core_ext/class/attribute'
3
+ require 'sequel'
4
+ require 'sequel_dm/version'
5
+ require 'sequel_dm/mapper'
6
+ require 'sequel_dm/dao'
data/sequel_dm.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sequel_dm/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "sequel_dm"
8
+ spec.version = SequelDM::VERSION
9
+ spec.authors = ["Albert Gazizov", "Ruslan Gatiyatov"]
10
+ spec.email = ["deeper4k@gmail.com", "ruslan.gatiyatov@gmail.com"]
11
+ spec.description = %q{Sequel based Data Mapper implementation}
12
+ spec.summary = %q{Sequel based Data Mapper implementation}
13
+ spec.homepage = "http://github.com/deeper4k/sequel_dm"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(spec)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activesupport"
22
+ spec.add_development_dependency "bundler", "~> 1.3"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_dependency "sequel", "~> 4.6"
25
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+ require 'sequel_dm'
3
+
4
+ describe "SequelDM::DAO" do
5
+ class Entity
6
+ end
7
+
8
+ class Mapper
9
+ def self.to_entity(hash)
10
+ Entity.new
11
+ end
12
+ end
13
+
14
+ describe ".dataset.row_proc" do
15
+ it "should return entity" do
16
+ dao = Class.new(SequelDM::DAO(:items))
17
+ dao.mapper = Mapper
18
+ dao.dataset.row_proc.call({}).should be_instance_of(Entity)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ describe SequelDM::Mapper do
4
+ module MapperTest
5
+
6
+ class Event
7
+ attr_accessor :name, :description, :when, :settings
8
+
9
+ def initialize(attrs = {})
10
+ @name = attrs[:name]
11
+ @description = attrs[:description]
12
+ @when = attrs[:when]
13
+ @settings = attrs[:settings ]
14
+ end
15
+ end
16
+
17
+ class EventMapper
18
+ include SequelDM::Mapper
19
+
20
+ map MapperTest::Event do
21
+ column :subject, to: :name
22
+ column :description
23
+ column :settings, load: ->(hash) { YAML.load(hash[:settings]) }, dump: ->(event) { YAML.dump(event.settings) }
24
+ end
25
+ end
26
+ end
27
+
28
+ describe ".to_entity" do
29
+ it "should build Event instance from hash using mappings" do
30
+ tomorrow = Time.now + 24*60*60
31
+ event = MapperTest::EventMapper.to_entity({
32
+ subject: "Meet parents",
33
+ description: "I need to meet them",
34
+ when: tomorrow,
35
+ settings: "---\n:important: true\n",
36
+ occurrences_number: 2,
37
+ })
38
+ event.should be_instance_of(MapperTest::Event)
39
+
40
+ event.name.should == "Meet parents"
41
+ event.description.should == "I need to meet them"
42
+ event.when.should == nil # unlisted column
43
+ event.settings.should == { important: true }
44
+ end
45
+ end
46
+
47
+ describe ".to_hash" do
48
+ it "should convert entity to hash" do
49
+ event = MapperTest::Event.new({
50
+ name: "Event",
51
+ description: "Description",
52
+ when: Time.now,
53
+ settings: { important: true },
54
+ })
55
+ MapperTest::EventMapper.to_hash(event).should == {
56
+ description: "Description",
57
+ settings: "---\n:important: true\n",
58
+ subject: "Event"
59
+ }
60
+ end
61
+ end
62
+ end
63
+
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'debugger'
4
+ require 'sequel'
5
+ require 'sequel_dm'
6
+
7
+ RSpec.configure do |config|
8
+ config.color_enabled = true
9
+
10
+ db = Sequel.mock(:fetch=>{:id => 1, :x => 1}, :numrows=>1, :autoid=>proc{|sql| 10})
11
+ def db.schema(*) [[:id, {:primary_key=>true}]] end
12
+ def db.reset() sqls end
13
+ def db.supports_schema_parsing?() true end
14
+ Sequel::Model.db = DB = db
15
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sequel_dm
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.2
6
+ platform: ruby
7
+ authors:
8
+ - Albert Gazizov
9
+ - Ruslan Gatiyatov
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2014-04-08 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ prerelease: false
17
+ name: activesupport
18
+ type: :runtime
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ! '>='
22
+ - !ruby/object:Gem::Version
23
+ version: '0'
24
+ none: false
25
+ requirement: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ none: false
31
+ - !ruby/object:Gem::Dependency
32
+ prerelease: false
33
+ name: bundler
34
+ type: :development
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ~>
38
+ - !ruby/object:Gem::Version
39
+ version: '1.3'
40
+ none: false
41
+ requirement: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '1.3'
46
+ none: false
47
+ - !ruby/object:Gem::Dependency
48
+ prerelease: false
49
+ name: rake
50
+ type: :development
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ none: false
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ none: false
63
+ - !ruby/object:Gem::Dependency
64
+ prerelease: false
65
+ name: sequel
66
+ type: :runtime
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ~>
70
+ - !ruby/object:Gem::Version
71
+ version: '4.6'
72
+ none: false
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '4.6'
78
+ none: false
79
+ description: Sequel based Data Mapper implementation
80
+ email:
81
+ - deeper4k@gmail.com
82
+ - ruslan.gatiyatov@gmail.com
83
+ executables: []
84
+ extensions: []
85
+ extra_rdoc_files: []
86
+ files:
87
+ - .gitignore
88
+ - .travis.yml
89
+ - Gemfile
90
+ - Gemfile.lock
91
+ - LICENSE.txt
92
+ - README.md
93
+ - Rakefile
94
+ - lib/sequel_dm.rb
95
+ - lib/sequel_dm/args_validator.rb
96
+ - lib/sequel_dm/dao.rb
97
+ - lib/sequel_dm/extensions/select_fields.rb
98
+ - lib/sequel_dm/mapper.rb
99
+ - lib/sequel_dm/mappings_dsl.rb
100
+ - lib/sequel_dm/version.rb
101
+ - sequel_dm.gemspec
102
+ - spec/sequel_dm/dao_spec.rb
103
+ - spec/sequel_dm/mapper_spec.rb
104
+ - spec/spec_helper.rb
105
+ homepage: http://github.com/deeper4k/sequel_dm
106
+ licenses:
107
+ - MIT
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ! '>='
115
+ - !ruby/object:Gem::Version
116
+ segments:
117
+ - 0
118
+ hash: -3775447022657045354
119
+ version: '0'
120
+ none: false
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ segments:
126
+ - 0
127
+ hash: -3775447022657045354
128
+ version: '0'
129
+ none: false
130
+ requirements: []
131
+ rubyforge_project:
132
+ rubygems_version: 1.8.23
133
+ signing_key:
134
+ specification_version: 3
135
+ summary: Sequel based Data Mapper implementation
136
+ test_files:
137
+ - spec/sequel_dm/dao_spec.rb
138
+ - spec/sequel_dm/mapper_spec.rb
139
+ - spec/spec_helper.rb