eac_rails_utils 0.24.0 → 0.25.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
  SHA256:
3
- metadata.gz: 10597063c9780ca174f0236d4bb7a87af4b5702d0617b84406ebf613afe7a822
4
- data.tar.gz: befada54dbc901d1d2e43d33606f1a3219fd288cf3a8d52178495e337fe4d8de
3
+ metadata.gz: 0b5ed5306142ed06d984e58c9e16e743eeb1553e0aeb71d20b0a74eedb184edb
4
+ data.tar.gz: 47b1a51b701dc1edcba208f0d67104e5b72cdfc64c147c734bf8c7578b302377
5
5
  SHA512:
6
- metadata.gz: 205e2134c2f1d5b5770dff69c1ec55b87fc454a563dc0e1c01623341d9b2d6701b81f502a01efcf3772bccbef45a6405ea4040bf295d2f851874fa094a2069fe
7
- data.tar.gz: d90285c437b1ad93874601d4077e1c1d86c3df012348860523a8bb70ae33c395fbc0cb23b0089cef75e3bc4255d59c8d3329bec4ca4004266bcae513fd12c676
6
+ metadata.gz: ed95ae67b0db7fea40dd3524f23dc1673b59b49a7058d76624f5094f541908997ebde03869c4f348dd31b743a4b6b8a94467388a5daa4895333ba326e9ba3a53
7
+ data.tar.gz: 98bf0813f2b764a5d5148afda0c024de7110b66f70322aec8e1a45fd4df2e9f3266541d46b8af59384a2d6856fe0901ca8d183cae7b16bea14bd717ca1914606
@@ -5,11 +5,16 @@ require 'eac_ruby_utils/core_ext'
5
5
  module EacRailsUtils
6
6
  module DataTableHelper
7
7
  class DataTable
8
+ CONTAINER_CSS_CLASS = %w[table-responsive].freeze
9
+ TABLE_CSS_CLASSES = %w[table table-striped].freeze
10
+
8
11
  common_constructor :view, :dataset, :setup_block, block_arg: true
9
12
 
10
13
  def output
11
- view.content_tag(:table, id: id) do
12
- head << body
14
+ view.content_tag(:div, class: CONTAINER_CSS_CLASS) do
15
+ view.content_tag(:table, id: id, class: TABLE_CSS_CLASSES) do
16
+ head << body
17
+ end
13
18
  end << script
14
19
  end
15
20
 
@@ -40,9 +45,9 @@ module EacRailsUtils
40
45
  def script
41
46
  view.javascript_tag <<~JS_CODE
42
47
  $(document).ready(function () {
43
- $('##{id}').DataTable({
44
- paging: #{setup.paging ? 'true' : 'false'} # rubocop:disable Rails/HelperInstanceVariable
45
- });
48
+ $('##{id}').DataTable({
49
+ paging: #{setup.paging ? 'true' : 'false'} # rubocop:disable Rails/HelperInstanceVariable
50
+ });
46
51
  });
47
52
  JS_CODE
48
53
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EacRailsUtils
4
+ module LinksHelper
5
+ # @param object [Object]
6
+ # @param options [Hash] All the values are passed to the link_to method with except of:
7
+ # * :action : argument +action+ for the method +object_path+.
8
+ # * :blank_value : text used when +object+ is blank.
9
+ # * :confirm_translation: translation key for attribute +data.confirm+.
10
+ # * :name : argument +name+ for the method +link_to+.
11
+ # * :title_translation: translation key for attribute +title+.
12
+ # @return [ActiveSupport::SafeBuffer] The link or the blank_sign.
13
+ class ObjectLink
14
+ acts_as_instance_method
15
+ common_constructor :view, :object, :options, default: [{}]
16
+
17
+ NON_LINK_TO_OPTIONS = %i[action blank_value confirm_translation name title_translation].freeze
18
+
19
+ # @return [ActiveSupport::SafeBuffer]
20
+ def result
21
+ view.value_or_sign(object, blank_value) do |value|
22
+ view.link_to name, view.object_path(value, action), link_to_options
23
+ end
24
+ end
25
+
26
+ # @return [String, nil]
27
+ def action
28
+ options[:action]
29
+ end
30
+
31
+ # @return [String, nil]
32
+ def blank_value
33
+ options[:blank_value]
34
+ end
35
+
36
+ # @return [String, nil]
37
+ def confirm
38
+ options[:confirm_translation].if_present do |v|
39
+ ::I18n.t(v, label: object.to_s)
40
+ end
41
+ end
42
+
43
+ # @return [String, nil]
44
+ def name
45
+ options[:name] || object.to_s
46
+ end
47
+
48
+ # @return [String, nil]
49
+ def title
50
+ options[:title_translation].if_present do |v|
51
+ ::I18n.t(v, label: object.to_s)
52
+ end
53
+ end
54
+
55
+ protected
56
+
57
+ def apply_confirm(options)
58
+ confirm.if_present do |v|
59
+ options[:data] ||= {}
60
+ options[:data][:confirm] = v
61
+ end
62
+ end
63
+
64
+ def apply_title(options)
65
+ title.if_present do |v|
66
+ options[:title] = v
67
+ end
68
+ end
69
+
70
+ # @return [Hash]
71
+ def link_to_options
72
+ %w[confirm title].each_with_object(options.except(*NON_LINK_TO_OPTIONS)) do |e, a|
73
+ send("apply_#{e}", a)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EacRailsUtils
4
+ module LinksHelper
5
+ class ObjectPath
6
+ acts_as_instance_method
7
+ common_constructor :view, :object, :action, default: [nil]
8
+
9
+ def result
10
+ current_class = object_class(object)
11
+ tried_paths = []
12
+ while current_class
13
+ path = object_path_by_class(current_class, action)
14
+ return view.send(path, object) if view.respond_to?(path)
15
+
16
+ tried_paths << path
17
+ current_class = current_class.superclass
18
+ end
19
+ raise "Path not found for {object: #{object.class}, action: \"#{action}\"}" \
20
+ "(Tried: #{tried_paths})"
21
+ end
22
+
23
+ protected
24
+
25
+ def object_path_by_class(klass, action)
26
+ path = "#{klass.name.underscore.tr('/', '_')}_url"
27
+ path = "#{action}_#{path}" if action.present?
28
+ path
29
+ end
30
+
31
+ def object_class(object)
32
+ return object.entity_class if object.respond_to?(:entity_class)
33
+ return object.__getobj__.class if object.respond_to?(:__getobj__)
34
+
35
+ object.class
36
+ end
37
+ end
38
+ end
39
+ end
@@ -2,20 +2,37 @@
2
2
 
3
3
  module EacRailsUtils
4
4
  module LinksHelper
5
+ LINK_OPTIONS = { target: '_blank' }.freeze
6
+ SHORT_LINK_OPTIONS = LINK_OPTIONS.merge(name: '').freeze
7
+ DETAIL_LINK_OPTIONS = LINK_OPTIONS.merge(action: 'detail')
8
+ SHORT_DELETE_LINK_OPTIONS = SHORT_LINK_OPTIONS.merge(
9
+ action: '', class: 'delete_link', method: :delete,
10
+ title_translation: 'eac_rails_utils.links.delete_object',
11
+ confirm_translation: 'eac_rails_utils.links.delete_confirm'
12
+ ).freeze
13
+ SHORT_EDIT_LINK_OPTIONS = SHORT_LINK_OPTIONS.merge(
14
+ action: 'edit', class: 'edit_link', title_translation: 'eac_rails_utils.links.edit_object'
15
+ ).freeze
16
+ SHORT_DETAIL_SHOW_LINK_OPTIONS = SHORT_LINK_OPTIONS.merge(
17
+ class: 'show_link', title_translation: 'eac_rails_utils.links.show_object'
18
+ ).freeze
19
+ SHORT_DETAIL_LINK_OPTIONS = SHORT_DETAIL_SHOW_LINK_OPTIONS.merge(action: 'detail')
20
+ SHORT_SHOW_LINK_OPTIONS = SHORT_DETAIL_SHOW_LINK_OPTIONS.merge(action: '')
21
+ SHOW_LINK_OPTIONS = LINK_OPTIONS.merge(action: '')
22
+
23
+ # @param object [Object]
24
+ # @param options [Hash]
25
+ # @return [ActiveSupport::SafeBuffer]
26
+ def detail_link(object, **options)
27
+ object_link object, DETAIL_LINK_OPTIONS.merge(options)
28
+ end
29
+
5
30
  def short_delete_link(object)
6
- short_object_link object, '', class: 'delete_link', method: :delete, target: '_blank',
7
- title: ::I18n.t('eac_rails_utils.links.delete_object',
8
- label: object.to_s),
9
- data: {
10
- confirm: ::I18n.t('eac_rails_utils.links.delete_confirm',
11
- label: object.to_s)
12
- }
31
+ object_link object, SHORT_DELETE_LINK_OPTIONS
13
32
  end
14
33
 
15
34
  def short_edit_link(object)
16
- short_object_link object, 'edit', class: 'edit_link', target: '_blank',
17
- title: ::I18n.t('eac_rails_utils.links.edit_object',
18
- label: object.to_s)
35
+ object_link object, SHORT_EDIT_LINK_OPTIONS
19
36
  end
20
37
 
21
38
  def short_goto_link(url)
@@ -27,53 +44,20 @@ module EacRailsUtils
27
44
  end
28
45
 
29
46
  def short_show_link(object)
30
- short_detail_show_link(object, false)
47
+ object_link object, SHORT_SHOW_LINK_OPTIONS
31
48
  end
32
49
 
33
50
  def short_detail_link(object)
34
- short_detail_show_link(object, true)
35
- end
36
-
37
- def object_path(object, action = nil)
38
- current_class = object_class(object)
39
- tried_paths = []
40
- while current_class
41
- path = object_path_by_class(current_class, action)
42
- return send(path, object) if respond_to?(path)
43
-
44
- tried_paths << path
45
- current_class = current_class.superclass
46
- end
47
- raise "Path not found for {object: #{object.class}, action: \"#{action}\"}" \
48
- "(Tried: #{tried_paths})"
49
- end
50
-
51
- private
52
-
53
- def short_detail_show_link(object, detail)
54
- short_object_link object,
55
- detail ? 'detail' : nil,
56
- class: 'show_link', target: '_blank',
57
- title: ::I18n.t('eac_rails_utils.links.show_object', label: object.to_s)
51
+ object_link object, SHORT_DETAIL_LINK_OPTIONS
58
52
  end
59
53
 
60
- def short_object_link(object, action = nil, options = {})
61
- value_or_sign(object, '') do |value|
62
- link_to '', object_path(value, action), options
63
- end
54
+ # @param object [Object]
55
+ # @param options [Hash]
56
+ # @return [ActiveSupport::SafeBuffer]
57
+ def show_link(object, **options)
58
+ object_link object, SHOW_LINK_OPTIONS.merge(options)
64
59
  end
65
60
 
66
- def object_path_by_class(klass, action)
67
- path = "#{klass.name.underscore.tr('/', '_')}_url"
68
- path = "#{action}_#{path}" if action.present?
69
- path
70
- end
71
-
72
- def object_class(object)
73
- return object.entity_class if object.respond_to?(:entity_class)
74
- return object.__getobj__.class if object.respond_to?(:__getobj__)
75
-
76
- object.class
77
- end
61
+ require_sub __FILE__, require_mode: :kernel
78
62
  end
79
63
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record/associations/builder/has_many'
4
+
5
+ module EacRailsUtils
6
+ module Models
7
+ module TablelessAssociations
8
+ class HasManyForActiveModel < ::ActiveRecord::Associations::Builder::HasMany
9
+ if ActiveRecord.version >= Gem::Version.new('5.0.0.beta')
10
+ AR_CALLBACK_METHODS = %i[define_callback before_validation after_validation before_save
11
+ after_save before_update after_update].freeze
12
+
13
+ def self.valid_options(_options)
14
+ super + %i[active_model
15
+ target_ids] - %i[through dependent source source_type counter_cache as]
16
+ end
17
+
18
+ def self.define_callbacks(model, reflection)
19
+ return unless AR_CALLBACK_METHODS.all? { |meth| respond_to?(meth) }
20
+
21
+ super
22
+ end
23
+ else
24
+ def valid_options
25
+ super + %i[active_model
26
+ target_ids] - %i[through dependent source source_type counter_cache as]
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EacRailsUtils
4
+ module Models
5
+ module TablelessAssociations
6
+ class HasManyForActiveModelAssociation < ActiveRecord::Associations::HasManyAssociation
7
+ # remove conditions: owner.new_record?, foreign_key_present?
8
+ def find_target?
9
+ !loaded? && klass
10
+ end
11
+
12
+ # no dependent action
13
+ def null_scope?
14
+ false
15
+ end
16
+
17
+ # not support counter_cache
18
+ def empty?
19
+ if loaded?
20
+ size.zero?
21
+ else
22
+ @target.blank? && !scope.exists?
23
+ end
24
+ end
25
+
26
+ # full replace simplely
27
+ def replace(other_array) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
28
+ original_target = load_target.dup
29
+ other_array.each { |val| raise_on_type_mismatch!(val) }
30
+ target_ids = reflection.options[:target_ids]
31
+ owner[target_ids] = other_array.map(&:id)
32
+
33
+ old_records = original_target - other_array
34
+ old_records.each do |record|
35
+ @target.delete(record)
36
+ end
37
+
38
+ other_array.each do |record|
39
+ if (index = @target.index(record))
40
+ @target[index] = record
41
+ else
42
+ @target << record
43
+ end
44
+ end
45
+ end
46
+
47
+ # no need transaction
48
+ def concat(*records) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
49
+ load_target
50
+ flatten_records = records.flatten
51
+ flatten_records.each { |val| raise_on_type_mismatch!(val) }
52
+ target_ids = reflection.options[:target_ids]
53
+ owner[target_ids] ||= []
54
+ owner[target_ids].concat(flatten_records.map(&:id))
55
+
56
+ flatten_records.each do |record|
57
+ if (index = @target.index(record))
58
+ @target[index] = record
59
+ else
60
+ @target << record
61
+ end
62
+ end
63
+
64
+ target
65
+ end
66
+
67
+ private
68
+
69
+ def get_records # rubocop:disable Naming/AccessorMethodName
70
+ return scope.to_a if reflection.scope_chain.any?(&:any?)
71
+
72
+ target_ids = reflection.options[:target_ids]
73
+ klass.where(id: owner[target_ids]).to_a
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -97,7 +97,9 @@ module EacRailsUtils
97
97
  def association_by_reflection(name)
98
98
  reflection = self.class.reflect_on_association(name)
99
99
  if reflection.options[:active_model]
100
- ::ActiveRecord::Associations::HasManyForActiveModelAssociation.new(self, reflection)
100
+ ::EacRailsUtils::Models::TablelessAssociations::HasManyForActiveModelAssociation.new(
101
+ self, reflection
102
+ )
101
103
  else
102
104
  reflection.association_class.new(self, reflection)
103
105
  end
@@ -4,8 +4,8 @@ require 'eac_rails_utils/models/tableless_associations/initialize_extension'
4
4
  require 'eac_rails_utils/models/tableless_associations/active_record_reflection'
5
5
  require 'eac_rails_utils/models/tableless_associations/autosave_association'
6
6
  require 'eac_rails_utils/models/tableless_associations/override_methods'
7
- require 'active_record/associations/builder/has_many_for_active_model'
8
- require 'active_record/associations/has_many_for_active_model_association'
7
+ require 'eac_rails_utils/models/tableless_associations/has_many_for_active_model'
8
+ require 'eac_rails_utils/models/tableless_associations/has_many_for_active_model_association'
9
9
  require 'active_support/core_ext/module'
10
10
 
11
11
  module EacRailsUtils
@@ -38,7 +38,7 @@ module EacRailsUtils
38
38
  scope = nil
39
39
  end
40
40
 
41
- reflection = ActiveRecord::Associations::Builder::HasManyForActiveModel
41
+ reflection = EacRailsUtils::Models::TablelessAssociations::HasManyForActiveModel
42
42
  .build(self, name, scope, options, &extension)
43
43
  ActiveRecord::Reflection.add_reflection self, name, reflection
44
44
 
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'activemodel/associations'
4
3
  require 'eac_rails_utils/patches/rails_5_2'
5
4
 
6
5
  module EacRailsUtils
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'activemodel/associations'
3
+ require 'eac_rails_utils/models/tableless_associations'
4
+ require 'eac_rails_utils/models/tableless_associations/hooks'
4
5
  require 'eac_ruby_utils/core_ext'
5
6
 
6
7
  module EacRailsUtils
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EacRailsUtils
4
- VERSION = '0.24.0'
4
+ VERSION = '0.25.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eac_rails_utils
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.24.0
4
+ version: 0.25.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - E.A.C.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-05 00:00:00.000000000 Z
11
+ date: 2024-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bootstrap-sass
@@ -36,14 +36,14 @@ dependencies:
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '0.122'
39
+ version: '0.123'
40
40
  type: :runtime
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '0.122'
46
+ version: '0.123'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rails
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -159,6 +159,8 @@ files:
159
159
  - app/helpers/eac_rails_utils/data_table_helper/setup.rb
160
160
  - app/helpers/eac_rails_utils/formatter_helper.rb
161
161
  - app/helpers/eac_rails_utils/links_helper.rb
162
+ - app/helpers/eac_rails_utils/links_helper/object_link.rb
163
+ - app/helpers/eac_rails_utils/links_helper/object_path.rb
162
164
  - app/helpers/eac_rails_utils/menus_helper.rb
163
165
  - app/helpers/eac_rails_utils/menus_helper/bootstrap_gui_builder.rb
164
166
  - app/helpers/eac_rails_utils/menus_helper/data_builder.rb
@@ -172,9 +174,6 @@ files:
172
174
  - config/initializers/json.rb
173
175
  - config/locales/en.yml
174
176
  - config/locales/pt-BR.yml
175
- - lib/active_record/associations/builder/has_many_for_active_model.rb
176
- - lib/active_record/associations/has_many_for_active_model_association.rb
177
- - lib/activemodel/associations.rb
178
177
  - lib/eac_rails_utils.rb
179
178
  - lib/eac_rails_utils/engine.rb
180
179
  - lib/eac_rails_utils/engine_helper.rb
@@ -194,6 +193,8 @@ files:
194
193
  - lib/eac_rails_utils/models/tableless_associations/active_record_reflection.rb
195
194
  - lib/eac_rails_utils/models/tableless_associations/association_scope_extension.rb
196
195
  - lib/eac_rails_utils/models/tableless_associations/autosave_association.rb
196
+ - lib/eac_rails_utils/models/tableless_associations/has_many_for_active_model.rb
197
+ - lib/eac_rails_utils/models/tableless_associations/has_many_for_active_model_association.rb
197
198
  - lib/eac_rails_utils/models/tableless_associations/hooks.rb
198
199
  - lib/eac_rails_utils/models/tableless_associations/initialize_extension.rb
199
200
  - lib/eac_rails_utils/models/tableless_associations/override_methods.rb
@@ -1,24 +0,0 @@
1
- module ActiveRecord::Associations::Builder # rubocop:disable Style/ClassAndModuleChildren, Style/FrozenStringLiteralComment
2
- class HasManyForActiveModel < HasMany
3
- if ActiveRecord.version >= Gem::Version.new('5.0.0.beta')
4
- AR_CALLBACK_METHODS = %i[define_callback before_validation after_validation before_save
5
- after_save before_update after_update].freeze
6
-
7
- def self.valid_options(_options)
8
- super + %i[active_model
9
- target_ids] - %i[through dependent source source_type counter_cache as]
10
- end
11
-
12
- def self.define_callbacks(model, reflection)
13
- return unless AR_CALLBACK_METHODS.all? { |meth| respond_to?(meth) }
14
-
15
- super
16
- end
17
- else
18
- def valid_options
19
- super + %i[active_model
20
- target_ids] - %i[through dependent source source_type counter_cache as]
21
- end
22
- end
23
- end
24
- end
@@ -1,72 +0,0 @@
1
- module ActiveRecord::Associations # rubocop:disable Style/ClassAndModuleChildren, Style/FrozenStringLiteralComment
2
- class HasManyForActiveModelAssociation < HasManyAssociation
3
- # remove conditions: owner.new_record?, foreign_key_present?
4
- def find_target?
5
- !loaded? && klass
6
- end
7
-
8
- # no dependent action
9
- def null_scope?
10
- false
11
- end
12
-
13
- # not support counter_cache
14
- def empty?
15
- if loaded?
16
- size.zero?
17
- else
18
- @target.blank? && !scope.exists?
19
- end
20
- end
21
-
22
- # full replace simplely
23
- def replace(other_array) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
24
- original_target = load_target.dup
25
- other_array.each { |val| raise_on_type_mismatch!(val) }
26
- target_ids = reflection.options[:target_ids]
27
- owner[target_ids] = other_array.map(&:id)
28
-
29
- old_records = original_target - other_array
30
- old_records.each do |record|
31
- @target.delete(record)
32
- end
33
-
34
- other_array.each do |record|
35
- if (index = @target.index(record))
36
- @target[index] = record
37
- else
38
- @target << record
39
- end
40
- end
41
- end
42
-
43
- # no need transaction
44
- def concat(*records) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
45
- load_target
46
- flatten_records = records.flatten
47
- flatten_records.each { |val| raise_on_type_mismatch!(val) }
48
- target_ids = reflection.options[:target_ids]
49
- owner[target_ids] ||= []
50
- owner[target_ids].concat(flatten_records.map(&:id))
51
-
52
- flatten_records.each do |record|
53
- if (index = @target.index(record))
54
- @target[index] = record
55
- else
56
- @target << record
57
- end
58
- end
59
-
60
- target
61
- end
62
-
63
- private
64
-
65
- def get_records # rubocop:disable Naming/AccessorMethodName
66
- return scope.to_a if reflection.scope_chain.any?(&:any?)
67
-
68
- target_ids = reflection.options[:target_ids]
69
- klass.where(id: owner[target_ids]).to_a
70
- end
71
- end
72
- end
@@ -1,13 +0,0 @@
1
- require 'active_model' # rubocop:disable Style/FrozenStringLiteralComment
2
- require 'active_record'
3
- require 'active_support'
4
- require 'eac_rails_utils/models/tableless_associations'
5
- require 'eac_rails_utils/models/tableless_associations/hooks'
6
-
7
- # Load Railtie
8
- begin
9
- require 'rails'
10
- rescue LoadError # rubocop:disable Lint/SuppressedException
11
- end
12
-
13
- require 'eac_rails_utils/models/tableless_associations/railtie' if defined?(Rails)