lolita 3.0.7 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/GUIDELINE +4 -0
- data/README.rdoc +50 -0
- data/VERSION +1 -1
- data/app/controllers/lolita/rest_controller.rb +27 -11
- data/app/helpers/components/lolita/configuration/list_component.rb +11 -0
- data/app/helpers/lolita_helper.rb +1 -1
- data/app/views/components/lolita/configuration/field/_display.html.erb +9 -0
- data/app/views/components/lolita/configuration/field/_label.html.erb +1 -0
- data/app/views/components/lolita/{field → configuration/field}/_object.html.erb +0 -0
- data/app/views/components/lolita/configuration/field/array/_display.html.erb +5 -0
- data/app/views/components/lolita/configuration/field/array/habtm/_display.html.erb +26 -0
- data/app/views/components/lolita/configuration/field/array/select/_display.html.erb +5 -0
- data/app/views/components/lolita/configuration/field/big_decimal/_display.html.erb +1 -0
- data/app/views/components/lolita/{field/_boolean.html.erb → configuration/field/boolean/_display.html.erb} +0 -0
- data/app/views/components/lolita/configuration/field/date/_display.html.erb +1 -0
- data/app/views/components/lolita/{field/_string.html.erb → configuration/field/float/_display.html.erb} +0 -0
- data/app/views/components/lolita/configuration/field/integer/_display.html.erb +1 -0
- data/app/views/components/lolita/configuration/field/string/_display.html.erb +5 -0
- data/app/views/components/lolita/{field/_disabled.html.erb → configuration/field/string/disabled/_display.html.erb} +0 -0
- data/app/views/components/lolita/{field/_password.html.erb → configuration/field/string/password/_display.html.erb} +0 -0
- data/app/views/components/lolita/configuration/field/string/text/_display.html.erb +34 -0
- data/app/views/components/lolita/configuration/field/time/_display.html.erb +1 -0
- data/app/views/components/lolita/{field_set → configuration/field_set}/_display.html.erb +1 -1
- data/app/views/components/lolita/configuration/list/_body.html.erb +5 -0
- data/app/views/components/lolita/{list → configuration/list}/_body_cell.html.erb +0 -0
- data/app/views/components/lolita/{list → configuration/list}/_checkbox_cell.html.erb +0 -0
- data/app/views/components/lolita/{list → configuration/list}/_checkbox_header.html.erb +0 -0
- data/app/views/components/lolita/configuration/list/_display.html.erb +12 -0
- data/app/views/components/lolita/configuration/list/_filter.html.erb +8 -0
- data/app/views/components/lolita/configuration/list/_header.html.erb +9 -0
- data/app/views/components/lolita/{list → configuration/list}/_header_cell.html.erb +0 -0
- data/app/views/components/lolita/{list → configuration/list}/_new_resource.html.erb +0 -0
- data/app/views/components/lolita/configuration/list/_paginator.html.erb +3 -0
- data/app/views/components/lolita/configuration/list/_row.html.erb +7 -0
- data/app/views/components/lolita/{list → configuration/list}/_title.html.erb +0 -0
- data/app/views/components/lolita/{list → configuration/list}/_tool_cell.html.erb +0 -0
- data/app/views/components/lolita/{list → configuration/list}/_tool_header.html.erb +0 -0
- data/app/views/components/lolita/configuration/tab/_display.html.erb +17 -0
- data/app/views/components/lolita/configuration/tab/_fields.html.erb +7 -0
- data/app/views/components/lolita/configuration/tab/content/_display.html.erb +1 -0
- data/app/views/components/lolita/configuration/tab/default/_display.html.erb +9 -0
- data/app/views/components/lolita/{tabs → configuration/tabs}/_display.html.erb +4 -3
- data/app/views/{lolita/layouts → layouts/lolita}/application.html.erb +0 -0
- data/app/views/{lolita/layouts → layouts/lolita}/application.html.erb_spec.rb +0 -0
- data/author +1 -1
- data/lib/lolita.rb +31 -12
- data/lib/lolita/adapter/active_record.rb +16 -5
- data/lib/lolita/adapter/mongoid.rb +2 -2
- data/lib/lolita/base_configuration.rb +44 -2
- data/lib/lolita/builder.rb +31 -14
- data/lib/lolita/configuration/column.rb +92 -86
- data/lib/lolita/configuration/columns.rb +65 -65
- data/lib/lolita/configuration/factory.rb +8 -8
- data/lib/lolita/configuration/field.rb +156 -100
- data/lib/lolita/configuration/field/array.rb +74 -0
- data/lib/lolita/configuration/field/big_decimal.rb +12 -0
- data/lib/lolita/configuration/field/boolean.rb +7 -5
- data/lib/lolita/configuration/field/date.rb +13 -0
- data/lib/lolita/configuration/field/integer.rb +7 -5
- data/lib/lolita/configuration/field/string.rb +8 -6
- data/lib/lolita/configuration/field/time.rb +13 -0
- data/lib/lolita/configuration/fields.rb +36 -0
- data/lib/lolita/configuration/filter.rb +63 -0
- data/lib/lolita/configuration/list.rb +101 -91
- data/lib/lolita/configuration/page.rb +1 -0
- data/lib/lolita/configuration/tab.rb +137 -131
- data/lib/lolita/configuration/tab/content.rb +14 -12
- data/lib/lolita/configuration/tab/default.rb +15 -13
- data/lib/lolita/configuration/tabs.rb +2 -2
- data/lib/lolita/controllers/component_helpers.rb +26 -14
- data/lib/lolita/controllers/internal_helpers.rb +14 -0
- data/lib/lolita/controllers/url_helpers.rb +47 -10
- data/lib/lolita/dbi/base.rb +50 -50
- data/lib/lolita/errors.rb +2 -2
- data/lib/lolita/hooks.rb +298 -0
- data/lib/lolita/hooks/named_hook.rb +122 -0
- data/lib/lolita/lazy_loader.rb +46 -46
- data/lib/lolita/mapping.rb +3 -2
- data/lib/lolita/navigation.rb +48 -0
- data/lib/lolita/observed_array.rb +7 -0
- data/lib/lolita/rails/routes.rb +29 -3
- data/lolita.gemspec +65 -48
- data/public/javascripts/lolita/tab.js +5 -0
- data/public/javascripts/rails.js +137 -137
- data/public/stylesheets/lolita/style.css +3 -1
- data/spec/builder_spec.rb +42 -0
- data/spec/configuration/field_spec.rb +29 -18
- data/spec/configuration/filter_spec.rb +60 -0
- data/spec/configuration/tab_spec.rb +28 -20
- data/spec/configuration/tabs_spec.rb +8 -4
- data/spec/controllers/lolita_rest_spec.rb +15 -0
- data/spec/hooks_spec.rb +191 -0
- data/spec/lolita_spec.rb +6 -4
- data/spec/navigation/tree_spec.rb +59 -0
- data/spec/rails_app/app/mongoid/post.rb +2 -0
- data/spec/rails_app/app/views/components/lolita/{list → configuration/list}/_body_cell.html.erb +0 -0
- data/spec/rails_app/config/application.rb +1 -0
- data/spec/rails_app/lib/lolita/configuration/field/my_custom_collection.rb +14 -0
- data/spec/simple_spec_helper.rb +1 -0
- data/spec/spec_helper.rb +1 -2
- metadata +66 -49
- data/README.md +0 -5
- data/app/helpers/components/lolita/list_component.rb +0 -9
- data/app/views/components/lolita/field/_collection.html.erb +0 -5
- data/app/views/components/lolita/field/_date.html.erb +0 -1
- data/app/views/components/lolita/field/_datetime.html.erb +0 -1
- data/app/views/components/lolita/field/_display.html.erb +0 -6
- data/app/views/components/lolita/field/_integer.html.erb +0 -1
- data/app/views/components/lolita/field/_label.html.erb +0 -1
- data/app/views/components/lolita/field/_select.html.erb +0 -1
- data/app/views/components/lolita/field/_text.html.erb +0 -27
- data/app/views/components/lolita/list/_body.html.erb +0 -5
- data/app/views/components/lolita/list/_display.html.erb +0 -11
- data/app/views/components/lolita/list/_header.html.erb +0 -9
- data/app/views/components/lolita/list/_paginator.html.erb +0 -4
- data/app/views/components/lolita/list/_row.html.erb +0 -7
- data/app/views/components/lolita/tab/_content.html.erb +0 -1
- data/app/views/components/lolita/tab/_default.html.erb +0 -11
- data/app/views/components/lolita/tab/_display.html.erb +0 -7
- data/app/views/components/lolita/tab/_fields.html.erb +0 -7
- data/lib/lolita/configuration/field/collection.rb +0 -71
- data/lib/lolita/configuration/field/datetime.rb +0 -10
- data/lib/lolita/configuration/field/disabled.rb +0 -10
- data/lib/lolita/configuration/field/password.rb +0 -10
- data/lib/lolita/configuration/field/text.rb +0 -10
- data/lib/lolita/hooks/base.rb +0 -58
- data/lib/lolita/hooks/component.rb +0 -15
- data/lib/lolita/hooks/hooks.rb +0 -15
@@ -1,20 +1,22 @@
|
|
1
1
|
module Lolita
|
2
2
|
module Configuration
|
3
|
-
|
3
|
+
module Tab
|
4
|
+
class Content < Lolita::Configuration::Tab::Base
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
def initialize(dbi,*args,&block)
|
7
|
+
@type=:content
|
8
|
+
super
|
9
|
+
set_default_fields
|
10
|
+
end
|
10
11
|
|
11
|
-
|
12
|
+
private
|
12
13
|
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
def set_default_fields
|
16
|
+
default_fields if @fields.empty?
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
19
21
|
end
|
20
22
|
end
|
@@ -1,22 +1,24 @@
|
|
1
1
|
module Lolita
|
2
2
|
module Configuration
|
3
|
-
|
3
|
+
module Tab
|
4
|
+
class Default < Lolita::Configuration::Tab::Base
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
def initialize *args,&block
|
7
|
+
self.type=:default
|
8
|
+
super
|
9
|
+
end
|
9
10
|
|
10
|
-
|
11
|
+
private
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
def set_default_fields
|
14
|
+
warn("Default fields are not set for DefaultTab.")
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
def validate
|
18
|
+
super
|
19
|
+
if fields.empty?
|
20
|
+
raise Lolita::NoFieldsGivenError, "At least one field must be specified for default tab."
|
21
|
+
end
|
20
22
|
end
|
21
23
|
end
|
22
24
|
end
|
@@ -105,7 +105,7 @@ module Lolita
|
|
105
105
|
end
|
106
106
|
|
107
107
|
def default_tab_types
|
108
|
-
Lolita::Configuration::Tab.default_types
|
108
|
+
Lolita::Configuration::Tab::Base.default_types
|
109
109
|
end
|
110
110
|
|
111
111
|
def set_tab_attributes(tab)
|
@@ -120,7 +120,7 @@ module Lolita
|
|
120
120
|
|
121
121
|
def build_element(element,&block)
|
122
122
|
current_tab=if element.is_a?(Hash) || element.is_a?(Symbol)
|
123
|
-
Lolita::Configuration::Tab.
|
123
|
+
Lolita::Configuration::Tab.add(@dbi,element,&block)
|
124
124
|
else
|
125
125
|
element
|
126
126
|
end
|
@@ -8,7 +8,7 @@ module Lolita
|
|
8
8
|
# Component should have fallowing module structure Components::[NameSpace]::[Component name]Component
|
9
9
|
# Components::Lolita::ListComponent
|
10
10
|
# ====Example
|
11
|
-
# render_component :"lolita/list", :dispaly
|
11
|
+
# render_component :"lolita/configuration/list", :dispaly
|
12
12
|
# # try to find /helpers/components/lolita/list_component.rb in every directory in $: that
|
13
13
|
# # ends with /helpers
|
14
14
|
# # require this file if found and extend self with Components::Lolita::ListComponent.
|
@@ -26,18 +26,20 @@ module Lolita
|
|
26
26
|
# render_component "lolita/list", :display
|
27
27
|
# render_component "lolita/list/display"
|
28
28
|
def render_component *args
|
29
|
+
|
29
30
|
@opts=args.extract_options!
|
30
31
|
name=args[0]
|
31
32
|
state=args[1]
|
32
33
|
format=@opts.delete(:format)
|
33
34
|
raise "Can't render component without name!" unless name
|
34
35
|
will_use_component name
|
36
|
+
partial_name=File.join("/components",name.to_s,state ? state.to_s : nil)
|
35
37
|
if format
|
36
38
|
with_format(format) do
|
37
|
-
render(:partial=>
|
39
|
+
render(:partial=>partial_name,:locals=>@opts)
|
38
40
|
end
|
39
41
|
else
|
40
|
-
render(:partial=>
|
42
|
+
render(:partial=>partial_name,:locals=>@opts)
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
@@ -51,26 +53,36 @@ module Lolita
|
|
51
53
|
|
52
54
|
# Require component helper file and extend current instance with component helper module.
|
53
55
|
# ====Example
|
54
|
-
# will_use_component :"lolita/list"
|
56
|
+
# will_use_component :"lolita/configuration/list"
|
55
57
|
def will_use_component component_name
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
58
|
+
helpers_for_component(component_name) do |possible_component_name|
|
59
|
+
@used_component_helpers||=[]
|
60
|
+
unless @used_component_helpers.include?(possible_component_name)
|
61
|
+
if path=component_helper_path(possible_component_name)
|
62
|
+
self.class.class_eval do
|
63
|
+
require path
|
64
|
+
end
|
65
|
+
class_name=possible_component_name.to_s.camelize
|
66
|
+
self.extend("Components::#{class_name}Component".constantize) rescue nil #FIXME too slow
|
61
67
|
end
|
62
|
-
|
63
|
-
self.extend("Components::#{class_name}Component".constantize)
|
68
|
+
@used_component_helpers<<possible_component_name
|
64
69
|
end
|
65
|
-
@used_component_helpers<<component_name
|
66
70
|
end
|
67
71
|
end
|
68
72
|
|
73
|
+
def helpers_for_component component_name
|
74
|
+
names=component_name.to_s.split("/")
|
75
|
+
start_index=1 # first is lolita
|
76
|
+
start_index.upto(names.size) do |index|
|
77
|
+
yield names.slice(0..index).join("/")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
69
81
|
# Find path for given component.
|
70
82
|
#
|
71
83
|
# component_helper_path :"lolita/list" #=> [path_to_lolita]/app/helpers/components/lolita/list_component.rb
|
72
84
|
def component_helper_path component_name
|
73
|
-
helper_paths
|
85
|
+
@helper_paths||=$:.reject{|p| !p.match(/\/helpers$/)}
|
74
86
|
get_path=lambda{|paths|
|
75
87
|
extra_path=component_name.to_s.split("/")
|
76
88
|
component=extra_path.pop
|
@@ -82,7 +94,7 @@ module Lolita
|
|
82
94
|
end
|
83
95
|
nil
|
84
96
|
}
|
85
|
-
path=get_path.call(helper_paths)
|
97
|
+
path=get_path.call(@helper_paths)
|
86
98
|
path
|
87
99
|
end
|
88
100
|
|
@@ -11,6 +11,7 @@ module Lolita
|
|
11
11
|
|
12
12
|
helper_method *helpers
|
13
13
|
prepend_before_filter :is_lolita_resource?
|
14
|
+
prepend_around_filter :switch_locale
|
14
15
|
end
|
15
16
|
|
16
17
|
# Return instance variable named as resource
|
@@ -63,11 +64,24 @@ module Lolita
|
|
63
64
|
end
|
64
65
|
|
65
66
|
def build_response_for(conf_part,options={})
|
67
|
+
# FIXME when asked for some resources that always create new object, there may
|
68
|
+
# not be any args, like lolita.report on something like that
|
69
|
+
|
66
70
|
@component_options=options
|
67
71
|
@component_object=resource_class.lolita.send(conf_part.to_sym)
|
68
72
|
@component_builder=@component_object.build(@component_options)
|
69
73
|
end
|
70
74
|
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def switch_locale
|
79
|
+
old_locale=I18n.locale
|
80
|
+
Lolita.locale=params[:locale]
|
81
|
+
I18n.locale=Lolita.locale
|
82
|
+
yield
|
83
|
+
I18n.locale=old_locale
|
84
|
+
end
|
71
85
|
end
|
72
86
|
end
|
73
87
|
end
|
@@ -2,24 +2,60 @@ module Lolita
|
|
2
2
|
module Controllers
|
3
3
|
module UrlHelpers
|
4
4
|
|
5
|
-
|
5
|
+
def self.included(model_klass)
|
6
|
+
model_klass.class_eval do
|
7
|
+
|
8
|
+
# Overrides url_for when controller or view uses this helper.
|
9
|
+
# It catches hash options and replaces with lolita named route
|
10
|
+
# Without this method routes always looks like /lolita/rest/[method]
|
11
|
+
# ====Example
|
12
|
+
# # in routes.rb
|
13
|
+
# lolita_for :posts
|
14
|
+
# # GET /lolita/posts
|
15
|
+
# # in view
|
16
|
+
# url_for #=> /lolita/posts
|
17
|
+
# url_for(:controller=>"/posts",:action=>:index) #=> /posts
|
18
|
+
# # GET /posts
|
19
|
+
# url_for #=> /posts
|
20
|
+
def url_for_with_lolita options = {}
|
21
|
+
if options.is_a?(Hash) && !options[:use_route] && self.respond_to?(:lolita_mapping)
|
22
|
+
controller = options[:controller].to_s
|
23
|
+
if Lolita.mappings[lolita_mapping.name].controllers.values.include?(controller)
|
24
|
+
resource_type = {
|
25
|
+
:index => :lolita_resources_path,
|
26
|
+
:new => :new_lolita_resource_path,
|
27
|
+
:edit => :edit_lolita_resource_path,
|
28
|
+
:create => :lolita_resources_path
|
29
|
+
}
|
30
|
+
action = (options[:action] || params[:action]).to_sym
|
31
|
+
options = self.send(resource_type[action] || :lolita_resource_path,options)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
url_for_without_lolita(options)
|
35
|
+
end
|
36
|
+
alias_method_chain :url_for, :lolita
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
6
40
|
|
41
|
+
protected
|
42
|
+
|
7
43
|
def lolita_resources_path(*args)
|
8
44
|
options=args.extract_options!
|
9
45
|
mapping=args[0]
|
10
|
-
send(lolita_resource_name(mapping
|
46
|
+
send(lolita_resource_name(:mapping=>mapping,:plural=>true),options)
|
11
47
|
end
|
12
|
-
|
48
|
+
|
13
49
|
def lolita_resource_path(*args) # TODO test
|
14
50
|
options=args.extract_options!
|
15
51
|
mapping=args[0]
|
16
|
-
send(lolita_resource_name(mapping),options)
|
52
|
+
send(lolita_resource_name(:mapping=>mapping),options)
|
17
53
|
end
|
18
54
|
|
19
55
|
def new_lolita_resource_path(*args)
|
20
56
|
options=args.extract_options!
|
21
57
|
mapping=args[0]
|
22
|
-
send(lolita_resource_name(mapping,:new),options)
|
58
|
+
send(lolita_resource_name(:mapping=>mapping,:action=>:new),options)
|
23
59
|
end
|
24
60
|
|
25
61
|
def edit_lolita_resource_path(*args)
|
@@ -27,14 +63,15 @@ module Lolita
|
|
27
63
|
options[:id]||=resource.id if resource
|
28
64
|
raise "Can edit resource without id." unless options[:id]
|
29
65
|
mapping=args[0]
|
30
|
-
send(lolita_resource_name(mapping,:edit),options)
|
66
|
+
send(lolita_resource_name(:mapping=>mapping,:action=>:edit),options)
|
31
67
|
end
|
32
68
|
|
33
|
-
def lolita_resource_name(
|
34
|
-
|
35
|
-
|
69
|
+
def lolita_resource_name(options={}) #TODO test if path is right
|
70
|
+
options.assert_valid_keys(:mapping,:plural,:action)
|
71
|
+
mapping=(options[:mapping]||lolita_mapping)
|
72
|
+
name=!options[:plural] ? mapping.name : mapping.plural
|
36
73
|
name="#{mapping.path}_#{name}"
|
37
|
-
:"#{action}#{action ? "_" : ""}#{name}_path"
|
74
|
+
:"#{options[:action]}#{options[:action] ? "_" : ""}#{name}_path"
|
38
75
|
end
|
39
76
|
end
|
40
77
|
end
|
data/lib/lolita/dbi/base.rb
CHANGED
@@ -1,50 +1,50 @@
|
|
1
|
-
module Lolita
|
2
|
-
module DBI
|
3
|
-
# Lolita::DBI::Base is DataBase Interface class, that handle the request to ORM classes.
|
4
|
-
# Depending on given class DBI::Base detect which ORM class is used and include right adapter
|
5
|
-
# for that class. Other Lolita classes that need to manipulate with data need to have dbi object
|
6
|
-
# or it can be created in that class.
|
7
|
-
# Lolita::DBI::Base support Mongoid and ActiveRecord::Base, or details see Lolita::Adapter.
|
8
|
-
class Base
|
9
|
-
|
10
|
-
attr_reader :adapter_name #return connected adapter name
|
11
|
-
attr_reader :klass # return related orm class object
|
12
|
-
attr_reader :adapter # connected Adaptee for adapter
|
13
|
-
# Expect ORM class that is supported by Lolita. See Adapter for available adapters.
|
14
|
-
def initialize(class_object)
|
15
|
-
@klass=class_object
|
16
|
-
detect_adapter
|
17
|
-
connect_adapter
|
18
|
-
end
|
19
|
-
|
20
|
-
# Detect which ORM class is given and based on it connect Adapter.
|
21
|
-
def detect_adapter
|
22
|
-
if defined?(Mongoid) && defined?(Mongoid::Document) && self.klass.ancestors.include?(Mongoid::Document)
|
23
|
-
@adapter_name=:mongoid
|
24
|
-
elsif defined?(ActiveRecord) && defined?(ActiveRecord::Base) && self.klass.ancestors.include?(ActiveRecord::Base)
|
25
|
-
@adapter_name=:active_record
|
26
|
-
else
|
27
|
-
raise NotORMClassError.new("Lolita::DBI::Base can not find appropriate #{self.klass} class adapter.")
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
# Connect Adapter by including adapter module into DBI::Base class.
|
32
|
-
def connect_adapter()
|
33
|
-
@adapter="Lolita::Adapter::#{self.adapter_name.to_s.camelize}".constantize.new(self)
|
34
|
-
end
|
35
|
-
|
36
|
-
def method_missing(metod,*args,&block)
|
37
|
-
@adapter.send(metod,*args,&block)
|
38
|
-
end
|
39
|
-
|
40
|
-
class << self
|
41
|
-
# Return Array of available adapters.
|
42
|
-
def adapters
|
43
|
-
Dir[File.expand_path(File.join(File.dirname(__FILE__),'..','adapter','**','*.rb'))].map {|f|
|
44
|
-
File.basename(f,".rb").to_sym
|
45
|
-
}.reject{|el| el==:abstract_adapter}
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
1
|
+
module Lolita
|
2
|
+
module DBI
|
3
|
+
# Lolita::DBI::Base is DataBase Interface class, that handle the request to ORM classes.
|
4
|
+
# Depending on given class DBI::Base detect which ORM class is used and include right adapter
|
5
|
+
# for that class. Other Lolita classes that need to manipulate with data need to have dbi object
|
6
|
+
# or it can be created in that class.
|
7
|
+
# Lolita::DBI::Base support Mongoid and ActiveRecord::Base, or details see Lolita::Adapter.
|
8
|
+
class Base
|
9
|
+
|
10
|
+
attr_reader :adapter_name #return connected adapter name
|
11
|
+
attr_reader :klass # return related orm class object
|
12
|
+
attr_reader :adapter # connected Adaptee for adapter
|
13
|
+
# Expect ORM class that is supported by Lolita. See Adapter for available adapters.
|
14
|
+
def initialize(class_object)
|
15
|
+
@klass=class_object
|
16
|
+
detect_adapter
|
17
|
+
connect_adapter
|
18
|
+
end
|
19
|
+
|
20
|
+
# Detect which ORM class is given and based on it connect Adapter.
|
21
|
+
def detect_adapter
|
22
|
+
if defined?(Mongoid) && defined?(Mongoid::Document) && self.klass.ancestors.include?(Mongoid::Document)
|
23
|
+
@adapter_name=:mongoid
|
24
|
+
elsif defined?(ActiveRecord) && defined?(ActiveRecord::Base) && self.klass.ancestors.include?(ActiveRecord::Base)
|
25
|
+
@adapter_name=:active_record
|
26
|
+
else
|
27
|
+
raise NotORMClassError.new("Lolita::DBI::Base can not find appropriate #{self.klass} class adapter.")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Connect Adapter by including adapter module into DBI::Base class.
|
32
|
+
def connect_adapter()
|
33
|
+
@adapter="Lolita::Adapter::#{self.adapter_name.to_s.camelize}".constantize.new(self)
|
34
|
+
end
|
35
|
+
|
36
|
+
def method_missing(metod,*args,&block)
|
37
|
+
@adapter.send(metod,*args,&block)
|
38
|
+
end
|
39
|
+
|
40
|
+
class << self
|
41
|
+
# Return Array of available adapters.
|
42
|
+
def adapters
|
43
|
+
Dir[File.expand_path(File.join(File.dirname(__FILE__),'..','adapter','**','*.rb'))].map {|f|
|
44
|
+
File.basename(f,".rb").to_sym
|
45
|
+
}.reject{|el| el==:abstract_adapter}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/lolita/errors.rb
CHANGED
@@ -7,7 +7,7 @@ module Lolita
|
|
7
7
|
class FieldTypeError < ArgumentError; end
|
8
8
|
class ModuleNotFound < ArgumentError; end
|
9
9
|
class NotFound < ArgumentError; end
|
10
|
-
class
|
11
|
-
class FieldNotFoundError < NameError; end
|
10
|
+
class ConfigurationClassNotFound < NameError; end
|
12
11
|
class AssociationError < ArgumentError; end
|
12
|
+
class HookNotFound < ArgumentError;end
|
13
13
|
end
|
data/lib/lolita/hooks.rb
ADDED
@@ -0,0 +1,298 @@
|
|
1
|
+
module Lolita
|
2
|
+
# Provide hook mechanism for Lolita. To use hooks for class start with including this in your own class.
|
3
|
+
# Next step is hook definition. This may be done using Lolita::Hooks#add_hook method.
|
4
|
+
# Hooks are stored in class <i>@hooks</i> variable, that is Hash and each key is hook name
|
5
|
+
# and each hook also is Hash that have <em>:methods</em> and <em>:blocks</em>
|
6
|
+
# keys. Both of those are Array, and each time you call callback method, like <i>before_save</i> and so on, block
|
7
|
+
# and/or methods is stored. <b>Each time</b> #fire is called all blocks and methods will be executed.
|
8
|
+
# It may look like this.
|
9
|
+
# class MyClass
|
10
|
+
# include Lolita::Hooks
|
11
|
+
# add_hook :before_save, :after_save
|
12
|
+
# end
|
13
|
+
# # This will define two hooks for MyClass.
|
14
|
+
# To add hook callback just call hook name on class and pass method(-s) or block.
|
15
|
+
# MyClass.after_save :write_log
|
16
|
+
# MyClass.before_save do
|
17
|
+
# validate(self)
|
18
|
+
# end
|
19
|
+
# ==Scopes
|
20
|
+
# Most times hook callbacks are defined for class like in previous example, but also it's possible to do it
|
21
|
+
# on class instances. Difference between calling it on class or on instance is that instance callbacks will
|
22
|
+
# be called only when event is fired on instance. Class callbacks will be called on class and also on instance
|
23
|
+
# callbacks.
|
24
|
+
# my_object=MyClass.new
|
25
|
+
# MyClass.before_save do
|
26
|
+
# puts "class callback"
|
27
|
+
# end
|
28
|
+
# my_object.before_save do
|
29
|
+
# puts "instance callback"
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# MyClass.fire(:before_save) #=>
|
33
|
+
# class_callback
|
34
|
+
#
|
35
|
+
# my_object.fire(:before_save) #=>
|
36
|
+
# class_callback
|
37
|
+
# instance_callback
|
38
|
+
# # As you can see, first class callbacks is called and after that instance callbacks.
|
39
|
+
#
|
40
|
+
# ==Firing events
|
41
|
+
# To execute callbacks, events should be called on object. Event names is same hooks names. #fire can be called
|
42
|
+
# on class or on instance. Also it is possible to pass block to fire event, that will replace callback block
|
43
|
+
# or if #let_content is called than it will work like wrapper, like this
|
44
|
+
# # this is continuation of previous code
|
45
|
+
# MyClass.fire(:before_save) do
|
46
|
+
# puts "replaced text"
|
47
|
+
# end
|
48
|
+
# # will produce #=> replaced text
|
49
|
+
#
|
50
|
+
# MyClass.fire(:before_save) do
|
51
|
+
# puts "before callback"
|
52
|
+
# let_content
|
53
|
+
# puts "after callback"
|
54
|
+
# end
|
55
|
+
# # this will produce #=>
|
56
|
+
# # before callback
|
57
|
+
# # class callback
|
58
|
+
# # after callback
|
59
|
+
# ==Named hooks
|
60
|
+
# See Lolita::Hooks::NamedHook for details.
|
61
|
+
module Hooks
|
62
|
+
def self.included(base)
|
63
|
+
base.extend(ClassMethods)
|
64
|
+
base.extend(CommonMethods)
|
65
|
+
base.class_eval{
|
66
|
+
include CommonMethods
|
67
|
+
include InstanceMethods
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
# Look for named hook with singular or plural name of method.
|
72
|
+
def self.method_missing method_name,*args, &block
|
73
|
+
if named_hook=(Lolita::Hooks::NamedHook.by_name(method_name))
|
74
|
+
named_hook[:_class]
|
75
|
+
else
|
76
|
+
super
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Shared methods between class and instance.
|
81
|
+
module CommonMethods
|
82
|
+
|
83
|
+
# All callbacks for class or instance.
|
84
|
+
def callbacks
|
85
|
+
var=self.instance_variable_get(:@callbacks)
|
86
|
+
unless var
|
87
|
+
var={}
|
88
|
+
self.instance_variable_set(:@callbacks,var)
|
89
|
+
end
|
90
|
+
instance_variable_get(:@callbacks)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
module ClassMethods
|
95
|
+
|
96
|
+
# Setter for #hook_scope.
|
97
|
+
def hooks_scope=(object)
|
98
|
+
@hooks_scope=object
|
99
|
+
end
|
100
|
+
|
101
|
+
# Hooks scope is used to execute callbacks. By default it is class itself.
|
102
|
+
def hooks_scope
|
103
|
+
@hooks_scope||self
|
104
|
+
end
|
105
|
+
|
106
|
+
# Setter for #callback_content
|
107
|
+
def given_callback_content=(content)
|
108
|
+
@given_callback_content=content
|
109
|
+
end
|
110
|
+
|
111
|
+
# Callback content is used to let callback content executed insede of fire block.
|
112
|
+
def given_callback_content
|
113
|
+
@given_callback_content
|
114
|
+
end
|
115
|
+
|
116
|
+
# All hooks for class. This is Array of hook names.
|
117
|
+
def hooks
|
118
|
+
@hooks||=[]
|
119
|
+
@hooks
|
120
|
+
end
|
121
|
+
|
122
|
+
# Reset all hooks and callbacks to defaults.
|
123
|
+
def clear_hooks
|
124
|
+
@hooks=[]
|
125
|
+
@callbacks={}
|
126
|
+
end
|
127
|
+
|
128
|
+
# This method is used to add hooks for class. It accept one or more hook names.
|
129
|
+
# ====Example
|
130
|
+
# add_hook :before_save
|
131
|
+
# MyClass.add_hooks :after_save, :around_save
|
132
|
+
def add_hook(*names)
|
133
|
+
(names||[]).each{|hook_name|
|
134
|
+
self.class_eval <<-HOOK,__FILE__,__LINE__+1
|
135
|
+
def self.#{hook_name}(*methods,&block)
|
136
|
+
options=methods.extract_options!
|
137
|
+
in_hooks_scope(options[:scope]) do
|
138
|
+
register_callback(:"#{hook_name}",*methods,&block)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def #{hook_name}(*method,&block)
|
143
|
+
self.class.#{hook_name}(*method,:scope=>self,&block)
|
144
|
+
end
|
145
|
+
HOOK
|
146
|
+
register_hook(hook_name)
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
# Fire is used to execute callback. Method accept one or more <i>hook_names</i> and optional block.
|
151
|
+
# It will raise error if hook don't exist for this class. Also it accept <em>:scope</em> options, that
|
152
|
+
# is used to #get_callbacks and #run_callbacks.
|
153
|
+
# ====Example
|
154
|
+
# MyClass.fire(:before_save,:after_save,:scope=>MyClass.new)
|
155
|
+
# # this will call callbacks in MyClass instance scope, that means that self will be MyClass instance.
|
156
|
+
def fire(*hook_names,&block)
|
157
|
+
options=hook_names.extract_options!
|
158
|
+
(hook_names || []).each do |hook_name|
|
159
|
+
raise Lolita::HookNotFound, "Hook #{hook_name} is not defined for #{self}." unless self.has_hook?(hook_name)
|
160
|
+
in_hooks_scope(options[:scope]) do
|
161
|
+
callback=get_callback(hook_name)
|
162
|
+
run_callback(callback,&block)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Is hook with <em>name</em> is defined for class.
|
168
|
+
def has_hook?(name)
|
169
|
+
self.hooks.include?(name.to_sym)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Try to recognize named fire methods like
|
173
|
+
# MyClass.fire_after_save # will call MyClass.fire(:after_save)
|
174
|
+
def method_missing(*args, &block)
|
175
|
+
unless self.recognize_hook_methods(*args,&block)
|
176
|
+
super
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Call callback block inside of fire block.
|
181
|
+
# ====Example
|
182
|
+
# MyClass.fire(:before_save) do
|
183
|
+
# do_stuff
|
184
|
+
# let_content # execute callback block(-s) in same scope as fire is executed.
|
185
|
+
# end
|
186
|
+
def let_content
|
187
|
+
if content=self.given_callback_content
|
188
|
+
run_block(self.given_callback_content)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Set #method_missing
|
193
|
+
def recognize_hook_methods method_name, *args, &block
|
194
|
+
if method_name.to_s.match(/^fire_(\w+)/)
|
195
|
+
self.fire($1,&block)
|
196
|
+
true
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
protected
|
201
|
+
|
202
|
+
# Switch between self and given <em>scope</em>. Block will be executed with <em>scope</em>.
|
203
|
+
# And after that it will switch back to self.
|
204
|
+
def in_hooks_scope(scope)
|
205
|
+
begin
|
206
|
+
self.hooks_scope=scope||self
|
207
|
+
yield
|
208
|
+
ensure
|
209
|
+
self.hooks_scope=self
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Run callback. Each callback is Hash with <i>:methods</i> Array and </i>:blocks</i> Array
|
214
|
+
def run_callback(callback,&block)
|
215
|
+
run_methods(callback[:methods],&block)
|
216
|
+
run_blocks(callback[:blocks],&block)
|
217
|
+
end
|
218
|
+
|
219
|
+
# Run methods from <em>methods</em> Array
|
220
|
+
def run_methods methods, &block
|
221
|
+
(methods||[]).each do |method_name|
|
222
|
+
hooks_scope.__send__(method_name,&block)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# Run blocks from <em>blocks</em> Array. Also it set #given_callback_content if block is given, this
|
227
|
+
# will allow to call #let_content. Each block is runned with #run_block.
|
228
|
+
def run_blocks blocks,&given_block
|
229
|
+
(blocks||[]).each do |block|
|
230
|
+
begin
|
231
|
+
if block_given?
|
232
|
+
self.given_callback_content=given_block
|
233
|
+
end
|
234
|
+
run_block(block,&given_block)
|
235
|
+
ensure
|
236
|
+
self.given_callback_content=nil
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# Run block in scope.
|
242
|
+
def run_block block, &given_block
|
243
|
+
hooks_scope.instance_eval(&block)
|
244
|
+
end
|
245
|
+
|
246
|
+
# Return all callbacks
|
247
|
+
# If scope is not class then it merge class callbacks with scope callbacks. That means that
|
248
|
+
# class callbacks always will be called before scope callbacks.
|
249
|
+
def get_callback(name)
|
250
|
+
scope_callbacks=hooks_scope.callbacks[name.to_sym] || {}
|
251
|
+
unless hooks_scope==self
|
252
|
+
class_callbacks=self.callbacks[name.to_sym] || {}
|
253
|
+
[:methods,:blocks].each do |attr|
|
254
|
+
scope_callbacks[attr]=((class_callbacks[attr] || [])+(scope_callbacks[attr] || [])).uniq
|
255
|
+
end
|
256
|
+
end
|
257
|
+
scope_callbacks
|
258
|
+
end
|
259
|
+
|
260
|
+
# Register callback with given scope.
|
261
|
+
def register_callback(name,*methods,&block)
|
262
|
+
temp_callback=hooks_scope.callbacks[name]||{}
|
263
|
+
temp_callback[:methods]||=[]
|
264
|
+
temp_callback[:methods]+=(methods||[]).compact
|
265
|
+
temp_callback[:blocks]||=[]
|
266
|
+
temp_callback[:blocks]<< block if block_given?
|
267
|
+
hooks_scope.callbacks[name]=temp_callback
|
268
|
+
end
|
269
|
+
|
270
|
+
# Register hook for scope.
|
271
|
+
def register_hook(name)
|
272
|
+
self.hooks<<name
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Methods for instance.
|
277
|
+
module InstanceMethods
|
278
|
+
|
279
|
+
# See Lolita::Hooks::ClassMethods#fire
|
280
|
+
def fire(*hook_names)
|
281
|
+
self.class.fire(*hook_names,:scope=>self)
|
282
|
+
end
|
283
|
+
|
284
|
+
# See Lolita::Hooks::ClassMethods#let_content
|
285
|
+
def let_content
|
286
|
+
self.class.let_content
|
287
|
+
end
|
288
|
+
|
289
|
+
# See Lolita::Hooks::ClassMethods#method_missing
|
290
|
+
def method_missing(*args,&block)
|
291
|
+
unless self.class.recognize_hook_methods(*args,&block)
|
292
|
+
super
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
end
|
298
|
+
end
|