Laravel, how to update two tables with a one-to-one relationship from a single form - laravel-5.1

I have these two very simple models:
class Episode extends Model
{
protected $fillable = ['number', 'language_code', 'published_at'];
function content()
{
return $this->hasOne('App\EpisodeContent');
}
}
class EpisodeContent extends Model
{
protected $table = 'episodes_content';
protected $fillable = ['episode_id', 'title', 'description'];
function episode()
{
return $this->belongsTo('App\Episode');
}
}
Where basically every Episode has one content. I could've used a single table, but I thought it could make sense to keep these sets of data separate.
In a form, I'd like to edit the Episode and its content at the same time, but after several attempts I haven't figured out how.
This is what I'm doing:
public function update(Request $request, $id)
{
$rules = [
'number' => 'required',
];
$this->validate($request, $rules);
$episode = Episode::with('content')->findOrFail($id);
$episode->published_at = $request->get('published_at');
$episode->number = $request->get('number');
$episode->content->title = $request->get('title');
$episode->update();
return redirect('admin/episodes');
}
This way, nothing changes in the episodes_content table.
In another attempt, I tried this:
$episode->published_at = $request->get('published_at');
$episode->number = $request->get('number');
$episode->active = $request->get('active');
$episodeContent = new EpisodeContent;
$episodeContent->title = $request->get('title');
$episode->content()->save($episodeContent);
This way, a new episodes_content row is created, while I'd like to update an existing one.

Try this I guess it will work
$episode = Episode::with('content',function($query) use($id){
$query->where('episode_id',$id);
})->where('id',$id);
Here I'm assuming your field name is id in parent table and episode_id in child table. Let me know if it doesn't work.

Related

Save/retrieve data by foreign key (API)

I'm attempting to set up my API for iOS app. This is my first time to use Laravel as a API so here is what I have in my tables:
Cars
Schema::create('cars', function (Blueprint $table) {
$table->increments('id');
$table->string('name')->nullable();
$table->string('age')->nullable();
$table->string('model')->nullable();
$table->string('color')->nullable();
Users
$table->increments('id');
$table->string('name')->nullable();
$table->string('street')->nullable();
$table->string('niehgborhood')->nullable();
$table->string('city')->nullable();
Contract
$table->increments('id');
$table->integer('user_id')->unsigned;
$table->foreign('user_id')->references('id')->on('users');
$table->integer('car_id')->unsigned;
$table->foreign('car_id')->references('id')->on('cars');
Models
protected $table = 'users';
protected $guarded = ['id'];
protected $fillable = ['phone', 'email',
'street','city','niehgborhood'
,'national_id'];
public function cars()
{
return $this->hasMany('App\User');
}
Users
protected $guarded = ['id'];
protected $fillable = ['name', 'age',
'color, model'
];
public function users()
{
return $this->belongsToMany('App\Cars');
}
in my controller I'm familiar with saving requested data
$user = JWTAuth::parseToken()->authenticate();
$user->phone = $request->phone;
$user->city = $request->city;
$user->save();
my goal with this project is to display the data(contracts) saved by iOS app users in my dashboard. For example, users info and the cars they are interested in my table. Can someone help me with what to do query in the controller(not views). Or provide helpful links for projects like this.
Your relation between User and Cars should be many-to-many. Please read the docs to apply this relation properly.
If your relations are in the place then you can do as:
$user = JWTAuth::parseToken()->authenticate();
$cars = $user->cars; // returns collection of cars associated with the user.
For example - In your User model define the following relation:
public function cars()
{
return $this->belongsToMany('App\Car');
}
Update
To save the user and associate cars, you can do it as following:
$user = JWTAuth::parseToken()->authenticate();
$user->phone = $request->phone;
$user->city = $request->city;
$user->save();
$car_ids = $request->car_ids; // it should return an array
$user->cars()->sync($car_ids);
There is more way to store the data. Please read the docs here for more info.

laravel updateOrCreate() with dynamic table

I'm working with project where tables are created all the time depending on user adding new properties, so for my model i have below
class Hotel extends Model
{
public function __construct($hotel)
{
$this->table = $hotel;
}
protected $fillable = ['date', 'sold', 'sold_diff', 'rev', 'rev_diff', 'row', 'date_col', 'sold_col', 'rev_col'];
}
and i can use the table in controller by doing
$hotel_table = new Hotel($table);
but i like to use Model::updateOrCreate() when I'm adding or updating rows in table, and I'm not sure how to do that.
This is the signature for the updateOrCreate function
"static Model updateOrCreate( array $attributes, array $values = array())"
For you to update or create, you can pass the condition that must be met to update the table to the first argument and the values to be updated to the second.
for example.
$primaryKey = isset($request->input('id')) ? $request->input('id') : null;
$myModel = Model::updateOrCreate(['id' => $primaryKey], $request->all());
so with this if id is in the request object the table will be updated but if not a new record will be created.
In Laravel 5.2 you can use simply like this
class Hotel extends Model
{
protect $table = 'hotels'
protected $fillable = ['date', 'sold', 'sold_diff', 'rev', 'rev_diff', 'row', 'date_col', 'sold_col', 'rev_col'];
// protected $guarded = []; you can use this instead of `$fillable` this is for all columns fillable
}
now in your controller you can use
Model::update();
Model::create();

Get and set the current value from the relationship between "maquina" and "emisor"

Related to this topic and this other topic I'm experimenting a issue. This is the SdrivingMaquinForm.class.php code:
class SdrivingMaquinaForm extends BaseSdrivingMaquinaForm {
protected $current_user;
public function configure() {
$this->current_user = sfContext::getInstance()->getUser()->getGuardUser();
unset($this['updated_at'], $this['created_at']);
$this->widgetSchema['idempresa'] = new sfWidgetFormInputHidden();
$id_empresa = $this->current_user->getSfGuardUserProfile()->getIdempresa();
$this->setDefault('idempresa', $id_empresa);
$this->widgetSchema['no_emisor'] = new sfWidgetFormDoctrineChoice(array('model' => 'SdrivingEmisor', 'add_empty' => 'Seleccione un Emisor', 'table_method' => 'fillChoice'));
$this->validatorSchema['idempresa'] = new sfValidatorPass();
$this->validatorSchema['no_emisor'] = new sfValidatorPass();
}
protected function doUpdateObject($values) {
parent::doUpdateObject($values);
if (isset($this['no_emisor'])) {
if ($this->isNew()) {
$sdrivingMaquinaEmisor = new SdrivingMaquinaEmisor();
$this->getObject()->setSdrivingMaquinaEmisor($sdrivingMaquinaEmisor);
} else {
$sdrivingMaquinaEmisor = $this->getObject()->getSdrivingMaquinaEmisor();
}
$sdrivingMaquinaEmisor->setIdemisor($this->values['no_emisor']);
}
}
}
And it works perfectly, if I create a new maquina values are saved correctly, if I edit a existent record once again values are saved correctly and if I delete a record then the relation is deleted too. So the problem is not in actions or method. The problem I'm having is when user select to edit the existent record. Field idempresa and patente (see the schema.yml at first post metioned here) gets theirs values but no_emisor doesn't so every time I want to edit the record I got the select with values, yes, but the selected value isn't the right because I get the add_empty value. How I fix that? Meaning how I assign the default value for the select based on the one existent on the relation between maquina and emisor?
EDIT: working on a possible solution
I'm trying this code:
public function executeEdit(sfWebRequest $request) {
$this->forward404Unless($sdriving_maquina = Doctrine_Core::getTable('SdrivingMaquina')->find(array($request->getParameter('idmaquina'))), sprintf('Object sdriving_maquina does not exist (%s).', $request->getParameter('idmaquina')));
$this->forward404Unless($sdriving_maquina_emisor = Doctrine_Core::getTable('SdrivingMaquinaEmisor')->find(array($request->getParameter('idmaquina'))), sprintf('Object sdriving_maquina_emisor does not exist (%s).', $request->getParameter('idmaquina')));
$this->form = new SdrivingMaquinaForm($sdriving_maquina, $sdriving_maquina_emisor);
}
But then how in the form configure() method I can access to $sdriving_maquina_emisor in order to use form setDefault() method?
EDIT: doUpdateObject($values)
See this is how my doUpdateObject($values) function looks like:
protected function doUpdateObject($values) {
parent::doUpdateObject($values);
if (isset($this['no_emisor'])) {
if ($this->isNew()) {
$sdrivingMaquinaEmisor = new SdrivingMaquinaEmisor();
$this->getObject()->setSdrivingMaquinaEmisor($sdrivingMaquinaEmisor);
} else {
$sdrivingMaquinaEmisor = $this->getObject()->getSdrivingMaquinaEmisor();
}
$sdrivingMaquinaEmisor->setIdemisor($this->values['no_emisor']);
}
}
Where exactly feet the code you leave for doUpdateObject()?
In these situations you always have to do 2 things:
load the default value from the doctrine record object into the form widget
update the doctrine object with the posted value
And most of the times you should use updateDefaultsFromObject and doUpdateObject symmetrically.
To load back the saved values override updateDefaultsFromObject:
// maybe you have to declare it as public if the parent class requires that
protected function updateDefaultsFromObject()
{
parent::updateDefaultsFromObject();
if (isset($this['no_emisor'])
{
$this->setDefault('no_emisor', $this->getObject()->getSdrivingMaquinaEmisor()->getIdemisor());
}
}
// and you can simplify this a little bit as well
protected function doUpdateObject($values)
{
parent::doUpdateObject($values);
if (isset($this['no_emisor']))
{
$this->getObject()->getSdrivingMaquinaEmisor()->setIdemisor($this->values['no_emisor']);
}
}

return table primary key name with tableGateway

I'm trying to create an abstract object for my Table Objects.
Today I have lots of object like: CategoriaTable, FornecedoresTable, etc that implement $this->tableGateway->insert(), $this->tableGateway->update(), etc
I created an TableAbstract that contains most of those functionallities, but I stuck on one problem:
// In CategoriaTable my table id is named cat_id
$this->tableGateway->update($object->getArrayCopy(),array('cat_id' => $object->getId()))
// But in FornecedoresTable my table id is named for_id
$this->tableGateway->update($object->getArrayCopy(),array('for_id' => $object->getId()))
How can I get from tableGateway the id of an table? There is an better way to do what I want?
I guess I could inject the id name in my object but I don't thing this is a good way to do that...
You can create new TableGateway class parameter.(In my case I created $this->primary;)
And if it is not set use Zend\Db\Metadata\Metadata to find it straight from db structure.
<?php
//...
use Zend\Db\TableGateway\AbstractTableGateway;
use Zend\Db\Metadata\Metadata;
class AbstractTable extends AbstractTableGateway
{
protected $primary;
public function getPrimary()
{
if (null === $this->primary) {
$metadata = new Metadata($this->adapter);
$constraints = $metadata->getTable($this->getTable()->getTable())
->getConstraints();
foreach ($constraints AS $constraint) {
if ($constraint->isPrimaryKey()) {
$primaryColumns = $constraint->getColumns();
$this->primary = $primaryColumns;
}
}
}
return $this->primary;
}
}
?>

Enity Framework 4.1 - One trip database update

Let's say I have this code:
class Score
{
public Update(int score)
{
update score but do not call (context.SaveChanges())
}
}
class Foo
{
public DoSomething(int update)
{
Score score = new Score();
score.Update(2);
SomeObj obj = (select object);
obj.Soo = 3;
context.SaveChanges();
}
}
Basically to make it work, I need to explicity provide SaveChanges in method Update. But when I have 4 such methods in row, and 34243 users want to update data, I don't think saving for each one in 4 trips would be a good idea.
Is there way in EF4.1 to delay database update the last moment, in provided example, Or I'm forced to explicity save for each method ?
EDIT:
For clarification. I tried to do not call SaveChanges in external method, and only one time where the changes mu be saved.
I will give an real example:
public class ScoreService : IScoreService
{
private JamiContext _ctx;
private IRepository<User> _usrRepo;
public ScoreService(IRepository<User> usrRepo)
{
_ctx = new JamiContext();
_usrRepo = usrRepo;
}
public void PostScore(int userId, GlobalSettings gs, string name)
{
User user = _ctx.UserSet.Where(x => x.Id == userId).FirstOrDefault();
if (name == "up")
{
user.Rating = user.Rating + gs.ScoreForLike;
}
else if (name == "down")
{
user.Rating = user.Rating - Math.Abs(gs.ScoreForDislike);
}
}
}
And Now:
public PostRating LikeDislike(User user, int postId, int userId, GlobalSettings set, string name)
{
PostRating model = new PostRating();
var post = (from p in _ctx.PostSet
where p.Id == postId
select p).FirstOrDefault();
if (name == "up")
{
post.Like = post.Like + 1;
model.Rating = post.Like - post.Dislike;
}
else if (name == "down")
{
post.Dislike = post.Dislike + 1;
model.Rating = post.Like - post.Dislike;
}
PostVote pv = new PostVote();
pv.PostId = post.Id;
pv.UserId = user.Id;
_ctx.PostVoteSet.Add(pv);
_scoreSrv.PostScore(userId, set, name);
_ctx.SaveChanges();
return model;
}
I this case user rating do not update, Until I call SaveChanges in PostScore
In your example it looks like PostScore and LikeDislike use different context instances. That is the source of your problem and there is no way to avoid calling multiple SaveChanges in that case. The whole operation is single unit of work and because of that it should use single context instance. Using multiple context instances in this case is wrong design.
Anyway even if you call single SaveChanges you will still have separate roundtrip to the database for each updated, inserted or deleted entity because EF doesn't support command batching.
The way to delay database update to the last moment is by not calling SaveChanges until the last moment.
You have complete control over this code, and if your code is calling SaveChanges after every update, then that needs changing.
This not really solves my entire problem, but at least I can use single instance of Context:
With Ninject:
Bind<JamiContext>().To<JamiContext>().InRequestScope();
And then constructor:
private JamiContext _ctx;
private IRepository<User> _usrRepo;
public ScoreService(IRepository<User> usrRepo, JamiContext ctx)
{
_ctx = ctx;
_usrRepo = usrRepo;
}

Resources