conjoin 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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