Active Model Serializer with Ember - how to add an element to the collection - ember.js

I have 2 models Country and Language linked with HABTM relation.
I'm using Rails API with ActiveModelSerializer and Ember JS as frontend.
So how is it possible to add a new language to country.languages collection ?
On the Ember side I'm trying to add a new language as follows:
#router
actions: {
saveLanguage(language) {
let controller = this.get('controller');
let country = controller.get('aCountry');
country.get('languages').pushObject(language);
country.save();
}
}
This calls CountriesController#update action in Rails.
Here is how I deserialize params hash in Rails controller:
#countries_controller.rb
def country_params
ActiveModelSerializers::Deserialization.jsonapi_parse!(params)
end
And here is what it returns:
{:code=>"BE", :name=>"BELGIUM", :modified_by=>"XXX", :id=>"5", :language_ids=>["374", "231", "69"]}
So I'm getting all I need:
country ID => id=5
languages IDS => both existing ones (2) and a new one.
How to properly update the country ? Thank you.

I figured out hot to add/delete an item to/from a association.
So on Ember side it looks like that:
actions: {
deleteLanguage(language) {
let controller = this.get('controller');
let country = controller.get('aCountry');
country.get('languages').removeObject(language);
country.set('modifiedBy', this.get('currentUser.user').get('username'))
country.save();
},
saveLanguage(language) {
let controller = this.get('controller');
let country = controller.get('aCountry');
country.get('languages').pushObject(language);
country.set('modifiedBy', this.get('currentUser.user').get('username'))
country.save();
}
And on the Rails side everything happens in the CountriesController:
class CountriesController < ApplicationController
...
def update
if #country.update(country_params)
json_response #country
else
json_response #country.errors, :unprocessable_entity
end
end
private
def find_country
#country = Country.includes(:languages).find(params[:id])
end
def country_params
ActiveModelSerializers::Deserialization.jsonapi_parse!(params,
only: [
:id,
:modified_by,
:languages
])
end
end
Sure, I'll have to add some errors handling on Ember side just to display a confirmation of the update or the errors.
Method json_response is just my custom helper defined as follows in concerns for controllers:
module Response
def json_response(object, status = :ok, opts = {})
response = {json: object, status: status}.merge(opts)
render response
end
end
Hope this helps. You can find more about mode relations in Ember Guide.

Related

Ember - create default record if model is empty

I have a simple User model, with two fields: name and user_id
When the app starts, I want to check if my User model contains at least one record - if not, I want to create a default "admin" user automatically.
I have the following code in my application route:
model() {
let user = this.store.findAll('user'); // find all users
let record = user.then(function(result) {
let first = result.get('firstObject'); // check if we at least one user
if (first) { // if true, then we have at least one user
let user_id = first.get('user_id'); // get the first user's id
// do some more stuff here
}
else {
console.log("no record found"); // no user found, let's create our default user
let user = this.store.createRecord("user", { // <-- THIS DOESNT WORK - where can I create my record?
user_id: 1,
name: 'admin'
});
}
});
}
This code works, except that I can't call this.store inside my promise function, since this now belongs to the function.
Would it be better to create an action, and then call that action in my else statement? But then, how would I trigger that action from the model itself?
Any ideas would be helpful at this point. It feels like I'm misunderstanding some fundamental law of Ember (again).
SOLUTION:
Annnnd it was missing an arrow.. this seems to preserve the this context, through some dark ECMAscript magic
let uniqueid = records.then(function(result) { // this returns an error (TypeError: this is undefined)
let uniqueid = this.store.createRecord("uniqueid", {
user_id: 1,
name: 'admin'
});
}
let uniqueid = records.then((result) => { // this works!
let uniqueid = this.store.createRecord("uniqueid", {
user_id: 1,
name: 'admin'
});
}
Thanks #sheriffderek !
I think that this mystery is a JavaScript mystery more than an Ember one.
jsFiddle just broke... and I'm giving up on my example BUT... I think that if you just use es2015 arrow functions, they won't rebind the 'this'
something
.then( (response)=> {
// ?
})
;
OR you can do a pre 2015 style var context = this; somewhere in the outer scope and then use that in the inner scope. Give it a try! : )

ember: waiting until instance-initializer completes before doing anything else

I have an instance-initializer (below) for a shopping cart. The cart is an ember-data model because I am persisting to the back end. I am wanting all instances of my ember app to have a cart ID assigned before doing anything else.
I know the ideal is async, but item prices on the site are dependent on the cart. I am finding it would be easier to handle both the front end and the back end if I can depend on the cart always existing.
How can I tell ember to not do anything else until the cart instance-initializer completes and the ember-data cart model resolves?
My instance initializer:
export function initialize(appInstance) {
let CartService = appInstance.factoryFor('service:cart');
let cart = CartService.create();
// check if there's a cart id present in localStorage
if (window.localStorage) {
if (window.localStorage.getItem('cart')) {
//previous cart id found
cart.loadCart(window.localStorage.getItem('cart'))
} else {
// no LS cart found, create a new one
cart.createCart();
}
}
appInstance.register('cart:main', cart, { instantiate: false });
appInstance.inject('route', 'cart', 'cart:main');
appInstance.inject('controller', 'cart', 'cart:main');
appInstance.inject('component', 'cart', 'cart:main');
}
export default {
name: 'cart',
initialize
};
It handles two scenarios: a cart exists in localStorage, or no cart exists, create a new one. Those two methods are:
// cart service methods
loadCart(jsonCart) {
// called from instance-initializer when a previous cart is found in localStorage
let incomingCart = JSON.parse(jsonCart); // the raw cart in LS
this.get('store').findRecord('cart', incomingCart.cartid).then(cart => {
cart.setProperties(incomingCart);
cart.save();
set(this, 'cartObj', cart);
});
},
createCart() {
// called from instance-initializer when no prev cart is found
let cart = this.get('store').createRecord('cart');
cart.save().then(response => {
window.localStorage.setItem('cart', JSON.stringify(response));
});
set(this, 'cartObj', cart);
}
I've the same issue at these days. I've found these alternatives:
Using application initializers (not application instance initializer): Application initializers have deferreadiness method
to defer the readiness of the application. I think this is not applicable for my situation, because my services are not initialized yet.
Using sync remote call: Browsers warn about this. It is bad for user experience.
Waiting async operation in application route: The application route's model hook will return a promise that will be satisfied when all async opearions are finished. Shown in this sample twiddle.
I think I'll prefer the third one. But I'm open for new ideas.

Finding a record to edit with logic in Rails

I am having trouble with the edit method on my Rail app. It was working until I started adding some logic to it.
I have a view with a link to the controller that should search the db and if a record exists for that user and that fixture redirects to the edit action and allow the user to edit the record if not then it redirects to the new action.
The action coming in is teamentry.
before_action :find_submisionedit, only: [:edit]
def teamentry
#venue = params[:fixture]
if (Submission.exists?(:user_id => current_user.id)) && (Submission.exists?(:p1_venue => params[:fixture]))
redirect_to action: "edit" , fixture: #venue
else
redirect_to action: "new" , fixture: #venue
end
end
The new action works fine. The edit throws up errors.
I am trying to use a before action.
def find_submisionedit
#submission = Submission.where(user_id = current_user.id) && (p1_venue = params[:fixture])
end
I am getting a no method error.
Im guessing this is because the before action is not finding the correct information?
You have the wrong syntax. It should be:
#submission = Submission.where(user_id: current_user.id, p1_venue: params[:fixture])
You could also apply this same logic in your teamentry method:
if Submission.where(user_id: current_user.id, p1_venue: params[:fixture]).any?
redirect_to action: "edit" , fixture: #venue
#...

RSpec/Rails post test issue with post data

Rails: 4.1.7
RSpec-rails version: 3.1.0
I am trying to write a request spec to test the create action for my BlogPost model. RSpec doesn't seem to like the data params that I am trying to pass in because I keep seeing the following error when running the test:
ActiveRecord::UnknownAttributeError:
unknown attribute: blog_post
RSpec code:
require 'rails_helper'
RSpec.describe BlogPost do
let!(:admin_user) { Fabricate(:admin_user) }
let!(:blog_post) { Fabricate(:blog_post) }
before { login(admin_user.email, admin_user.password) }
describe 'POST /admin/blog_posts' do
before do
post admin_blog_posts_path, blog_post: {
body: 'body text',
title: 'title text',
cover_image: '/assets/post.png',
summary: 'cool post bruh',
live_demo_url: 'livedemo.com',
live_demo_url_text: 'click here',
github_source: 'github.com/awesome'
}
end
it 'should redirect to the blog posts index page' do
expect(response).to redirect_to(admin_blog_posts_path)
follow_redirect!
end
end
end
There is something about using the word blog_post it doesn't seem to like. Because I tried changing it to an arbitrary word like so and the error went away:
post admin_blog_posts_path, someresource: {
title: 'title text'
}
Also I have a put request spec, which is also using blog_post and that works fine:
describe 'PUT /admin/blog_posts/:id' do
before do
put admin_blog_post_path(blog_post.id), blog_post: {
title: 'My new title'
}
end
...
end
So I'm not really sure why RSpec doesn't like my post admin_blog_posts_path, blog_post ... syntax. Any ideas?
Noob status over here.
In my controller create action I had:
#blog_post = BlogPost.new(permitted_params)
It was raising the error because I passed in the entire params so it read blog_post as an attribute for the BlogPost resource.
The fix: #blog_post = BlogPost.new(permitted_params[:blog_post])

Ember-data - "Attempted to handle event `deleteRecord` on *record* while in state root.deleted.saved."

I'm trying to use ember-data within my app to manage follower/following relations. I'm having an issue where if the user hits the toggle on/off ember throws "Attempted to handle event deleteRecord on while in state root.deleted.saved." Any one run into this before?
Action code below
actions: {
follow: function(model){
var component = this;
var store = this.get('targetObject.store');
var session = this.get('targetObject.session');
this.set('isFollowed', true)
/* Follower Side */
Ember.RSVP.hash({
profile: store.find('profile', model),
follower: session.get('currentUser.profile')
}).then(function(rsvp){
var follower = store.createRecord('follower', {
profile: rsvp.profile,
follower: rsvp.follower,
status: 'approved'
});
var followed = store.createRecord('followed', {
profile: rsvp.follower,
followed: rsvp.profile,
status: 'approved'
});
followed.save().then(function(savedFollowed){
rsvp.follower.get('followeds').addObject(savedFollowed);
});
follower.save().then(function(savedFollower){
rsvp.profile.get('followers').addObject(savedFollower);
});
})
},
unfollow: function(model){
var component = this;
var store = this.get('targetObject.store');
var session = this.get('targetObject.session');
this.set('isFollowed', false)
/* Remove Follower Side */
component.set('isFollowed', false)
Ember.RSVP.hash({
profile: store.find('profile', model),
follower: session.get('currentUser.profile')
}).then(function(rsvp){
/* Delete Follower Side */
var follower_id = rsvp.follower.get('id');
rsvp.profile.get('followers').forEach(function(item){
if(follower_id == item.get('followLink')){
item.destroyRecord();
}
})
var profile_id = rsvp.profile.get('id');
rsvp.follower.get('followeds').forEach(function(item){
if(profile_id == item.get('followLink')){
item.destroyRecord();
}
})
})
}
}
UPDATE
I resolved the issue - thank you GJK for the response. For those who run into this issue - because I was adding the record to the parent models "hasMany" relation manually using "addObject" - when I deleted the record, I also needed to remove it from that relation so that it didn't exist in the parents "hasMany" and this come up in the delete loop again. Long story short the solution was to add 'removeObject(item)' i.e...
item.destroyRecord();
rsvp.follower.get('followeds').removeObject(item);
item.destroyRecord();
rsvp.profile.get('followeds').removeObject(item)
root.deleted.saved means that your model has already been deleted, and the change has been persisted to the server. My guess would be that followers and followeds are sets of users that are not necessarily disjoint.