padrino-admin 0.2.9 → 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,82 @@
1
+ require 'openssl'
2
+ require 'digest/sha1'
3
+
4
+ module Padrino
5
+ module Admin
6
+
7
+ class AdapterError < StandardError; end
8
+
9
+ module Adapters
10
+ # This method it's used for register for the specified ORM the extensions.
11
+ def self.register(adapter, klass=nil)
12
+ klass ||= Account
13
+ case adapter
14
+ when :active_record
15
+ ActiveRecord::Base.send(:include, Padrino::Admin::Adapters::Ar::Base)
16
+ klass.send(:include, Padrino::Admin::Adapters::Ar::Account)
17
+ when :data_mapper
18
+ DataMapper::Model.descendants.each { |d| d.send(:include, Padrino::Admin::Adapters::Dm::Base) }
19
+ klass.send(:include, Padrino::Admin::Adapters::Dm::Account)
20
+ when :mongo_mapper
21
+ MongoMapper::Document.append_inclusions(Padrino::Admin::Adapters::Mm::Base)
22
+ klass.send(:include, Padrino::Admin::Adapters::Mm::Account)
23
+ else
24
+ raise Padrino::Admin::AdapterError, "The adapter #{adapter.inspect} is not supported, available adapters are: " +
25
+ ":active_record, :data_mapper, :mongo_mapper"
26
+ end
27
+ end
28
+
29
+ # Here standard extension
30
+ module Base
31
+ # TODO
32
+ end
33
+ # Here extension for account
34
+ #
35
+ # An Account must have these column/fields:
36
+ #
37
+ # role:: String
38
+ # email:: String (used as login)
39
+ # crypted_password:: String
40
+ # salt:: String
41
+ #
42
+ # We add to Account model some methods:
43
+ #
44
+ # Account.authenticate(email, password):: check if exist an account for the given email and return it if password match
45
+ # account.password_clean:: return the decrypted password of an Account instance
46
+ #
47
+ module AccountUtils
48
+
49
+ def self.included(base) #:nodoc:
50
+ base.extend ClassMethods
51
+ base.send :include, InstanceMethods
52
+ end
53
+
54
+ module ClassMethods
55
+ # This method it's for authentication purpose
56
+ def authenticate(email, password)
57
+ account = first(:conditions => { :email => email })
58
+ account && account.password_clean == password ? account : nil
59
+ end
60
+ end
61
+
62
+ module InstanceMethods
63
+ # This method it's used for retrive the original password.
64
+ def password_clean
65
+ crypted_password.decrypt(salt)
66
+ end
67
+
68
+ private
69
+ def generate_password
70
+ return if password.blank?
71
+ self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{email}--") if new_record?
72
+ self.crypted_password = password.encrypt(self.salt)
73
+ end
74
+
75
+ def password_required
76
+ crypted_password.blank? || !password.blank?
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,75 @@
1
+ module Padrino
2
+ module Admin
3
+ module Adapters
4
+ module Ar
5
+
6
+ # Here basic functions for interact with ActiveRecord
7
+ module Base
8
+ def self.included(base)
9
+ base.send :include, Padrino::Admin::Adapters::Base
10
+ base.send :include, InstanceMethods
11
+ base.extend ClassMethods
12
+ base.class_eval do
13
+ named_scope :ext_search, lambda { |params|
14
+ conditions = nil
15
+
16
+ if !params[:query].blank? && !params[:fields].blank?
17
+ filters = params[:fields].split(",").collect { |f| "#{f} LIKE ?" }.compact
18
+ conditions = [filters.join(" OR ")].concat((1..filters.size).collect { "%#{params[:query]}%" })
19
+ end
20
+
21
+ { :conditions => conditions }
22
+ }
23
+ named_scope :ext_paginate, lambda { |params|
24
+ params.symbolize_keys!
25
+ order = params[:sort].blank? && params[:dir].blank? ? nil : "#{params[:sort]} #{params[:dir]}"
26
+ { :order => order, :limit => params[:limit], :offset => params[:start] }
27
+ }
28
+ end
29
+ end
30
+
31
+ module InstanceMethods
32
+ end
33
+
34
+ module ClassMethods
35
+ # Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example:
36
+ # Person.human_attribute_name("first_name") # => "First name"
37
+ # This used to be depricated in favor of humanize, but is now preferred, because it automatically uses the I18n
38
+ # module now.
39
+ # Specify +options+ with additional translating options.
40
+ def human_attribute_name(attribute_key_name, options = {})
41
+ defaults = self_and_descendants.map do |klass|
42
+ :"#{klass.name.underscore}.#{attribute_key_name}"
43
+ end
44
+ defaults << options[:default] if options[:default]
45
+ defaults.flatten!
46
+ defaults << attribute_key_name.to_s.humanize
47
+ options[:count] ||= 1
48
+ I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:model, :attributes]))
49
+ end
50
+ end
51
+ end
52
+
53
+ # Here extension for account for ActiveRecord
54
+ module Account
55
+ # Extend our class when included
56
+ def self.included(base)
57
+ base.send :include, Padrino::Admin::Adapters::AccountUtils
58
+ base.send :attr_accessor, :password
59
+ # Validations
60
+ base.validates_presence_of :email
61
+ base.validates_presence_of :password, :if => :password_required
62
+ base.validates_presence_of :password_confirmation, :if => :password_required
63
+ base.validates_length_of :password, :within => 4..40, :if => :password_required
64
+ base.validates_confirmation_of :password, :if => :password_required
65
+ base.validates_length_of :email, :within => 3..100
66
+ base.validates_uniqueness_of :email, :case_sensitive => false
67
+ base.validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
68
+ # Callbacks
69
+ base.before_save :generate_password
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,131 @@
1
+ module Padrino
2
+ module Admin
3
+ module Adapters
4
+ module Dm
5
+
6
+ # Here basic functions for interact with DataMapper
7
+ module Base
8
+
9
+ def self.included(base)
10
+ base.send :include, Padrino::Admin::Adapters::Base
11
+ base.send :include, InstanceMethods
12
+ base.extend ClassMethods
13
+ end
14
+
15
+ module InstanceMethods
16
+ # This method allow us to don't see deprecations
17
+ def new_record?; new?; end
18
+ end
19
+
20
+ module ClassMethods
21
+ def self_and_descendants #:nodoc:
22
+ klass = self
23
+ classes = [klass]
24
+ while klass != klass.base_class
25
+ classes << klass = klass.superclass
26
+ end
27
+ classes
28
+ rescue
29
+ # OPTIMIZE this rescue is to fix this test: ./test/cases/reflection_test.rb:56:in `test_human_name_for_column'
30
+ # Appearantly the method base_class causes some trouble.
31
+ # It now works for sure.
32
+ [self]
33
+ end
34
+
35
+ # Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example:
36
+ # Person.human_attribute_name("first_name") # => "First name"
37
+ # This used to be depricated in favor of humanize, but is now preferred, because it automatically uses the I18n
38
+ # module now.
39
+ # Specify +options+ with additional translating options.
40
+ def human_attribute_name(attribute_key_name, options = {})
41
+ defaults = self_and_descendants.map do |klass|
42
+ :"#{klass.name.underscore}.#{attribute_key_name}"
43
+ end
44
+ defaults << options[:default] if options[:default]
45
+ defaults.flatten!
46
+ defaults << attribute_key_name.to_s.humanize
47
+ options[:count] ||= 1
48
+ I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:model, :attributes]))
49
+ end
50
+
51
+ # Return the name of the sql table
52
+ def table_name
53
+ storage_names[:default]
54
+ end
55
+
56
+ # Perform a basic fulltext search/ordering for the given columns
57
+ #
58
+ # +params+ is an hash like that:
59
+ # { :sort => "name", :dir => "ASC", :fields=>"name,surname,company", :query => 'daddye' }
60
+ #
61
+ # In this example we search in columns name, surname, company the string daddye and then we order by
62
+ # column +name+
63
+ def ext_search(params)
64
+ params.symbolize_keys!
65
+
66
+ # We need a basic query
67
+ query = {}
68
+
69
+ if params[:query].present? && params[:fields].present?
70
+ # Here we build some like: ["name LIKE ?", "surname LIKE ?"]
71
+ fields = params[:fields].split(",").collect { |f| "#{f.strip.downcase} LIKE ?" }
72
+ # Here we build some like: ["name LIKE ? OR surname LIKE ?", "%foo%", "%foo%"]
73
+ query[:conditions] = [fields.join(" OR ")].concat(1.upto(fields.length).collect { "%#{params[:query]}%" })
74
+ end
75
+
76
+ # Now we can perform a search
77
+ all(query)
78
+ end
79
+
80
+ def ext_paginate(params)
81
+ params.symbolize_keys!
82
+
83
+ # We need a basic query
84
+ query = {}
85
+
86
+ # First we need to sort our record
87
+ if params[:sort].present? && params[:dir].to_s =~ /^(asc|desc)$/
88
+ query[:order] = [params[:sort].to_sym.send(params[:dir].to_s.downcase)]
89
+ end
90
+
91
+ # Now time to limit/offset it
92
+ query[:limit] = params[:limit].to_i if params[:limit].to_i > 0
93
+ query[:offset] = params[:offset].to_i if params[:offset].to_i > 0
94
+
95
+ # Now we can perform ording/limiting
96
+ all(query)
97
+ end
98
+ end
99
+ end
100
+
101
+ # Here extension for account for DataMapper
102
+ module Account
103
+ # Extend our class when included
104
+ def self.included(base)
105
+ super
106
+ base.send :include, Padrino::Admin::Adapters::AccountUtils
107
+ base.send :include, DataMapper::Validate
108
+ base.send :attr_accessor, :password, :password_confirmation
109
+ # Properties
110
+ base.property :email, String
111
+ base.property :crypted_password, String
112
+ base.property :salt, String
113
+ base.property :role, String
114
+ # Validations
115
+ base.validates_present :email, :role
116
+ base.validates_present :password, :if => :password_required
117
+ base.validates_present :password_confirmation, :if => :password_required
118
+ base.validates_length :password, :min => 4, :max => 40, :if => :password_required
119
+ base.validates_is_confirmed :password, :if => :password_required
120
+ base.validates_length :email, :min => 3, :max => 100
121
+ base.validates_is_unique :email, :case_sensitive => false
122
+ base.validates_format :email, :with => :email_address
123
+ base.validates_format :role, :with => /[A-Za-z]/
124
+ # Callbacks
125
+ base.before :save, :generate_password
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,49 @@
1
+ module Padrino
2
+ module Admin
3
+ module Adapters
4
+ module Mm
5
+
6
+ # Here basic functions for interact with MongoMapper
7
+ module Base
8
+ def self.included(base)
9
+ base.send :include, Padrino::Admin::Adapters::Base
10
+ base.send :include, InstanceMethods
11
+ base.extend ClassMethods
12
+ end
13
+
14
+ module InstanceMethods
15
+ end
16
+
17
+ module ClassMethods
18
+ end
19
+ end
20
+
21
+ # Here extension for account for DataMapper
22
+ module Account
23
+ # Extend our class when included
24
+ def self.included(base)
25
+ super
26
+ base.send :include, Padrino::Admin::Adapters::AccountUtils
27
+ base.send :attr_accessor, :password, :password_confirmation
28
+ # Properties
29
+ base.key :email, String
30
+ base.key :crypted_password, String
31
+ base.key :salt, String
32
+ base.key :role, String
33
+ # Validations
34
+ base.validates_presence_of :email
35
+ base.validates_presence_of :password, :if => :password_required
36
+ base.validates_presence_of :password_confirmation, :if => :password_required
37
+ base.validates_length_of :password, :within => 4..40, :if => :password_required
38
+ base.validates_confirmation_of :password, :if => :password_required
39
+ base.validates_length_of :email, :within => 3..100
40
+ base.validates_uniqueness_of :email, :case_sensitive => false
41
+ base.validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
42
+ # Callbacks
43
+ base.before_save :generate_password
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -3,167 +3,169 @@ require 'yaml'
3
3
  require 'erb'
4
4
  require 'json/pure'
5
5
 
6
- module ExtJs
6
+ module Padrino
7
+ module ExtJs
7
8
 
8
- class ConfigError < RuntimeError; end
9
+ class ConfigError < RuntimeError; end
9
10
 
10
- # This class it's used for JSON variables.
11
- # Normally if we convert this { :function => "alert('Test')" } will be:
12
- #
13
- # { "function": "alert('Test')" }
14
- #
15
- # But if in our javascript need to "eval" this function is not possible because
16
- # it's a string.
17
- #
18
- # Using ExtJs::Variable the result will be:
19
- #
20
- # { "function" : alert('Test') }
21
- #
22
- # Normally an ExtJs Variable can be handled with ExtJs Config like:
23
- #
24
- # function: !js alert('Test')
25
- #
26
- class Variable < String
27
- yaml_as "tag:yaml.org,2002:js"
11
+ # This class it's used for JSON variables.
12
+ # Normally if we convert this { :function => "alert('Test')" } will be:
13
+ #
14
+ # { "function": "alert('Test')" }
15
+ #
16
+ # But if in our javascript need to "eval" this function is not possible because
17
+ # it's a string.
18
+ #
19
+ # Using Padrino::ExtJs::Variable the result will be:
20
+ #
21
+ # { "function" : alert('Test') }
22
+ #
23
+ # Normally an ExtJs Variable can be handled with ExtJs Config like:
24
+ #
25
+ # function: !js alert('Test')
26
+ #
27
+ class Variable < String
28
+ yaml_as "tag:yaml.org,2002:js"
28
29
 
29
- def to_json(*a) #:nodoc:
30
- self
30
+ def to_json(*a) #:nodoc:
31
+ self
32
+ end
31
33
  end
32
- end
33
34
 
34
- # This class it's used for write in a new and simple way json.
35
- #
36
- # In ExtJs framework generally each component have a configuration written in json.
37
- #
38
- # Write this config in ruby it's not the best choice think this example:
39
- #
40
- # # A Generic grid config in JavaScript:
41
- # var gridPanel = new Ext.grid.GridPanel({
42
- # bbar: gridPanelPagingToolbar,
43
- # clicksToEdit: 1,
44
- # cm: gridPanelColumnModel,
45
- # region: "center",
46
- # sm: gridPanelCheckboxSelectionModel,
47
- # viewConfig: {"forceFit":true},
48
- # plugins: [new Ext.grid.Search()],
49
- # border: false,
50
- # tbar: gridPanelToolbar,
51
- # id: "grid-accounts",
52
- # bodyBorder: false,
53
- # store: gridPanelGroupingStore,
54
- # view: gridPanelGroupingView
55
- # });
56
- #
57
- # # A Gneric grid config in Ruby:
58
- # { :bbar => ExtJs::Variable.new('gridPanelPagingToolbar'), :clicksToEdit => 1,
59
- # :cm => ExtJs::Variable.new('gridPanelColumnModel'), :region => "center",
60
- # :sm => ExtJs::Variable.new('gridPanelCheckboxSelectionModel'),
61
- # :viewConfig => { :forceFit => true }, plugins => [ExtJs::Variable.new('new Ext.grid.Search()'].
62
- # :border => false ... more more code...
63
- #
64
- # As you can see writing json in pure ruby (in this case with hash) require much time and is
65
- # <tt>less</tt> readable.
66
- #
67
- # For this reason we build an ExtJs Config, that basically it's an yaml file with
68
- # some new great functions so the example above will be:
69
- #
70
- # # A Generic grid config in ExtJs Config:
71
- # gridPanel:
72
- # bbar: !js gridPanelPagingToolbar
73
- # clicksToEdit: 1,
74
- # cm: !js gridPanelColumnModel
75
- # region: center
76
- # sm: !js gridPanelCheckboxSelectionModel
77
- # viewConfig:
78
- # forceFit: true
79
- # plugins: [!js new Ext.grid.Search()]
80
- # border: false
81
- # tbar: !js gridPanelToolbar
82
- # id: grid-accounts
83
- # bodyBorder: false
84
- # store: !js gridPanelGroupingStore
85
- # view: !js gridPanelGroupingView
86
- #
87
- # Now you see that it's more readable, simple and coincise!
88
- #
89
- # But our ExtJs config can act also as an xml or an erb partial. See this code:
90
- #
91
- # # A template
92
- # tempate:
93
- # tbar:
94
- # here: a custom config
95
- # grid:
96
- # viewConfig:
97
- # forceFit: true
98
- # plugins: [!js new Ext.grid.Search()]
99
- # border: false
100
- #
101
- # We can "grep" this config in our template with:
102
- #
103
- # # A generic grid
104
- # gridPanel:
105
- # <<: %template/grid
106
- # border: true
107
- #
108
- # The result will be:
109
- #
110
- # gridPanel:
111
- # viewConfig:
112
- # forceFit: true
113
- # plugins: [!js new Ext.grid.Search()]
114
- # border: true # overidden
115
- #
116
- # See our test for more complex examples.
117
- #
118
- class Config < Hash
119
-
120
- # Initialize a new config parsing an Hash
121
- def initialize(data)
122
- @data = data
123
- parsed = parse(@data)
124
- super
125
- replace parsed
126
- end
35
+ # This class it's used for write in a new and simple way json.
36
+ #
37
+ # In ExtJs framework generally each component have a configuration written in json.
38
+ #
39
+ # Write this config in ruby it's not the best choice think this example:
40
+ #
41
+ # # A Generic grid config in JavaScript:
42
+ # var gridPanel = new Ext.grid.GridPanel({
43
+ # bbar: gridPanelPagingToolbar,
44
+ # clicksToEdit: 1,
45
+ # cm: gridPanelColumnModel,
46
+ # region: "center",
47
+ # sm: gridPanelCheckboxSelectionModel,
48
+ # viewConfig: {"forceFit":true},
49
+ # plugins: [new Ext.grid.Search()],
50
+ # border: false,
51
+ # tbar: gridPanelToolbar,
52
+ # id: "grid-accounts",
53
+ # bodyBorder: false,
54
+ # store: gridPanelGroupingStore,
55
+ # view: gridPanelGroupingView
56
+ # });
57
+ #
58
+ # # A Gneric grid config in Ruby:
59
+ # { :bbar => Padrino::ExtJs::Variable.new('gridPanelPagingToolbar'), :clicksToEdit => 1,
60
+ # :cm => Padrino::ExtJs::Variable.new('gridPanelColumnModel'), :region => "center",
61
+ # :sm => Padrino::ExtJs::Variable.new('gridPanelCheckboxSelectionModel'),
62
+ # :viewConfig => { :forceFit => true }, plugins => [Padrino::ExtJs::Variable.new('new Ext.grid.Search()'].
63
+ # :border => false ... more more code...
64
+ #
65
+ # As you can see writing json in pure ruby (in this case with hash) require much time and is
66
+ # <tt>less</tt> readable.
67
+ #
68
+ # For this reason we build an ExtJs Config, that basically it's an yaml file with
69
+ # some new great functions so the example above will be:
70
+ #
71
+ # # A Generic grid config in ExtJs Config:
72
+ # gridPanel:
73
+ # bbar: !js gridPanelPagingToolbar
74
+ # clicksToEdit: 1,
75
+ # cm: !js gridPanelColumnModel
76
+ # region: center
77
+ # sm: !js gridPanelCheckboxSelectionModel
78
+ # viewConfig:
79
+ # forceFit: true
80
+ # plugins: [!js new Ext.grid.Search()]
81
+ # border: false
82
+ # tbar: !js gridPanelToolbar
83
+ # id: grid-accounts
84
+ # bodyBorder: false
85
+ # store: !js gridPanelGroupingStore
86
+ # view: !js gridPanelGroupingView
87
+ #
88
+ # Now you see that it's more readable, simple and coincise!
89
+ #
90
+ # But our ExtJs config can act also as an xml or an erb partial. See this code:
91
+ #
92
+ # # A template
93
+ # tempate:
94
+ # tbar:
95
+ # here: a custom config
96
+ # grid:
97
+ # viewConfig:
98
+ # forceFit: true
99
+ # plugins: [!js new Ext.grid.Search()]
100
+ # border: false
101
+ #
102
+ # We can "grep" this config in our template with:
103
+ #
104
+ # # A generic grid
105
+ # gridPanel:
106
+ # <<: %template/grid
107
+ # border: true
108
+ #
109
+ # The result will be:
110
+ #
111
+ # gridPanel:
112
+ # viewConfig:
113
+ # forceFit: true
114
+ # plugins: [!js new Ext.grid.Search()]
115
+ # border: true # overidden
116
+ #
117
+ # See our test for more complex examples.
118
+ #
119
+ class Config < Hash
127
120
 
128
- # Load a new config from an yml file and return a parsed hash.
129
- def self.load_file(path, binding=nil)
130
- self.load(File.read(path), binding)
131
- end
121
+ # Initialize a new config parsing an Hash
122
+ def initialize(data)
123
+ @data = data
124
+ parsed = parse(@data)
125
+ super
126
+ replace parsed
127
+ end
132
128
 
133
- # Load a new config from a yaml "string" and return a parsed hash.
134
- def self.load(string, binding=nil)
135
- self.new YAML.parse(ERB.new(string).result(binding))
136
- end
129
+ # Load a new config from an yml file and return a parsed hash.
130
+ def self.load_file(path, binding=nil)
131
+ self.load(File.read(path), binding)
132
+ end
137
133
 
138
- private
139
- def parse(node=nil, key=nil)
140
- case node.value
141
- when String
142
- if node.value =~ /^%{1}(.*)/
143
- node = parse(@data.select($1).first)
144
- end
145
- node.respond_to?(:transform) ? node.transform : node
146
- when Hash
147
- parsed = {}
148
- node.value.each do |k,v|
149
- if k.value == "<<"
150
- node = parse(v)
151
- if node.is_a?(Hash)
152
- node.merge!(parsed)
134
+ # Load a new config from a yaml "string" and return a parsed hash.
135
+ def self.load(string, binding=nil)
136
+ self.new YAML.parse(ERB.new(string).result(binding))
137
+ end
138
+
139
+ private
140
+ def parse(node=nil, key=nil)
141
+ case node.value
142
+ when String
143
+ if node.value =~ /^%{1}(.*)/
144
+ node = parse(@data.select($1).first)
145
+ end
146
+ node.respond_to?(:transform) ? node.transform : node
147
+ when Hash
148
+ parsed = {}
149
+ node.value.each do |k,v|
150
+ if k.value == "<<"
151
+ node = parse(v)
152
+ if node.is_a?(Hash)
153
+ node.merge!(parsed)
154
+ end
155
+ parsed = node
156
+ else
157
+ parsed[k.value] = parse(v)
153
158
  end
154
- parsed = node
155
- else
156
- parsed[k.value] = parse(v)
157
159
  end
158
- end
159
- parsed
160
- when Array
161
- parsed = []
162
- node.value.each do |v|
163
- node = parse(v)
164
- node.is_a?(Array) ? parsed.concat(node) : parsed.push(node)
165
- end
166
- parsed
160
+ parsed
161
+ when Array
162
+ parsed = []
163
+ node.value.each do |v|
164
+ node = parse(v)
165
+ node.is_a?(Array) ? parsed.concat(node) : parsed.push(node)
166
+ end
167
+ parsed
168
+ end
167
169
  end
168
170
  end
169
171
  end