Injecting relationships in DBIx::Class - perl

I have a handful of DBIx::Class::Core objects that model various database tables.
For some of those models (those that have a 'queue' column), I have another class inject subs (basically, to 'move' the model object along it's queue states).
I'd like to also have that class inject has_many relationships ala
class($name)->has_many('queue_history','MySchema::Result::QueueHistory',
{ 'foreign.record_id'=>'self.id' },
{ where => { type => $name }} );
but I can't seem to get the relationships to register properly (keep getting "No Such Relationship" errors - however, when calling the relationship method on the sources provides back the relationship).
Any clues as to what's wrong?

After some digging around, the following works:
$class = $schema->class($name)->has_many('queue_history','MySchema::Result::QueueHistory',
{ 'foreign.record_id'=>'self.id' },
{ where => { type => $name }} );
$schema->unregister_source($name);
$schema->register_class($name,$class);
The key being the unregister/register methods in order to generate all the appropriate other methods that get added by having a new has_many relationship.

Related

DbiX::Class / Creating tree of test data without persisting

I'm using DBIx::Class as or mapper for my Perl project. When it comes to generating test data I'm using DBIx::Class::ResultSet::new to create new entities in memory. In order to link entities with relationships I use set_from_related. This works absolutely flawless until I try to set the value(s) for a has_many relationship. Pseudo example:
# Table 'AUTHOR' has
# one-to-one (belongs_to) relationship named 'country' to table 'COUNTRY'
# one-to-many (has_many) relationship named 'books' to table 'BOOK'
my $s = Schema::getSchema();
my $author = $s->resultset('Author')->new({ name => 'Jon Doe', year_of_birth => 1982 });
my $country = $s->resultset('Country')->new({ name => 'Germany', iso_3166_code => 'DE' });
my $book = $s->resultset('Book')->new({ title => 'A star far away', publishing_year => 2002 });
# Now let's make 'em known to each other
$author->set_from_related('country', $country);
$author->set_from_related('books', $book);
# At this point
# $author->country is defined
# $author->books->first is undef <<<---- Problem
I cannot find a suitable method in the DBIx::Class::Relationship::Base documentation. The one closest to what I need is add_to_$rel but this method creates (persists) the entities. This is not an option for me as some of the entites used in my project don't belong to me (no write permission).
Does anyone have an idea how to add entities in memory for a has_many relationship ?
That's not possible as newly created result objects don't have their primary key column(s) populated until they are persisted to the database.
What you possibly want is to use multi-create and run that inside a transaction.

Apply where condition to related models in polymorphic relation laravel 8

I am facing an issue in Polymorphic relations where I can not make whereHas to work. The relation code is working fine to return the related models, but need to apply where conditions to the relations of the models connected with the polymorphic relations?
My polymorphic relation is morph mapped by two models. eg: Events and Meetings both models have their own relations to store their assignees (Belongs to many) I need to apply where condition to assignees relation to fetch the required data, In my polymorphic relation, I got the data with assignees but not with the condition applied.
class MainEvant extends Model
{
public function eventheadable()
{
return $this->morphTo('eventhead', 'event_type_id', 'event_id')
->morphWith([ Events::class=>['assignee'],
Meeting::class=>['assignee'], ]);
}
}
I have also faced a similar issue recently, I have solved it by this way, not sure about the quality of the code
In your Controller ,
MainEvant::with('eventheadable')->whereHasMorph('eventheadable',Events::class,function ($query) {
$query->whereHas('assignee', function ($q) {
$q->where(); // Your Condition
});
})->orwhereHasMorph('eventheadable', Meeting::class,function ($query) {
$query->whereHas('assignee', function ($q) {
$q->where(); // Your Condition
});
})->get();
Also Check this out in the official doc,
Docs: https://laravel.com/docs/8.x/eloquent-relationships#querying-morph-to-relationships

Laravel Backpack: Show a Many to Many relationship in Show Operation

Couldn't find this in the docs.
Is there any standard way, without creating a custom widget, or overriding the view template, to show a Many to Many relationships in a CRUD's showOperation in Backpack for Laravel? If the answer is NO, what would be your approach to implement it?
Let's say I have a Course Model, and a User model, and there is a Many to Many between both
class Course extends Model
{
public function students()
{
return $this->belongsToMany(User::class, 'course_students');
}
}
class User extends Model
{
public function courses()
{
return $this->belongsToMany(Course::class, 'course_students');
}
}
In the Show Operation for the Course. How do I show a Table with all students?
Indeed, you can use the relationship column for this
Excerpt:
Output the related entries, no matter the relationship:
1-n relationships - outputs the name of its one connected entity;
n-n relationships - enumerates the names of all its connected entities;
Its name and definition is the same as for the relationship field
type:
[
// any type of relationship
'name' => 'tags', // name of relationship method in the model
'type' => 'relationship',
'label' => 'Tags', // Table column heading
// OPTIONAL
// 'entity' => 'tags', // the method that defines the relationship in your Model
// 'attribute' => 'name', // foreign key attribute that is shown to user
// 'model' => App\Models\Category::class, // foreign key model
],
Backpack tries to guess which attribute to show for the related item.
Something that the end-user will recognize as unique. If it's
something common like "name" or "title" it will guess it. If not, you
can manually specify the attribute inside the column definition, or
you can add public $identifiableAttribute = 'column_name'; to your
model, and Backpack will use that column as the one the user finds
identifiable. It will use it here, and it will use it everywhere you
haven't explicitly asked for a different attribute.

Can I join the column names with search_related in DBIx?

I have a DBIx Class schema where I have;
A Device that has many Interfaces
An Interface has many Rules Applied
Each Rule has many Rule Entries.
I want to search for all of the Rule Entries for a Particular device name and Rule Name.
I am still learning DBIx so I don’t know if this is even the most efficient way.
I am doing this like so;
my $rs = $self->search( { devicename => ‘DeviceA’ } )->search_related('interfaces')->search_related(’Rules’, { rulename => ‘RuleA’ } )->search_related(‘RuleEntries’, {},
{ columns => [qw/source destination port/], result_class => 'DBIx::Class::ResultClass::HashRefInflator'} );
What I am trying to do is get the ‘RuleName’ as a column of my result set.
at the moment I’m getting all of the Rule Entries for DeviceA with a RuleName on an interface called RuleA, The columns returned are
‘source destination port’.
I want this to look like
‘rulename source destination port’
As you are already restricting the rule name it doesn't make sense to query it from the database.
Besides that you should always search for objects of the type you want to get back, in your case that's rule entries:
my $rs = $schema->resultset('Rule_Entries')->search({
'rel_device.name' => 'DeviceA',
'rel_rule.name' => 'Rule',
},{
columns => [ 'rel_rule.name', 'me.source', 'me.destination', 'me.port' ],
join => { rel_rule => { rel_interface => 'rel_device' }},
});
It seems your doing something very similar what I do: storing firewall rules. You might want to have the rule directly related to the device and the interface being an optional attribute of the rule because some vendors don't have interface specific rules (Checkpoint).

Rethinking relational many-to-many relationships for MongoDB

I am just starting a new Rails 3 project using Mongoid ORM for MongoDB. There is just one thing I can not get my head around, and that is how to effectively have a many-to-many relationship. Now there is a good chance that I may be approaching this problem wrong, but as far as I know, there is at least two containers in my project that need a many-to-many relationship. I would prefer to treat both models as "first class" models and allocate each with its own container.
This is the simplest way I can think to structure my many-to-many relationship:
// Javascript pseudo modeling
// -------------------- Apps
{
app: {
_id: "app1",
name: "A",
event_ids: ["event1","event2"]
}
}
{
app: {
_id: "app2",
name: "B",
event_ids: ["event1"]
}
}
// -------------------- Events
{
event: {
_id: "event1",
name: "Event 1",
}
}
{
event: {
_id: "event2",
name: "Event 2",
}
}
As far as I can tell this is the minimum amount of information need to infer a many-to-many relationship. My assumption is that I might have to have a map reduce procedure to determine what apps belong to an event. I would also have to write post commit/save hooks on Event to update App.event_ids if an app is added to or removed from an event model.
Am I on the right track here? If someone has any Mongoid or Mongomapper code examples of a many-to-many relationship working, could you please share.
Your structure can work and you don't need a mapreduce function to determine what apps belong to an event. You can query the app collection on an eventid. You can index field collection.event_ids.
If you don't want to search apps on an eventid but on a event name, you will need to add that event name to the app collection (denormalization). That means that you also have to update the app collection when the name of an event changes. I don't know if that happens very often?
You often have to denormalize when you use MongoDB, so you don't store the minimal amount of information but you store some things "twice".
I was able to implement this design using Mongoid. I wrote extensive tests and I was able to get my solution working; however, I am not satisfied with my implementation. I believe that my implementation would be a difficult to maintain.
I'm posting my non-elegant solution here. Hopefully, this will help someone with the start of a better implementation.
class App
include Mongoid::Document
field :name
references_one :account
references_many :events, :stored_as => :array, :inverse_of => :apps
validates_presence_of :name
end
class Event
include Mongoid::Document
field :name, :type => String
references_one :account
validates_presence_of :name, :account
before_destroy :remove_app_associations
def apps
App.where(:event_ids => self.id).to_a
end
def apps= app_array
unless app_array.kind_of?(Array)
app_array = [app_array]
end
# disassociate existing apps that are not included in app_array
disassociate_apps App.where(:event_ids => self.id).excludes(:id => app_array.map(&:id)).to_a
# find existing app relationship ids
existing_relationship_ids = App.where(:event_ids => self.id, :only => [:id]).map(&:id)
# filter out existing relationship ids before making the new relationship to app
push_apps app_array.reject { |app| existing_relationship_ids.include?(app.id) }
end
def push_app app
unless app.event_ids.include?(self.id)
app.event_ids << self.id
app.save!
end
end
def disassociate_app app
if app.event_ids.include?(self.id)
app.event_ids -= [self.id]
app.save!
end
end
def push_apps app_array
app_array.each { |app| push_app(app) }
end
def disassociate_apps app_array
app_array.each { |app| disassociate_app(app) }
end
def remove_app_associations
disassociate_apps apps
end
end