rom-rb / rom Goto Github PK
View Code? Open in Web Editor NEWData mapping and persistence toolkit for Ruby
Home Page: https://rom-rb.org
License: MIT License
Data mapping and persistence toolkit for Ruby
Home Page: https://rom-rb.org
License: MIT License
I just install latest rom gem and receive this strange error after requiring 'rom' in irb
NameError: undefined local variable or method `include_comparison_methods' for #<Axiom::Equalizer:0x00000002419c48>
from /home/valikos/.rbenv/versions/1.9.3-p484/lib/ruby/gems/1.9.1/gems/axiom-0.1.1/lib/axiom/support/equalizer.rb:18:in `initialize'
I'm confused because I am watching on installed source code of axiom-0.1.1/lib/axiom/support/equalizer.rb
file
def initialize(*keys)
@keys = keys
define_methods
include_comparison_methods
module_eval do
include Adamantium
memoize :hash
end
freeze
end
and source on github
def initialize(*)
super
module_eval do
include Adamantium
memoize :hash
end
end
I'm have this problem on three different workstations.
Sorry if this problem does not apply to rom project, because it is related with axiom.
When I require the code from the example on the ReadMe page in IRB, I get the following error:
NameError: undefined local variable or method `include_comparison_methods' for #Axiom::Equalizer:0x927235c
Do I need to change anything?
For now ROM will simply leak the underlaying data manipulation interface coming with the adapter. For instance in case of Sequel you can simply use insert
, update
and delete
and it's your job to encapsulate that somehow. I can imagine that it should be possible to come up with some conventions for dealing with data manipulation using adapters so that it's easier to achieve nice encapsulation. Time will tell.
I'm reporting this issue so that we can have a place for everyone to chime in and discuss various strategies.
Building complex queries with adapters could be simplified using higher-level interfaces. For example when using Sequel adapter we could have a set of methods for easier joins where details like correct column names and join keys are being set automatically.
It is, however, a rabbit hole in the long term but it's something to consider nevertheless. I believe if we narrow down the functionality to the minimum we could have something that's reliable.
Here's an example of what I'm talking about:
# let's say we have users and tasks relations that we can join
rom.relations do
users do
# some index view where we need users and tasks together
def index
# with a simplified interface via helpers it *could* look like that
users.join(tasks)
# without it there's a lot of things we need to take care of
users.join(tasks, user_id: Sequel.qualify(:users, :id)).
select(some_nasty_code_generating_the_list_of_column_names)
end
end
end
Initially let's just support model.new(tuple)
and we can extend it in the future versions to support more advanced mapping with coercions and whatnot.
This also requires finalization when models are constantize.
Environment#finalize
that will constantize all the models that were defined as stringsIn ROM::Mapper::Header, line 19 a call is made to Axiom::Relation::Header.coerce
, however Axiom is not listed in the gemspec of rom-mapper, or loaded in rom-mapper.rb
.
Initial work on the adapter already started in rom-rb/rom-redis - we're looking for people interested in helping out there!
I have the following environment defined (the same as the example):
require 'rom'
require 'axiom-do-adapter'
require 'do_sqlite3'
class User
attr_reader :id, :name
def initialize(attributes)
@id, @name = attributes.values_at(:id, :name)
end
end
env = ROM::Environment.setup(sqlite: 'sqlite3://#{Rails.root}/db/development.sqlite3') do
schema do
base_relation :users do
repository :sqlite
attribute :id, Integer
attribute :name, String
key :id
end
end
mapping do
users do
map :id, :name
model User
end
end
end
I then try to use a session
to insert a new record.
env.session do |session|
user = session[:users].new(id: 1, name: 'Jane')
session[:users].save(user)
session.flush
end
This does not error out but the record is never saved to the sqlite database. I tried the saving a slot with the example repo but no luck either.
Is this feature supported?
The README should explain the philosophy behind the project, main differences between ROM and other typical ruby ORMs and show how to quickly get started with ROM showing some basic examples. It should also have a nice CREDITS section mentioning DM2, Axiom and people behind all the initial efforts.
/cc @elskwid HELP NEEDED HERE :)
Hello there!
I have a question about attributes
We have a schema
env.schema do
base_relation :users do
repository :memory
attribute :id, Integer
attribute :name, String
key :id
end
end
Is there a way to take the attributes and pass them to the model? Because we can have a lot of attributes
class User
def initialize(attributes)
@id, @name = attributes.values_at(:id, :name)
end
end
Or whether in ROM support of Virtus for extend model?
Some database drivers return tuples with string keys, like moped for example. This could affect the model builder if it expects symbolized tuples. This could be improved by either symbolizing keys before returning the tuples from relation (configurable on the adapter level) or tweaking model builders so that they can support either symbolized or stringified keys.
Hello team,
Today I tried to use Rom 0.2.0 and I found that we have to do manual mapping to map attributes:
env[:users].restrict(id: 961781).rename(:id => :accountid).one
Before, that upgrade it worked without explicit calling the rename method, now it is required to get a proper field name in DB.
Is there any possibility to get auto-mapping while defining restriction?
Thank you for your hard work!!!
Required interface is #each
+ yielding hash-like objects.
Header
class so that accessing attribute information is simpleHeader::Attribute
and subclasses for primitive and embedded types#header
to adapter and in-memory RA layers so that it is always possible to ask for the header attributesHello team,
I tried to use ROM with simple Sqlite database, but it doesn't work. My code:
require 'rom-relation'
require 'rom-mapper'
require 'rom/support/axiom/adapter/sqlite3'
rom = ROM::Environment.setup(sqlite3: 'sqlite3://test.sql')
rom.schema do
base_relation :users do
repository :sqlite3
attribute :id, Integer
attribute :name, String
end
end
class User
attr_accessor :id, :name
end
rom.mapping do
repos do
model User
map :id, :name
end
end
puts rom[:users].to_a.inspect
The error which I receive:
/home/dnesteryuk/.rvm/gems/ruby-1.9.3-p429/gems/rom-relation-0.1.2/lib/rom/repository.rb:56:in `[]=': undefined method `[]=' for #<Axiom::Adapter::Sqlite3:0x00000001bc4a48> (NoMethodError)
from /home/dnesteryuk/.rvm/gems/ruby-1.9.3-p429/gems/rom-relation-0.1.2/lib/rom/schema/definition.rb:38:in `base_relation'
from test.rb:8:in `block in <main>'
from /home/dnesteryuk/.rvm/gems/ruby-1.9.3-p429/gems/rom-relation-0.1.2/lib/rom/schema.rb:43:in `instance_eval'
from /home/dnesteryuk/.rvm/gems/ruby-1.9.3-p429/gems/rom-relation-0.1.2/lib/rom/schema.rb:43:in `call'
from /home/dnesteryuk/.rvm/gems/ruby-1.9.3-p429/gems/rom-relation-0.1.2/lib/rom/environment.rb:66:in `schema'
from test.rb:7:in `<main>'
Should it work? Or may be it is not supported yet?
I'm trying to use demo example from the webpage(http://rom-rb.org/).
But looks like the only version of rom released is 0.0.1 and package is empty except LICENSE
and README.md
files.
Using git repos in Gemfile also didn't help:
gem 'rom-relation' , :git => 'https://github.com/rom-rb/rom-relation.git'
gem 'rom-mapper' , :git => 'https://github.com/rom-rb/rom-mapper.git'
gem 'rom-session' , :git => 'https://github.com/rom-rb/rom-session.git'
gem 'rom' , :git => 'https://github.com/rom-rb/rom.git'
bundle install
...
There was a LoadError while loading rom.gemspec:
cannot load such file -- rom-relation from
/home/me/.rvm/gems/ruby-2.0.0-p247@rom-test/bundler/gems/rom-68daa8cebb1d/rom.gemspec:2:in `<main>'
Does it try to require a relative path? That's been removed in Ruby 1.9.
It would be good to have some wiki with HOWTO to setup development environment.
For consistency this should be similar to model definition interface:
rom.mappers do
define(:with_tasks, parent: users) do
group :tasks do
model name: 'Task'
end
end
define(:with_address, parent: users) do
wrap :address do
model name: 'Address'
end
end
end
Writing model classes is tedious. When you have a schema in place in most of the cases you can and you probably want to simply get the model classes generated for you.
A common pattern is to insert a tuple into a relation and get an object back. In case of Sequel adapter we get the PK value back so a sequel mapper should be able to build a newly persisted object and return it back:
env.relations.users.insert(name: "Jane") # this would return the tuple with PK set
env.relations.users(mapper: true).insert(name: "Jane") # this would return an instance of User model
There should be some sort of either explicit and/or automatic migration support. Ideally with the following features:
irb
or define complex migrations for db/migrations/
.db/migrations/
.Support the old DataObjects adapter libraries. They were a pretty solid set of C-exts and Java-exts.
The roadmap says that associations should be supported, but I can't find any documentation on how to do more than one model in one repository in any of the examples. How do I declare associations and/or foreign keys for joins? The last code I could find was this: solnic/rom-relation@38136a8 , but there's no explanation of what I use in place of has
from before that commit. Are association still pending work? I would like to use rom to allow in-memory emulation of https://github.com/rapid7/metasploit_data_models when the user doesn't have access to and/or want to setup postgres. If you feel that rom is missing too many features at this time to do this, please let me know.
This means something more or less like this (DSL ideas are welcome!):
rom.schema do
base_relation(:users) do
repository :sqlite
attribute :user_id, Integer
attribute :name, String
attribute :country, String
end
users do
def from_country(code)
filter(country: code)
end
end
end
rom.relations.users.from_country("PL")
Here's an idea I have how we could define in-memory relations that can span multiple repositories:
rom = ROM.setup(sqlite: 'sqlite::memory', redis: 'redis://localhost') do
schema do
base_relation(:posts) do
repository :sqlite
attribute :post_id, Integer
attribute :title, String
end
base_relation(:tags) do
repository :redis
attribute :post_id, Integer
attribute :tags, Array
end
relation(:posts_with_tags) do
join(posts, tags)
end
end
end
rom.relations.posts_with_tags # this would return joined relation
This is also known as "embedded objects":
rom.relations do
users do
def with_address
natural_join(addresses)
end
end
end
rom.mappers do
define(:users) do
model name: 'User'
attribute :name
wrap address: [:street, :zipcode, :city]
end
end
Before you had renamed this project to Rom, I had possibility to use Virtus which provides very handy features:
class Book
include Virtus.model
attribute :page_numbers, Array[Integer]
end
Do we have such possibility now with Rom? Unfortunately, now we have to describe attributes in a schema. It means we cannot use Virtus anymore, only attributes provided by Rom/Axiom.
Thanks.
Right now only model name: "User"
works but it would blow up with model name: "Entities::User"
Currently mapper definition interface only allows to specify what attributes should be mapped. It would be nice to be able to specify coercions too. How those coercions are going to be handled will depend on the model builder type. In case of PORO we could have something trivial like relying on built-in coercion methods in ruby, for Virtus builder we can simply use its type-definition system, more Morpher we should construct nodes that provide coercions (this is still a WIP in Morpher though) and so on.
The interface could look like that:
rom.mappers do
define(:users) do
attribute :_id, type: :string
end
end
Refactoring in rom-mapper made me realize it's really a good idea to collapse all 3 pieces into a single repo as there are still way too many moving parts and I find it a PITA to manage 4 projects separately. It would also give better visibility of what you are breaking when introducing refactorings like in this branch (it breaks integration with both rom-relation and rom-session). I also don't think anybody will use session or mapper or relation as standalone gems, honestly. ROM is clearly becoming a convenient DSL on top of lower-level tools (axiom, morpher, probably more will come) so I don't see any point in maintaining complexity that comes with the separation. It is also way easier for people to understand the project when it's in a single repo. We can always extract things later when we see that it makes sense (I don't see any sense at the moment).
Main motivation behind this DSL is to have a simple way of defining how relations are mapped to ruby objects. I'd like to make it as concise as possible and automate as much as possible. I can imagine implementing entity classes is a tedious thing to do and personally I'd prefer ROM to generate them for me, however, I can also see the need for having an ability to re-open those classes to add some behavior hence defining constants might be a good idea.
Notice that with a data mapper you can't have a single User
model because you often may need a user object that's an aggregate with some child objects and for that you need a separate model. My gut feeling is telling me ROM will move us towards behavior-less entity objects that are simple "data capsules" and nothing more, we'll see.
Here's a draft of what I'm thinking about:
# assuming we have a base relation called :users with attributes :id, :name, :email
rom.mappers do
users do
# possible to explicitly set model class
model User
# or tell rom to generate it for us
model name: 'Entities::User', type: :virtus
# with support for different model types, like anima, poro etc. whatever we want
# we can define common mappings for all relations in the root
map :id, :name, :email
# assuming we have a users.with_tasks relation
with_tasks do
group tasks: [:title, :priority]
end
# in sql-based relations it is common to prefix attribute names to avoid name clashes
# so we can tell the mapper about it
with_tasks(prefix: true) do
group tasks: [:title, :priority]
end
end
end
This DSL is mostly inspired by the previous work on ROM and @snusnu's ramom.
Here are results of benchmarks/basic.rb
rom-rb/rom (master«) % COUNT=100 benchmarks/basic.rb
user system total real
seed 0.050000 0.010000 0.060000 ( 0.047423)
delete 0.250000 0.000000 0.250000 ( 0.254174)
seed and delete 0.330000 0.000000 0.330000 ( 0.336303)
rom-rb/rom (master«) % COUNT=200 benchmarks/basic.rb
user system total real
seed 0.130000 0.000000 0.130000 ( 0.130733)
delete 1.030000 0.010000 1.040000 ( 1.033373)
seed and delete 1.170000 0.000000 1.170000 ( 1.176047)
rom-rb/rom (master«) % COUNT=400 benchmarks/basic.rb
user system total real
seed 0.360000 0.010000 0.370000 ( 0.361748)
delete 4.130000 0.010000 4.140000 ( 4.140899)
seed and delete 4.540000 0.010000 4.550000 ( 4.549963)
It seems like this grows exponentially. I'm attaching perftools results with focus on Axiom.
Adapter.register(self)
would do the trickAdapter::Sequel.schema
that returns an array of supported schemas like ['sqlite', 'postgres', ...]
etc. and then something like Adapter[schema]
would return a class for the schema (by going through registered adapter classes)There's a gem configuration problem with sequel and sqlite3.
A common mapper feature is to rename an attribute in the result tuple. Interface idea:
rom.mappers do
# let's say this is mongo so we get :_id in the tuple
# and would like to rename it to :id
define(:users) do
attribute :id, from: :_id
end
end
Possible interface ideas:
env.mapping do
users do
map :id, :name
model User, :loader => :allocator
end
end
This will require some tweaking to make it work with new adapters.
There's a lot of duplication in schema, relation and mapper registry classes. Would be nice to come up with a common abstraction and use inheritance here to dry-up this mess.
This will make it possible to define relations in separate files.
Currently env object gives access to all the registries:
# current registry objects
env.schema # returns schema registry with *base relations*
env.mappers # returns registry with mappers for *base relations*
env.relations # returns registry with "public relations" that should be used directly on the application level
# accessing individual objects inside registries works with either [](name) or .name
env.schema.users == env.schema[:users]
env.mappers.users == env.mappers[:users]
env.relations.users == env.relations[:users]
# relation registry accepts additional options hash
env.relations.users(mapper: true) # this will wrap the relation with a mapper object so the relation will yield models rather than hashes
Here's a couple of potential improvements:
env
object ie env.users
since that's the top level public relation registry we want to use on the application levelEnv#mappers
private and come up with a better interface for accessing mapped relations (I don't like env.relations.users(mapper: true)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.