I went through all API documentation of Yii 2.0 to find a way to reverse back to relation class name from a model attribute.
let us suppose that class Customer has a relation
$this->hasOne(Country::className(), ['id' => 'countryId']);
and in a controller function the parameter was the attribute "countryId". How is it possible to detect the class name for the related model
Get the name of the class by removing Id from the end of the variable and capitalize it. But I cannot image any situation where this would be a normal development practice. You can also define am array to make this translation for the model.
You can try to use http://php.net/manual/en/intro.reflection.php to get the names of all the functions and try to guess the name of the relation / model based on the name of the field. If you name your classes and relation fields in a proper name then you should be able to try to again guess the model.
This still feels like a hack, create a function that returns the name of the model based on the field... easiest solution. I know you try to be lazy but this is a hacky way of programming.
I'm not very clear on what data you have to start with here. If you only have a column countryId I am not sure. But say you have the relation name 'country' and the following code in your Customer model:
public function getCountry()
{
return $this->hasOne(Country::className(), ['id' => 'countryId']);
}
This is what I would do:
$relationName = 'country';
$customer = new Customer;
$relation = $customer->getRelation($relationName);
$relationModelClass = $relation->modelClass;
You could look at \yii\db\ActiveQuery::joinWithRelations() for how they do it.
Related
What is a good way to avoid duplication of a class instance when it is created using the __init__() function.
This question is a result of this issue.
Context (using employee class example):
Lets say I have an employee class: __init__(self,name,dept)
I also have a method, employee.info(self) that prints out name and dept of any employee.
However a user could just add an employee by calling a=employee(args..). They could do it multiple times using the same instance variable a, but different employee names.
This will cause issues if they try to print a.info(), as each time a different employee name will be printed.
Is there a better way to do this? I know it is possible to have the __init__() "pass" and define a new method to create an instance.
Expect results:
>>Adam=employee('marketing')
>>Adam.info()
>>Adam works in marketing.
OR
>>a=employee('Adam','marketing')
>>a=employee('Mary','marketing')
>>Error: employee instance with identifier "a" already exists.
>>Use employee.update() method to modify existing record.
Is there a cleaner way of doing it? (as you might guess, I am still learning python).
Or is it good practice to write an explicit function (instead of a class method) to add new employees?
what you try is impossible, because in
a=employee('Adam','marketing')
a is not an object but a variable that points to the object employee('Adam','marketing').
When you do
a=employee('Mary','marketing')
you say to python that now, a must now not point to the object employee('Adam','marketing') but to the object employee('Mary','marketing'). And then, if you have no more variable to reference the object employee('Adam','marketing'), the garbage collector will destroy it.
You must consider that in python all is object, but not the variables that are only references to manipulate objects.
I have been racking my brains over the same problem and have finally managed to figure out a workaround :
Create a dictionary that stores the employee name and the related object like this :
total_emp_dict = {}
Add this inside the def __init__ of the class employee : total_emp_dict[name] = self. This will ensure to add each employee name as key and the object associated will be added as value.
Now create a new function outside & before the employee class is defined, let's call it create_new_emp. It will go like this :
#function to check and avoid duplicate assignment of instances
def create_new_emp(name, dept):
if name in total_emp_dict:
return total_emp_dict[name]
else:
return employee(name, dept)
When creating a any new employee, use this new function : a = create_new_emp("Adam", HR) instead of a = employee("Adam", HR)
Explanation : This function will ensure that duplicate assignment is not done. If "a" is already assigned to "Adam", this function will return object of "Adam" to "a", when called again. But if nothing is assigned to "a", then this function will handover to the attributes (name, dept) to employee class and create a new instance. This instance will then be assigned to "a".
I don't know if this is the best solution for this problem, but honestly this is the only solution I have found so far and it works great for me without much fuss / extra code space. Hope it works for you too! :)
I'm new to Yii and used to Kohana. I have two models:
1. sampleRequests -> (this has many SampleShares)
2. sampleShares
In my case I have already instantiated an object (sampleRequest) and I am trying to get a particular sampleShares based on added criteria.
In Kohana I could do:
$myShare = $this->sampleShares->where('user_id','=',$my_user_id)->find();
It Yii I have tried something similar:
$myShare = $this->sampleShares->find(array(
'condition'=>'user_id:user_id',
'params'=>array(':user_id'=>$my_user_id)));
The previous does not work for me however, the following will work:
$myShare = SampleShare::model()->find(array(
'condition'=>'sample_id=:id AND user_id=:user_id',
'params'=>array(':id'=>$this->id, ':user_id'=>Yii::app()->user->id)));
So my question is, can you grab related models with appended select criteria to the relationships established in Yii (particular with the instance or $this)? (Similar to my Koahana example?) If so how do you do that?
$myShare = SampleShare::model()->findAll(array('condition'=>'sample_id='.$id.' AND user_id='.$user_id));
and relation query example
$myShare = SampleShare::model()->with('sampleRequests')->findAll(array('condition'=>'sample_id='.$id.' AND user_id='.$user_id));
you can find the realtion name in your model file...
And then you can get the related table records using $myShare->sampleRequests
you can set relationship between the tables in your model file, like this:
public function relations()
{
return array(
'category' => array(self::BELONGS_TO, 'CategoryCategory', 'category_id'),
);
}
how to use:
$model=Blog::model()->with('category')->findAll(); //object
Nevermind I figured it out. I just had to replace:
$this->sampleShares->find(
with
$this->with('sampleShares')->find(
Background: Project is a Data Import utility for importing data from tsv files into a EF5 DB through DbContext.
Problem: I need to do a lookup for ForeignKeys while doing the import. I have a way to do that but the retrieval if the ID is not functioning.
So I have a TSV file example will be
Code Name MyFKTableId
codevalue namevalue select * from MyFKTable where Code = 'SE'
So when I process the file and Find a '...Id' column I know I need to do a lookup to find the FK The '...' is always the entity type so this is super simple. The problem I have is that I don't have access to the properties of the results of foundEntity
string childEntity = column.Substring(0, column.Length - 2);
DbEntityEntry recordType = myContext.Entry(childEntity.GetEntityOfReflectedType());
DbSqlQuery foundEntity = myContext.Set(recordType.Entity.GetType()).SqlQuery(dr[column])
Any suggestion would be appreciated. I need to keep this generic so we can't use known type casting. The Id Property accessible from IBaseEntity so I can cast that, but all other entity types must be not be fixed
Note: The SQL in the MyFKTableId value is not a requirement. If there is a better option allowing to get away from SqlQuery() I would be open to suggestions.
SOLVED:
Ok What I did was create a Class called IdClass that only has a Guid Property for Id. Modified my sql to only return the Id. Then implemented the SqlQuery(sql) call on the Database rather than the Set([Type]).SqlQuery(sql) like so.
IdClass x = ImportFactory.AuthoringContext.Database.SqlQuery<IdClass>(sql).FirstOrDefault();
SOLVED:
Ok What I did was create a Class called IdClass that only has a Guid Property for Id. Modified my sql to only return the Id. Then implemented the SqlQuery(sql) call on the Database rather than the Set([Type]).SqlQuery(sql) like so.
IdClass x = ImportFactory.AuthoringContext.Database.SqlQuery<IdClass>(sql).FirstOrDefault();
So my friend and me were discussing on whether to use object or id in DQL
I searched on internet but I couldn't find anything in doctrine documentation to see which is the best practice
So the problem is like this
Imagine you have the Category object
You want to get the products that have the price less than 10 so for better performance you make a DQL in the product repository and pass the $category object
My friend says we should pass $category->getId() and search by Id like this
public function findCheapProducts($categoryId){
return $this->createQueryBuilder('p')
->where('p.category = :categoryId')
->andWhere('p.price < :price')
->setParameters(array(
'categoryId' => $categoryId,
'price' => 10
));
}
But what I say is like this
public function findCheapProducts(Category $category){
return $this->createQueryBuilder('p')
->where('p.category = :categoryId')
->andWhere('p.price < :price')
->setParameters(array(
'categoryId' => $category,
'price' => 10
));
}
I wrote the code here so the error or things doesn't really matter to me here
The objective is to see which is the better way to put a condition by the identifier
I'm mixing both depending on the user input. When I already have the object I pass it, if I only have the ID like after a form submission I pass the ID as it would be nonsense to load the model just to pass it to into the querybuilder. (ofc. you need a proper form validation in this case to avoid wrong IDs)
# student.rb
has_and_belongs_to_many :courses
# course.rb
has_and_belongs_to_many :students
I'm trying to create a scope in the students model that will check if they are enrolled in a course.
The best I've come up with is:
scope :unenrolled, where(Student.courses.count => 0)
But then I get the error message
undefined method `courses'
Anybody offer any suggestions?
Alright then. So here's your code:
scope :unenrolled, where(Student.courses.count => 0)
The first problem here is the thing that's causing the error: You're calling the instance method courses on the class Student. As the name implies, you can only call an instance method on an instance of a class, not on the class itself. For example:
jim = Student.find(123)
jims_courses = jim.courses
But here's the kicker: When you call scope you're in the class context, i.e. the code isn't inside an instance method, so it gets called when your model is first declared. There's no instance at that time so you can't just call courses like you would from within one of Student's instance methods.
But that's kind of moot since you've slightly misunderstood how where works. The argument(s) you give to where are supposed to be conditions that correspond to what you would put after WHERE in an SQL query. For example where(:eye_color => 'brown') will be turned into an SQL WHERE clause like WHERE eye_color = 'brown'. :eye_color => 'brown' is just a Hash with the key :eye_color whose value is 'brown'. Calling a function on the left side of => doesn't make sense unless the function returns the name of a column/attribute in your model that ActiveRecord will understand.
So now let's figure out what you should do. If you were writing an SQL query it would look something like this:
SELECT `students`.*, COUNT(`courses_students`.*) AS `courses_count`
FROM `students`
JOIN `courses_students` ON `students`.`id` = `courses_students`.`student_id`
WHERE `courses_count` = '0'
GROUP BY `courses_students`.`student_id`;
This translates roughly to an ActiveRecord query like this:
Student.joins(:courses). // AR automatically joins courses though courses_students
select('students., COUNT(courses.) AS courses_count').
where('courses_count = 0').
group('id')
And you can plunk that directly into your scope:
scope :unenrolled, joins(:courses).
select('students.*, COUNT(courses.*) AS courses_count').
where('courses_count = 0').
group('courses.course_id')
Note: These queries are a bit off-the-cuff and may require a bit of tweaking. The easiest way to build complicated ActiveRecord queries is by entering them directly into the Rails console until you get the results you want.
Hope that's helpful!