carload 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8cd7d8e37f5576e78d936449c1e5037208db1789
4
- data.tar.gz: feed52ae809a23d8cdc1f591e8ea76760e387945
3
+ metadata.gz: 9dc8244c3cd30b96538af399c5d325a32c7b46f2
4
+ data.tar.gz: 038e2a71da51255577b35a15fc21a8b6b66fb7b8
5
5
  SHA512:
6
- metadata.gz: cbc8dcf229e5789e2f7517b8b8c7e1b717abfc63372fd76bc537571969072a0f260d8cd5b90b56aba35ea5d5780f20a6b3a2cb7222552eb1828548ba528100d2
7
- data.tar.gz: 4faa773c891477b8f1b72c9b54e33ecebdd55d8c9e333ced189a8acec895c24309d54bf4ae4ca14fd4133ef4075c5d74e92cf4d1aa78b52ce68ead454f862df8
6
+ metadata.gz: 7a24677546621e1a187042e2e27ea5b603495590543b624133ab06151ddb423e2d7ad07695241277f52f3b9efc212bee433b87583140749be9e91a9693818d23
7
+ data.tar.gz: 99b19227d0f62ad5ae1081e5fa1deb04becb1373ec97f88f40514d9493e265b3e4bc84907d829afa908bad798287e7c62da828f22c9798fa3f60d18ccf42e770
data/README.md CHANGED
@@ -26,9 +26,24 @@ You can edit the initializer `config/initializers/carload.rb` for example:
26
26
 
27
27
  ```ruby
28
28
  Carload.setup do |config|
29
+ # Set the title that will be displayed on the browser tab area.
30
+ config.page.title = nil
31
+
32
+ # Set the footer text that will be displayed on each page.
33
+ config.page.footer = nil
34
+
35
+ # Set the colors of page elements.
36
+ config.page.main_color = nil
37
+ config.page.text_color = 'black'
38
+ config.page.button_color = nil
39
+ config.page.button_text_color = nil
40
+
29
41
  # Specify which authentication solution is used. Currently, we only support Devise.
30
42
  config.auth_solution = :devise
31
43
 
44
+ # Set which file upload solution is used. Currently, we only support Carrierwave.
45
+ config.upload_solution = :carrierwave
46
+
32
47
  # Set the actions used to discern user's permission to access dashboard.
33
48
  #
34
49
  # config.dashboard.permits_user.<method> = '...'
@@ -87,6 +102,8 @@ class Dashboard < Carload::Dashboard
87
102
  end
88
103
  ```
89
104
 
105
+ - You can run another generator `rails g carload:dash <model_name>` to generate the above content automatically (it may ask you some question).
106
+
90
107
  - Make sure you have the necessary I18n translation files, for example:
91
108
 
92
109
  ```yaml
@@ -15,3 +15,18 @@
15
15
  .select2-container {
16
16
  min-width: 174px;
17
17
  }
18
+
19
+ .select2-search__field {
20
+ line-height: 22px;
21
+ }
22
+ .select2-search__field:focus {
23
+ box-shadow: none !important;
24
+ }
25
+ .select2-selection__choice {
26
+ line-height: 22px;
27
+ background-color: $main-color !important;
28
+ color: white;
29
+ }
30
+ .select2-selection__choice__remove {
31
+ color: white !important;
32
+ }
@@ -0,0 +1,5 @@
1
+ $main-color: <%= Carload.page.main_color || '#8C7DA6' %>;
2
+ $text-color: <%= Carload.page.text_color || '#3F3F44' %>;
3
+ $button-color-1: <%= Carload.page.button_color_1 || Carload.page.button_color || '#60b044' %>;
4
+ $button-color-2: <%= Carload.page.button_color_2 || Carload.page.button_color || '#8add6d' %>;
5
+ $button-text-color: <%= Carload.page.button_text_color || 'white' %>;
@@ -51,19 +51,21 @@ th {
51
51
  }
52
52
 
53
53
  .btn-primary {
54
- color: #fff;
54
+ color: $button-text-color;
55
55
  text-shadow: 0 -1px 0 rgba(0,0,0,0.15);
56
- background-color: #60b044;
57
- background-image: -webkit-linear-gradient(#8add6d, #60b044);
58
- background-image: linear-gradient(#8add6d, #60b044);
59
- border-color: #5ca941;
56
+ background-color: $button-color-1;
57
+ background-image: -webkit-linear-gradient($button-color-2, $button-color-1);
58
+ background-image: linear-gradient($button-color-2, $button-color-1);
59
+ border: none;
60
+ height: 34px;
60
61
  }
61
62
  .btn-primary:hover {
62
- color: #fff;
63
- background-color: #569e3d;
64
- background-image: -webkit-linear-gradient(#79d858, #569e3d);
65
- background-image: linear-gradient(#79d858, #569e3d);
66
- border-color: #4a993e;
63
+ color: lighten($button-text-color, 20%);
64
+ background-color: lighten($button-color-1, 20%);
65
+ background-image: -webkit-linear-gradient(lighten($button-color-2, 20%), lighten($button-color-1, 20%));
66
+ background-image: linear-gradient(lighten($button-color-2, 20%), lighten($button-color-1, 20%));
67
+ border: none;
68
+ height: 34px;
67
69
  }
68
70
 
69
71
  .form-control:hover {
@@ -75,3 +77,7 @@ th {
75
77
  border-color: $main-color !important;
76
78
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px $main-color !important;
77
79
  }
80
+
81
+ .label-primary {
82
+ background-color: $main-color;
83
+ }
@@ -25,11 +25,13 @@ $half-spacing: 8px;
25
25
  border-top-right-radius: 0;
26
26
  }
27
27
  a {
28
+ color: $text-color;
28
29
  border-color: $main-color;
29
30
  }
30
31
  a:hover {
31
32
  background-color: lighten($main-color, 20%);
32
33
  border-color: lighten($main-color, 20%);
34
+ color: white;
33
35
  }
34
36
  a.active {
35
37
  background-color: $main-color;
@@ -64,6 +66,9 @@ $half-spacing: 8px;
64
66
  td {
65
67
  overflow: scroll;
66
68
  }
69
+ th:first-child, td:first-child {
70
+ padding-left: $spacing;
71
+ }
67
72
  th:last-child {
68
73
  width: 50px;
69
74
  }
@@ -78,22 +83,19 @@ $half-spacing: 8px;
78
83
  #dashboard-index-buttons {
79
84
  display: inline-block;
80
85
  margin-right: $spacing;
81
- }
82
- #new_q {
83
- display: inline-block;
86
+ line-height: 34px;
84
87
  }
85
88
 
86
- .search-fields {
87
- display: inline-flex;
88
- .form-group {
89
- display: inline-flex;
90
- margin-right: 5px;
91
- margin-bottom: 0;
89
+ #search-input {
90
+ position: relative;
91
+ .icon {
92
+ position: absolute;
93
+ bottom: 8px;
94
+ left: 8px;
95
+ color: #ccc;
92
96
  }
93
- .form-group:last-child {
94
- margin-right: 0;
97
+ input {
98
+ width: 100%;
99
+ padding-left: 30px;
95
100
  }
96
101
  }
97
- .search-buttons {
98
- display: inline-flex;
99
- }
@@ -8,14 +8,20 @@ module Carload
8
8
 
9
9
  before_action :set_model
10
10
  before_action :set_object, only: [:edit, :update, :destroy]
11
+ before_action :transform_polymorphic_params, only: [:create, :update]
12
+
11
13
  include Croppable
14
+ include ApplicationHelper
12
15
 
13
16
  def index
14
17
  authorize :carload_dashboard, :index? unless Carload.auth_solution == :none
15
- @search = @model_class.search(params[:q])
16
- @objects = @search.result.page(params[:page])
18
+ if params[:search].present?
19
+ @query = params[:search][:query]
20
+ @objects = @model_class.search(params[:search][:query]).page(params[:page])
21
+ else
22
+ @objects = @model_class.page(params[:page])
23
+ end
17
24
  @show_attributes = Dashboard.model(@model_name).index_page[:shows][:attributes] + [:created_at, :updated_at]
18
- @search_attributes = Dashboard.model(@model_name).index_page[:searches][:attributes]
19
25
  render "dashboard/#{@model_names}/index.html.erb"
20
26
  end
21
27
 
@@ -82,5 +88,15 @@ module Carload
82
88
  def rescue_unmanaged_model_error exception
83
89
  redirect_to dashboard_error_path(message: exception.message)
84
90
  end
91
+
92
+ def transform_polymorphic_params
93
+ polymorphic_params = {}
94
+ params[@model_name].each do |key, value|
95
+ if polymorphic? key
96
+ polymorphic_params["#{key}_id"], polymorphic_params["#{key}_type"] = value.split(',')
97
+ end
98
+ end
99
+ params[@model_name].merge! polymorphic_params
100
+ end
85
101
  end
86
102
  end
@@ -8,8 +8,24 @@ module Carload
8
8
  end
9
9
  end
10
10
 
11
+ def polymorphic? attribute_name
12
+ Dashboard.model(@model_name).associated_models.each_value do |associated_model|
13
+ return associated_model[:name] if attribute_name =~ /#{associated_model[:name]}/ and associated_model[:polymorphic]
14
+ end
15
+ false
16
+ end
17
+
11
18
  def image? attribute_name
12
19
  attribute_name.to_s =~ /image|logo|img/
13
20
  end
21
+
22
+ def id_or_ids associated_model
23
+ case associated_model[:association_type]
24
+ when :has_many
25
+ "#{associated_model[:name]}_ids"
26
+ else
27
+ "#{associated_model[:name]}_id"
28
+ end
29
+ end
14
30
  end
15
31
  end
@@ -1,59 +1,45 @@
1
1
  module Carload
2
2
  module DashboardHelper
3
- def generate_input form, model_name, attribute_name, column
4
- if Dashboard::ModelSpec.foreign_key? attribute_name
3
+ def generate_input form, model_name, attribute_name, options = {}
4
+ if options[:polymorphic]
5
+ form.input attribute_name,
6
+ collection: @model_class.send(attribute_name.to_s.pluralize),
7
+ selected: options[:value],
8
+ input_html: { class: 'use-select2' }
9
+ elsif attribute_name =~ /_id$/
5
10
  associated_model = attribute_name.gsub(/_id$/, '').to_sym
6
- options = Dashboard.model(model_name).associated_models[associated_model]
7
- label_attribute = options[:choose_by]
8
- if options[:polymorphic]
9
- forms = ''
10
- options[:available_models].each_with_index do |real_model, i|
11
- forms << form.input(attribute_name,
12
- label: t("activerecord.attributes.#{model_name}.#{real_model}.#{label_attribute}"),
13
- collection: real_model.camelize.constantize.all,
14
- label_method: label_attribute,
15
- value_method: :id,
16
- input_html: {
17
- class: 'use-select2',
18
- data: {
19
- placeholder: t('carload.placeholder.select', thing: t("activerecord.attributes.#{real_model}.#{label_attribute}"))
20
- }
21
- },
22
- wrapper_html: {
23
- id: "#{real_model.camelize}-#{label_attribute}"
24
- }
25
- )
26
- end
27
- # Add JavaScript to select which real model to work on.
28
- forms << <<-EOT
29
- <script>
30
- $('.#{model_name}_#{associated_model}_id').hide()
31
- if ($('##{model_name}_#{associated_model}_type').val() != '') {
32
- $('#' + $('##{model_name}_#{associated_model}_type').val() + '-#{label_attribute}').show()
33
- }
34
- $('##{model_name}_#{associated_model}_type').change(function() {
35
- $('.#{model_name}_#{associated_model}_id').hide()
36
- $('#' + $(this).val() + '-#{label_attribute}').show()
37
- $('.package_packagable_id > .select2-container').css('width', '100%')
38
- })
39
- </script>
40
- EOT
41
- raw forms.html_safe
42
- else
43
- form.association associated_model,
44
- label_method: label_attribute,
45
- label: t("activerecord.models.#{associated_model}"),
46
- input_html: {
47
- class: 'use-select2',
48
- data: {
49
- placeholder: t('carload.placeholder.select', thing: t("activerecord.attributes.#{associated_model}.#{label_attribute}"))
50
- }
11
+ association_specs = Dashboard.model(model_name).associated_models[associated_model]
12
+ label_attribute = association_specs[:choose_by]
13
+ form.association associated_model,
14
+ label_method: label_attribute,
15
+ label: t("activerecord.models.#{associated_model}"),
16
+ input_html: {
17
+ class: 'use-select2',
18
+ data: {
19
+ placeholder: t('carload.placeholder.select', thing: t("activerecord.attributes.#{associated_model}.#{label_attribute}"))
51
20
  }
52
- end
21
+ }
22
+ elsif attribute_name =~ /_ids$/
23
+ # Mandle many-to-many association.
24
+ associated_model = attribute_name.gsub(/_ids$/, '').to_sym
25
+ association_specs = Dashboard.model(model_name).associated_models[associated_model]
26
+ label_attribute = association_specs[:choose_by]
27
+ form.input attribute_name,
28
+ label: t("activerecord.attributes.#{associated_model}.#{label_attribute}") + " (#{t("activerecord.models.#{associated_model}")})",
29
+ collection: associated_model.to_s.camelize.constantize.all,
30
+ label_method: label_attribute,
31
+ value_method: :id,
32
+ input_html: {
33
+ class: 'use-select2',
34
+ multiple: true,
35
+ data: {
36
+ placeholder: t('carload.placeholder.select', thing: t("activerecord.attributes.#{associated_model}.#{label_attribute}"))
37
+ }
38
+ }
53
39
  elsif attribute_name =~ /_type$/
54
40
  associated_model = attribute_name.gsub(/_type$/, '').to_sym
55
- options = Dashboard.model(model_name).associated_models[associated_model]
56
- form.input attribute_name, collection: options[:available_models].map(&:camelize),
41
+ association_specs = Dashboard.model(model_name).associated_models[associated_model]
42
+ form.input attribute_name, collection: association_specs[:instance_models].map{ |x| x.to_s.camelize },
57
43
  input_html: {
58
44
  class: 'use-select2',
59
45
  data: {
@@ -67,20 +53,29 @@ module Carload
67
53
  end
68
54
  end
69
55
 
70
- def generate_search_input form, model_name, attribute
71
- if attribute[:options]
72
- form.input "#{attribute[:name].to_s.gsub('.', '_')}_#{attribute[:term]}",
73
- required: false, label: false, collection: attribute[:options],
74
- input_html: {
75
- class: 'use-select2',
76
- data: {
77
- placeholder: t('carload.placeholder.select', thing: t("activerecord.attributes.#{model_name}.#{attribute[:name]}"))
78
- }
79
- }
80
- else
81
- form.input "#{attribute[:name].to_s.gsub('.', '_')}_#{attribute[:term]}",
82
- placeholder: t("activerecord.attributes.#{@model_name}.#{attribute[:name]}"),
83
- required: false, label: false
56
+ def generate_show_title attribute
57
+ case attribute
58
+ when Symbol
59
+ begin
60
+ t("activerecord.attributes.#{@model_name}.#{attribute}", raise: true)
61
+ rescue
62
+ t("carload.activerecord.#{attribute}", raise: true)
63
+ end
64
+ when String
65
+ begin
66
+ t("activerecord.attributes.#{@model_name}.#{attribute}", raise: true)
67
+ rescue
68
+ "#{t("activerecord.attributes.#{attribute}", raise: true)} (#{t("activerecord.models.#{attribute.split('.').first.to_s.singularize}", raise: true)})"
69
+ end
70
+ when Array
71
+ if attribute.first == :pluck
72
+ raise UnsupportedError.new("attribute #{attribute}") if attribute.size != 3
73
+ model_name = attribute[1].to_s.singularize
74
+ attribute_name = attribute[2]
75
+ "#{t("activerecord.attributes.#{model_name}.#{attribute_name}", raise: true)} (#{t("activerecord.models.#{model_name}", raise: true)})"
76
+ else
77
+ "#{t("activerecord.attributes.#{attribute.join('.')}", raise: true)} (#{t("activerecord.models.#{attribute[0].to_s.singularize}", raise: true)})"
78
+ end
84
79
  end
85
80
  end
86
81
 
@@ -89,7 +84,20 @@ module Carload
89
84
  when Symbol
90
85
  object.send attribute
91
86
  when String
92
- eval "object.#{attribute.gsub('.', '&.')}"
87
+ res = eval "object.#{attribute.gsub('.', '&.')}"
88
+ case res
89
+ when String
90
+ res
91
+ when Array
92
+ raw res.map { |x| "<span class='label label-primary'>#{x}</span>" }.join(' ')
93
+ end
94
+ when Array
95
+ if attribute.first == :pluck
96
+ raise UnsupportedError.new("attribute #{attribute}") if attribute.size != 3
97
+ generate_show object, "#{attribute[1].to_s.pluralize}.pluck(:#{attribute[2]})"
98
+ else
99
+ generate_show object, attribute.join('.')
100
+ end
93
101
  end
94
102
  end
95
103
  end
@@ -1,7 +1,19 @@
1
1
  <%= simple_form_for @object, url: "/carload/dashboard/#{@model_names}/#{@object.id}" do |f| %>
2
+ <!-- Normal attributes -->
2
3
  <% @model_class.columns_hash.each do |name, column| %>
3
- <% next if Dashboard::ModelSpec::SkippedAttributes.include? name %>
4
- <%= generate_input(f, @model_name, name, column) rescue nil %>
4
+ <% next if Carload::ModelSpec::SkippedAttributes.include? name or polymorphic? name %>
5
+ <%= generate_input f, @model_name, name %>
6
+ <% end %>
7
+ <!-- Polymorphics -->
8
+ <% Dashboard.model(@model_name).associated_models.each_value do |associated_model| %>
9
+ <% next if associated_model[:polymorphic] != true %>
10
+ <%= generate_input f, @model_name, associated_model[:name], polymorphic: true,
11
+ value: "#{@object.send("#{associated_model[:name]}_id")},#{@object.send("#{associated_model[:name]}_type")}" %>
12
+ <% end %>
13
+ <!-- Join tables -->
14
+ <% Dashboard.model(@model_name).associated_models.each_value do |associated_model| %>
15
+ <% next if not associated_model[:join_table] %>
16
+ <%= generate_input f, @model_name, id_or_ids(associated_model) %>
5
17
  <% end %>
6
18
  <%= f.button :submit, t('carload.action.submit'), class: 'btn btn-primary' %>
7
19
  <% end %>
@@ -9,13 +9,7 @@
9
9
  <tr>
10
10
  <th width='20'>ID</th>
11
11
  <% @show_attributes.each do |attribute| %>
12
- <th width='100'>
13
- <% begin %>
14
- <%= t("activerecord.attributes.#{@model_name}.#{attribute}", raise: true) %>
15
- <% rescue %>
16
- <%= t("carload.activerecord.#{attribute}") %>
17
- <% end %>
18
- </th>
12
+ <th width='100'><%= generate_show_title attribute rescue attribute %></th>
19
13
  <% end %>
20
14
  <th width='20'><%= t('carload.actions') %></th>
21
15
  </tr>
@@ -1,10 +1,6 @@
1
- <%= simple_form_for @search, url: dashboard_search_path(@model_names), method: :post do |f| %>
2
- <div class='search-fields'>
3
- <% @search_attributes.each do |attribute| %>
4
- <%= generate_search_input f, @model_name, attribute %>
5
- <% end %>
6
- </div>
7
- <div class='search-buttons'>
8
- <%= f.submit t('carload.action.search'), class: 'btn btn-primary' %>
9
- </div>
10
- <% end %>
1
+ <div id='search-input'>
2
+ <%= form_for :search, url: dashboard_search_path(@model_names), method: :get do |f| %>
3
+ <span class='icon'><%= fa_icon('search') %></span>
4
+ <%= f.text_field :query, class: 'form-control', placeholder: 'Search ...', value: @query %>
5
+ <% end %>
6
+ </div>
@@ -1,7 +1,7 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
- <title>Carload</title>
4
+ <title><%= Carload.page.title || 'Carload' %></title>
5
5
  <meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0'/>
6
6
  <%= stylesheet_link_tag 'carload/dashboard', media: 'all' %>
7
7
  <%= javascript_include_tag 'carload/dashboard' %>
@@ -25,7 +25,7 @@
25
25
  </div>
26
26
 
27
27
  <footer class='center'>
28
- <%= raw t('carload.footer.message') %>
28
+ <%= raw Carload.page.footer || t('carload.footer.message') %>
29
29
  </footer>
30
30
  </div>
31
31
 
@@ -0,0 +1,7 @@
1
+ class CarloadEnableZhparserExtension < ActiveRecord::Migration[5.0]
2
+ def change
3
+ enable_extension :zhparser
4
+ execute 'create text search configuration zhparser (parser = zhparser);'
5
+ execute 'alter text search configuration zhparser add mapping for n,v,a,i,e,l with simple;'
6
+ end
7
+ end
@@ -0,0 +1,52 @@
1
+ module Carload
2
+ module AssociationPipelines
3
+ def association_pipelines
4
+ [ :pipeline_1, :pipeline_2, :pipeline_3 ]
5
+ end
6
+
7
+ # Find polymorphic instance models.
8
+ def pipeline_1 association
9
+ return unless association.options[:polymorphic]
10
+ associated_model = @associated_models[association.name]
11
+ ActiveRecord::Base.descendants.each do |model|
12
+ next if model.name == 'ApplicationRecord' or model.name.underscore == @name.to_s
13
+ model.reflect_on_all_associations.each do |model_association|
14
+ next unless model_association.options[:as] == association.name
15
+ associated_model[:instance_models] ||= []
16
+ associated_model[:instance_models] << model.name.underscore.to_sym
17
+ if not associated_model[:attributes]
18
+ associated_model[:attributes] = model.column_names - ModelSpec::SkippedAttributes
19
+ else
20
+ associated_model[:attributes] = associated_model[:attributes] & model.column_names
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ # Add possible attributes to let user choose.
27
+ def pipeline_2 association
28
+ return unless associated_model = @associated_models[association.name.to_s.singularize.to_sym]
29
+ model = association.name.to_s.singularize.camelize.constantize rescue return
30
+ associated_model[:attributes] ||= []
31
+ associated_model[:attributes] = model.column_names - ModelSpec::SkippedAttributes
32
+ associated_model[:attributes] = associated_model[:attributes] - [
33
+ "#{@name}_id",
34
+ "#{associated_model[:polymorphic]}_id",
35
+ "#{associated_model[:polymorphic]}_type"
36
+ ]
37
+ end
38
+
39
+ # Add has-many permitted attribute.
40
+ def pipeline_3 association
41
+ _association = (association.delegate_reflection rescue nil) || association
42
+ return unless _association.class == ActiveRecord::Reflection::HasManyReflection
43
+ # Exclude join table.
44
+ return unless @klass.reflect_on_all_associations.select { |x| x.options[:through] == _association.name }.empty?
45
+ @attributes[:permitted].each do |permitted|
46
+ next unless permitted.class == Hash
47
+ return if permitted.keys.first == :"#{_association.class_name.underscore}_ids"
48
+ end
49
+ @attributes[:permitted] << { :"#{_association.class_name.underscore}_ids" => [] }
50
+ end
51
+ end
52
+ end
@@ -1,112 +1,12 @@
1
1
  module Carload
2
2
  class Dashboard
3
- class ModelSpec
4
- attr_accessor :default, :attributes, :index_page, :associated_models
5
-
6
- SkippedAttributes = [
7
- 'id', 'created_at', 'updated_at',
8
- 'encrypted_password', 'reset_password_token',
9
- 'reset_password_sent_at', 'remember_created_at',
10
- 'sign_in_count', 'current_sign_in_at',
11
- 'last_sign_in_at', 'current_sign_in_ip',
12
- 'last_sign_in_ip'
13
- ].freeze
14
-
15
- def self.foreign_key? attribute
16
- attribute =~ /_id$/
17
- end
18
-
19
- def foreign_key? attribute
20
- ModelSpec.foreign_key? attribute
21
- end
22
-
23
- def self.polymorphic? model_class, attribute
24
- return false unless foreign_key? attribute
25
- model_class.column_names.include? "#{attribute.to_s.gsub('_id', '')}_type"
26
- end
27
-
28
- def polymorphic? model_class, attribute
29
- ModelSpec.polymorphic? model_class, attribute
30
- end
31
-
32
- def initialize model_class = nil
33
- @default = false
34
- @attributes = ExtendedHash.new
35
- @index_page = ExtendedHash.new
36
- @index_page[:shows] = ExtendedHash.new
37
- @index_page[:searches] = ExtendedHash.new
38
- @associated_models = {}
39
- if model_class
40
- @attributes[:permitted] = model_class.column_names - SkippedAttributes
41
- @attributes[:permitted].each do |attribute|
42
- @index_page[:shows][:attributes] ||= []
43
- @index_page[:searches][:attributes] ||= []
44
- if foreign_key? attribute
45
- associated_model = attribute.gsub('_id', '')
46
- @associated_models[associated_model] = {
47
- choose_by: nil, # Wait for setting.
48
- polymorphic: polymorphic?(model_class, attribute),
49
- model: model_class.name.underscore.to_sym
50
- }
51
- else
52
- @index_page[:shows][:attributes] << attribute
53
- @index_page[:searches][:attributes] << { name: attribute.to_sym, term: :cont }
54
- end
55
- end
56
- end
57
- end
58
-
59
- def changed? spec
60
- not @attributes[:permitted] == spec.attributes[:permitted] or
61
- not @index_page[:searches][:attributes] == spec.index_page[:searches][:attributes]
62
- end
63
-
64
- def revise_stage_1!
65
- # Handle polymorphic associated models if necessary.
66
- @associated_models.each do |associated_model, options|
67
- next if not options or not options[:polymorphic]
68
- ActiveRecord::Base.descendants.each do |model|
69
- next if model.name == 'ApplicationRecord'
70
- if not model.reflect_on_all_associations.map(&:name).select { |x| x.to_s =~ /\b(#{options[:model]}|#{options[:model].to_s.pluralize})\b/ }.empty?
71
- options[:available_models] ||= []
72
- options[:available_models] << model.name.underscore
73
- if not options[:common_attributes]
74
- options[:common_attributes] = model.column_names - SkippedAttributes
75
- else
76
- options[:common_attributes] = options[:common_attributes] & model.column_names
77
- end
78
- end
79
- end
80
- end
81
- end
82
-
83
- def revise_stage_2!
84
- # Handle associated models if necessary.
85
- @associated_models.each do |associated_model, options|
86
- next if not options
87
- if options[:polymorphic]
88
- # There should be a <associated_model>_type already.
89
- @index_page[:shows][:attributes] << "#{associated_model}.#{options[:choose_by]}"
90
- @index_page[:searches][:attributes].each do |attribute|
91
- next unless attribute[:name] == :"#{associated_model}_type"
92
- attribute[:options] = options[:available_models]
93
- end
94
- else
95
- @index_page[:searches][:attributes] << {
96
- name: "#{associated_model}.#{options[:choose_by]}",
97
- term: :cont
98
- }
99
- end
100
- end
101
- end
102
- end
103
-
104
3
  class << self
105
4
  def model name, &block
106
5
  name = name.to_sym
107
6
  if block_given?
108
7
  @@models ||= {}
109
8
  spec = @@models[name] || ModelSpec.new
9
+ spec.name = name
110
10
  yield spec
111
11
  @@models[name] = spec
112
12
  else
@@ -118,12 +18,9 @@ module Carload
118
18
  model_a = options.keys.first
119
19
  model_b = options.values.first
120
20
  options.shift
21
+ options[:name] = model_b
121
22
  @@models[model_a] ||= ModelSpec.new
122
23
  @@models[model_a].associated_models[model_b] = options
123
- unless options[:polymorphic]
124
- @@models[model_b] ||= ModelSpec.new
125
- @@models[model_b].associated_models[model_a] = nil
126
- end
127
24
  end
128
25
 
129
26
  def models
@@ -153,10 +50,10 @@ module Carload
153
50
  RUBY
154
51
  default = false
155
52
  next if spec.associated_models.empty?
156
- spec.associated_models.each do |associated_model, options|
157
- next if not options.class == Hash or not options[:choose_by]
53
+ spec.associated_models.each_value do |associated_model|
54
+ next unless associated_model[:choose_by]
158
55
  content << <<-RUBY
159
- associate(#{{ name.to_sym => associated_model.to_sym }.merge options})
56
+ associate(#{{ name.to_sym => associated_model[:name], choose_by: associated_model[:choose_by].to_sym }})
160
57
  RUBY
161
58
  end
162
59
  end
@@ -8,5 +8,54 @@ module Carload
8
8
  require_dependency file
9
9
  end
10
10
  end
11
+
12
+ config.after_initialize do
13
+ # Fill up associations of models.
14
+ Dashboard.models.each do |name, spec|
15
+ spec.klass = name.to_s.camelize.constantize
16
+ spec.klass.reflect_on_all_associations.each do |association|
17
+ spec.handle_association association, rescue: true
18
+ end
19
+ end
20
+ # Reopen model classes to add pg text search.
21
+ Dictionaries = {
22
+ en: 'english',
23
+ :'zh-CN' => 'zhparser'
24
+ }.freeze
25
+ Dashboard.models.each do |name, spec|
26
+ # NOTE: Only direct columns are included.
27
+ attributes = spec.index_page.searches.attributes.select { |x| x[:name].class == Symbol }.map { |x| x[:name] }
28
+ spec.klass.class_eval do
29
+ include PgSearch
30
+ pg_search_scope :search,
31
+ against: attributes,
32
+ using: {
33
+ tsearch: {
34
+ prefix: true,
35
+ negation: true,
36
+ dictionary: Dictionaries[I18n.locale]
37
+ }
38
+ }
39
+ end
40
+ end
41
+ # Reopen model classes to handle polymorphic association.
42
+ Dashboard.models.each do |name, spec|
43
+ spec.associated_models.values.select { |x| x[:polymorphic] == true }.each do |associated_model|
44
+ polymorphic_objects = []
45
+ associated_model[:instance_models].each do |instance_model|
46
+ Dashboard.model(instance_model).klass.all.each do |object|
47
+ polymorphic_objects << ["#{object.class} - #{object.send(associated_model[:choose_by])}", "#{object.id},#{object.class}"]
48
+ end
49
+ end
50
+ spec.klass.class_eval do
51
+ class_eval <<-RUBY
52
+ def self.#{associated_model[:name].to_s.pluralize}
53
+ #{polymorphic_objects}
54
+ end
55
+ RUBY
56
+ end
57
+ end
58
+ end
59
+ end
11
60
  end
12
61
  end
@@ -0,0 +1,98 @@
1
+ module Carload
2
+ class ModelSpec
3
+ include AssociationPipelines
4
+
5
+ attr_accessor :name, :klass, :default, :attributes, :index_page, :associated_models
6
+
7
+ SkippedAttributes = [
8
+ 'id', 'created_at', 'updated_at',
9
+ 'encrypted_password', 'reset_password_token',
10
+ 'reset_password_sent_at', 'remember_created_at',
11
+ 'sign_in_count', 'current_sign_in_at',
12
+ 'last_sign_in_at', 'current_sign_in_ip',
13
+ 'last_sign_in_ip'
14
+ ].freeze
15
+
16
+ AssociationTypes = {
17
+ ActiveRecord::Reflection::BelongsToReflection => :belongs_to,
18
+ ActiveRecord::Reflection::HasOneReflection => :has_one,
19
+ ActiveRecord::Reflection::HasManyReflection => :has_many,
20
+ ActiveRecord::Reflection::ThroughReflection => :through
21
+ }
22
+
23
+ def initialize model_class = nil
24
+ @default = false
25
+ @attributes = ExtendedHash.new
26
+ @index_page = ExtendedHash.new
27
+ @index_page[:shows] = ExtendedHash.new
28
+ @index_page[:shows][:attributes] ||= []
29
+ @index_page[:searches] = ExtendedHash.new
30
+ @index_page[:searches][:attributes] ||= []
31
+ @associated_models = {}
32
+ if model_class
33
+ @name = model_class.name.underscore
34
+ @klass = model_class
35
+ @attributes[:permitted] = (model_class.column_names - SkippedAttributes).map(&:to_sym)
36
+ # Handle model associations.
37
+ model_class.reflect_on_all_associations.each do |association|
38
+ handle_association association
39
+ end
40
+ @attributes[:permitted].each do |attribute|
41
+ next if attribute.class == Hash
42
+ @index_page[:shows][:attributes] << attribute
43
+ @index_page[:searches][:attributes] << { name: attribute.to_sym, term: :cont }
44
+ end
45
+ end
46
+ end
47
+
48
+ def changed? spec
49
+ not @default == spec.default or
50
+ not @attributes[:permitted] == spec.attributes[:permitted] or
51
+ not @index_page[:shows][:attributes] == spec.index_page[:shows][:attributes] or
52
+ not @index_page[:searches][:attributes] == spec.index_page[:searches][:attributes]
53
+ end
54
+
55
+ def revise!
56
+ # Handle associated models if necessary.
57
+ @associated_models.each_value do |associated_model|
58
+ next unless associated_model[:choose_by]
59
+ if associated_model[:association_type] == :has_many
60
+ show_name = [:pluck, associated_model[:name].to_s.pluralize.to_sym, associated_model[:choose_by]]
61
+ else
62
+ show_name = "#{associated_model[:name]}.#{associated_model[:choose_by]}"
63
+ end
64
+ @index_page[:shows][:attributes].delete_if { |x| x == :"#{associated_model[:name]}_id" }
65
+ @index_page[:shows][:attributes] << show_name
66
+ end
67
+ end
68
+
69
+ def handle_association association, options = {}
70
+ begin
71
+ _association = (association.delegate_reflection rescue nil) || association
72
+ name = (_association.klass.name.underscore.to_sym rescue nil) || _association.name
73
+ association_type = AssociationTypes[_association.class]
74
+ polymorphic = association.options[:polymorphic] || association.options[:as]
75
+ foreign_key = @klass.column_names.include?("#{(_association.klass.name.underscore rescue nil) || _association.name}_id")
76
+ join_table = association.options[:through].to_s.singularize.to_sym if association.options[:through]
77
+ @associated_models[name] = {
78
+ name: name,
79
+ association_type: association_type,
80
+ polymorphic: polymorphic,
81
+ foreign_key: foreign_key,
82
+ join_table: join_table,
83
+ choose_by: nil
84
+ }.merge @associated_models[name] || {}
85
+ association_pipelines.each { |pipeline| send pipeline, association }
86
+ # Delete join-table model!
87
+ if association.options[:through]
88
+ @associated_models.delete association.options[:through]
89
+ @associated_models.delete association.options[:through].to_s.singularize.to_sym
90
+ end
91
+ rescue => e
92
+ raise e unless options[:rescue]
93
+ raise e if not e&.original_exception&.class == PG::UndefinedTable and
94
+ not e.class == ActiveRecord::NoDatabaseError
95
+ end
96
+ end
97
+ end
98
+ end
@@ -1,3 +1,3 @@
1
1
  module Carload
2
- VERSION = '0.3.0'
2
+ VERSION = '0.4.0'
3
3
  end
data/lib/carload.rb CHANGED
@@ -4,13 +4,17 @@ Gem.loaded_specs['carload'].dependencies.each do |dependency|
4
4
  end
5
5
 
6
6
  require 'carload/extended_hash'
7
+ require 'carload/association_pipelines'
8
+ require 'carload/model_spec'
7
9
  require 'carload/dashboard'
8
10
  require 'carload/engine'
9
11
  require 'carload/exceptions'
10
12
 
11
13
  module Carload
12
14
  def self.setup &block
15
+ # Read in configuration.
13
16
  @@config = ExtendedHash.new
17
+ @@config[:page] = ExtendedHash.new
14
18
  @@config[:dashboard] = ExtendedHash.new
15
19
  @@config[:dashboard][:permits_user] = ExtendedHash.new
16
20
  yield @@config
@@ -20,7 +24,7 @@ module Carload
20
24
  if not [:carrierwave].include? @@config[:upload_solution]
21
25
  raise UnsupportedError.new("upload solution #{@@config[:upload_solution]}")
22
26
  end
23
-
27
+ # Define configuation helpers.
24
28
  @@config.each do |key, value|
25
29
  define_singleton_method key do
26
30
  value
@@ -8,34 +8,35 @@ module Carload
8
8
  model_specs = {}
9
9
  Rails.application.eager_load! # It is necessary to load models manually.
10
10
  ActiveRecord::Base.descendants.each do |model| # Rails 5 can use ApplicationRecord.
11
- next if model.name == 'ApplicationRecord'
11
+ next if model.name == 'ApplicationRecord' or model.name == 'PgSearch::Document'
12
12
  name = model.name.underscore
13
- model_specs[name] = Dashboard::ModelSpec.new model
13
+ model_specs[name] = ModelSpec.new model
14
14
  end
15
15
  spec = model_specs[model]
16
16
  if not spec.associated_models.empty?
17
- spec.revise_stage_1!
18
17
  cli = HighLine.new
19
- cli.say "\nModel #{model} has associated with other models."
20
- spec.associated_models.each do |associated_model, options|
21
- spec.associated_models[associated_model][:choose_by] = cli.choose do |menu|
22
- menu.prompt = "Choose the attribute of model #{associated_model} for choosing in #{model}? "
23
- attributes = options[:polymorphic] ? options[:common_attributes] : model_specs[associated_model].attributes.permitted
24
- attributes.each do |attribute|
25
- next if attribute =~ /_id$/
26
- menu.choice attribute.to_sym
18
+ spec.associated_models.each_value do |associated_model|
19
+ next if associated_model[:attributes].empty?
20
+ if associated_model[:polymorphic]
21
+ attributes = associated_model[:attributes]
22
+ else
23
+ attributes = model_specs[associated_model[:name].to_s].attributes.permitted.select { |x| x.class != Hash }
24
+ end
25
+ if attributes.size == 1
26
+ associated_model[:choose_by] = attributes.first
27
+ else
28
+ associated_model[:choose_by] = cli.choose do |menu|
29
+ menu.prompt = "Choose the attribute of model #{associated_model[:name]} for choosing in #{model}? "
30
+ attributes.each do |attribute|
31
+ next if attribute.to_s =~ /_id$/
32
+ menu.choice attribute
33
+ end
27
34
  end
28
35
  end
29
36
  end
30
- spec.revise_stage_2!
37
+ spec.revise!
31
38
  end
32
39
  # Check if model exists in dashboard file, but it may be changed.
33
- begin
34
- load 'app/carload/dashboard.rb'
35
- rescue LoadError
36
- Dashboard.models[model.to_sym] = spec
37
- Dashboard.write 'app/carload/dashboard.rb'
38
- end
39
40
  if not Dashboard.models.keys.include? model.to_sym or Dashboard.model(model).changed? spec
40
41
  Dashboard.models[model.to_sym] = spec
41
42
  Dashboard.write 'app/carload/dashboard.rb'
@@ -26,5 +26,15 @@ require 'carload'
26
26
  def copy_dashboard_file
27
27
  copy_file 'dashboard.rb', 'app/carload/dashboard.rb'
28
28
  end
29
+
30
+ def copy_migration_files
31
+ # Copy migrations if necessary.
32
+ case I18n.locale
33
+ when :'zh-CN'
34
+ ['20161030074822_carload_enable_zhparser_extension.rb'].each do |file|
35
+ copy_file "#{Carload::Engine.root}/db/migrate/#{file}", "db/migrate/#{file}"
36
+ end
37
+ end
38
+ end
29
39
  end
30
40
  end
@@ -1,8 +1,20 @@
1
1
  Carload.setup do |config|
2
- # Specify which authentication solution is used. Currently, we only support Devise.
2
+ # Set the title that will be displayed on the browser tab area.
3
+ config.page.title = nil
4
+
5
+ # Set the footer text that will be displayed on each page.
6
+ config.page.footer = nil
7
+
8
+ # Set the colors of page elements.
9
+ config.page.main_color = nil
10
+ config.page.text_color = 'black'
11
+ config.page.button_color = nil
12
+ config.page.button_text_color = nil
13
+
14
+ # Set which authentication solution is used. Currently, we only support Devise.
3
15
  config.auth_solution = :devise
4
16
 
5
- # Specify which file upload solution is used. Currently, we only support Carrierwave.
17
+ # Set which file upload solution is used. Currently, we only support Carrierwave.
6
18
  config.upload_solution = :carrierwave
7
19
 
8
20
  # Set the actions used to discern user's permission to access dashboard.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: carload
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Li Dong
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-05 00:00:00.000000000 Z
11
+ date: 2016-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -129,19 +129,19 @@ dependencies:
129
129
  - !ruby/object:Gem::Version
130
130
  version: '3.3'
131
131
  - !ruby/object:Gem::Dependency
132
- name: ransack
132
+ name: pg_search
133
133
  requirement: !ruby/object:Gem::Requirement
134
134
  requirements:
135
135
  - - "~>"
136
136
  - !ruby/object:Gem::Version
137
- version: '1.8'
137
+ version: '1.0'
138
138
  type: :runtime
139
139
  prerelease: false
140
140
  version_requirements: !ruby/object:Gem::Requirement
141
141
  requirements:
142
142
  - - "~>"
143
143
  - !ruby/object:Gem::Version
144
- version: '1.8'
144
+ version: '1.0'
145
145
  - !ruby/object:Gem::Dependency
146
146
  name: kaminari
147
147
  requirement: !ruby/object:Gem::Requirement
@@ -227,7 +227,7 @@ files:
227
227
  - app/assets/javascripts/carload/dashboard.js
228
228
  - app/assets/stylesheets/carload/_select2.scss
229
229
  - app/assets/stylesheets/carload/application.scss
230
- - app/assets/stylesheets/carload/colors.scss
230
+ - app/assets/stylesheets/carload/colors.scss.erb
231
231
  - app/assets/stylesheets/carload/common.scss
232
232
  - app/assets/stylesheets/carload/dashboard.scss
233
233
  - app/assets/stylesheets/carload/error.scss
@@ -264,11 +264,14 @@ files:
264
264
  - config/locales/simple_form.en.yml
265
265
  - config/locales/zh-CN.yml
266
266
  - config/routes.rb
267
+ - db/migrate/20161030074822_carload_enable_zhparser_extension.rb
267
268
  - lib/carload.rb
269
+ - lib/carload/association_pipelines.rb
268
270
  - lib/carload/dashboard.rb
269
271
  - lib/carload/engine.rb
270
272
  - lib/carload/exceptions.rb
271
273
  - lib/carload/extended_hash.rb
274
+ - lib/carload/model_spec.rb
272
275
  - lib/carload/version.rb
273
276
  - lib/generators/carload/USAGE
274
277
  - lib/generators/carload/dash_generator.rb
@@ -1,2 +0,0 @@
1
- $main-color: #8C7DA6;
2
- $text-color: #3F3F44;