Comments (26)
@joneslee85 π
By looking at that module, I'm not sure if it can be reused for the purpose. It uses low level Sequel knowledge to update those timestamps. However, I guess we can easily code our own.
Your proposed API is per collection basis, which is great. At the low level Sql::Query
and Sql::Collection
instances don't have specific Ruby types. The design is different at the higher level where we have a concrete repository (Ruby type) per mapped collection.
In code:
Sql::Query.new(users_sql_collection).class # => always a Sql::Query instance
Sql::Query.new(books_sql_collection).class # => always a Sql::Query instance
vs
UserRepository # => concrete Ruby type
BookRepository # => concrete Ruby type
If we need a Ruby module to include somewhere, probably the candidate is a repository.
from model.
@joneslee85 Another candidate can be a coercer. We instantiate it one for each database table, when we load the mapped collection. We could inject the functionality there.
The advantage is to keep this functionality at the level of the Mapping::Collection
. Because the proposed #timestamps
API is local to that object.
If we expand #attribute
with a :default
option, we can easily implement this feature:
attribute :city, String, default: 'Rome'
attribute :delivered_at, Time, default: ->{ Time.now }
def timestamps
attribute :updated_at, Time, default: -> { Time.now }
attribute :created_at, Time, default: -> { Time.now }
end
What do you think?
from model.
@jodosha brilliant idea on the coercer. +1 for the latter suggestion
from model.
π
from model.
should default option saved in database or if the value is null return the default option?
from model.
@AlfonsoUceda Can you please expand your question, not sure to have understood it well. Sorry.
from model.
@joneslee85 Mr @AlfonsoUceda made me think about one thing: repository and coercer don't set the expected data on an entity π’
For instance, if we have an age
attribute as integer, when we persist an User, the repository returns an entity slightly changed. Only the id
attribute is set.
u = User.new(age: '32')
u = UserRepository.persist(u)
u.id # => 1
u.age # => "32" - WRONG, we expected an integer here.
So I'm afraid that if we implement timestamps via coercer the following sync problem will happen:
u = User.new(name: 'Luca')
UserRepository.persist(u) # => this operation will persist `created_at` and `updated_at` in the database
u.created_at # => nil - WRONG, it should return that timestamp.
from model.
Yes, it should be available in entity too
about defualt option in mapper I think it could be another issue, expanding my question, suppose we have this mapper:
Suppose we have this mapper:
mapper = Lotus::Model::Mapper.new do
collection :users do
entity User
attribute :id, Integer
attribute :name, String
attribute :admin, Boolean, default: -> { false }
end
end
u = User.new(name: 'Luca', admin: true)
UserRepository.persist(u)
u.admin #=> true
u = User.new(name: 'Luca')
UserRepository.persist(u)
u.admin #=> should be false instead nil, isn't it?
from model.
@AlfonsoUceda yes.
from model.
ok ;)
from model.
@jodosha you catch a very good case there. What do you think about auto-reload entity after repository persist it?
from model.
@joneslee85 Doing this via coercer is a bad idea :( It can work when the value is nil
(created_at
on a non-persisted entity). But when it comes to update an already existing timestamp (updated_at
), it won't replace it with a default value.
from model.
I think the default option should be in the migrations and not in the coercer, but I am thinking the migrations are only available for SQL, and in the memory or file adapter this can't be achieved :S
The created_at set when before the entity is created and the updated_at before any modification but what is the correct place to do this?
from model.
@AlfonsoUceda Right, migration-less/schema-less databases (e.g. a JSON store) don't have default values or types. Although there are ways to coerce data to certain data types natively within some of these databases, not every database has that functionality. It would have to take place at the application level.
Is there a reason #create
only updates the id versus the whole entity reference? My guess is because the database interface only returns that value upon creation?
# sql_adapter.rb
def create(collection, entity)
entity.id = command(
query(collection)
).create(entity)
entity
end
from model.
@AlfonsoUceda @angeloashmore This isn't only a matter of schema(less) databases. If a column isn't mapped it's entirely ignored by the framework.
@angeloashmore RE entity: that is my fault. I'm sorry. I already discussed with @joneslee85 about this problem. You have opened a ticket for this, let's discuss over there.
from model.
It appears the Repository is the best place for this to happen. Maintaining timestamps should occur at #persist
.
I wrote this quick module and it seems to work. I haven't written any tests for it yet, however.
https://gist.github.com/angeloashmore/05ef7f035f694cfa56d7
Required setup
- The database must accept
created_at
andupdated_at
. - The entity must respond to
#created_at=
and#updated_at=
. - The repository must set
created_at
andupdated_at
before persisting/creating/updating. This is done by includingLotus::Repository::Timestamps
. - The mapper must include
created_at
andupdated_at
.
# order_repository.rb
class OrderRepository
include Lotus::Repository
include Lotus::Repository::Timestamps
end
# mapping.rb
mapping do
collection :orders do
entity Order
attribute :id, String
attribute :billing_info, Hash
attribute :created_at, Time
attribute :updated_at, Time
end
end
# order.rb
class Order
include Lotus::Entity
attributes :billing_info, :created_at, :updated_at
end
Issues with this approach
Repetition
There is a lot of repetition of created_at
and updated_at
. Because of the way Repositories, Mappers, and Entities are separated, it seems it is necessary to declare the attributes in all places.
What do you think of having helper methods to automatically include this? You will still need to declare them everywhere, but as @joneslee85 suggested, it could turn this:
collection :orders do
# ...
attribute :created_at, Time
attribute :updated_at, Time
end
β¦to this:
collection :orders do
# ...
timestamps
end
β¦and this:
class Order
include Lotus::Entity
attributes :billing_info, :created_at, :updated_at
end
β¦to this:
class Order
include Lotus::Entity
attributes :billing_info, timestamps
end
Entity equivalency
This will modify updated_at
anytime #update
is called even if the entity did not actually change.
For example:
order = OrderRepository.find(23)
order.billing_info #=> { address: '123 Some Street' }
order.updated_at #=> 2015-02-20 10:00:00 UTC
order.billing_info = { address: '123 Some Street' } # what it already is
OrderRepository.persist(order)
order.updated_at #=> 2015-02-20 10:20:35 UTC
How will the repository know if the entity was actually modified? One possible solution: http://stackoverflow.com/a/1964155
from model.
@angeloashmore Thanks for the detailed investigation. I apologize for the late reply.
I do agree that there is a lot of repetition. I suggest to make it work and then to make it DRY. Would you love to craft a PR based on that gist? Thank you very much!
from model.
Is anybody works on this feature?
I can do task based on a proposed solution and then continue implementing this feature, if not.
from model.
@dsnipe there isn't someone working on this, please feel free ;)
from model.
@AlfonsoUceda cool :)
so, I done with provided gist in PR #169
My plan is:
- Implement timestamps helpers for mapper and entity
- Equivalency for entities. We can use https://github.com/dkubb/equalizer or create our own solution to avoid dependency.
from model.
avoid new dependencies please, create a custom solution for lotus ;)
thanks!
El 01/04/2015 15:52, "Dmitry Tymchuk" [email protected] escribiΓ³:
@AlfonsoUceda https://github.com/AlfonsoUceda cool :)
so, I done with provided gist in PR
My plan is:
- Implement timestamps helpers for mapper and entity
- Equivalency for entities. We can use
https://github.com/dkubb/equalizer or create our own solution to avoid
dependency.Reply to this email directly or view it on GitHub
#134 (comment).
from model.
After creating draft for timestamp implementation, I realised, that may be better place for defining timestamps will be in configuration.
Global defining for all entities
Lotus::Model.configure do
timestamps :all
end
Global defining with exceptions
Lotus::Model.configure do
timestamps :all, exclude: ['Admin', 'Moderator']
end
Defining timestamps only for specific entities
Lotus::Model.configure do
timestamps ['User', 'Article', 'Blog'] # not sure about it
end
It gives ability to manage timestamps from one place and generate migrations already with needed columns.
What are think about it?
from model.
IMHO I think your suggestion is over killing for I don't find much gains in doing so. I'd rather let users self-define in their entity for sake of consistency and simplicity. Just my 5cent
from model.
@joneslee85 actually I used timestamps almost on all entities usually. Thats why I suggest using it in global scope.
but maybe you're right and simplest way is a better way :) I'll finish #169
btw, I see some duplication of functionality in #167 and this one β there are any reasons that both solutions are necessary?
from model.
@dsnipe much thanks, regarding #167, it is overlapping, however I'd like to ask you to wait for TeamLeo who is now working on it. π
from model.
from model.
Related Issues (20)
- Date with BC note in PostgreSQL parsed incorrectly
- Optimistic Locking HOT 2
- Repository #find_or_create_by HOT 2
- Database column defaults are not replacing empty string or nil values HOT 4
- Sqlite3 'dumps database schema.sql' test failing HOT 3
- Private method 'Array' in hanami entity HOT 2
- Extra query with assoc and that make app slow HOT 1
- Associations don't seem to work well with as: aliases. HOT 3
- What's the recommend way to use transactions? HOT 3
- NoMethodError: undefined method `one' for #<Hanami::Model::Associations::HasMany> HOT 4
- BigDecimal.new error with Postgres HOT 16
- Can't prepare testing PostgreSQL database HOT 5
- #to_hash and implicit conversion HOT 4
- Error
- db prepare raises Postgres createdb error HOT 14
- Unable to control production DB log HOT 1
- [QUESTION] There is a "How to" guide for the test/development process? HOT 2
- Use original error in case role is missing for Postgres HOT 1
- Change default dataset configured in ROM HOT 1
- Outdated dependencies HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. πππ
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google β€οΈ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from model.