hugg-orm 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.
- checksums.yaml +7 -0
- data/lib/hugg_orm.rb +9 -0
- data/lib/hugg_orm/connection.rb +71 -0
- data/lib/hugg_orm/persistence.rb +204 -0
- data/lib/hugg_orm/query.rb +98 -0
- data/lib/hugg_orm/version.rb +3 -0
- metadata +121 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fcefba25435c17f1d48c113fb95cd455999188bd
|
4
|
+
data.tar.gz: 9fa82007c4e17a0565d2e38098ea312eebfceefa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 98439bedf84eafe456465061514d3dd00d8e8d879cc82f81fceb8fb1236df73d4971ff409e8a8c16449d88301cf370a86ff9136e81fdef276aad3e7a2af3fa1d
|
7
|
+
data.tar.gz: 167741b1ed8bdd9970ab6cda82957a3b2e7f2466c4c1b5088e6126e9dfe22d801cf4beb4da1b403e35f6d80352462b3469dccc3626722b4831990309d7be4cd1
|
data/lib/hugg_orm.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'pg'
|
3
|
+
|
4
|
+
module HuggORM
|
5
|
+
class Connection
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
RETRIES = 1
|
9
|
+
|
10
|
+
attr_reader :options
|
11
|
+
|
12
|
+
def self.db
|
13
|
+
@_db ||= self.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def connection
|
17
|
+
return @connection unless @connection.nil?
|
18
|
+
connect(@options)
|
19
|
+
@connection
|
20
|
+
end
|
21
|
+
|
22
|
+
def options(opts = {})
|
23
|
+
@options = opts
|
24
|
+
end
|
25
|
+
|
26
|
+
def connect(opts = {})
|
27
|
+
@options = opts
|
28
|
+
@connection = PG.connect(opts)
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def disconnect
|
33
|
+
connection.finish if @connection
|
34
|
+
@connection = nil
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def reconnect
|
39
|
+
disconnect
|
40
|
+
connect(@options)
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def method_missing(meth, *args, &block)
|
45
|
+
if connection.respond_to?(meth)
|
46
|
+
method_retry(meth, *args, &block)
|
47
|
+
else
|
48
|
+
super
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def method_retry(method, *args, &block)
|
53
|
+
tries ||= RETRIES + 1
|
54
|
+
connection.send(method, *args, &block)
|
55
|
+
rescue PG::ConnectionBad, PG::AdminShutdown
|
56
|
+
if (tries -= 1) > 0
|
57
|
+
reconnect
|
58
|
+
retry
|
59
|
+
end
|
60
|
+
raise
|
61
|
+
end
|
62
|
+
|
63
|
+
def exec(*args)
|
64
|
+
method_retry(:exec, *args)
|
65
|
+
end
|
66
|
+
|
67
|
+
def exec_params(*args)
|
68
|
+
method_retry(:exec_params, *args)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
module HuggORM
|
2
|
+
module Persistence
|
3
|
+
include Comparable
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.send(:include, InstanceMethods)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
attr_accessor :_table_name, :_primary_key, :_key_field, :_exclude_fields, :_include_fields
|
12
|
+
|
13
|
+
def table_name(table_name = nil)
|
14
|
+
self._table_name = table_name unless table_name.nil?
|
15
|
+
self._table_name
|
16
|
+
end
|
17
|
+
|
18
|
+
def primary_key(primary_key = nil)
|
19
|
+
self._primary_key = primary_key unless primary_key.nil?
|
20
|
+
self._primary_key
|
21
|
+
end
|
22
|
+
|
23
|
+
def key_field(key_field = nil)
|
24
|
+
self._key_field = key_field.to_sym unless key_field.nil?
|
25
|
+
self._key_field ||= :id
|
26
|
+
end
|
27
|
+
|
28
|
+
def exclude_fields(*fields)
|
29
|
+
self._exclude_fields = fields unless fields.empty?
|
30
|
+
self._exclude_fields ||= []
|
31
|
+
end
|
32
|
+
|
33
|
+
def include_fields(*fields)
|
34
|
+
self._include_fields = fields unless fields.empty?
|
35
|
+
self._include_fields ||= []
|
36
|
+
end
|
37
|
+
|
38
|
+
def find(id)
|
39
|
+
where("#{primary_key} = ?", [id]).select.first
|
40
|
+
end
|
41
|
+
|
42
|
+
def all
|
43
|
+
results = new_query.select
|
44
|
+
new_collection(results)
|
45
|
+
end
|
46
|
+
|
47
|
+
def select(query = nil, where: nil, order: nil, limit: nil, offset: nil)
|
48
|
+
return new_collection(query.select) if query
|
49
|
+
|
50
|
+
query = new_query
|
51
|
+
query.where(where[:conditions], where[:values]) if where
|
52
|
+
query.order(order) if order
|
53
|
+
query.limit(limit) if limit
|
54
|
+
query.offset(offset) if offset
|
55
|
+
|
56
|
+
new_collection(query.select)
|
57
|
+
end
|
58
|
+
|
59
|
+
def create(data)
|
60
|
+
new(data) if new_query.insert(data) > 0
|
61
|
+
end
|
62
|
+
|
63
|
+
def update(id, data)
|
64
|
+
results = where("#{primary_key} = ?", [id]).update(data, true)
|
65
|
+
new(results.first) unless results.empty?
|
66
|
+
end
|
67
|
+
|
68
|
+
def destroy(id)
|
69
|
+
where("#{primary_key} = ?", [id]).delete > 0
|
70
|
+
end
|
71
|
+
|
72
|
+
def count
|
73
|
+
new_query.count
|
74
|
+
end
|
75
|
+
|
76
|
+
def where(conditions, values = [])
|
77
|
+
new_query.where(conditions, values)
|
78
|
+
end
|
79
|
+
|
80
|
+
def limit(limit, offset = nil)
|
81
|
+
new_query.limit(limit, offset)
|
82
|
+
end
|
83
|
+
|
84
|
+
def order(*args)
|
85
|
+
new_query.order(*args)
|
86
|
+
end
|
87
|
+
|
88
|
+
def new_query
|
89
|
+
Query.new(table_name)
|
90
|
+
end
|
91
|
+
|
92
|
+
def new_collection(data_ary)
|
93
|
+
data_ary.map { |data| new(data) }
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_pgtimestamp(field)
|
97
|
+
"to_timestamp(#{field}, 'YYYY-MM-DD HH24:MI:SS')"
|
98
|
+
end
|
99
|
+
|
100
|
+
def string_to_pgtimestamp(string)
|
101
|
+
to_pgtimestamp("'#{string}'")
|
102
|
+
end
|
103
|
+
|
104
|
+
def escape_like(str = '')
|
105
|
+
pat = Regexp.union("\\", "%", "_")
|
106
|
+
str.gsub(pat) { |mat| ["\\", mat].join }
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
module InstanceMethods
|
112
|
+
attr_accessor :uuid, :data
|
113
|
+
|
114
|
+
def initialize(data = {})
|
115
|
+
@data = {}
|
116
|
+
update_attributes(data) unless data.empty?
|
117
|
+
end
|
118
|
+
|
119
|
+
def key
|
120
|
+
@data[self.class.key_field]
|
121
|
+
end
|
122
|
+
|
123
|
+
def set_id
|
124
|
+
# Set id based on key field, but only if id isn't already set and
|
125
|
+
# the key method has been overridden and returns data.
|
126
|
+
@data[self.class.key_field] = key if @data[self.class.key_field].nil? && key
|
127
|
+
end
|
128
|
+
|
129
|
+
def update_attributes(update_data)
|
130
|
+
# Remove fields not set to be included
|
131
|
+
unless self.class.include_fields.empty?
|
132
|
+
update_data = self.class.include_fields.each_with_object({}) do |key, hash|
|
133
|
+
hash[key] = update_data[key] if update_data.has_key? key
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Update all fields unless they have been excluded
|
138
|
+
update_data.each do |field, value|
|
139
|
+
@data[field] = value unless self.class.exclude_fields.include?(field)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Set id based on key method.
|
143
|
+
set_id
|
144
|
+
|
145
|
+
self
|
146
|
+
end
|
147
|
+
|
148
|
+
def destroy
|
149
|
+
if self.class.destroy(key)
|
150
|
+
@data = {}
|
151
|
+
@state = :destroyed
|
152
|
+
end
|
153
|
+
|
154
|
+
self
|
155
|
+
end
|
156
|
+
|
157
|
+
def save
|
158
|
+
updated_data = self.class.where("#{self.class.primary_key} = ?", [key]).update(@data, true)
|
159
|
+
|
160
|
+
if updated_data != 0
|
161
|
+
@data = updated_data
|
162
|
+
@state = :updated
|
163
|
+
else
|
164
|
+
self.class.new_query.insert(@data)
|
165
|
+
@state = :created
|
166
|
+
end
|
167
|
+
|
168
|
+
self
|
169
|
+
end
|
170
|
+
|
171
|
+
def destroyed?
|
172
|
+
@state == :destroyed
|
173
|
+
end
|
174
|
+
|
175
|
+
def updated?
|
176
|
+
@state == :updated
|
177
|
+
end
|
178
|
+
|
179
|
+
def created?
|
180
|
+
@state == :created
|
181
|
+
end
|
182
|
+
|
183
|
+
def dump
|
184
|
+
str = "#{self.class.name}:\n"
|
185
|
+
@data.sort.each do |field, value|
|
186
|
+
str << " #{field}: #{value}\n"
|
187
|
+
end
|
188
|
+
str
|
189
|
+
end
|
190
|
+
|
191
|
+
def fetch(*args)
|
192
|
+
@data.send(:fetch, *args)
|
193
|
+
end
|
194
|
+
|
195
|
+
def has_field?(field)
|
196
|
+
@data.has_key?(field)
|
197
|
+
end
|
198
|
+
|
199
|
+
def <=>(o)
|
200
|
+
o.kind_of?(self.class) && @data == o.data ? 0 : nil
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module HuggORM
|
2
|
+
class Query
|
3
|
+
attr_reader :table, :column_name, :values,
|
4
|
+
:_command, :_where, :_order, :_limit, :_offset, :_returning
|
5
|
+
|
6
|
+
def initialize(table, column_name = 'data')
|
7
|
+
@table = table
|
8
|
+
@column_name = column_name
|
9
|
+
@values = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
HuggORM::Connection.db.exec_params(to_sql, @values)
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_sql
|
17
|
+
sql = "#{@_command} #{@_where} #{@_order} #{@_limit} #{@_offset} #{@_returning}".strip
|
18
|
+
sql.gsub!(/\s{2,}/, ' ')
|
19
|
+
sql
|
20
|
+
end
|
21
|
+
|
22
|
+
def insert(data)
|
23
|
+
@_command = "INSERT INTO #{@table} (#{@column_name}) VALUES (#{next_placeholder})"
|
24
|
+
@values << data.to_json
|
25
|
+
pg_result = run
|
26
|
+
pg_result.cmd_tuples
|
27
|
+
end
|
28
|
+
|
29
|
+
def update(data, return_data = false)
|
30
|
+
@_command = "UPDATE #{@table} SET #{@column_name} = #{@column_name} || #{next_placeholder}::jsonb"
|
31
|
+
@values << data.to_json
|
32
|
+
|
33
|
+
if return_data
|
34
|
+
@_returning = "RETURNING #{@column_name}"
|
35
|
+
return run.map { |tuple| deserialize(tuple["#{@column_name}"]) }
|
36
|
+
end
|
37
|
+
|
38
|
+
run.cmd_tuples
|
39
|
+
end
|
40
|
+
|
41
|
+
def select
|
42
|
+
@_command = "SELECT #{@column_name} FROM #{@table}"
|
43
|
+
pg_result = run
|
44
|
+
pg_result.map { |tuple| deserialize(tuple["#{@column_name}"]) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def delete
|
48
|
+
@_command = "DELETE FROM #{@table}"
|
49
|
+
run.cmd_tuples
|
50
|
+
end
|
51
|
+
|
52
|
+
def count
|
53
|
+
@_command = "SELECT COUNT(*) FROM #{@table}"
|
54
|
+
run.getvalue(0, 0).to_i
|
55
|
+
end
|
56
|
+
|
57
|
+
def where(conditions, values = [])
|
58
|
+
values.size.times { |i| conditions.sub!(/\?/, next_placeholder(i + 1)) }
|
59
|
+
@_where = "WHERE #{conditions}"
|
60
|
+
@values += values
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
def limit(limit, offset = nil)
|
65
|
+
@_limit = "LIMIT #{next_placeholder}"
|
66
|
+
@values << limit
|
67
|
+
self.offset(offset) if offset
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
def offset(offset)
|
72
|
+
@_offset = "OFFSET #{next_placeholder}"
|
73
|
+
@values << offset
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
def order(*args)
|
78
|
+
fields = []
|
79
|
+
args.first.each_pair { |field, order| fields << "#{wrap_json(field)} #{order}" }
|
80
|
+
@_order = "ORDER BY #{fields.join(', ')}"
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def deserialize(data)
|
87
|
+
JSON.parse(data, symbolize_names: true)
|
88
|
+
end
|
89
|
+
|
90
|
+
def next_placeholder(offset = 1)
|
91
|
+
"$#{@values.size + offset}"
|
92
|
+
end
|
93
|
+
|
94
|
+
def wrap_json(field)
|
95
|
+
"#{@column_name}->>'#{field}'"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
metadata
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hugg-orm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Olle Johansson
|
8
|
+
- Lúcio Rosa
|
9
|
+
- Avidity
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2016-10-31 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: pg
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - "~>"
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0.8'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - "~>"
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '0.8'
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: activesupport
|
31
|
+
requirement: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - "~>"
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '4.2'
|
36
|
+
type: :runtime
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - "~>"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '4.2'
|
43
|
+
- !ruby/object:Gem::Dependency
|
44
|
+
name: bundler
|
45
|
+
requirement: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '1.11'
|
50
|
+
type: :development
|
51
|
+
prerelease: false
|
52
|
+
version_requirements: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - "~>"
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '1.11'
|
57
|
+
- !ruby/object:Gem::Dependency
|
58
|
+
name: rake
|
59
|
+
requirement: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '10.0'
|
64
|
+
type: :development
|
65
|
+
prerelease: false
|
66
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - "~>"
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '10.0'
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: rspec
|
73
|
+
requirement: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - "~>"
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '3.0'
|
78
|
+
type: :development
|
79
|
+
prerelease: false
|
80
|
+
version_requirements: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - "~>"
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '3.0'
|
85
|
+
description: Ruby Object Relational Mapper for JSON-based models with PostgreSQL
|
86
|
+
email:
|
87
|
+
- code@avidity.se
|
88
|
+
executables: []
|
89
|
+
extensions: []
|
90
|
+
extra_rdoc_files: []
|
91
|
+
files:
|
92
|
+
- lib/hugg_orm.rb
|
93
|
+
- lib/hugg_orm/connection.rb
|
94
|
+
- lib/hugg_orm/persistence.rb
|
95
|
+
- lib/hugg_orm/query.rb
|
96
|
+
- lib/hugg_orm/version.rb
|
97
|
+
homepage: https://github.com/avidity/hugg-orm
|
98
|
+
licenses:
|
99
|
+
- MIT
|
100
|
+
metadata: {}
|
101
|
+
post_install_message:
|
102
|
+
rdoc_options: []
|
103
|
+
require_paths:
|
104
|
+
- lib
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
requirements: []
|
116
|
+
rubyforge_project:
|
117
|
+
rubygems_version: 2.5.1
|
118
|
+
signing_key:
|
119
|
+
specification_version: 4
|
120
|
+
summary: Ruby ORM for Postgresql
|
121
|
+
test_files: []
|