Clean association definitions with Ruby Sequel - ruby

I am using Jeremy Evan's Sequel to populate an (SQLite) database with data I scrape from web pages.
The database involves a number of many_to_many relationships that I express with Associations.
The associations are created in class definitions, which are always evaluated when the script is run.
Importantly, the association class definitions need to have the necessary tables in place.
Thus the table creation methods should be in the top level with the association definitions.
Here is an example:
module Thing
db = Sequel.Sqlite('data.sqlite')
db.create_table(:clients)
String :client_id, :primary_key => true
String :client_data
end
db.create_table(:orders)
String :order_id, :primary_key => true
String :order_data
end
db.create_table(:orders_clients)
String :order_id
String :client_id
primary_key [:order_id,:client_id]
end
class Person < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
class Order < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
end
First of all, I think that this is a rather dirty solution, since my method calls and class definitions sit in the same namespace.
If I try to separate the class definitions, I get No database associated with Sequel::Model error (which makes sense, but I want to defer the evaluation of the association definitions, having those after the table calls, whenever they might happen).
I want to be able to create the tables and associations in a method call. Thus, I could for example pass the name of the new database file:
def create_tables_and_schema (database_name)
db = Sequel.Sqlite(database_name)
db.create_table... #three of those, as above
class Person < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
class Order < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
end
What I think is needed is a different way to express table relations.
Any suggestions on approach and style are appreciated. Please ask for clarifications if the explanation is confusing.

Your method calls and class definitions do not need to sit in the same namespace, it's just that the tables need to be created before the model classes. An easy way to separate them is to move the table creation to a separate file. Also, usually you assign the database object to a constant.
create_tables.rb:
DB.create_table(:clients)
String :client_id, :primary_key => true
String :client_data
end
DB.create_table(:orders)
String :order_id, :primary_key => true
String :order_data
end
DB.create_table(:orders_clients)
String :order_id
String :client_id
primary_key [:order_id,:client_id]
end
models.rb:
DB = Sequel.sqlite('data.sqlite')
require 'create_tables'
class Person < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
class Order < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
You mentioned that you want to create the tables and associations in a method call, but that doesn't make sense if you are creating classes with constants. The main reason to create them via a method call is to allow for multiple databases at runtime, but that wouldn't work with your model classes since they are defined with constant names.
If you don't need multiple databases at runtime, the example above should work if you just want to separate the table creation from the model creation.
If you do need multiple databases at runtime, then creating the tables and models via a method call makes sense, but you need to create anonymous model classes, as otherwise you will have problems.

Related

Rails overriding active record setter in a relation

I want to override the << setter in my relation. For example, given:
class Library < ActiveRecord::Base
has_many :groups
def readers
groups.find_by(name: 'readers').users
end
end
class Group < ActiveRecord::Base
has_many :group_memberships
has_many :users, through: :group_memberships
end
class GroupMembership < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
class User < ActiveRecord::Base
has_many :groups, through :group_membership
end
I want to do something like
someLibrary.readers << user1
and some additional things to happen after this.
The code should look something like:
def <<(objects)
super objects
#do other things here
end
Where should it be? I guess in Group, like:
class Group
...
def users<<(objects)
super objects
#do stuff
end
end
but I only want to do it when I'm invoking << on readers.
I want to know if there is a way to know whether I'm invoking << on a group users relationship, or whether I have access to group object when I'm invoking << method on group users through the relationship.
I want to do it because it looks nice. The easiest way would be to define separate method to set readers (and be more explicit), but I want to know if it is possible in activerecord or in ruby.
edit:
Yeah I know that overriding core methods is bad thing and people go to hell for that, yada yada yada.
I'm just curious how it's done. Like, for learning purposes.
Besides the aim is just to override the << method on that particular relation so probable there might be some justification why someone might want to do it.
Obligatory disclaimer:
I do not recommend that you do this, in 'important' code. Changing the behaviour of methods like this will confuse the hell out of other developers (as well as your future self), and lead to all sorts of unintended behavioural changes!
But assuming that this is 'just for fun'...
Based on the information above, someLibrary.readers returns a collection of User records. So all we need to do is add the desired behaviour to that class.
Normally you can do this by just defining a class method, in one of two ways:
class User
def self.foo
puts 'this works!'
end
class << self
def bar
puts 'this works too!'
end
end
end
With the above in place, you can call the methods like:
someLibrary.readers.foo
someLibrary.readers.bar
...However, there is some rails black magic going on under the hood here. someLibrary.readers is actually an instance of User::ActiveRecord_Associations_CollectionProxy, and the above methods are being picked up dynamically and appended to ActiveRecord::Associations::CollectionProxy.
Because of this dynamic method definition, it is not possible to override existing Rails methods (such as <<) in this manner. Instead, we'll need to monkey-patch the User::ActiveRecord_Associations_CollectionProxy class directly:
class User
class ActiveRecord_Associations_CollectionProxy
def <<(objects)
super(objects)
# do stuff
end
end
end
If you're looking for a better way of doing this however, I'd recommend using a service object design pattern. You can then encapsulate any more complex/custom logic relating to creating/updating/deleting users, libraries, etc. in a clean and isolated abstraction.
The more established way to do this...
class Library < ActiveRecord::Base
has_many :groups
has_one :reader_group -> {groups.find_by(name: 'readers')}
has_many :readers, through: :reader_group, class_name: 'User', foreign_key: 'user_id'
end
And that's it. You can now do
my_library.readers << another_user

Ruby on Rails inheritance? [duplicate]

How to implement inheritance with active records?
For example, I want a class Animal, class Dog, and class Cat.
How would the model and the database table mapping be?
Rails supports Single Table Inheritance.
From the AR docs:
Active Record allows inheritance by
storing the name of the class in a
column that by default is named "type"
(can be changed by overwriting
Base.inheritance_column). This means
that an inheritance looking like this:
class Company < ActiveRecord::Base; end
class Firm < Company; end
class Client < Company; end
class PriorityClient < Client; end
When you do Firm.create(:name =>
"37signals"), this record will be
saved in the companies table with type
= "Firm". You can then fetch this row again using Company.find(:first, "name
= ‘37signals’") and it will return a Firm object.
If you don‘t have a type column
defined in your table, single-table
inheritance won‘t be triggered. In
that case, it‘ll work just like normal
subclasses with no special magic for
differentiating between them or
reloading the right type with find.
A pretty good tutorial is here: http://juixe.com/techknow/index.php/2006/06/03/rails-single-table-inheritance/
Models:
class Animal < ActiveRecord::Base; end
class Dog < Animal; end
class Cat < Animal; end
Migration:
class CreateAnimals < ActiveRecord::Migration
def self.up
create_table :animals do |t|
# Other attributes...
t.string :type
end
end
def self.down
drop_table :animals
end
end
ActiveRecord supports mapping inheritance hierarchies to a single table(Single-table inheritance. Table would have a column type which stores name of actual class and is used to select other class-specific columns.
It is possible to implement multi-table inheritance mapping, as shown here, but this particular way is not portable, AFAIK.
Delegated Types
One particular way of doing this is via Delegated Types - this makes sense only if you want to paginate all animals together, and to view cats and dogs together, then the delegated type is particularly useful. I also like it because you don't need to have empty columns, for where it doesn't make sense, as is the case with Single Table Inheritance solutions.
# Schema: entries[ id, created_at, updated_at, animalable_type, animalable_id ]
class Animal < ApplicationRecord
delegated_type :animalable, types: %w[ Cat Dog ]
end
module Animalable
extend ActiveSupport::Concern
included do
has_one :animal, as: :animalable, touch: true
end
end
# Schema: cats[ id, selfishness_level ]
class Cat < ApplicationRecord
include Animalable
end
# Schema: dogs[ id, favourite_game, wag_tail_level ]
class Dog < ApplicationRecord
include Animalable
end

Rails metaprogramming: singleton_class and associations

I'm trying to understand metaprogramming in rails, creating validations and associations dynamically on a class.
Let's say I have the following models:
class House < ActiveRecord::Base
belongs_to :owner
end
class Owner < ActiveRecord::Base
end
Now let's say my House model has a boolean attribute is_ownable, and I only want the house to have the owner association if is_ownable==true.
I thought this would work:
class House < ActiveRecord::Base
after_initialize :create_associations
after_find :create_associations
def create_associations
if self.is_ownable
self.singleton_class.belongs_to :owner
end
end
end
Now when I build or find a record of House, the create_associations function gets called with no errors, but then when I try to access the House.first.owner it throws ActiveRecord::AssociationNotFoundError.
Am I misunderstanding something about how AR associations work?
I hate to say it but this is probably a bad idea. Models should have consistent relationships even if they're not utilized on every model. This is not only against the spirit of ActiveRecord or Ruby, but object oriented programming in general. In most cases objects of a particular class are expected to have an identical interface for the sake of consistency and clarity. Adding methods to individual objects is permitted, but there should be exceptional circumstances to justify such a thing.
That's not to say you can't get the effect you want in a more idiomatic way:
class House < ActiveRecord::Base
belongs_to :owner
validates :validate_owner_assignment
protected
def validate_owner_assignment
if (self.ownable? and !self.owner)
self.errors.add(:owner, "is required if ownable")
elsif (!self.ownable? and self.owner)
self.errors.add(:owner, "cannot be assigned if not ownable")
end
end
end
Now assigning owner will trigger a save failure of type ActiveRecord::RecordInvalid if the expectations aren't met.
I'd advocate calling your booleans x and not is_x to reduce verbosity. The vast majority of the time the is_ part is redundant.

Is there a way in MongoMapper to achieve similar behavior as AR's includes method?

Is there a feature equivalent in MongoMapper to this:
class Model < ActiveRecord::Base
belongs_to :x
scope :with_x, includes(:x)
end
When running Model.with_x, this avoids N queries to X.
Is there a similar feature in MongoMapper?
When it's a belongs_to relationship, you can turn on the identity map and run two queries, once for your main documents and then one for all the associated documents. That's the best you can do since Mongo doesn't support joins.
class Comment
include MongoMapper::Document
belongs_to :user
end
class User
include MongoMapper::Document
plugin MongoMapper::Plugins::IdentityMap
end
#comments = my_post.comments # query 1
users = User.find(#comments.map(&:user_id)) # query 2
#comments.each do |comment|
comment.user.name # user pulled from identity map, no query fired
end
(Mongoid has a syntax for eager loading, but it works basically the same way.)

Rails 3 scope only select certain attributes for a has_many relationship

This looks like it should be something pretty easy but I can't seem to get it to work. I have a model with a has_many relationship and I'd like a scope on the parent that allows me to select only certain attributes for each.
An example:
class Bakery < ActiveRecord::Base
has_many :pastries
scope :summary, select([:id, :name, 'some option calling pastries.summary'])
class Pastry < ActiveRecord::Base
belongs_to :bakery
scope :summary, select([:id, :image_url])
I'd like to be able to call something like Bakery.first.summary and get a Bakery model with only the id and name populated and for each pastry in it's pastries array to only have the id and image_url attributes populated.
You could do this, but it won't affect the SQL queries that are made as a result (assuming you're trying to optimise the underlying query?):
class Pastry
...
def summary
{
:id => self.id,
:image_url => self.image_url
}
end
end
class Bakery
...
def summary
pastries.collect {|i| i.summary }
end
end
This would then give you an array of hashes, not model instances.
ActiveRecord doesn't behave how you're expecting with models - it will fetch whatever data it thinks you need. You could look at using the Sequel gem instead, or executing a raw SQL query such as:
Pastry.find_by_sql("SELECT id, name from ...")
But this could give you unexpected behaviour.

Resources