当你使用轻量级 Sinatra 做项目主框架,比如简单的 API 项目、MicroService,然后为了数据库模型操作引入的 Rails,你该怀疑了。
打开你的 Gemfile.lock 文件,找到以下类似代码
rails (5.0.1)
actioncable (= 5.0.1)
actionmailer (= 5.0.1)
actionpack (= 5.0.1)
actionview (= 5.0.1)
activejob (= 5.0.1)
activemodel (= 5.0.1)
activerecord (= 5.0.1)
activesupport (= 5.0.1)
bundler (>= 1.3.0, < 2.0)
railties (= 5.0.1)
sprockets-rails (>= 2.0.0)
其实只需引入 activerecord 就可以。
最近在重温《Ruby 元编程》一书,有一章节讲到 attr_accessor 是怎么实现的。
User < ActiveRecord::Base
你不觉得这是件神奇的事吗?为什么 Model 继承了 ActiveRecord::Base,就可以使用 attr_accessor、各种 ORM 操作。 当然 源代码能告诉你为什么。
然后自己用 Sinatra 做控制器层,自己尝试写个简单版 gem mini_active_record来实现, 项目的目录结构基本与 Rails 保持一致,如下
一步一步实现下来,attr_accessor、validates、establish_connection、attribute_names、attributes 以及各种 CRUD 的操作, 有遇到实现问题时去看看官方 activerecord 的源代码,顿然明朗了。比如:
validates 用define_method "#{attribute}="
(PS:官方不是这么实现的啦~~),那就跟 attr_accessor 有重复定义attribute=
方法了,
那么在 Model 里使用 attr_accessor、validates 有先后顺序依赖。
establish_connection 的单例模式实现,以及如何加载数据库的配置参数?
模型对象关系查询问题,如 User.find_by(id: 1)
转换成对应 SQL,使用 gem arel 解决。
CRUD 的操作需要数据库适配、持久化,需要保持已修改的模型数据 和 上次与数据库同步的数据,这样 Model.persisted?、changed?等方法才能正常高效的运作。
说了这么多,总结一句就是 Sinatra 项目 mini_active_record,
gem 'activerecord' # gem 'mini_active_record'
加入这行代码,项目也能正常运作。
赞,看完 Ruby 元编程之后尝试看了点 activerecord,被各种方法调用来调用去的看晕了,估计功力还不够深把。最近正在看sinatra
的源码,感觉简单很多。
要想模仿 ActiveRecord 的要慎重,这是个“PhotoShop”级别的库,包含各种想得到或者你觉得现在没用将来很需要的特性。
看下 rails 5.1.4 中 ActiveRecord 定义的 module 有 150 多个(不包含 Class 及子 module)
ActiveRecord::Attributes,
ActiveRecord::Migration,
ActiveRecord::ExplainRegistry,
ActiveRecord::Migrator,
ActiveRecord::Fixture,
ActiveRecord::NoDatabaseError,
ActiveRecord::ConnectionTimeoutError,
ActiveRecord::ExclusiveConnectionTimeoutError,
ActiveRecord::QueryCache,
ActiveRecord::Core,
ActiveRecord::ConnectionHandling,
ActiveRecord::CounterCache,
ActiveRecord::DynamicMatchers,
ActiveRecord::Enum,
ActiveRecord::InternalMetadata,
ActiveRecord::Explain,
ActiveRecord::Inheritance,
ActiveRecord::ModelSchema,
ActiveRecord::NestedAttributes,
ActiveRecord::NoTouching,
ActiveRecord::TouchLater,
ActiveRecord::Persistence,
ActiveRecord::Querying,
ActiveRecord::CollectionCacheKey,
ActiveRecord::ReadonlyAttributes,
ActiveRecord::RecordInvalid,
ActiveRecord::Reflection,
ActiveRecord::RuntimeRegistry,
ActiveRecord::Sanitization,
ActiveRecord::Schema,
ActiveRecord::SchemaDumper,
ActiveRecord::SchemaMigration,
ActiveRecord::Scoping,
ActiveRecord::Serialization,
ActiveRecord::StatementCache,
ActiveRecord::Suppressor,
ActiveRecord::Timestamp,
ActiveRecord::Transactions,
ActiveRecord::Translation,
ActiveRecord::Validations,
ActiveRecord::SecureToken,
ActiveRecord::ActiveRecordError,
ActiveRecord::ConnectionNotEstablished,
ActiveRecord::ConnectionAdapters,
ActiveRecord::Aggregations,
ActiveRecord::Associations,
ActiveRecord::AttributeAssignment,
ActiveRecord::AttributeMethods,
ActiveRecord::AutosaveAssociation,
ActiveRecord::Attribute,
ActiveRecord::LegacyYamlAdapter,
ActiveRecord::AssociationRelation,
ActiveRecord::NullRelation,
ActiveRecord::Relation,
ActiveRecord::QueryMethods,
ActiveRecord::PredicateBuilder,
ActiveRecord::SpawnMethods,
ActiveRecord::FinderMethods,
ActiveRecord::Batches,
ActiveRecord::Result,
ActiveRecord::TableMetadata,
ActiveRecord::Delegation,
ActiveRecord::VERSION,
ActiveRecord::SerializationTypeMismatch,
ActiveRecord::Coders,
ActiveRecord::RecordNotFound,
ActiveRecord::AdapterNotSpecified,
ActiveRecord::AssociationTypeMismatch,
ActiveRecord::AssociationNotFoundError,
ActiveRecord::RecordNotSaved,
ActiveRecord::Locking,
ActiveRecord::RecordNotDestroyed,
ActiveRecord::RecordNotUnique,
ActiveRecord::InvalidForeignKey,
ActiveRecord::AdapterNotFound,
ActiveRecord::SubclassNotFound,
ActiveRecord::HasManyThroughOrderError,
ActiveRecord::WrappedDatabaseException,
ActiveRecord::NotNullViolation,
ActiveRecord::ValueTooLong,
ActiveRecord::MismatchedForeignKey,
ActiveRecord::PreparedStatementCacheExpired,
ActiveRecord::PreparedStatementInvalid,
ActiveRecord::HasManyThroughAssociationNotFoundError,
ActiveRecord::StaleObjectError,
ActiveRecord::ReadOnlyRecord,
ActiveRecord::AttributeAssignmentError,
ActiveRecord::MultiparameterAssignmentErrors,
ActiveRecord::UnknownPrimaryKey,
ActiveRecord::AttributeSet,
ActiveRecord::ImmutableRelation,
ActiveRecord::TransactionRollbackError,
ActiveModel::UnknownAttributeError,
ActiveRecord::Deadlocked,
ActiveRecord::SerializationFailure,
ActiveRecord::HasManyThroughCantDissociateNewRecords,
ActiveRecord::IrreversibleOrderError,
ActiveRecord::HasManyThroughNestedAssociationsAreReadonly,
ActiveRecord::HasManyThroughAssociationPolymorphicSourceError,
ActiveRecord::TestFixtures,
ActiveRecord::HasManyThroughAssociationPolymorphicThroughError,
ActiveRecord::DeleteRestrictionError,
ActiveRecord::HasManyThroughAssociationPointlessSourceTypeError,
ActiveRecord::HasOneThroughCantAssociateThroughCollection,
ActiveRecord::HasOneAssociationPolymorphicThroughError,
ActiveRecord::InverseOfAssociationNotFoundError,
ActiveRecord::Store,
ActiveRecord::AttributeDecorators,
ActiveRecord::LazyAttributeHash,
ActiveRecord::ThroughCantAssociateThroughHasOneOrManyReflection,
ActiveRecord::ReadOnlyAssociation,
ActiveRecord::AmbiguousSourceReflectionForThroughAssociation,
ActiveRecord::FixtureSet,
ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection,
ActiveRecord::HasOneThroughCantAssociateThroughHasOneOrManyReflection,
ActiveRecord::HasManyThroughCantAssociateNewRecords,
ActiveRecord::HasManyThroughSourceAssociationNotFoundError,
ActiveRecord::ThroughNestedAssociationsAreReadonly,
ActiveRecord::Integration,
ActiveRecord::Base,
ActiveRecord::EagerLoadPolymorphicError,
ActiveRecord::StatementInvalid,
ActiveRecord::AttributeMutationTracker,
ActiveRecord::ExplainSubscriber,
ActiveRecord::Calculations,
ActiveRecord::MigrationError,
ActiveRecord::IrreversibleMigration,
ActiveRecord::DuplicateMigrationVersionError,
ActiveRecord::DuplicateMigrationNameError,
ActiveRecord::UnknownMigrationVersionError,
ActiveRecord::IllegalMigrationNameError,
ActiveRecord::PendingMigrationError,
ActiveRecord::ConcurrentMigrationError,
ActiveRecord::NoEnvironmentInSchemaError,
ActiveRecord::ProtectedEnvironmentError,
ActiveRecord::Type,
ActiveRecord::EnvironmentMismatchError,
ActiveRecord::DefineCallbacks,
ActiveRecord::LogSubscriber,
ActiveRecord::SuppressorRegistry,
ActiveRecord::TypeCaster,
ActiveRecord::Callbacks,
ActiveRecord::Railties,
ActiveRecord::NullMutationTracker,
ActiveRecord::DangerousAttributeError,
ActiveRecord::HasOneThroughNestedAssociationsAreReadonly,
ActiveRecord::ConfigurationError,
ActiveRecord::Railtie,
ActiveRecord::FixtureClassNotFound,
ActiveRecord::MigrationProxy,
ActiveRecord::NullMigration,
ActiveRecord::TypeConflictError,
ActiveRecord::RangeError,
ActiveRecord::TransactionIsolationError,
ActiveRecord::Rollback,
ActiveRecord::Tasks
一般人用得最多的是 Migration、Validations、Scoping、Attributes、AttributeMethods、Callbacks、FinderMethods、QueryMethods、CounterCache、Associations 等 module 提供的 DSL。
有些高级点如 Locking、Store、SpawnMethods(提供 merge 合并其他 model 的 scoping)、QueryCache(不少人不知道可在 rake task 中显式使用)、TouchLater、SecureToken(提供 generate_unique_secure_token)、PredicateBuilder(定制 query 的 predicate)、Aggregations(提供 composed_of macro)、Suppressor(临时跳过 save 操作)在所适应的场景中很便利却容易被忽略。
另外想吐槽一点是各种 Error module 没有放到一个 Error namespace 下,或者放到与 feature 同名的 module 下也能接受。
Sequel 这点比较好,只有 44 个 modules
Sequel.constants.sort.each{ |m| puts m if Sequel.const_get(m).is_a?(Module) }
ASTTransformer
AdapterNotFound
BasicObject
(irb):7: warning: constant Sequel::BeforeHookFailed is deprecated
BeforeHookFailed
CheckConstraintViolation
ConnectionPool
ConstraintViolation
Database
DatabaseConnectionError
DatabaseDisconnectError
DatabaseError
Dataset
DeprecatedIdentifierMangling
Deprecation
Error
ForeignKeyConstraintViolation
HookFailed
Inflections
InvalidOperation
InvalidValue
LiteralString
MassAssignmentRestriction
Mock
Model
NoExistingObject
NoMatchingRow
NotNullConstraintViolation
Plugins
PoolTimeout
Qualifier
Rollback
SQL
SQLTime
Schema
SerializationFailure
ThreadedConnectionPool
Timezones
(irb):7: warning: constant Sequel::UnbindDuplicate is deprecated
UnbindDuplicate
(irb):7: warning: constant Sequel::Unbinder is deprecated
Unbinder
UndefinedAssociation
UniqueConstraintViolation
UnmodifiedIdentifiers
VIRTUAL_ROW
ValidationFailed
我们原来是用 sinatra + ar,后来还是用回了 rails。rails 真的很成熟了。如果要用到某些个 gem,也对 rails 支持很好,否则还要自己配置适配
引入 ActiveRecord 其实就可以考虑不要上 Sinatra 了,ActiveRecord 还依赖 ActiveSupport,一通下来,已经半个 Rails 都导入进来了。除了写路由的方法发生了变化,总的来说没有区别。特别是加个 WebSocket 还要自己处理集群平衡。如果使用 Redis (Ohm) 做数据库,那感觉还是挺爽的,明显会变快不少。重写一个 ActiveRecord 可以考虑直接上 Sequel 吧,基本满足需求。
ActiveRecord 最大的问题是,架构上没有做分层,导致理解他和扩展他的难度非常高,看上去各用途功能很好的放在不同的 module 里,实际上内部的代码是相互耦合的
AR 太重了,Sequel 轻量很多。
但是 Sequel 里一些 module 实现和文档并不一致,而且载入多个的话要注意顺序... 坑度感觉和配置 postcss 差不多。
不过大部分 module 你都不需要,某些 DSL 也是白费劲,Sequel.lit
就可以了。
不用 rails 时,习惯 Sinatra + DataMapper。
ActiveRecord 依赖拉起来确实太大了。不过作为百科全书式的库,总是在其它库缺少某个功能需要抄段代码时,让你放心。copy-paste
编程必备
到头来你们自己做的哪一个有 Rails 的全面和完整?或者维护的有 Rails 这么正常?解决 BUG 有 Rails 来的及时?Rails 是多少人验证过的。所以放弃吧,好好的在 Rails 基础上优化或许才是靠谱的方案。
并不这么觉得,jQuery 也是优化过的,前端很多人还是各走各的路。此条回复。
目前在项目中大量使用 Cuba+Sequel,的确有些东西不如 AR 方便,这个体会到了,ActiveSupport 等等缺失,需要自己零时去打 patch。
当然啦,你如果 monkey patch 都不用自己打的程序,写起来就不 Ruby 啦。