conjoin 0.0.1 → 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.
@@ -0,0 +1,92 @@
1
+ module Conjoin
2
+ module Auth
3
+ if Conjoin.env.mounted?
4
+ require 'warden'
5
+ require 'devise'
6
+ include Devise::TestHelpers
7
+ end
8
+
9
+ class << self
10
+ attr_accessor :app
11
+ end
12
+
13
+ def self.setup app
14
+ self.app = app
15
+
16
+ if not Conjoin.env.mounted?
17
+ require 'shield'
18
+ app.plugin Shield::Helpers
19
+ app.use Shield::Middleware, "/login"
20
+ end
21
+ end
22
+
23
+ def login_path
24
+ req.env['REQUEST_URI'][/login/] ? true : false
25
+ end
26
+
27
+ def logout_path
28
+ req.env['REQUEST_URI'][/logout/] ? true : false
29
+ end
30
+
31
+ def current_user
32
+ if not Conjoin.env.mounted?
33
+ authenticated(VendorWizard::User)
34
+ else
35
+ req.env['warden'].authenticate(scope: :user)
36
+ end
37
+ end
38
+
39
+ def logged_in?
40
+ current_user ? true : false
41
+ end
42
+
43
+ def sign_in *args
44
+ if args.length > 1
45
+ user, scope = args
46
+ else
47
+ scope = :user
48
+ user = args.first
49
+ end
50
+
51
+ if Auth.app.mounted?
52
+ @request = req
53
+ super scope, user
54
+ else
55
+ session.clear
56
+ session['VendorWizard::User'] = user.id
57
+ end
58
+ end
59
+
60
+ class Routes < Conjoin::Cuba
61
+ plugin Auth
62
+
63
+ define do
64
+ on login_path do
65
+ on get do
66
+ user = UserLoginForm.new
67
+ res.write view("login", user: user)
68
+ end
69
+
70
+ on post do
71
+ user = UserLoginForm.new
72
+ user.attributes = req[:user_login]
73
+
74
+ if user.valid_only?(:email, :password) && login(User, user.email, user.password)
75
+ res.redirect req[:return] || '/'
76
+ else
77
+ user.password = nil
78
+ end
79
+
80
+ res.write view("login", user: user)
81
+ end
82
+ end
83
+
84
+ on logout_path do
85
+ logout User
86
+
87
+ res.redirect '/'
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,15 @@
1
+ module Conjoin
2
+ module ClassMethods
3
+ attr_accessor :root
4
+
5
+ def env
6
+ @env ||= EnvString.new(
7
+ ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
8
+ )
9
+ end
10
+ end
11
+
12
+ def self.included(base)
13
+ base.extend(ClassMethods)
14
+ end
15
+ end
@@ -0,0 +1,26 @@
1
+ require "rack/csrf"
2
+
3
+ module Conjoin
4
+ module Csrf
5
+ # Public: Sugar to include a csrf tag
6
+ #
7
+ # Examples:
8
+ #
9
+ # <form action="/new">
10
+ # <%= csrf_tag %>
11
+ # <input type="text" />
12
+ # </form>
13
+ def csrf_tag
14
+ Rack::Csrf.tag(env)
15
+ end
16
+
17
+ # Public: Sugar to access the csrf token
18
+ #
19
+ # Examples:
20
+ #
21
+ # <%= csrf_token %>
22
+ def csrf_token
23
+ Rack::Csrf.token(env)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,72 @@
1
+ require "cuba"
2
+ require "cuba/render"
3
+
4
+ module Conjoin
5
+ class Cuba < ::Cuba
6
+ class << self
7
+ def settings= settings
8
+ @settings = settings
9
+ end
10
+
11
+ def root; @root ||= Dir.pwd; end
12
+ def root= root
13
+ @root = root
14
+ end
15
+
16
+ def initialize!
17
+ # Initializers
18
+ Dir["#{root}/config/initializers/**/*.rb"].each { |rb| require rb }
19
+
20
+ # Permissions
21
+ Dir["#{root}/app/models/permissions/**/*.rb"].each {|rb| require rb }
22
+ Dir["#{root}/app/permissions/**/*.rb"].each {|rb| require rb }
23
+
24
+ # Models
25
+ Dir["#{root}/app/models/*/*.rb"].each {|rb| require rb }
26
+ Dir["#{root}/app/models/**/*.rb"].each {|rb| require rb }
27
+
28
+ # Forms
29
+ Dir["#{root}/app/forms/*/*.rb"].each {|rb| require rb }
30
+ Dir["#{root}/app/forms/**/*.rb"].each {|rb| require rb }
31
+
32
+ # Assets
33
+ require "#{root}/config/assets"
34
+
35
+ # Presenters
36
+ Dir["#{root}/app/presenters/**/*.rb"].each { |rb| require rb }
37
+
38
+ # Mailers
39
+ Dir["#{root}/app/mailers/**/*.rb"].each { |rb| require rb }
40
+
41
+ # Routes
42
+ Dir["#{root}/app/routes/**/*.rb"].each { |rb| require rb }
43
+ require "#{root}/config/routes"
44
+ end
45
+ end
46
+
47
+ module Render
48
+ include ::Cuba::Render
49
+
50
+ def self.setup(app)
51
+ app.settings[:render] ||= {}
52
+ app.settings[:render][:template_engine] ||= "slim"
53
+ app.settings[:render][:layout] ||= "layouts/app"
54
+ app.settings[:render][:views] ||= "#{app.root}/app/views"
55
+ app.settings[:render][:options] ||= {
56
+ default_encoding: Encoding.default_external
57
+ }
58
+ end
59
+
60
+ alias original_partial partial
61
+
62
+ def view(template, locals = {}, layout = settings[:render][:layout])
63
+ original_partial(layout, { content: original_partial(template, locals) }.merge(locals))
64
+ end
65
+
66
+ def partial template, locals = {}
67
+ partial_template = template.gsub(/([a-zA-Z_]+)$/, '_\1')
68
+ render(template_path(partial_template), locals, settings[:render][:options])
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,17 @@
1
+ module Conjoin
2
+ class EnvString < String
3
+ [:production, :development, :test, :staging].each do |env|
4
+ define_method "#{env}?" do
5
+ self == env.to_s
6
+ end
7
+ end
8
+
9
+ def mounted?
10
+ defined?(::Rails) ? true : false
11
+ end
12
+
13
+ def console?
14
+ ENV['CONJOIN_CONSOLE'] ? true : false
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,334 @@
1
+ require "rack/csrf"
2
+
3
+ module Conjoin
4
+ module FormBuilder
5
+ INPUTS = [
6
+ :boolean, :checkbox, :date, :decimal, :file, :hidden,
7
+ :integer, :password, :radio, :select, :state, :string,
8
+ :time
9
+ ]
10
+
11
+ def self.setup app
12
+ require 'mab/kernel_method'
13
+
14
+ app.use Rack::Csrf
15
+ app.plugin Conjoin::Csrf
16
+ # Dir["#{File.dirname(__FILE__)}/plugin/inputs/**/*.rb"].each { |rb| require rb }
17
+ INPUTS.each do |input|
18
+ require_relative "inputs/#{input}"
19
+ end
20
+
21
+ Dir["#{app.root}/app/inputs/**/*.rb"].each { |rb| require rb }
22
+ end
23
+
24
+ def form_for record, options = {}, &block
25
+ raise ArgumentError, "Missing block" unless block_given?
26
+
27
+ if as = options.delete(:as)
28
+ model_name = as
29
+ elsif record.is_form?
30
+ model_name = record.class.model_name.to_s.gsub(/\w+::/, '').gsub(/Form$/, '').underscore
31
+ else
32
+ model_name = record.class.model_name.singular
33
+ end
34
+
35
+ fields = Fields.new self, [model_name], record, block
36
+
37
+ form_options = {
38
+ class: 'form-horizontal',
39
+ role: 'form',
40
+ method: 'post',
41
+ novalidate: 'true',
42
+ remote: true,
43
+ action: options.delete(:url) || "/" + record.class.model_name.plural
44
+ }.merge! options
45
+
46
+ if form_options.delete(:remote)
47
+ form_options['data-remote'] = 'true'
48
+ end
49
+
50
+ mab do
51
+ form form_options do
52
+ input type: 'hidden', name: '_method', value: (record.id ? 'patch' : 'post')
53
+ text! csrf_tag
54
+ text! fields.render
55
+ end
56
+ end
57
+ end
58
+
59
+ class Fields < Struct.new(:app, :models, :record, :block, :index)
60
+ def render
61
+ html = block.call(self, index)
62
+ names = [].concat models
63
+ names << 'id'
64
+
65
+ mab do
66
+ if record.id
67
+ input type: 'hidden', name: nested_names_for(names), value: record.id
68
+ end
69
+ text! html
70
+ end
71
+ end
72
+
73
+ def submit options = {}
74
+ mab do
75
+ input type: 'submit',
76
+ value: options[:value] || 'Submit',
77
+ class: 'btn'
78
+ end
79
+ end
80
+
81
+ def nested_names_for names
82
+ # create field names that map to the correct models
83
+ names.each_with_index.map do |field, i|
84
+ i != 0 ? "[#{field}]" : field
85
+ end.join
86
+ end
87
+
88
+ def association field_name, options = {}
89
+ options[:is_association] = true
90
+ input field_name, options
91
+ end
92
+
93
+ def input_field field_name, options = {}
94
+ options[:wrapper] = false
95
+ input field_name, options
96
+ end
97
+
98
+ def input field_name, options = {}
99
+ names = [].concat models
100
+ if options.delete(:is_association)
101
+ names << "#{field_name.to_s.singularize}_ids"
102
+ names << ''
103
+ else
104
+ names << field_name
105
+ end
106
+
107
+ # create field names that map to the correct models
108
+ nested_name = nested_names_for names
109
+
110
+ record_class = record.class.model_name.name.constantize
111
+
112
+ if as = options.delete(:as)
113
+ record_type = as.to_s.classify
114
+ elsif record_class.mini_record_columns \
115
+ and mini_column = record_class.mini_record_columns[field_name] \
116
+ and input_as = mini_column[:input_as]
117
+ record_type = input_as.to_s.classify
118
+ else
119
+ record_type = record_class.columns_hash[field_name.to_s].type.to_s.classify
120
+ end
121
+
122
+ if mini_column and opts = mini_column[:input_options]
123
+ options = opts.merge options
124
+ end
125
+
126
+ input_class = "Conjoin::FormBuilder::#{record_type}Input".constantize
127
+
128
+ data = OpenStruct.new({
129
+ name: nested_name,
130
+ record: record,
131
+ value: record.send(field_name),
132
+ options: options,
133
+ errors: record.errors.messages[field_name],
134
+ names: names
135
+ })
136
+
137
+ new_input = input_class.new data, app, record
138
+
139
+ if record_type != 'Hidden' \
140
+ and not options.key(:wrapper) and options[:wrapper] != false
141
+ wrapper field_name.to_s, nested_name, new_input, options
142
+ else
143
+ new_input.render
144
+ end
145
+ end
146
+
147
+ def fields_for field_name, options = {}, &block
148
+ names = [].concat models
149
+
150
+ associated_record = record.send field_name
151
+
152
+ if scope = options.delete(:scope)
153
+ associated_record = associated_record.send(scope, *options.delete(:scope_args))
154
+ end
155
+
156
+ if select = options.delete(:select)
157
+ associated_record = Hash[associated_record.each_with_index.map {|a, i| [i, a]}]
158
+
159
+ select.each do |key, select_array|
160
+ select_array = [select_array] unless select_array.is_a? Array
161
+
162
+ associated_record.select! do |k, v|
163
+ select_array.include? :"#{v[key]}"
164
+ end
165
+ end
166
+ end
167
+
168
+ if name = options.delete(:name)
169
+ field_name = name
170
+ end
171
+
172
+ names << "#{field_name}_attributes"
173
+
174
+ if !associated_record.kind_of? ActiveRecord::Associations::CollectionProxy \
175
+ and !associated_record.kind_of? ActiveRecord::AssociationRelation \
176
+ and !associated_record.kind_of? Array \
177
+ and !associated_record.kind_of? Hash
178
+ fields = Fields.new app, names, associated_record, block, 0
179
+ fields.render
180
+ else
181
+ html = ''
182
+ if associated_record.kind_of? Array
183
+ associated_record.each_with_index do |current_record, i|
184
+ nested_names = [].concat names
185
+ nested_names << i
186
+
187
+ fields = Fields.new app, nested_names, current_record, block, i
188
+ html += fields.render
189
+ end
190
+ else
191
+ associated_record.each do |i, current_record|
192
+ nested_names = [].concat names
193
+ nested_names << i
194
+
195
+ fields = Fields.new app, nested_names, current_record, block, i
196
+ html += fields.render
197
+ end
198
+ end
199
+
200
+ html
201
+ end
202
+
203
+ # rescue
204
+ # raise "No associated record #{field_name} for #{record.class}"
205
+ end
206
+
207
+ def errors_for attr
208
+ mab do
209
+ span class: 'has-error has-feedback form-control-feedback' do
210
+ text! record.errors.messages[attr.to_sym].try :join, ', '
211
+ end
212
+ end
213
+ end
214
+
215
+ private
216
+
217
+ def id_for field_name
218
+ field_name.gsub(/[^a-z0-9]/, '_').gsub(/__/, '_').gsub(/_$/, '')
219
+ end
220
+
221
+ def required?(obj, attr, options)
222
+ if options.key?(:required)
223
+ options[:required]
224
+ else
225
+ target = (obj.class == Class) ? obj : obj.class
226
+ presence = target.validators_on(attr).select { |t| t.class.to_s == 'ActiveRecord::Validations::PresenceValidator' }
227
+ if presence.any?
228
+ is_required = true
229
+
230
+ presence.each do |p|
231
+ if p.options[:if]
232
+ is_required &= p.options[:if].call(record)
233
+ end
234
+
235
+ if p.options[:unless]
236
+ is_required &= !p.options[:if].call(record)
237
+ end
238
+ end
239
+ else
240
+ is_required = false
241
+ end
242
+
243
+ is_required
244
+ end
245
+ end
246
+
247
+ def errors? obj, attr
248
+ obj.errors.messages[attr.to_sym]
249
+ end
250
+
251
+ def wrapper field_name, nested_name, input, options
252
+ if w = options[:wrapper] and w.is_a? String
253
+ label_width, input_width = w.split ','
254
+ else
255
+ label_width, input_width = [3, 9]
256
+ end
257
+
258
+ mab do
259
+ div class: "form-group #{errors?(record, field_name) ? 'has-error has-feedback' : ''}" do
260
+ label for: id_for(nested_name), class: "control-label col-sm-#{label_width}" do
261
+ if required? record, field_name, options
262
+ abbr title: 'required' do
263
+ text '*'
264
+ end
265
+ end
266
+
267
+ i18n_s = nested_name.gsub(/.+\[([a-z\_]+)\](?:.*|)\[([a-z\_]+)\]$/, 'model.\1.\2').gsub(/_attributes/, '')
268
+
269
+ i18n_name = options[:label] || R18n.t(i18n_s.gsub(/^model/, 'form.label')) | R18n.t.form.label[field_name] | R18n.t(i18n_s) | field_name.titleize
270
+
271
+ text! i18n_name
272
+ end
273
+ div class: "col-sm-#{input_width}" do
274
+ text! input.render
275
+ if errors = errors?(record, field_name)
276
+ span class: 'help-block has-error' do
277
+ text errors.join ', '
278
+ end
279
+ span class: 'fa fa-times form-control-feedback'
280
+ end
281
+ end
282
+ end
283
+ end
284
+ end
285
+ end
286
+
287
+ class Input
288
+ attr_accessor :app, :data, :options, :record
289
+
290
+ def initialize data, app, record
291
+ @data = data
292
+ @app = app
293
+ @record = record
294
+ @options = {
295
+ name: data.name,
296
+ type: :text,
297
+ id: id,
298
+ value: data.value,
299
+ class: ''
300
+ }.merge! data.options
301
+ options[:class] += ' form-control'
302
+ @options
303
+ end
304
+
305
+ def id
306
+ data.name.gsub(/[^a-z0-9]/, '_').gsub(/__/, '_').gsub(/_$/, '')
307
+ end
308
+
309
+ def nested_name
310
+ # create field names that map to the correct models
311
+ data.names.each_with_index.map do |field, i|
312
+ i != 0 ? "[#{field}]" : field
313
+ end.join
314
+ end
315
+
316
+ def errors?
317
+ data.errors
318
+ end
319
+
320
+ def render
321
+ if options[:type] == :hidden \
322
+ or (options.key?(:wrapper) and options[:wrapper] == false)
323
+ options[:class] = options[:class].gsub(/form-control/, '')
324
+ end
325
+
326
+ display
327
+ end
328
+
329
+ def display
330
+ mab { input options }
331
+ end
332
+ end
333
+ end
334
+ end