I have a common set of models extracted into a gem with namespaced models as such:
module Gemifive
class Activity < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :activities
end
end
That's in the gem "gemifive". So far, so good.
In an app where I use this gem's models, I can do the following: Gemifive::Activity.where(user_id: user.id). This works fine because that table has a user_id column:
SELECT "gemifive_activities".* FROM "gemifive_activities" WHERE "gemifive_activities"."user_id" = 18`
However, the following does not work work: Gemifive::Activity.where(user: user). This generates the following SQL, which is invalid:
SELECT "gemifive_activities".* FROM "gemifive_activities" WHERE "gemifive_activities"."user" = 18
I can access Gemifive::Activity.first.user just fine, so I know the belongs_to association is working. Why can't I use this ActiveRecord convention?
Gemifive::Activity.where(user: user)
This is invalid ARel, plain and simple. It has nothing to do with your models being namespaced. In the above code, ARel is using the key of the Hash as the column name, and it will be used verbatim. What you can do is
Gemifive::Activity.where(user_id: user)
Related
I'm using the gem discard and also rails-admin. I would like to know which is the best approach to list my active users in a shop in rails admin, taking into consideration the users kept
I've created a method in the model shop:
class Shop < ApplicationRecord
include ShopRailsAdmin
has_many :users
def active_users
users.kept
end
end
In rails admin I'm using:
field :active_users do
label 'Users'
end
But I'm receiving an AssociationRelation instead of a CollectionProxy so in the view, the association looks like
#<User::ActiveRecord_AssociationRelation:0x00007f9c34c1f8e0>
Is there another way to do this so I can avoid defining the method in the model shop?
PD: the tag should have been also discard but it does not exist and I could not create it.
Thanks!
You need to define it as an scoped association
class Shop < ApplicationRecord
include ShopRailsAdmin
has_many :users
has_many :active_users, -> lambda {
where(discarded_at: nil)
}, class_name: 'User'
end
I'm assuming you did not personalize the discard_column.
Rails admin should display them right.
Lately I've grown weary of littering my app/models directory with pointless boilerplate models such as:
Join models that always contain a couple belongs_tos and nothing else.
Status log models that just include SomeConcern and make a couple macro calls.
Revision tracking models that again, just include a concern and call a macro.
These models only exist to support has_many and has_many ... through: associations.
Adding model concerns that generate these models as needed clears simplifies the app/models directory. So instead of:
has_many :model_things
has_many :things, through: :model_things
and a trivial app/models/model_thing.rb that says:
class ModelThing < ApplicationRecord
belongs_to :model
belongs_to :thing
end
I can have a ThingSupport concern with a has_things macro that:
Creates the has_many :model_things association based on the class name and some options to has_things.
Creates the has_many :things, through: :model_things association.
Find or create the Model::Thing (see below for why this name is used) class with a call like:
ModuleUtil.find_or_create(join_model_name) do
Class.new(ApplicationRecord) do
# Set the table name, call belongs_to as needed, call concern methods, ...
end
end
where ModuleUtil.find_or_create is a simple method that uses String#constantize to find the desired module (if it exists) or create it using the block and Object#const_set if it can't be found.
All the model and association names can be built using the usual Rails conventions from the caller's class name and some options to has_things for special cases.
The question is am I playing with fire here? What can go wrong with this sort of chicanery?
One problem that I've already come across is that the model classes that are generated don't exist on their own so they cannot be directly referenced from an ActiveJob (such as a deliver_later mailer). For example, if loading Model creates the ModelThing association model then you can't reference a ModelThing in a mailer argument because ActiveJob won't know that you have to load the Model class before ModelThing exists. However, this can be solved by using Model::Thing instead so that constantize will look for Model (and find it in app/models/model.rb) before trying to find Model::Thing (which will exist because constantize will have just loaded Model which creates Model::Thing). Am I missing something else?
I have no idea if I'm following you or not. So, if this is way off target, please say so and I'll delete.
Focusing in on the join model bit, I also got tired of that flim flam. So, I created a model like:
module ActsAsHaving
class HasA < ActiveRecord::Base
validates :haser_type, :haser_id, :hased_type, :hased_id, presence: true
belongs_to :hased, polymorphic: true
belongs_to :haser, polymorphic: true
acts_as_taggable
def haser=(thing)
self.haser_type = thing.class.name
self.haser_id = thing.id
end
def haser
haser_type.constantize.find_by(id: haser_id)
end
def hased=(thing)
self.hased_type = thing.class.name
self.hased_id = thing.id
end
def hased
hased_type.constantize.find_by(id: hased_id)
end
end
end
I didn't use the built-in accessors and validations because I sometimes use this to join non-AR records (which I grab from remote API services some of which belong to me and some of which don't but that's a longer story).
Anyway, I then wrote an acts_as_having macro that let me do stuff like:
class Person < ActiveRecord::Base
acts_as_having :health_events, class_name: "Foo::Event", tag_with: "health_event", remote: true
acts_as_having :program_events, class_name: "Foo::Event", tag_with: "program_event", remote: true
acts_as_having :email_addresses, :phone_numbers, :physical_addresses
end
Which gives me stuff like:
#person.email_addresses
#person.email_addresses << #email_address
etc...
I can do the inverse like:
class EmailAddress < ActiveRecord::Base
acts_as_had_by :person
end
Which gives me stuff like:
#email_address.person
etc...
Then, I wrapped all that junk up into a gem. Now I rarely create join models unless they have some specific requirements that I can't shoe horn into my acts_as_having bit.
Anyway, I don't know if it's playing with fire or not. I don't even know if I'm making sense or addressing your concept. But, I started my gem about three years ago and I haven't regretted it. So, there's that.
I have the following models
class Business < ActiveRecord::Base
has_and_belongs_to_many :categories
validates_presence_of :category_ids
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :businesses
end
I am setting the relationship through the business creation form, using the category_ids attribute.
I tried using validates_presence_of, however, this is not validating the existence of a category.
I can manipulate the form through the browser, give a non-existing ID for a category. After submitting the form, I get an error:
Couldn't find Category with id=181723
Edit:
Added the following custom validation method, but I am still getting the same error, as if the validation was not being run.
class Business < ActiveRecord::Base
has_and_belongs_to_many :categories
validate :categories_exist
def categories_exist
category_ids.each do |c|
errors.add(:category_ids, :category_doesnt_exist) unless Category.exists? c
end
end
end
There's probably a variety of ways you could achieve this but I'd recommend looking at Custom Validations and ActiveRecord Callbacks.
You can check out the validates_existence gem. This gem has been very useful to me for validating if foreign keys correspond to legitimate parent records. As described in the readme:
This plugin library adds ActiveRecord models a way to check if a
:belongs_to association actually exists upon saving.
I have some issues with the polymorphic associations in Rails 3. My model looks like this:
class Address < ActiveRecord::Base
belongs_to :contactable, :polymorphic => true
end
class OrganisationUnit < ActiveRecord::Base
# some other associations
end
# Subclass of OrganisationUnit
class Company < OrganisationUnit
has_one :address, :as => :contactable
end
Now, when I want to get the Address of a Company, Rails generates the following SQL-Query:
SELECT `addresses`.* FROM `addresses` WHERE (`addresses`.contactable_id = 1021 AND `addresses`.contactable_type = 'OrganisationUnit') LIMIT 1
In my opinion it's wrong, because the contactable_type should be "Company".
Is there any way I can fix this or tell rails that OrganisationUnit is just an abstract base class?
The is an expected behavior. When you link a STI table to a polymorphic association, Rails stores the base class name rather than the inherited class names. The STI type conversion happens after the object lookup by id.
I'm using rails 3, and began my application with ActiveRecord. Now, I have many models, and the relations are starting to get complicated, and some could be more simply expressed with a Document-Oriented structure, so I'd like to try migrating to MongoDB and use Mongoid.
I've always heard that you didn't have to eitheer use all MongoDB or nothing, but that you could use the two in parallel while migrating. I don't see how to go about this from the docs though.
For example, I have:
class User < ActiveRecord::Base
has_many :items
has_many :products, :through => :items
end
class Product < ActiveRecord::Base
has_many :items
end
class Item < ActiveRecord::Base
belongs_to :user
belongs_to :product
# alot of data that fits a hierarchical document-oriented structure
end
I'd like to ideally begin by replacing my Item activerecord model with a Mongoid document, so my items are stored in MongoDB, and my Users and Products can stay in my SQL DB
Thing is, I don't see how to do this. Am I going about this the right way?
Perhaps another alternative is to keep a base AR Item
class Item < ActiveRecord::Base
has_one :mongodb_item ?? # I know this is wrong
end
class MongodbItem
include Mongoid::Document
belongs_to AR_Item ??? # I know this is also wrong
end
Thanks!
What I did was just mock the relationship with methods in each the AR model and the Mongoid model like so.
# visit_session.rb
class VisitSession
include Mongoid::Document
include Mongoid::Timestamps
field :user_id, type: Integer
index({user_id: 1},{name: :user_id_index})
# Mock a belongs_to relationship with User model
def user
User.find(self.user_id)
end
end
# user.rb
class User < ActiveRecord::Base
# Mock a has_many relationship with VisitSession Mongoid model
def visit_sessions
VisitSession.where(user_id: self.id)
end
end
Of course you won't have all the AR methods on VisitSession Mongoid model but you'll at least be able to mock the relationship between the two fairly well.
Hope this helps.
I don't see any reason why you couldn't have both ActiveRecord and Mongoid models in the same application. That being said, I'm almost certain that you'll run into issues if you try to create relationships between your ActiveRecord and Mongoid models.
If your ActiveRecord models are heavily inter-related, but better suited to a document structure, then I would suggest just biting the bullet and converting them all to Mongoid documents. I had to do this recently on a (large-ish) project, and it's significantly less stressful than you would think.
If you have good unit tests for your models, then it should be a total snap. If you don't - write your unit tests first, make sure they pass with ActiveRecord and then start migrating things over to Mongoid.
i created a module for spoofing the relation in active record models.
module MongoRelations
def belongs_to_mongo(name, options = {})
id_name = "mongo_#{name}_id".to_sym
mongo_model = options[:through] || "Mongo::#{name.to_s.camelize}".constantize
define_method(name) do
id = send(id_name)
mongo_model.find(id) if id.present?
end
define_method("#{name}=") do |value|
send("#{id_name}=".to_sym, value.try(:id).to_s)
end
end
end
In my SQL table, I name my mongo relations using the convention mongo_XXX_id, instead of XXX_id
I also namespace all my mongo models under Mongo::
in my active record model
class Foo < ActiveRecord::Base
belongs_to_mongo :XXX
end
which allows
Foo.new.XXX = Mongo.find('123')
Foo.XXX
or
Foo.new.XXX_id = '123'
Foo.XXX
... just for tracking purpose,
I'd like to add something I just found out on this field:
DRY up your SQL+NoSQL Rails projects