padrino-admin 0.2.9 → 0.4.5

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,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