activeentity 0.0.1.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +42 -0
- data/README.md +145 -0
- data/Rakefile +29 -0
- data/lib/active_entity.rb +73 -0
- data/lib/active_entity/aggregations.rb +276 -0
- data/lib/active_entity/associations.rb +146 -0
- data/lib/active_entity/associations/embedded/association.rb +134 -0
- data/lib/active_entity/associations/embedded/builder/association.rb +100 -0
- data/lib/active_entity/associations/embedded/builder/collection_association.rb +69 -0
- data/lib/active_entity/associations/embedded/builder/embedded_in.rb +38 -0
- data/lib/active_entity/associations/embedded/builder/embeds_many.rb +13 -0
- data/lib/active_entity/associations/embedded/builder/embeds_one.rb +16 -0
- data/lib/active_entity/associations/embedded/builder/singular_association.rb +28 -0
- data/lib/active_entity/associations/embedded/collection_association.rb +188 -0
- data/lib/active_entity/associations/embedded/collection_proxy.rb +310 -0
- data/lib/active_entity/associations/embedded/embedded_in_association.rb +31 -0
- data/lib/active_entity/associations/embedded/embeds_many_association.rb +15 -0
- data/lib/active_entity/associations/embedded/embeds_one_association.rb +19 -0
- data/lib/active_entity/associations/embedded/singular_association.rb +35 -0
- data/lib/active_entity/attribute_assignment.rb +85 -0
- data/lib/active_entity/attribute_decorators.rb +90 -0
- data/lib/active_entity/attribute_methods.rb +330 -0
- data/lib/active_entity/attribute_methods/before_type_cast.rb +78 -0
- data/lib/active_entity/attribute_methods/primary_key.rb +98 -0
- data/lib/active_entity/attribute_methods/query.rb +35 -0
- data/lib/active_entity/attribute_methods/read.rb +47 -0
- data/lib/active_entity/attribute_methods/serialization.rb +90 -0
- data/lib/active_entity/attribute_methods/time_zone_conversion.rb +91 -0
- data/lib/active_entity/attribute_methods/write.rb +63 -0
- data/lib/active_entity/attributes.rb +165 -0
- data/lib/active_entity/base.rb +303 -0
- data/lib/active_entity/coders/json.rb +15 -0
- data/lib/active_entity/coders/yaml_column.rb +50 -0
- data/lib/active_entity/core.rb +281 -0
- data/lib/active_entity/define_callbacks.rb +17 -0
- data/lib/active_entity/enum.rb +234 -0
- data/lib/active_entity/errors.rb +80 -0
- data/lib/active_entity/gem_version.rb +17 -0
- data/lib/active_entity/inheritance.rb +278 -0
- data/lib/active_entity/integration.rb +78 -0
- data/lib/active_entity/locale/en.yml +45 -0
- data/lib/active_entity/model_schema.rb +115 -0
- data/lib/active_entity/nested_attributes.rb +592 -0
- data/lib/active_entity/readonly_attributes.rb +47 -0
- data/lib/active_entity/reflection.rb +441 -0
- data/lib/active_entity/serialization.rb +25 -0
- data/lib/active_entity/store.rb +242 -0
- data/lib/active_entity/translation.rb +24 -0
- data/lib/active_entity/type.rb +73 -0
- data/lib/active_entity/type/date.rb +9 -0
- data/lib/active_entity/type/date_time.rb +9 -0
- data/lib/active_entity/type/decimal_without_scale.rb +15 -0
- data/lib/active_entity/type/hash_lookup_type_map.rb +25 -0
- data/lib/active_entity/type/internal/timezone.rb +17 -0
- data/lib/active_entity/type/json.rb +30 -0
- data/lib/active_entity/type/modifiers/array.rb +72 -0
- data/lib/active_entity/type/registry.rb +92 -0
- data/lib/active_entity/type/serialized.rb +71 -0
- data/lib/active_entity/type/text.rb +11 -0
- data/lib/active_entity/type/time.rb +21 -0
- data/lib/active_entity/type/type_map.rb +62 -0
- data/lib/active_entity/type/unsigned_integer.rb +17 -0
- data/lib/active_entity/validate_embedded_association.rb +305 -0
- data/lib/active_entity/validations.rb +50 -0
- data/lib/active_entity/validations/absence.rb +25 -0
- data/lib/active_entity/validations/associated.rb +60 -0
- data/lib/active_entity/validations/length.rb +26 -0
- data/lib/active_entity/validations/presence.rb +68 -0
- data/lib/active_entity/validations/subset.rb +76 -0
- data/lib/active_entity/validations/uniqueness_in_embedding.rb +99 -0
- data/lib/active_entity/version.rb +10 -0
- data/lib/tasks/active_entity_tasks.rake +6 -0
- metadata +155 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveEntity
|
4
|
+
# = Active Entity Errors
|
5
|
+
#
|
6
|
+
# Generic Active Entity exception class.
|
7
|
+
class ActiveEntityError < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
# Raised when the single-table inheritance mechanism fails to locate the subclass
|
11
|
+
# (for example due to improper usage of column that
|
12
|
+
# {ActiveEntity::Base.inheritance_attribute}[rdoc-ref:ModelSchema::ClassMethods#inheritance_attribute]
|
13
|
+
# points to).
|
14
|
+
class SubclassNotFound < ActiveEntityError
|
15
|
+
end
|
16
|
+
|
17
|
+
# Raised when an object assigned to an association has an incorrect type.
|
18
|
+
#
|
19
|
+
# class Ticket < ActiveEntity::Base
|
20
|
+
# has_many :patches
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# class Patch < ActiveEntity::Base
|
24
|
+
# belongs_to :ticket
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# # Comments are not patches, this assignment raises AssociationTypeMismatch.
|
28
|
+
# @ticket.patches << Comment.new(content: "Please attach tests to your patch.")
|
29
|
+
class AssociationTypeMismatch < ActiveEntityError
|
30
|
+
end
|
31
|
+
|
32
|
+
# Raised when unserialized object's type mismatches one specified for serializable field.
|
33
|
+
class SerializationTypeMismatch < ActiveEntityError
|
34
|
+
end
|
35
|
+
|
36
|
+
# Raised when association is being configured improperly or user tries to use
|
37
|
+
# offset and limit together with
|
38
|
+
# {ActiveEntity::Base.has_many}[rdoc-ref:Associations::ClassMethods#has_many] or
|
39
|
+
# {ActiveEntity::Base.has_and_belongs_to_many}[rdoc-ref:Associations::ClassMethods#has_and_belongs_to_many]
|
40
|
+
# associations.
|
41
|
+
class ConfigurationError < ActiveEntityError
|
42
|
+
end
|
43
|
+
|
44
|
+
# Raised on attempt to update record that is instantiated as read only.
|
45
|
+
class ReadOnlyRecord < ActiveEntityError
|
46
|
+
end
|
47
|
+
|
48
|
+
# Raised when attribute has a name reserved by Active Entity (when attribute
|
49
|
+
# has name of one of Active Entity instance methods).
|
50
|
+
class DangerousAttributeError < ActiveEntityError
|
51
|
+
end
|
52
|
+
|
53
|
+
# Raised when unknown attributes are supplied via mass assignment.
|
54
|
+
UnknownAttributeError = ActiveModel::UnknownAttributeError
|
55
|
+
|
56
|
+
# Raised when an error occurred while doing a mass assignment to an attribute through the
|
57
|
+
# {ActiveEntity::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
|
58
|
+
# The exception has an +attribute+ property that is the name of the offending attribute.
|
59
|
+
class AttributeAssignmentError < ActiveEntityError
|
60
|
+
attr_reader :exception, :attribute
|
61
|
+
|
62
|
+
def initialize(message = nil, exception = nil, attribute = nil)
|
63
|
+
super(message)
|
64
|
+
@exception = exception
|
65
|
+
@attribute = attribute
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Raised when there are multiple errors while doing a mass assignment through the
|
70
|
+
# {ActiveEntity::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=]
|
71
|
+
# method. The exception has an +errors+ property that contains an array of AttributeAssignmentError
|
72
|
+
# objects, each corresponding to the error while assigning to an attribute.
|
73
|
+
class MultiparameterAssignmentErrors < ActiveEntityError
|
74
|
+
attr_reader :errors
|
75
|
+
|
76
|
+
def initialize(errors = nil)
|
77
|
+
@errors = errors
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveEntity
|
4
|
+
# Returns the version of the currently loaded Active Entity as a <tt>Gem::Version</tt>
|
5
|
+
def self.gem_version
|
6
|
+
Gem::Version.new VERSION::STRING
|
7
|
+
end
|
8
|
+
|
9
|
+
module VERSION
|
10
|
+
MAJOR = 0
|
11
|
+
MINOR = 0
|
12
|
+
TINY = 1
|
13
|
+
PRE = "beta1"
|
14
|
+
|
15
|
+
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,278 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash/indifferent_access"
|
4
|
+
|
5
|
+
module ActiveEntity
|
6
|
+
# == Single table inheritance
|
7
|
+
#
|
8
|
+
# Active Entity allows inheritance by storing the name of the class in a column that by
|
9
|
+
# default is named "type" (can be changed by overwriting <tt>Base.inheritance_attribute</tt>).
|
10
|
+
# This means that an inheritance looking like this:
|
11
|
+
#
|
12
|
+
# class Company < ActiveEntity::Base; end
|
13
|
+
# class Firm < Company; end
|
14
|
+
# class Client < Company; end
|
15
|
+
# class PriorityClient < Client; end
|
16
|
+
#
|
17
|
+
# When you do <tt>Firm.create(name: "37signals")</tt>, this record will be saved in
|
18
|
+
# the companies table with type = "Firm". You can then fetch this row again using
|
19
|
+
# <tt>Company.where(name: '37signals').first</tt> and it will return a Firm object.
|
20
|
+
#
|
21
|
+
# Be aware that because the type column is an attribute on the record every new
|
22
|
+
# subclass will instantly be marked as dirty and the type column will be included
|
23
|
+
# in the list of changed attributes on the record. This is different from non
|
24
|
+
# Single Table Inheritance(STI) classes:
|
25
|
+
#
|
26
|
+
# Company.new.changed? # => false
|
27
|
+
# Firm.new.changed? # => true
|
28
|
+
# Firm.new.changes # => {"type"=>["","Firm"]}
|
29
|
+
#
|
30
|
+
# If you don't have a type column defined in your table, single-table inheritance won't
|
31
|
+
# be triggered. In that case, it'll work just like normal subclasses with no special magic
|
32
|
+
# for differentiating between them or reloading the right type with find.
|
33
|
+
#
|
34
|
+
# Note, all the attributes for all the cases are kept in the same table. Read more:
|
35
|
+
# https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
|
36
|
+
#
|
37
|
+
module Inheritance
|
38
|
+
extend ActiveSupport::Concern
|
39
|
+
|
40
|
+
included do
|
41
|
+
# Determines whether to store the full constant name including namespace when using STI.
|
42
|
+
# This is true, by default.
|
43
|
+
class_attribute :store_full_sti_class, instance_writer: false, default: true
|
44
|
+
end
|
45
|
+
|
46
|
+
module ClassMethods
|
47
|
+
# Determines if one of the attributes passed in is the inheritance column,
|
48
|
+
# and if the inheritance column is attr accessible, it initializes an
|
49
|
+
# instance of the given subclass instead of the base class.
|
50
|
+
def new(attributes = nil, &block)
|
51
|
+
if abstract_class? || self == Base
|
52
|
+
raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated."
|
53
|
+
end
|
54
|
+
|
55
|
+
if has_attribute?(inheritance_attribute)
|
56
|
+
subclass = subclass_from_attributes(attributes)
|
57
|
+
|
58
|
+
if subclass.nil? && base_class?
|
59
|
+
subclass = subclass_from_attributes(_default_attributes)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
if subclass && subclass != self
|
64
|
+
subclass.new(attributes, &block)
|
65
|
+
else
|
66
|
+
super
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns +true+ if this does not need STI type condition. Returns
|
71
|
+
# +false+ if STI type condition needs to be applied.
|
72
|
+
def descends_from_active_entity?
|
73
|
+
if self == Base
|
74
|
+
false
|
75
|
+
elsif superclass.abstract_class?
|
76
|
+
superclass.descends_from_active_entity?
|
77
|
+
else
|
78
|
+
superclass == Base || !has_attribute?(inheritance_attribute)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def finder_needs_type_condition? #:nodoc:
|
83
|
+
# This is like this because benchmarking justifies the strange :false stuff
|
84
|
+
:true == (@finder_needs_type_condition ||= descends_from_active_entity? ? :false : :true)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns the class descending directly from ActiveEntity::Base, or
|
88
|
+
# an abstract class, if any, in the inheritance hierarchy.
|
89
|
+
#
|
90
|
+
# If A extends ActiveEntity::Base, A.base_class will return A. If B descends from A
|
91
|
+
# through some arbitrarily deep hierarchy, B.base_class will return A.
|
92
|
+
#
|
93
|
+
# If B < A and C < B and if A is an abstract_class then both B.base_class
|
94
|
+
# and C.base_class would return B as the answer since A is an abstract_class.
|
95
|
+
def base_class
|
96
|
+
unless self < Base
|
97
|
+
raise ActiveEntityError, "#{name} doesn't belong in a hierarchy descending from Active Entity"
|
98
|
+
end
|
99
|
+
|
100
|
+
if superclass == Base || superclass.abstract_class?
|
101
|
+
self
|
102
|
+
else
|
103
|
+
superclass.base_class
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns whether the class is a base class.
|
108
|
+
# See #base_class for more information.
|
109
|
+
def base_class?
|
110
|
+
base_class == self
|
111
|
+
end
|
112
|
+
|
113
|
+
# Set this to +true+ if this is an abstract class (see
|
114
|
+
# <tt>abstract_class?</tt>).
|
115
|
+
# If you are using inheritance with Active Entity and don't want a class
|
116
|
+
# to be considered as part of the STI hierarchy, you must set this to
|
117
|
+
# true.
|
118
|
+
# +ApplicationRecord+, for example, is generated as an abstract class.
|
119
|
+
#
|
120
|
+
# Consider the following default behaviour:
|
121
|
+
#
|
122
|
+
# Shape = Class.new(ActiveEntity::Base)
|
123
|
+
# Polygon = Class.new(Shape)
|
124
|
+
# Square = Class.new(Polygon)
|
125
|
+
#
|
126
|
+
# Shape.table_name # => "shapes"
|
127
|
+
# Polygon.table_name # => "shapes"
|
128
|
+
# Square.table_name # => "shapes"
|
129
|
+
# Shape.create! # => #<Shape id: 1, type: nil>
|
130
|
+
# Polygon.create! # => #<Polygon id: 2, type: "Polygon">
|
131
|
+
# Square.create! # => #<Square id: 3, type: "Square">
|
132
|
+
#
|
133
|
+
# However, when using <tt>abstract_class</tt>, +Shape+ is omitted from
|
134
|
+
# the hierarchy:
|
135
|
+
#
|
136
|
+
# class Shape < ActiveEntity::Base
|
137
|
+
# self.abstract_class = true
|
138
|
+
# end
|
139
|
+
# Polygon = Class.new(Shape)
|
140
|
+
# Square = Class.new(Polygon)
|
141
|
+
#
|
142
|
+
# Shape.table_name # => nil
|
143
|
+
# Polygon.table_name # => "polygons"
|
144
|
+
# Square.table_name # => "polygons"
|
145
|
+
# Shape.create! # => NotImplementedError: Shape is an abstract class and cannot be instantiated.
|
146
|
+
# Polygon.create! # => #<Polygon id: 1, type: nil>
|
147
|
+
# Square.create! # => #<Square id: 2, type: "Square">
|
148
|
+
#
|
149
|
+
# Note that in the above example, to disallow the creation of a plain
|
150
|
+
# +Polygon+, you should use <tt>validates :type, presence: true</tt>,
|
151
|
+
# instead of setting it as an abstract class. This way, +Polygon+ will
|
152
|
+
# stay in the hierarchy, and Active Entity will continue to correctly
|
153
|
+
# derive the table name.
|
154
|
+
attr_accessor :abstract_class
|
155
|
+
|
156
|
+
# Returns whether this class is an abstract class or not.
|
157
|
+
def abstract_class?
|
158
|
+
defined?(@abstract_class) && @abstract_class == true
|
159
|
+
end
|
160
|
+
|
161
|
+
def sti_name
|
162
|
+
store_full_sti_class ? name : name.demodulize
|
163
|
+
end
|
164
|
+
|
165
|
+
def inherited(subclass)
|
166
|
+
subclass.instance_variable_set(:@_type_candidates_cache, Concurrent::Map.new)
|
167
|
+
super
|
168
|
+
end
|
169
|
+
|
170
|
+
protected
|
171
|
+
|
172
|
+
# Returns the class type of the record using the current module as a prefix. So descendants of
|
173
|
+
# MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
|
174
|
+
def compute_type(type_name)
|
175
|
+
if type_name.start_with?("::")
|
176
|
+
# If the type is prefixed with a scope operator then we assume that
|
177
|
+
# the type_name is an absolute reference.
|
178
|
+
ActiveSupport::Dependencies.constantize(type_name)
|
179
|
+
else
|
180
|
+
type_candidate = @_type_candidates_cache[type_name]
|
181
|
+
if type_candidate && type_constant = ActiveSupport::Dependencies.safe_constantize(type_candidate)
|
182
|
+
return type_constant
|
183
|
+
end
|
184
|
+
|
185
|
+
# Build a list of candidates to search for
|
186
|
+
candidates = []
|
187
|
+
name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" }
|
188
|
+
candidates << type_name
|
189
|
+
|
190
|
+
candidates.each do |candidate|
|
191
|
+
constant = ActiveSupport::Dependencies.safe_constantize(candidate)
|
192
|
+
if candidate == constant.to_s
|
193
|
+
@_type_candidates_cache[type_name] = candidate
|
194
|
+
return constant
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
raise NameError.new("uninitialized constant #{candidates.first}", candidates.first)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
203
|
+
|
204
|
+
# Called by +instantiate+ to decide which class to use for a new
|
205
|
+
# record instance. For single-table inheritance, we check the record
|
206
|
+
# for a +type+ column and return the corresponding class.
|
207
|
+
def discriminate_class_for_record(record)
|
208
|
+
if using_single_table_inheritance?(record)
|
209
|
+
find_sti_class(record[inheritance_attribute])
|
210
|
+
else
|
211
|
+
super
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def using_single_table_inheritance?(record)
|
216
|
+
record[inheritance_attribute].present? && has_attribute?(inheritance_attribute)
|
217
|
+
end
|
218
|
+
|
219
|
+
def find_sti_class(type_name)
|
220
|
+
type_name = base_class.type_for_attribute(inheritance_attribute).cast(type_name)
|
221
|
+
subclass = begin
|
222
|
+
if store_full_sti_class
|
223
|
+
ActiveSupport::Dependencies.constantize(type_name)
|
224
|
+
else
|
225
|
+
compute_type(type_name)
|
226
|
+
end
|
227
|
+
rescue NameError
|
228
|
+
raise SubclassNotFound,
|
229
|
+
"The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \
|
230
|
+
"This error is raised because the attribute '#{inheritance_attribute}' is reserved for storing the class in case of inheritance. " \
|
231
|
+
"Please rename this attribute if you didn't intend it to be used for storing the inheritance class " \
|
232
|
+
"or overwrite #{name}.inheritance_attribute to use another attribute for that information."
|
233
|
+
end
|
234
|
+
unless subclass == self || descendants.include?(subclass)
|
235
|
+
raise SubclassNotFound, "Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}"
|
236
|
+
end
|
237
|
+
subclass
|
238
|
+
end
|
239
|
+
|
240
|
+
# Detect the subclass from the inheritance column of attrs. If the inheritance column value
|
241
|
+
# is not self or a valid subclass, raises ActiveEntity::SubclassNotFound
|
242
|
+
def subclass_from_attributes(attrs)
|
243
|
+
attrs = attrs.to_h if attrs.respond_to?(:permitted?)
|
244
|
+
if attrs.is_a?(Hash)
|
245
|
+
subclass_name = attrs[inheritance_attribute] || attrs[inheritance_attribute.to_sym]
|
246
|
+
|
247
|
+
if subclass_name.present?
|
248
|
+
find_sti_class(subclass_name)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def initialize_dup(other)
|
255
|
+
super
|
256
|
+
ensure_proper_type
|
257
|
+
end
|
258
|
+
|
259
|
+
private
|
260
|
+
|
261
|
+
def initialize_internals_callback
|
262
|
+
super
|
263
|
+
ensure_proper_type
|
264
|
+
end
|
265
|
+
|
266
|
+
# Sets the attribute used for single table inheritance to this class name if this is not the
|
267
|
+
# ActiveEntity::Base descendant.
|
268
|
+
# Considering the hierarchy Reply < Message < ActiveEntity::Base, this makes it possible to
|
269
|
+
# do Reply.new without having to set <tt>Reply[Reply.inheritance_attribute] = "Reply"</tt> yourself.
|
270
|
+
# No such attribute would be set for objects of the Message class in that example.
|
271
|
+
def ensure_proper_type
|
272
|
+
klass = self.class
|
273
|
+
if klass.finder_needs_type_condition?
|
274
|
+
_write_attribute(klass.inheritance_attribute, klass.sti_name)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/string/filters"
|
4
|
+
|
5
|
+
module ActiveEntity
|
6
|
+
module Integration
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
# Returns a +String+, which Action Pack uses for constructing a URL to this
|
10
|
+
# object. The default implementation returns this record's id as a +String+,
|
11
|
+
# or +nil+ if this record's unsaved.
|
12
|
+
#
|
13
|
+
# For example, suppose that you have a User model, and that you have a
|
14
|
+
# <tt>resources :users</tt> route. Normally, +user_path+ will
|
15
|
+
# construct a path with the user object's 'id' in it:
|
16
|
+
#
|
17
|
+
# user = User.find_by(name: 'Phusion')
|
18
|
+
# user_path(user) # => "/users/1"
|
19
|
+
#
|
20
|
+
# You can override +to_param+ in your model to make +user_path+ construct
|
21
|
+
# a path using the user's name instead of the user's id:
|
22
|
+
#
|
23
|
+
# class User < ActiveEntity::Base
|
24
|
+
# def to_param # overridden
|
25
|
+
# name
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# user = User.find_by(name: 'Phusion')
|
30
|
+
# user_path(user) # => "/users/Phusion"
|
31
|
+
def to_param
|
32
|
+
# We can't use alias_method here, because method 'id' optimizes itself on the fly.
|
33
|
+
id && id.to_s # Be sure to stringify the id for routes
|
34
|
+
end
|
35
|
+
|
36
|
+
module ClassMethods
|
37
|
+
# Defines your model's +to_param+ method to generate "pretty" URLs
|
38
|
+
# using +method_name+, which can be any attribute or method that
|
39
|
+
# responds to +to_s+.
|
40
|
+
#
|
41
|
+
# class User < ActiveEntity::Base
|
42
|
+
# to_param :name
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# user = User.find_by(name: 'Fancy Pants')
|
46
|
+
# user.id # => 123
|
47
|
+
# user_path(user) # => "/users/123-fancy-pants"
|
48
|
+
#
|
49
|
+
# Values longer than 20 characters will be truncated. The value
|
50
|
+
# is truncated word by word.
|
51
|
+
#
|
52
|
+
# user = User.find_by(name: 'David Heinemeier Hansson')
|
53
|
+
# user.id # => 125
|
54
|
+
# user_path(user) # => "/users/125-david-heinemeier"
|
55
|
+
#
|
56
|
+
# Because the generated param begins with the record's +id+, it is
|
57
|
+
# suitable for passing to +find+. In a controller, for example:
|
58
|
+
#
|
59
|
+
# params[:id] # => "123-fancy-pants"
|
60
|
+
# User.find(params[:id]).id # => 123
|
61
|
+
def to_param(method_name = nil)
|
62
|
+
if method_name.nil?
|
63
|
+
super()
|
64
|
+
else
|
65
|
+
define_method :to_param do
|
66
|
+
if (default = super()) &&
|
67
|
+
(result = send(method_name).to_s).present? &&
|
68
|
+
(param = result.squish.parameterize.truncate(20, separator: /-/, omission: "")).present?
|
69
|
+
"#{default}-#{param}"
|
70
|
+
else
|
71
|
+
default
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|