obitum-rails_admin 0.0.3 → 0.0.4
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.
- data/Gemfile +3 -0
- data/README.md +19 -69
- data/Rakefile +6 -1
- data/app/assets/javascripts/rails_admin/rails_admin.js.erb +10 -5
- data/app/controllers/rails_admin/main_controller.rb +9 -7
- data/config/initializers/mongoid_extensions.rb +4 -0
- data/lib/rails_admin/abstract_model.rb +55 -1
- data/lib/rails_admin/adapters/active_record.rb +20 -38
- data/lib/rails_admin/adapters/mongoid.rb +333 -0
- data/lib/rails_admin/adapters/mongoid/abstract_object.rb +32 -0
- data/lib/rails_admin/adapters/mongoid/extension.rb +27 -0
- data/lib/rails_admin/config/fields/base.rb +4 -4
- data/lib/rails_admin/config/fields/factories/belongs_to_association.rb +5 -2
- data/lib/rails_admin/config/fields/factories/serialized.rb +1 -1
- data/lib/rails_admin/config/fields/types.rb +2 -1
- data/lib/rails_admin/config/fields/types/all.rb +2 -0
- data/lib/rails_admin/config/fields/types/belongs_to_association.rb +1 -1
- data/lib/rails_admin/config/fields/types/bson_object_id.rb +42 -0
- data/lib/rails_admin/config/fields/types/enum.rb +2 -2
- data/lib/rails_admin/config/fields/types/mongoid_type.rb +25 -0
- data/lib/rails_admin/config/fields/types/polymorphic_association.rb +1 -1
- data/lib/rails_admin/config/fields/types/serialized.rb +1 -1
- data/lib/rails_admin/engine.rb +1 -0
- data/lib/rails_admin/version.rb +1 -1
- data/spec/dummy_app/Gemfile +2 -0
- data/spec/dummy_app/app/models/article.rb +9 -0
- data/spec/dummy_app/app/models/author.rb +6 -0
- data/spec/dummy_app/app/models/mongoid_field_test.rb +22 -0
- data/spec/dummy_app/app/models/tag.rb +7 -0
- data/spec/dummy_app/config/environments/development.rb +2 -2
- data/spec/dummy_app/config/mongoid.yml +17 -0
- data/spec/dummy_app/db/seeds.rb +7 -7
- data/spec/factories.rb +23 -0
- data/spec/integration/basic/create/rails_admin_basic_create_spec.rb +13 -0
- data/spec/integration/basic/update/rails_admin_basic_update_spec.rb +28 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/tableless.rb +27 -0
- data/spec/unit/adapters/active_record_spec.rb +335 -37
- data/spec/unit/adapters/mongoid/abstract_object_spec.rb +30 -0
- data/spec/unit/adapters/mongoid_spec.rb +581 -0
- data/spec/unit/config/fields/base_spec.rb +9 -0
- metadata +280 -44
- data/app/assets/javascripts/rails_admin/jquery-ui-1.8.16.custom.js +0 -5271
data/Gemfile
CHANGED
@@ -29,6 +29,8 @@ group :development, :test do
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
+
gem 'bson_ext'
|
33
|
+
gem 'mongoid'
|
32
34
|
gem 'cancan'
|
33
35
|
end
|
34
36
|
|
@@ -40,6 +42,7 @@ group :debug do
|
|
40
42
|
|
41
43
|
platform :mri_19 do
|
42
44
|
gem 'ruby-debug19'
|
45
|
+
gem 'simplecov', :require => false
|
43
46
|
end
|
44
47
|
end
|
45
48
|
|
data/README.md
CHANGED
@@ -18,61 +18,6 @@ Hoeven][plukevdh], and [Rein Henrichs][reinh].
|
|
18
18
|
[plukevdh]: https://github.com/plukevdh
|
19
19
|
[reinh]: https://github.com/reinh
|
20
20
|
|
21
|
-
## <a name="announcements"></a>Announcements
|
22
|
-
|
23
|
-
* Actions
|
24
|
-
|
25
|
-
Custom actions are finally there. Default should stay put. To customize them, please have a look at:
|
26
|
-
|
27
|
-
|
28
|
-
[Wiki: actions](https://github.com/sferik/rails_admin/wiki/Actions)<br>
|
29
|
-
[Documented source code: Base Action class](https://github.com/sferik/rails_admin/blob/master/lib/rails_admin/config/actions/base.rb)
|
30
|
-
|
31
|
-
|
32
|
-
* Translations
|
33
|
-
|
34
|
-
Translations have been totally revamped (for custom actions)
|
35
|
-
|
36
|
-
Translation keys for actions now look like:
|
37
|
-
|
38
|
-
```yaml
|
39
|
-
|
40
|
-
en:
|
41
|
-
admin:
|
42
|
-
actions:
|
43
|
-
<action_name>:
|
44
|
-
title: "..."
|
45
|
-
menu: "..."
|
46
|
-
breadcrumb: "..."
|
47
|
-
|
48
|
-
```
|
49
|
-
|
50
|
-
Other keys may have changed too.
|
51
|
-
|
52
|
-
Old outdated translations have been removed, you must now fetch them from the wiki.
|
53
|
-
|
54
|
-
See [Wiki](https://github.com/sferik/rails_admin/wiki/Translations) for more informations.
|
55
|
-
|
56
|
-
Thanks a lot to all the translators sharing their work, sorry for the added worked (it was really needed).
|
57
|
-
|
58
|
-
* History
|
59
|
-
|
60
|
-
If you wish to continue using the old history feature, please add this to your initializer:
|
61
|
-
|
62
|
-
```ruby
|
63
|
-
config.audit_with :history, User
|
64
|
-
```
|
65
|
-
|
66
|
-
Alternatively, [PaperTrail](https://github.com/airblade/paper_trail) is now officially compatible. Install it, add `has_paper_trail` to the models you wish to track, and add this instead to your initializer:
|
67
|
-
|
68
|
-
```ruby
|
69
|
-
config.audit_with :paper_trail, User
|
70
|
-
```
|
71
|
-
|
72
|
-
Change `User` with the class you use with Devise.
|
73
|
-
|
74
|
-
By default, there won't be any history shown.
|
75
|
-
|
76
21
|
## <a name="features"></a>Features
|
77
22
|
|
78
23
|
* Display database tables
|
@@ -88,6 +33,7 @@ By default, there won't be any history shown.
|
|
88
33
|
* User action history (internally or via [PaperTrail](https://github.com/airblade/paper_trail))
|
89
34
|
* Supported ORMs
|
90
35
|
* ActiveRecord
|
36
|
+
* Mongoid [new]
|
91
37
|
|
92
38
|
## <a name="demo"></a>Demo
|
93
39
|
|
@@ -119,9 +65,10 @@ It will modify your `config/routes.rb`, adding:
|
|
119
65
|
mount RailsAdmin::Engine => '/admin', :as => 'rails_admin' # Feel free to change '/admin' to any namespace you need.
|
120
66
|
```
|
121
67
|
|
122
|
-
It will add an intializer that will help you getting started. (head for config/initializers/rails_admin.rb)
|
68
|
+
It will also add an intializer that will help you getting started. (head for config/initializers/rails_admin.rb)
|
123
69
|
|
124
|
-
|
70
|
+
Optionally, you may wish to set up [Cancan](https://github.com/ryanb/cancan),
|
71
|
+
[PaperTrail](https://github.com/airblade/paper_trail), [CKeditor](https://github.com/galetahub/ckeditor)
|
125
72
|
|
126
73
|
More on that in the [Wiki](https://github.com/sferik/rails_admin/wiki)
|
127
74
|
|
@@ -138,21 +85,23 @@ You should now be able to administer your site at
|
|
138
85
|
All configuration documentation has moved to the wiki: https://github.com/sferik/rails_admin/wiki
|
139
86
|
|
140
87
|
## <a name="support"></a>Support
|
141
|
-
Please see [list of
|
88
|
+
Please see [list of known issues](https://github.com/sferik/rails_admin/wiki/Known-issues) first.
|
89
|
+
|
90
|
+
If you have a question, please check this README, the wiki, and the list of known issues.
|
142
91
|
|
143
|
-
If you have a question, you can ask the [official RailsAdmin mailing
|
92
|
+
If you still have a question, you can ask the [official RailsAdmin mailing
|
144
93
|
list](http://groups.google.com/group/rails_admin) or ping sferik on IRC in
|
145
94
|
[#railsadmin on
|
146
95
|
irc.freenode.net](http://webchat.freenode.net/?channels=railsadmin).
|
147
96
|
|
148
|
-
Check this README and the wiki first.
|
149
|
-
|
150
97
|
If you think you found a bug in RailsAdmin, you can [submit an
|
151
|
-
issue](https://github.com/sferik/rails_admin#issues)
|
152
|
-
No feature requests or questions please (the mailing list is
|
98
|
+
issue](https://github.com/sferik/rails_admin#issues).
|
99
|
+
No feature requests or questions please (the mailing list is
|
100
|
+
active and is the preferred venue for feature requests and questions).
|
153
101
|
|
154
102
|
## <a name="contributing"></a>Contributing
|
155
|
-
In the spirit of [free software](http://www.fsf.org/licensing/essays/free-sw.html),
|
103
|
+
In the spirit of [free software](http://www.fsf.org/licensing/essays/free-sw.html),
|
104
|
+
**everyone** is encouraged to help improve this project.
|
156
105
|
|
157
106
|
Here are some ways *you* can contribute:
|
158
107
|
|
@@ -170,7 +119,7 @@ Here are some ways *you* can contribute:
|
|
170
119
|
|
171
120
|
## <a name="issues"></a>Submitting an Issue
|
172
121
|
We use the [GitHub issue tracker](https://github.com/sferik/rails_admin/issues) to track bugs and
|
173
|
-
features. Before submitting a bug report or feature request, check to make sure it hasn't already
|
122
|
+
features. Before submitting a bug report or feature request, please check to make sure it hasn't already
|
174
123
|
been submitted. You can indicate support for an existing issue by voting it up. When submitting a
|
175
124
|
bug report, please include a [Gist](https://gist.github.com/) that includes a stack trace and any
|
176
125
|
details that may be necessary to reproduce the bug, including your gem version, Ruby version, and
|
@@ -179,17 +128,18 @@ operating system. Ideally, a bug report should include a pull request with faili
|
|
179
128
|
## <a name="pulls"></a>Submitting a Pull Request
|
180
129
|
1. Fork the project.
|
181
130
|
2. Create a topic branch.
|
182
|
-
3. Implement your feature or bug fix. *NOTE* - there's a small test app located in the
|
131
|
+
3. Implement your feature or bug fix. *NOTE* - there's a small test app located in the
|
132
|
+
spec/dummy_app directory that you can use to experiment with rails_admin.
|
183
133
|
4. Add documentation for your feature or bug fix.
|
184
134
|
5. Run `bundle exec rake doc:yard`. If your changes are not 100% documented, go back to step 4.
|
185
135
|
6. Add specs for your feature or bug fix.
|
186
136
|
7. Run `bundle exec rake spec`. If your changes are not 100% covered, go back to step 6.
|
187
137
|
8. Commit and push your changes.
|
188
|
-
9. Submit a pull request. Please do not include changes to the gemspec, version, or history file.
|
138
|
+
9. Submit a pull request. Please do not include changes to the gemspec, version, or history file.
|
139
|
+
(If you want to create your own version for some reason, please do so in a separate commit.)
|
189
140
|
|
190
141
|
## <a name="versions"></a>Supported Ruby Versions
|
191
|
-
This library aims to support and is [tested against][travis] the following Ruby
|
192
|
-
implementations:
|
142
|
+
This library aims to support and is [tested against][travis] the following Ruby implementations:
|
193
143
|
|
194
144
|
* Ruby 1.8.7
|
195
145
|
* Ruby 1.9.2
|
data/Rakefile
CHANGED
@@ -2,19 +2,24 @@
|
|
2
2
|
theme = ENV['RAILS_ADMIN_THEME'] || :default
|
3
3
|
require_asset 'jquery'
|
4
4
|
require_asset 'jquery_ujs'
|
5
|
-
require_asset 'jquery_nested_form'
|
6
|
-
require_asset 'rails_admin/ra.nested-form-hooks'
|
7
5
|
require_asset 'jquery.remotipart'
|
8
|
-
require_asset '
|
9
|
-
require_asset '
|
10
|
-
require_asset '
|
6
|
+
require_asset 'jquery.effects.core'
|
7
|
+
require_asset 'jquery.ui.sortable'
|
8
|
+
require_asset 'jquery.ui.autocomplete'
|
9
|
+
require_asset 'jquery.ui.datepicker'
|
11
10
|
require_asset 'rails_admin/jquery.ui.timepicker'
|
12
11
|
require_asset 'rails_admin/ra.datetimepicker'
|
13
12
|
require_asset 'rails_admin/ra.filter-box'
|
14
13
|
require_asset 'rails_admin/ra.filtering-multiselect'
|
15
14
|
require_asset 'rails_admin/ra.filtering-select'
|
16
15
|
require_asset 'rails_admin/ra.remote-form'
|
16
|
+
require_asset 'rails_admin/jquery.pjax'
|
17
|
+
require_asset 'jquery_nested_form'
|
18
|
+
require_asset 'rails_admin/ra.nested-form-hooks'
|
19
|
+
require_asset 'bootstrap'
|
17
20
|
require_asset 'rails_admin/ui'
|
18
21
|
require_asset "rails_admin/themes/#{theme}/ui"
|
19
22
|
require_asset 'rails_admin/custom/ui'
|
20
23
|
%>
|
24
|
+
|
25
|
+
|
@@ -30,8 +30,10 @@ module RailsAdmin
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def list_entries(model_config = @model_config, auth_scope_key = :index, additional_scope = get_association_scope_from_params, pagination = !(params[:associated_collection] || params[:all]))
|
33
|
-
scope =
|
34
|
-
|
33
|
+
scope = model_config.abstract_model.scoped
|
34
|
+
if auth_scope = @authorization_adapter && @authorization_adapter.query(auth_scope_key, model_config.abstract_model)
|
35
|
+
scope = scope.merge(auth_scope)
|
36
|
+
end
|
35
37
|
scope = scope.instance_eval(&additional_scope) if additional_scope
|
36
38
|
|
37
39
|
get_collection(model_config, scope, pagination)
|
@@ -53,23 +55,23 @@ module RailsAdmin
|
|
53
55
|
field = model_config.list.fields.find{ |f| f.name.to_s == params[:sort] }
|
54
56
|
|
55
57
|
column = if field.nil? || field.sortable == true # use params[:sort] on the base table
|
56
|
-
"#{abstract_model.
|
58
|
+
"#{abstract_model.table_name}.#{params[:sort]}"
|
57
59
|
elsif field.sortable == false # use default sort, asked field is not sortable
|
58
|
-
"#{abstract_model.
|
60
|
+
"#{abstract_model.table_name}.#{model_config.list.sort_by}"
|
59
61
|
elsif field.sortable.is_a?(String) && field.sortable.include?('.') # just provide sortable, don't do anything smart
|
60
62
|
field.sortable
|
61
63
|
elsif field.sortable.is_a?(Hash) # just join sortable hash, don't do anything smart
|
62
64
|
"#{field.sortable.keys.first}.#{field.sortable.values.first}"
|
63
65
|
elsif field.association? # use column on target table
|
64
|
-
"#{field.associated_model_config.abstract_model.
|
66
|
+
"#{field.associated_model_config.abstract_model.table_name}.#{field.sortable}"
|
65
67
|
else # use described column in the field conf.
|
66
|
-
"#{abstract_model.
|
68
|
+
"#{abstract_model.table_name}.#{field.sortable}"
|
67
69
|
end
|
68
70
|
|
69
71
|
reversed_sort = (field ? field.sort_reverse? : model_config.list.sort_reverse?)
|
70
72
|
{:sort => column, :sort_reverse => (params[:sort_reverse] == reversed_sort.to_s)}
|
71
73
|
end
|
72
|
-
|
74
|
+
|
73
75
|
def redirect_to_on_success
|
74
76
|
notice = t("admin.flash.successful", :name => @model_config.label, :action => t("admin.actions.#{@action.key}.done"))
|
75
77
|
if params[:_add_another]
|
@@ -20,15 +20,38 @@ module RailsAdmin
|
|
20
20
|
rescue LoadError, NameError
|
21
21
|
nil
|
22
22
|
end
|
23
|
+
|
24
|
+
@@polymorphic_parents = {}
|
25
|
+
|
26
|
+
def polymorphic_parents(adapter, name)
|
27
|
+
@@polymorphic_parents[adapter.to_sym] ||= {}.tap do |hash|
|
28
|
+
all(adapter).each do |am|
|
29
|
+
am.associations.select{|r| r[:as] }.each do |association|
|
30
|
+
(hash[association[:as].to_sym] ||= []) << am.model
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
@@polymorphic_parents[adapter.to_sym][name.to_sym]
|
35
|
+
end
|
36
|
+
|
37
|
+
# For testing
|
38
|
+
def reset_polymorphic_parents
|
39
|
+
@@polymorphic_parents = {}
|
40
|
+
end
|
23
41
|
end
|
24
42
|
|
25
43
|
def initialize(m)
|
26
44
|
@model_name = m.to_s
|
27
|
-
# ActiveRecord
|
28
45
|
if m.ancestors.map(&:to_s).include?('ActiveRecord::Base') && !m.abstract_class?
|
46
|
+
# ActiveRecord
|
29
47
|
@adapter = :active_record
|
30
48
|
require 'rails_admin/adapters/active_record'
|
31
49
|
extend Adapters::ActiveRecord
|
50
|
+
elsif m.ancestors.map(&:to_s).include?('Mongoid::Document')
|
51
|
+
# Mongoid
|
52
|
+
@adapter = :mongoid
|
53
|
+
require 'rails_admin/adapters/mongoid'
|
54
|
+
extend Adapters::Mongoid
|
32
55
|
end
|
33
56
|
end
|
34
57
|
|
@@ -52,5 +75,36 @@ module RailsAdmin
|
|
52
75
|
def pretty_name
|
53
76
|
model.model_name.human
|
54
77
|
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def get_filtering_duration(operator, value)
|
82
|
+
date_format = I18n.t("admin.misc.filter_date_format", :default => I18n.t("admin.misc.filter_date_format", :locale => :en)).gsub('dd', '%d').gsub('mm', '%m').gsub('yy', '%Y')
|
83
|
+
case operator
|
84
|
+
when 'between'
|
85
|
+
start_date = value[1].present? ? (beginning_of_date(Date.strptime(value[1], date_format)) rescue false) : false
|
86
|
+
end_date = value[2].present? ? (Date.strptime(value[2], date_format).end_of_day rescue false) : false
|
87
|
+
when 'today'
|
88
|
+
start_date = beginning_of_date(Date.today)
|
89
|
+
end_date = Date.today.end_of_day
|
90
|
+
when 'yesterday'
|
91
|
+
start_date = beginning_of_date(Date.yesterday)
|
92
|
+
end_date = Date.yesterday.end_of_day
|
93
|
+
when 'this_week'
|
94
|
+
start_date = beginning_of_date(Date.today.beginning_of_week)
|
95
|
+
end_date = Date.today.end_of_week.end_of_day
|
96
|
+
when 'last_week'
|
97
|
+
start_date = beginning_of_date(1.week.ago.to_date.beginning_of_week)
|
98
|
+
end_date = 1.week.ago.to_date.end_of_week.end_of_day
|
99
|
+
else # default
|
100
|
+
start_date = (beginning_of_date(Date.strptime(Array.wrap(value).first, date_format)) rescue false)
|
101
|
+
end_date = (Date.strptime(Array.wrap(value).first, date_format).end_of_day rescue false)
|
102
|
+
end
|
103
|
+
[start_date, end_date]
|
104
|
+
end
|
105
|
+
|
106
|
+
def beginning_of_date(date)
|
107
|
+
date.beginning_of_day
|
108
|
+
end
|
55
109
|
end
|
56
110
|
end
|
@@ -7,11 +7,6 @@ module RailsAdmin
|
|
7
7
|
DISABLED_COLUMN_TYPES = [:tsvector, :blob, :binary, :spatial]
|
8
8
|
AR_ADAPTER = ::ActiveRecord::Base.configurations[Rails.env]['adapter']
|
9
9
|
LIKE_OPERATOR = AR_ADAPTER == "postgresql" ? 'ILIKE' : 'LIKE'
|
10
|
-
BEGINNING_OF_DAY = if AR_ADAPTER == "postgresql"
|
11
|
-
lambda { |date| date.beginning_of_day }
|
12
|
-
else
|
13
|
-
lambda { |date| date.yesterday.end_of_day }
|
14
|
-
end
|
15
10
|
|
16
11
|
def new(params = {})
|
17
12
|
AbstractObject.new(model.new(params))
|
@@ -90,6 +85,14 @@ module RailsAdmin
|
|
90
85
|
end
|
91
86
|
end
|
92
87
|
|
88
|
+
def table_name
|
89
|
+
model.table_name
|
90
|
+
end
|
91
|
+
|
92
|
+
def serialized_attributes
|
93
|
+
model.serialized_attributes.keys
|
94
|
+
end
|
95
|
+
|
93
96
|
private
|
94
97
|
|
95
98
|
def query_conditions(query, fields = config.list.fields.select(&:queryable?))
|
@@ -168,30 +171,12 @@ module RailsAdmin
|
|
168
171
|
"%#{value}"
|
169
172
|
when 'is', '='
|
170
173
|
"#{value}"
|
174
|
+
else
|
175
|
+
return
|
171
176
|
end
|
172
177
|
["(#{column} #{LIKE_OPERATOR} ?)", value]
|
173
178
|
when :datetime, :timestamp, :date
|
174
|
-
|
175
|
-
case operator
|
176
|
-
when 'between'
|
177
|
-
start_date = value[1].present? ? (Date.strptime(value[1], date_format).instance_eval(&BEGINNING_OF_DAY) rescue false) : false
|
178
|
-
end_date = value[2].present? ? (Date.strptime(value[2], date_format).end_of_day rescue false) : false
|
179
|
-
when 'today'
|
180
|
-
start_date = Date.today.instance_eval(&BEGINNING_OF_DAY)
|
181
|
-
end_date = Date.today.end_of_day
|
182
|
-
when 'yesterday'
|
183
|
-
start_date = Date.yesterday.instance_eval(&BEGINNING_OF_DAY)
|
184
|
-
end_date = Date.yesterday.end_of_day
|
185
|
-
when 'this_week'
|
186
|
-
start_date = Date.today.beginning_of_week.instance_eval(&BEGINNING_OF_DAY)
|
187
|
-
end_date = Date.today.end_of_week.end_of_day
|
188
|
-
when 'last_week'
|
189
|
-
start_date = 1.week.ago.to_date.beginning_of_week.instance_eval(&BEGINNING_OF_DAY)
|
190
|
-
end_date = 1.week.ago.to_date.end_of_week.end_of_day
|
191
|
-
else # default
|
192
|
-
start_date = (Date.strptime(Array.wrap(value).first, date_format).instance_eval(&BEGINNING_OF_DAY) rescue false)
|
193
|
-
end_date = (Date.strptime(Array.wrap(value).first, date_format).end_of_day rescue false)
|
194
|
-
end
|
179
|
+
start_date, end_date = get_filtering_duration(operator, value)
|
195
180
|
|
196
181
|
if start_date && end_date
|
197
182
|
["(#{column} BETWEEN ? AND ?)", start_date, end_date]
|
@@ -206,22 +191,19 @@ module RailsAdmin
|
|
206
191
|
end
|
207
192
|
end
|
208
193
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
end
|
217
|
-
end
|
194
|
+
if AR_ADAPTER == "postgresql"
|
195
|
+
def beginning_of_date(date)
|
196
|
+
date.beginning_of_day
|
197
|
+
end
|
198
|
+
else
|
199
|
+
def beginning_of_date(date)
|
200
|
+
date.yesterday.end_of_day
|
218
201
|
end
|
219
|
-
@@polymorphic_parents[name.to_sym]
|
220
202
|
end
|
221
203
|
|
222
204
|
def association_model_lookup(association)
|
223
205
|
if association.options[:polymorphic]
|
224
|
-
RailsAdmin::
|
206
|
+
RailsAdmin::AbstractModel.polymorphic_parents(:active_record, association.name) || []
|
225
207
|
else
|
226
208
|
association.klass
|
227
209
|
end
|
@@ -242,7 +224,7 @@ module RailsAdmin
|
|
242
224
|
end
|
243
225
|
|
244
226
|
def association_polymorphic_lookup(association)
|
245
|
-
association.options[:polymorphic]
|
227
|
+
!!association.options[:polymorphic]
|
246
228
|
end
|
247
229
|
|
248
230
|
def association_primary_key_lookup(association)
|
@@ -0,0 +1,333 @@
|
|
1
|
+
require 'mongoid'
|
2
|
+
require 'rails_admin/config/sections/list'
|
3
|
+
require 'rails_admin/adapters/mongoid/abstract_object'
|
4
|
+
|
5
|
+
module RailsAdmin
|
6
|
+
module Adapters
|
7
|
+
module Mongoid
|
8
|
+
STRING_TYPE_COLUMN_NAMES = [:name, :title, :subject]
|
9
|
+
|
10
|
+
def new(params = {})
|
11
|
+
AbstractObject.new(model.new)
|
12
|
+
end
|
13
|
+
|
14
|
+
def get(id)
|
15
|
+
if object = model.where(:_id=>BSON::ObjectId(id)).first
|
16
|
+
AbstractObject.new object
|
17
|
+
else
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def scoped
|
23
|
+
model.scoped
|
24
|
+
end
|
25
|
+
|
26
|
+
def first(options = {},scope=nil)
|
27
|
+
all(options, scope).first
|
28
|
+
end
|
29
|
+
|
30
|
+
def all(options = {},scope=nil)
|
31
|
+
scope ||= self.scoped
|
32
|
+
scope = scope.includes(options[:include]) if options[:include]
|
33
|
+
scope = scope.limit(options[:limit]) if options[:limit]
|
34
|
+
scope = scope.any_in(:_id => options[:bulk_ids]) if options[:bulk_ids]
|
35
|
+
scope = scope.where(query_conditions(options[:query])) if options[:query]
|
36
|
+
scope = scope.where(filter_conditions(options[:filters])) if options[:filters]
|
37
|
+
scope = scope.page(options[:page]).per(options[:per]) if options[:page] && options[:per]
|
38
|
+
scope = if options[:sort] && options[:sort_reverse]
|
39
|
+
scope.desc(options[:sort])
|
40
|
+
elsif options[:sort]
|
41
|
+
scope.asc(options[:sort])
|
42
|
+
else
|
43
|
+
scope
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def count(options = {},scope=nil)
|
48
|
+
all(options.merge({:limit => false, :page => false}), scope).count
|
49
|
+
end
|
50
|
+
|
51
|
+
def destroy(objects)
|
52
|
+
Array.wrap(objects).each &:destroy
|
53
|
+
end
|
54
|
+
|
55
|
+
def primary_key
|
56
|
+
:_id
|
57
|
+
end
|
58
|
+
|
59
|
+
def associations
|
60
|
+
model.associations.values.map do |association|
|
61
|
+
{
|
62
|
+
:name => association.name.to_sym,
|
63
|
+
:pretty_name => association.name.to_s.tr('_', ' ').capitalize,
|
64
|
+
:type => association_type_lookup(association.macro),
|
65
|
+
:model_proc => Proc.new { association_model_proc_lookup(association) },
|
66
|
+
:primary_key_proc => Proc.new { association_primary_key_lookup(association) },
|
67
|
+
:foreign_key => association_foreign_key_lookup(association),
|
68
|
+
:foreign_type => association_foreign_type_lookup(association),
|
69
|
+
:as => association_as_lookup(association),
|
70
|
+
:polymorphic => association_polymorphic_lookup(association),
|
71
|
+
:inverse_of => association_inverse_of_lookup(association),
|
72
|
+
:read_only => nil,
|
73
|
+
:nested_form => nil
|
74
|
+
}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def properties
|
79
|
+
@properties if @properties
|
80
|
+
@properties = model.fields.map do |name,field|
|
81
|
+
ar_type =
|
82
|
+
if name == '_type'
|
83
|
+
{ :type => :mongoid_type, :length => 1024 }
|
84
|
+
elsif field.type.to_s == 'String'
|
85
|
+
if (length = length_validation_lookup(name)) && length < 256
|
86
|
+
{ :type => :string, :length => length }
|
87
|
+
elsif STRING_TYPE_COLUMN_NAMES.include?(name.to_sym)
|
88
|
+
{ :type => :string, :length => 255 }
|
89
|
+
else
|
90
|
+
{ :type => :text, :length => nil }
|
91
|
+
end
|
92
|
+
else
|
93
|
+
{
|
94
|
+
"Array" => { :type => :serialized, :length => nil },
|
95
|
+
"BigDecimal" => { :type => :string, :length => 1024 },
|
96
|
+
"Boolean" => { :type => :boolean, :length => nil },
|
97
|
+
"BSON::ObjectId" => { :type => :bson_object_id, :length => nil },
|
98
|
+
"Date" => { :type => :date, :length => nil },
|
99
|
+
"DateTime" => { :type => :datetime, :length => nil },
|
100
|
+
"Float" => { :type => :float, :length => nil },
|
101
|
+
"Hash" => { :type => :serialized, :length => nil },
|
102
|
+
"Integer" => { :type => :integer, :length => nil },
|
103
|
+
"Time" => { :type => :datetime, :length => nil },
|
104
|
+
"Object" => { :type => :bson_object_id, :length => nil },
|
105
|
+
}[field.type.to_s] or raise "Need to map field #{field.type.to_s} for field name #{name} in #{model.inspect}"
|
106
|
+
end
|
107
|
+
|
108
|
+
{
|
109
|
+
:name => field.name.to_sym,
|
110
|
+
:pretty_name => field.name.to_s.gsub('_', ' ').strip.capitalize,
|
111
|
+
:nullable? => true,
|
112
|
+
:serial? => false,
|
113
|
+
}.merge(ar_type)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def table_name
|
118
|
+
model.collection.name
|
119
|
+
end
|
120
|
+
|
121
|
+
def serialized_attributes
|
122
|
+
# Mongoid Array and Hash type columns are mapped to RA serialized type
|
123
|
+
# through type detection in self#properties.
|
124
|
+
[]
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def query_conditions(query, fields = config.list.fields.select(&:queryable?))
|
130
|
+
statements = []
|
131
|
+
|
132
|
+
fields.each do |field|
|
133
|
+
conditions_per_collection = {}
|
134
|
+
field.searchable_columns.flatten.each do |column_infos|
|
135
|
+
collection_name, column_name = column_infos[:column].split('.')
|
136
|
+
statement = build_statement(column_name, column_infos[:type], query, field.search_operator)
|
137
|
+
if statement
|
138
|
+
conditions_per_collection[collection_name] ||= []
|
139
|
+
conditions_per_collection[collection_name] << statement
|
140
|
+
end
|
141
|
+
end
|
142
|
+
statements.concat make_condition_for_current_collection(field, conditions_per_collection)
|
143
|
+
end
|
144
|
+
|
145
|
+
if statements.any?
|
146
|
+
{ '$or' => statements }
|
147
|
+
else
|
148
|
+
{}
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# filters example => {"string_field"=>{"0055"=>{"o"=>"like", "v"=>"test_value"}}, ...}
|
153
|
+
# "0055" is the filter index, no use here. o is the operator, v the value
|
154
|
+
def filter_conditions(filters, fields = config.list.fields.select(&:filterable?))
|
155
|
+
statements = []
|
156
|
+
|
157
|
+
filters.each_pair do |field_name, filters_dump|
|
158
|
+
filters_dump.each do |filter_index, filter_dump|
|
159
|
+
conditions_per_collection = {}
|
160
|
+
field = fields.find{|f| f.name.to_s == field_name}
|
161
|
+
next unless field
|
162
|
+
field.searchable_columns.each do |column_infos|
|
163
|
+
collection_name, column_name = column_infos[:column].split('.')
|
164
|
+
statement = build_statement(column_name, column_infos[:type], filter_dump[:v], (filter_dump[:o] || 'default'))
|
165
|
+
if statement
|
166
|
+
conditions_per_collection[collection_name] ||= []
|
167
|
+
conditions_per_collection[collection_name] << statement
|
168
|
+
end
|
169
|
+
end
|
170
|
+
if conditions_per_collection.any?
|
171
|
+
field_statements = make_condition_for_current_collection(field, conditions_per_collection)
|
172
|
+
if field_statements.length > 1
|
173
|
+
statements << { '$or' => field_statements }
|
174
|
+
else
|
175
|
+
statements << field_statements.first
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
if statements.any?
|
182
|
+
{ '$and' => statements }
|
183
|
+
else
|
184
|
+
{}
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def build_statement(column, type, value, operator)
|
189
|
+
# this operator/value has been discarded (but kept in the dom to override the one stored in the various links of the page)
|
190
|
+
return if operator == '_discard' || value == '_discard'
|
191
|
+
|
192
|
+
# filtering data with unary operator, not type dependent
|
193
|
+
if operator == '_blank' || value == '_blank'
|
194
|
+
return { column => {'$in' => [nil, '']} }
|
195
|
+
elsif operator == '_present' || value == '_present'
|
196
|
+
return { column => {'$nin' => [nil, '']} }
|
197
|
+
elsif operator == '_null' || value == '_null'
|
198
|
+
return { column => nil }
|
199
|
+
elsif operator == '_not_null' || value == '_not_null'
|
200
|
+
return { column => {'$ne' => nil} }
|
201
|
+
elsif operator == '_empty' || value == '_empty'
|
202
|
+
return { column => '' }
|
203
|
+
elsif operator == '_not_empty' || value == '_not_empty'
|
204
|
+
return { column => {'$ne' => ''} }
|
205
|
+
end
|
206
|
+
# now we go type specific
|
207
|
+
case type
|
208
|
+
when :boolean
|
209
|
+
return { column => false } if ['false', 'f', '0'].include?(value)
|
210
|
+
return { column => true } if ['true', 't', '1'].include?(value)
|
211
|
+
when :integer, :belongs_to_association
|
212
|
+
return if value.blank?
|
213
|
+
{ column => value.to_i } if value.to_i.to_s == value
|
214
|
+
when :string, :text
|
215
|
+
return if value.blank?
|
216
|
+
value = case operator
|
217
|
+
when 'default', 'like'
|
218
|
+
Regexp.compile(Regexp.escape(value))
|
219
|
+
when 'starts_with'
|
220
|
+
Regexp.compile("^#{Regexp.escape(value)}")
|
221
|
+
when 'ends_with'
|
222
|
+
Regexp.compile("#{Regexp.escape(value)}$")
|
223
|
+
when 'is', '='
|
224
|
+
value.to_s
|
225
|
+
else
|
226
|
+
return
|
227
|
+
end
|
228
|
+
{ column => value }
|
229
|
+
when :datetime, :timestamp, :date
|
230
|
+
start_date, end_date = get_filtering_duration(operator, value)
|
231
|
+
|
232
|
+
if start_date && end_date
|
233
|
+
{ column => { '$gte' => start_date, '$lte' => end_date } }
|
234
|
+
elsif start_date
|
235
|
+
{ column => { '$gte' => start_date } }
|
236
|
+
elsif end_date
|
237
|
+
{ column => { '$lte' => end_date } }
|
238
|
+
end
|
239
|
+
when :enum
|
240
|
+
return if value.blank?
|
241
|
+
{ column => { "$in" => Array.wrap(value) } }
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def association_model_proc_lookup(association)
|
246
|
+
if association.polymorphic? && association.macro == :referenced_in
|
247
|
+
RailsAdmin::AbstractModel.polymorphic_parents(:mongoid, association.name) || []
|
248
|
+
else
|
249
|
+
association.klass
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def association_foreign_type_lookup(association)
|
254
|
+
if association.polymorphic? && association.macro == :referenced_in
|
255
|
+
association.inverse_type.try(:to_sym) || :"#{association.name}_type"
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def association_as_lookup(association)
|
260
|
+
association.as.try :to_sym
|
261
|
+
end
|
262
|
+
|
263
|
+
def association_polymorphic_lookup(association)
|
264
|
+
!!association.polymorphic? && association.macro == :referenced_in
|
265
|
+
end
|
266
|
+
|
267
|
+
def association_primary_key_lookup(association)
|
268
|
+
:_id # todo
|
269
|
+
end
|
270
|
+
|
271
|
+
def association_inverse_of_lookup(association)
|
272
|
+
association.inverse_of.try :to_sym
|
273
|
+
end
|
274
|
+
|
275
|
+
def association_foreign_key_lookup(association)
|
276
|
+
association.foreign_key.to_sym rescue nil
|
277
|
+
end
|
278
|
+
|
279
|
+
def association_type_lookup(macro)
|
280
|
+
case macro.to_sym
|
281
|
+
when :referenced_in, :embedded_in
|
282
|
+
:belongs_to
|
283
|
+
when :references_one, :embeds_one
|
284
|
+
:has_one
|
285
|
+
when :references_many, :embeds_many
|
286
|
+
:has_many
|
287
|
+
when :references_and_referenced_in_many
|
288
|
+
:has_and_belongs_to_many
|
289
|
+
else
|
290
|
+
raise "Unknown association type: #{macro.inspect}"
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def length_validation_lookup(name)
|
295
|
+
shortest = model.validators.select do |validator|
|
296
|
+
validator.attributes.include?(name.to_sym) &&
|
297
|
+
validator.class == ActiveModel::Validations::LengthValidator
|
298
|
+
end.min{|a, b| a.options[:maximum] <=> b.options[:maximum] }
|
299
|
+
if shortest
|
300
|
+
shortest.options[:maximum]
|
301
|
+
else
|
302
|
+
false
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def make_condition_for_current_collection(target_field, conditions_per_collection)
|
307
|
+
result =[]
|
308
|
+
conditions_per_collection.each do |collection_name, conditions|
|
309
|
+
if collection_name == table_name
|
310
|
+
# conditions referring current model column are passed directly
|
311
|
+
result.concat conditions
|
312
|
+
else
|
313
|
+
# otherwise, collect ids of documents that satisfy search condition
|
314
|
+
result.concat perform_search_on_associated_collection(target_field.name, conditions)
|
315
|
+
end
|
316
|
+
end
|
317
|
+
result
|
318
|
+
end
|
319
|
+
|
320
|
+
def perform_search_on_associated_collection(field_name, conditions)
|
321
|
+
target_association = associations.find{|a| a[:name] == field_name }
|
322
|
+
return [] unless target_association
|
323
|
+
model = target_association[:model_proc].call
|
324
|
+
case target_association[:type]
|
325
|
+
when :belongs_to, :has_and_belongs_to_many
|
326
|
+
[{ target_association[:foreign_key].to_s => { '$in' => model.where('$or' => conditions).all.map{|r| r.send(target_association[:primary_key_proc].call)} }}]
|
327
|
+
when :has_many
|
328
|
+
[{ target_association[:primary_key_proc].call.to_s => { '$in' => model.where('$or' => conditions).all.map{|r| r.send(target_association[:foreign_key])} }}]
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|