menilite 0.1.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: 8bd4f42ddb852546849eaf34e1ad3247d19b7365
4
+ data.tar.gz: 572240d87bfb87dc8c75747a427e3e6e4ed08afe
5
+ SHA512:
6
+ metadata.gz: 6c3cfffbe205a5c878560d0fa28aee2d357ce4351b277c8f134dcdf6c6257d0a3aae86f0eb500eb37e1ee54cfece83c3d66a4c211179bde49f596afaff4fd30b
7
+ data.tar.gz: 157c094db9e46ecf109730510cdcbfa8c82d7592d99d0f37a47708c20fa78d3b096349a331f44e0566c64de2bb731db383fa67e98a8304b01b2b175810f8c68a
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.12.5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in menilite.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # Menilite
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/menilite`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'menilite'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install menilite
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/menilite.
36
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "menilite"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/lib/menilite.rb ADDED
@@ -0,0 +1,16 @@
1
+ require "menilite/version"
2
+
3
+ if RUBY_ENGINE == "opal"
4
+ require 'menilite/model'
5
+ require 'menilite/controller'
6
+ require 'menilite/client/store'
7
+ else
8
+ require 'opal'
9
+ require 'menilite/model'
10
+ require 'menilite/controller'
11
+ require 'menilite/router'
12
+ require 'menilite/server/activerecord_store'
13
+
14
+ Opal.append_path File.expand_path('../', __FILE__).untaint
15
+ Opal.append_path File.expand_path('../../vendor', __FILE__).untaint
16
+ end
@@ -0,0 +1,84 @@
1
+ require 'browser/http'
2
+
3
+ module Menilite
4
+ class Store
5
+ def initialize
6
+ @tables = {}
7
+ end
8
+
9
+ def self.instance
10
+ @instance ||= Store.new
11
+ end
12
+
13
+ def register(model_class)
14
+ @tables[model_class] = {}
15
+ end
16
+
17
+ def [](model_class)
18
+ @tables[model_class]
19
+ end
20
+
21
+ def find(model_class, id)
22
+ return self[model_class][id] if self[model_class][id]
23
+
24
+ res = Browser::HTTP.get!("api/#{model_class.to_s}/#{id}")
25
+ model = model_class.new(res.json)
26
+ @tables[model_class][id] = model
27
+ end
28
+
29
+ def save(model)
30
+ is_array = model.is_a?(Array)
31
+ models = is_array ? model : [ model ]
32
+ model_class = models.first.class
33
+ table = @tables[model_class]
34
+ Browser::HTTP.post("api/#{model_class.to_s}", models.to_json) do
35
+ on :success do |res|
36
+ results = res.json.map do |value|
37
+ if table.has_key?(value[:id])
38
+ table[value[:id]].update(value)
39
+ table[value[:id]]
40
+ else
41
+ table[value[:id]] = model_class.new(value)
42
+ end
43
+ end
44
+
45
+ yield(is_array ? results : results.first) if block_given?
46
+ end
47
+
48
+ on :failure do |res|
49
+ puts ">> Error: #{res.error}"
50
+ puts ">>>> save: #{model.inspect}"
51
+ end
52
+ end
53
+ end
54
+
55
+ def fetch(model_class, filter: nil, order: nil, &block)
56
+ tables = @tables
57
+ params = filter && (?? + filter.map {|k,v| "#{k}=#{v}" }.join(?&))
58
+ params = (params ? params + ?& : ??) + "order=#{[order].flatten.join(?,)}" if order
59
+ Browser::HTTP.get("api/#{model_class}#{params}") do
60
+ on :success do |res|
61
+ tables[model_class] = res.json.map {|value| [value[:id], model_class.new(value)] }.to_h
62
+ yield tables[model_class].values if block_given?
63
+ end
64
+
65
+ on :failure do |res|
66
+ puts ">> Error: #{res.error}"
67
+ puts ">>>> save: #{model.inspect}"
68
+ end
69
+ end
70
+ end
71
+
72
+ def delete(mdoel_class)
73
+ @tables[model_class] = {}
74
+ end
75
+
76
+ def max(model_class, field_name)
77
+ res = Browser::HTTP.get!("api/#{model_class}?order=#{field_name}")
78
+ if res.json.last
79
+ model = model_class.new(res.json.last)
80
+ model.fields[field_name]
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,66 @@
1
+ if RUBY_ENGINE == 'opal'
2
+ require 'browser/http'
3
+ require 'opal-parser'
4
+ end
5
+
6
+ module Menilite
7
+ class Controller
8
+ unless RUBY_ENGINE == 'opal'
9
+ def initialize(session, settings)
10
+ @settings = settings
11
+ @session = session
12
+ end
13
+ end
14
+
15
+ def session
16
+ @session
17
+ end
18
+
19
+ def settings
20
+ @settings
21
+ end
22
+
23
+ class << self
24
+ def action_info
25
+ @action_info ||= {}
26
+ end
27
+
28
+ def before_action_handlers
29
+ @before_action_handlers ||= []
30
+ end
31
+
32
+ ActionInfo = Struct.new(:name, :args, :options)
33
+
34
+ def action(name, options = {}, &block)
35
+ action_info[name.to_s] = ActionInfo.new(name, block.parameters, options)
36
+ if RUBY_ENGINE == 'opal'
37
+ method = Proc.new do |*args, &callback| # todo: should adopt keyword parameters
38
+ action_url = self.respond_to?(:namespace) ? "api/#{self.class.namespace}/#{name}" : "api/#{name}"
39
+ post_data = {}
40
+ post_data[:args] = args
41
+ Browser::HTTP.post(action_url, post_data.to_json) do
42
+ on :success do |res|
43
+ callback.call(:success, res) if callback
44
+ end
45
+
46
+ on :failure do |res|
47
+ callback.call(:failure, res) if callback
48
+ end
49
+ end
50
+ end
51
+ self.instance_eval do
52
+ define_singleton_method(name, method)
53
+ end
54
+ else
55
+ self.instance_eval do
56
+ define_method(name, block)
57
+ end
58
+ end
59
+ end
60
+
61
+ def before_action(options = {}, &block)
62
+ before_action_handlers << { proc: block, options: options }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,282 @@
1
+ require 'securerandom'
2
+ if RUBY_ENGINE == 'opal'
3
+ require 'browser/http'
4
+ require 'opal-parser'
5
+ end
6
+
7
+ class String
8
+ def camel_case
9
+ return self if self !~ /_/ && self =~ /[A-Z]+.*/
10
+ split('_').map{|e| e.capitalize}.join
11
+ end
12
+ end
13
+
14
+ module Menilite
15
+ class Model
16
+ attr_reader :fields
17
+
18
+ def initialize(fields = {})
19
+ self.class.init
20
+
21
+ if RUBY_ENGINE == 'opal'
22
+ fields = fields.clone
23
+ else
24
+ fields = fields.map{|k,v| [k.to_sym, v] }.to_h
25
+ end
26
+
27
+ defaults = self.class.field_info.map{|k, d| [d.name, d.params[:default]] if d.params.has_key?(:default) }.compact.to_h
28
+ @guid = fields.delete(:id) || SecureRandom.uuid
29
+ @fields = defaults.merge(fields)
30
+ @listeners = {}
31
+ end
32
+
33
+ def id
34
+ @guid
35
+ end
36
+
37
+ def save(&block)
38
+ self.class.store.save(self, &block)
39
+ self
40
+ end
41
+
42
+ def update(data)
43
+ case data
44
+ when self.class
45
+ @fields.merge(data.fields)
46
+ when Hash
47
+ @fields.merge(data)
48
+ when String
49
+ @fields.merge(JSON.parse(json))
50
+ end
51
+ end
52
+
53
+ def update!(data, &block)
54
+ self.update(data)
55
+ self.save(&block)
56
+ end
57
+
58
+ def on(event, *field_names, &block)
59
+ field_names.each {|file_name| set_listener(event, file_name, &block) }
60
+ end
61
+
62
+ def handle_event(event, field_name, value)
63
+ get_listeners(event, field_name).each do |listener|
64
+ listener.call(value)
65
+ end
66
+ value
67
+ end
68
+
69
+ class << self
70
+ def init
71
+ if Model.subclasses.has_key?(self) && !Model.subclasses[self]
72
+ store.register(self)
73
+ Model.subclasses[self] = true
74
+ end
75
+ end
76
+
77
+ def field_info
78
+ @field_info ||= {}
79
+ end
80
+
81
+ def action_info
82
+ @action_info ||= {}
83
+ end
84
+
85
+ def save(collection, &block)
86
+ self.init
87
+ self.store.save(collection, &block)
88
+ end
89
+
90
+ def create(fields={}, &block)
91
+ self.init
92
+ self.new(fields).save(&block)
93
+ end
94
+
95
+ def delete_all
96
+ self.init
97
+ store.delete(self)
98
+ end
99
+
100
+ def fetch(filter: nil, order: nil)
101
+ self.init
102
+ filter = filter.map{|k, v| type_convert(k, v) }.to_h if filter
103
+ store.fetch(self, filter: filter, order: order) do |list|
104
+ yield list if block_given?
105
+ list
106
+ end
107
+ end
108
+
109
+ def type_convert(key, value)
110
+ field_info = self.field_info[key.to_s] || self.field_info[key.to_s.sub(/_id\z/,'')]
111
+ raise "no such field #{key} in #{self}" unless field_info
112
+ converted = case field_info.type
113
+ when :boolean
114
+ value.is_a?(String) ? (value == 'true' ? true : false) : value
115
+ else
116
+ value
117
+ end
118
+ [key, converted]
119
+ end
120
+
121
+ def store
122
+ Store.instance
123
+ end
124
+
125
+ def subclasses
126
+ @subclasses ||= {}
127
+ end
128
+
129
+ def inherited(child)
130
+ subclasses[child] = false
131
+ end
132
+
133
+ FieldInfo = Struct.new(:name, :type, :params)
134
+
135
+ def field(name, type = :string, params = {})
136
+ params.merge!(client: true, server: true)
137
+
138
+ if RUBY_ENGINE == 'opal'
139
+ return unless params[:client]
140
+ else
141
+ return unless params[:server]
142
+ end
143
+
144
+ field_info[name.to_s] = FieldInfo.new(name, type, params)
145
+
146
+ self.instance_eval do
147
+ if type == :reference
148
+ field_name = "#{name}_id"
149
+
150
+ define_method(name) do
151
+ id = @fields[field_name.to_sym]
152
+ model_class = Object.const_get(name.to_s.camel_case)
153
+ model_class[id]
154
+ end
155
+
156
+ define_method(name.to_s + "=") do |value|
157
+ @fields[field_name.to_sym] = value.id
158
+ end
159
+ else
160
+ field_name = name.to_s
161
+ end
162
+
163
+ define_method(field_name) do
164
+ @fields[field_name.to_sym]
165
+ end
166
+
167
+ define_method(field_name + "=") do |value|
168
+ unless type_validator(type).call(value, name)
169
+ raise 'type error'
170
+ end
171
+ @fields[field_name.to_sym] = value
172
+ handle_event(:change, field_name.to_sym, value)
173
+ end
174
+ end
175
+ end
176
+
177
+ ActionInfo = Struct.new(:name, :args, :options)
178
+
179
+ def action(name, options = {}, &block)
180
+ action_info[name.to_s] = ActionInfo.new(name, block.parameters, options)
181
+ if RUBY_ENGINE == 'opal'
182
+ method = Proc.new do |model, *args, &callback| # todo: should adopt keyword parameters
183
+ action_url = options[:on_create] || options[:class] ? "api/#{self}/#{name}" : "api/#{self}/#{model.id}/#{name}"
184
+ post_data = {}
185
+ post_data[:model] = model.to_h if options[:on_create]
186
+ post_data[:args] = args
187
+ Browser::HTTP.post(action_url, post_data.to_json) do
188
+ on :success do |res|
189
+ callback.call(:success, res) if callback
190
+ end
191
+
192
+ on :failure do |res|
193
+ callback.call(:failure, res) if callback
194
+ end
195
+ end
196
+ end
197
+ self.instance_eval do
198
+ if options[:class]
199
+ define_singleton_method(name) {|*args, &callback| method.call(self, *args, &callback) }
200
+ else
201
+ define_method(name) {|*args, &callback| method.call(self, *args, &callback) }
202
+ end
203
+ end
204
+ else
205
+ self.instance_eval do
206
+ if options[:class]
207
+ define_singleton_method(name, block)
208
+ else
209
+ define_method(name, block)
210
+ end
211
+ end
212
+ end
213
+ end
214
+
215
+ def find(id)
216
+ self.init
217
+
218
+ case id
219
+ when String
220
+ self[id]
221
+ when Hash
222
+ self.fetch(filter:id).first
223
+ end
224
+
225
+ end
226
+
227
+ def [](id)
228
+ self.init
229
+ store.find(self, id)
230
+ end
231
+
232
+ def max(field_name)
233
+ self.init
234
+ store.max(self, field_name)
235
+ end
236
+ end
237
+
238
+ def type_validator(type)
239
+ case type
240
+ when :string
241
+ -> (value, name) { value.is_a? String }
242
+ when :int
243
+ -> (value, name) { value.is_a? Integer }
244
+ when :boolean
245
+ -> (value, name) { value == true || value == false }
246
+ when :date
247
+ -> (value, name) { value.is_a? Date }
248
+ when :time
249
+ -> (value, name) { value.is_a? Time }
250
+ when :reference
251
+ -> (value, name) { valiedate_reference(value, name) }
252
+ end
253
+ end
254
+
255
+ def valiedate_reference(value, name)
256
+ return false unless value.is_a? String
257
+
258
+ model_class = Object.const_get(name.camel_case)
259
+ not model_class[value].nil?
260
+ end
261
+
262
+ def to_h
263
+ @fields.merge(id: @guid)
264
+ end
265
+
266
+ def to_json(arg)
267
+ @fields.merge(id: @guid).to_json
268
+ end
269
+
270
+ private
271
+
272
+ def get_listeners(event, field_name)
273
+ @listeners[event].try {|l1| l1[field_name] || [] } || []
274
+ end
275
+
276
+ def set_listener(event, field_name, &block)
277
+ @listeners[event] ||= {}
278
+ @listeners[event][field_name] ||= []
279
+ @listeners[event][field_name] << block
280
+ end
281
+ end
282
+ end
@@ -0,0 +1,104 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/json'
3
+ require 'json'
4
+
5
+ class Class
6
+ def subclass_of?(klass)
7
+ raise ArgumentError.new unless klass.is_a?(Class)
8
+
9
+ if self == klass
10
+ true
11
+ else
12
+ if self.superclass
13
+ self.superclass.subclass_of?(klass)
14
+ else
15
+ false
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ module Menilite
22
+ class Router
23
+ def initialize(*classes)
24
+ @classes = classes
25
+ end
26
+
27
+ def before_action_handlers(klass, action)
28
+ @handlers ||= @classes.select{|c| c.subclass_of?(Menilite::Controller) }.map{|c| c.before_action_handlers }.flatten
29
+ @handlers.reject do |c|
30
+ [c[:options][:expect]].flatten.any? do |expect|
31
+ (classname, _, name) = expect.to_s.partition(?#)
32
+ (classname == klass.name) && (name.empty? || name == action.to_s)
33
+ end
34
+ end
35
+ end
36
+
37
+ def routes(settings)
38
+ classes = @classes
39
+ router = self
40
+ Sinatra.new do
41
+ enable :sessions
42
+
43
+ classes.each do |klass|
44
+ case
45
+ when klass.subclass_of?(Menilite::Model)
46
+ klass.init
47
+ resource_name = klass.name
48
+ get "/#{resource_name}" do
49
+ router.before_action_handlers(klass, 'index').each {|h| self.instance_eval(&h[:proc]) }
50
+ order = params.delete('order')&.split(?,)
51
+ data = klass.fetch(filter: params, order: order)
52
+ json data.map(&:to_h)
53
+ end
54
+
55
+ get "/#{resource_name}/:id" do
56
+ router.before_action_handlers(klass, 'get').each {|h| self.instance_eval(&h[:proc]) }
57
+ json klass[params[:id]].to_h
58
+ end
59
+
60
+ post "/#{resource_name}" do
61
+ router.before_action_handlers(klass, 'post').each {|h| self.instance_eval(&h[:proc]) }
62
+ data = JSON.parse(request.body.read)
63
+ results = data.map do |model|
64
+ instance = klass.new model.map{|key, value| [key.to_sym, value] }.to_h
65
+ instance.save
66
+ instance
67
+ end
68
+
69
+ json results.map(&:to_h)
70
+ end
71
+
72
+ klass.action_info.each do |name, action|
73
+ path = action.options[:on_create] || action.options[:class] ? "/#{resource_name}/#{action.name}" : "/#{resource_name}/#{action.name}/:id"
74
+
75
+ post path do
76
+ router.before_action_handlers(klass, action.name).each {|h| self.instance_eval(&h[:proc]) }
77
+ data = JSON.parse(request.body.read)
78
+ result = if action.options[:on_create]
79
+ klass.new(data["model"]).send(action.name, *data["args"])
80
+ elsif action.options[:class]
81
+ klass.send(action.name, *data["args"])
82
+ else
83
+ klass[params[:id]].send(action.name, *data["args"])
84
+ end
85
+ json result
86
+ end
87
+ end
88
+ when klass.subclass_of?(Menilite::Controller)
89
+ klass.action_info.each do |name, action|
90
+ path = klass.respond_to?(:namespace) ? "/#{klass.namespace}/#{action.name}" : "/#{action.name}"
91
+ post path do
92
+ router.before_action_handlers(klass, action.name).each {|h| self.instance_eval(&h[:proc]) }
93
+ data = JSON.parse(request.body.read)
94
+ controller = klass.new(session, settings)
95
+ result = controller.send(action.name, *data["args"])
96
+ json result
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,116 @@
1
+ require 'sinatra/activerecord'
2
+
3
+ module Menilite
4
+ module ActiveRecord
5
+ def self.create_model(model_class)
6
+ klass = Class.new(::ActiveRecord::Base) do
7
+ model_class.field_info.select{|name, field| field.type == :reference }.each do |name, field|
8
+ belongs_to field.name, primary_key: 'guid', foreign_key: name + '_guid', class_name: name.capitalize
9
+ #klass.instance_eval { define_method(name + '_id') { send(name + '_guid') } }
10
+ end
11
+ end
12
+ self.const_set(model_class.to_s, klass)
13
+ end
14
+ end
15
+
16
+ class Store
17
+ def initialize
18
+ @tables = {}
19
+ @armodels = {}
20
+ end
21
+
22
+ def self.instance
23
+ @instance ||= Menilite::Store.new
24
+ end
25
+
26
+ def register(model_class)
27
+ @tables[model_class] = {}
28
+ @armodels[model_class] = Menilite::ActiveRecord.create_model(model_class)
29
+ end
30
+
31
+ def armodel(model_class)
32
+ @armodels[model_class]
33
+ end
34
+
35
+ def find(model_class, id)
36
+ ar_obj = @armodels[model_class].find_by(guid: id)
37
+ to_model(ar_obj, model_class) if ar_obj
38
+ end
39
+
40
+ def save(model)
41
+ is_array = model.is_a?(Array)
42
+ models = is_array ? model : [ model ]
43
+ model_class = models.first.class
44
+
45
+ models.each do |m|
46
+ ar_obj = @armodels[model_class].find_by(guid: m.id)
47
+ if ar_obj
48
+ ar_obj.update!(attributes(m))
49
+ else
50
+ @armodels[model_class].create!(attributes(m))
51
+ end
52
+ end
53
+
54
+ yield model if block_given?
55
+ end
56
+
57
+ def fetch(model_class, filter: nil, order: nil)
58
+ assoc = @armodels[model_class].all
59
+
60
+ assoc = assoc.where(filter_condition(model_class, filter)) if filter
61
+ assoc = assoc.order([order].flatten.map(&:to_sym)) if order
62
+
63
+ yield assoc.map {|ar| to_model(ar, model_class) } || [] if block_given?
64
+ end
65
+
66
+ def delete(model_class)
67
+ @armodels[model_class].delete_all
68
+ end
69
+
70
+ def max(model_class, field_name)
71
+ fetch(model_class).max(field_name.to_sym)
72
+ end
73
+
74
+ def to_model(ar_obj, model_class)
75
+ model_class.new(fields(ar_obj, model_class))
76
+ end
77
+
78
+ private
79
+
80
+ def [](model_class)
81
+ @tables[model_class]
82
+ end
83
+
84
+ def filter_condition(model_class, filter)
85
+ references = model_class.field_info.values.select{|i| i.type == :reference}
86
+ filter.clone.tap do |hash|
87
+ references.each do |r|
88
+ hash["#{r.name}_guid".to_sym] = hash.delete("#{r.name}_id".to_sym) if hash.has_key?("#{r.name}_id".to_sym)
89
+ end
90
+
91
+ hash[:guid] = hash.delete(:id) if hash.has_key?(:id)
92
+ end
93
+ end
94
+
95
+ def attributes(model)
96
+ references = model.class.field_info.values.select{|i| i.type == :reference}
97
+ model.to_h.tap do |hash|
98
+ references.each do |r|
99
+ hash["#{r.name}_guid".to_sym] = hash.delete("#{r.name}_id".to_sym)
100
+ end
101
+
102
+ hash[:guid] = hash.delete(:id)
103
+ end
104
+ end
105
+
106
+ def fields(ar_obj, model_class)
107
+ references = model_class.field_info.values.select{|i| i.type == :reference}
108
+ ar_obj.attributes.tap do |hash|
109
+ references.each do |r|
110
+ hash["#{r.name}_id"] = hash.delete("#{r.name}_guid")
111
+ end
112
+ hash["id"] = hash.delete("guid")
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,73 @@
1
+ require 'json'
2
+
3
+ module Menilite
4
+ class Store
5
+ DEFAULT_DB_DIR = './.store'
6
+
7
+ def initialize(db_dir = DEFAULT_DB_DIR)
8
+ @tables = {}
9
+ @db_dir = db_dir
10
+ Dir.mkdir(@db_dir) unless Dir.exist?(@db_dir)
11
+ end
12
+
13
+ def self.instance
14
+ @instance ||= Store.new
15
+ end
16
+
17
+ def register(model_class)
18
+ @tables[model_class] = {}
19
+ end
20
+
21
+ def [](model_class)
22
+ @tables[model_class]
23
+ end
24
+
25
+ def find(model_class, id)
26
+ return self[model_class][id] if self[model_class][id]
27
+
28
+ fetch(model_class)
29
+ @tables[model_class][id]
30
+ end
31
+
32
+ def save(model)
33
+ is_array = model.is_a?(Array)
34
+ models = is_array ? model : [ model ]
35
+ model_class = models.first.class
36
+
37
+ models.each do |m|
38
+ @tables[model_class][m.id] = m
39
+ end
40
+
41
+ File.open(filename(model_class), "w") do |file|
42
+ file.write @tables[model_class].values.to_json
43
+ end
44
+
45
+ yield model if block_given?
46
+ end
47
+
48
+ def fetch(model_class, filter: nil, order: nil)
49
+ File.open filename(model_class) do |file|
50
+ records = JSON.parse(file.read)
51
+ records.select! {|r| filter.all? {|k,v| r[k.to_s] == v } } if filter
52
+ records.sort_by!{|r| [order].flatten.map{|o| r[o.to_s] } } if order
53
+ @tables[model_class] = records.map {|m| [m["id"], model_class.new(m)] }.to_h
54
+ end
55
+
56
+ yield @tables[model_class].values || [] if block_given?
57
+ end
58
+
59
+ def delete(model_class)
60
+ filename = self.filename(model_class)
61
+ File.delete(filename) if File.exist?(filename)
62
+ end
63
+
64
+ def max(model_class, field_name)
65
+ fetch(model_class)
66
+ @tables[model_class].reduce(nil) {|v, max| v.fields[field_name] < max ? max : v.fields[field_name] }
67
+ end
68
+
69
+ def filename(model_class)
70
+ @db_dir + "/#{model_class}.db"
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,3 @@
1
+ module Menilite
2
+ VERSION = "0.1.0"
3
+ end
data/menilite.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'menilite/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "menilite"
8
+ spec.version = Menilite::VERSION
9
+ spec.authors = ["youchan"]
10
+ spec.email = ["youchan01@gmail.com"]
11
+
12
+ spec.summary = %q{Isomorphic models between client side opal and server side ruby.}
13
+ spec.description = %q{This is isomorphic models for sharing between client side and server side.}
14
+ spec.homepage = "https://github.com/youchan/menilite"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.12"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec", "~> 3.0"
24
+
25
+ spec.add_runtime_dependency "sinatra-activerecord"
26
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: menilite
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - youchan
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-08-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.12'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sinatra-activerecord
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: This is isomorphic models for sharing between client side and server
70
+ side.
71
+ email:
72
+ - youchan01@gmail.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".travis.yml"
80
+ - Gemfile
81
+ - README.md
82
+ - Rakefile
83
+ - bin/console
84
+ - bin/setup
85
+ - lib/menilite.rb
86
+ - lib/menilite/client/store.rb
87
+ - lib/menilite/controller.rb
88
+ - lib/menilite/model.rb
89
+ - lib/menilite/router.rb
90
+ - lib/menilite/server/activerecord_store.rb
91
+ - lib/menilite/server/store.rb
92
+ - lib/menilite/version.rb
93
+ - menilite.gemspec
94
+ homepage: https://github.com/youchan/menilite
95
+ licenses: []
96
+ metadata: {}
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubyforge_project:
113
+ rubygems_version: 2.5.1
114
+ signing_key:
115
+ specification_version: 4
116
+ summary: Isomorphic models between client side opal and server side ruby.
117
+ test_files: []