Destroying child record destroys parent - ruby-on-rails

EDIT: I solved my problem. Instead of a type 'DELETE' AJAX call, I used a type 'POST' call with the {:method: 'DELETE'} as its data value. This seemed to do the trick.
I'm trying to allow deletion of a child element in Rails 4 via an AJAX request, and I'm having this issue. I have a Badge model which has many BadgeElements. The BadgeElements controller contains a destroy method which I know works because in my Show view, the elements are properly being deleted. It's when I try to delete them in my JavaScript file with AJAX that I'm having trouble. Here's my code:
function deleteElement(id) {
var path_root = $('form').attr('action');
var delete_path = path_root + "/badge_elements/" + id;
$.ajax({
url: delete_path,
type: 'DELETE'
});
$('form').load(path_root + "/edit form");
}
The delete_path attribute evaluates to /badges/[badge_id]/badge_elements/[id], where [id] is the id of the specific element in question. When this function is executed, I get a 400 error at the path ending in /badges, which tells me the badge itself is being deleted. I can confirm this by reloading the page and getting the error "Could not find Badge with badge_id = 4", etc. For reference, here's the model and destroy method for BadgeElement:
badge_element.rb:
class BadgeElement < ActiveRecord::Base
belongs_to :badge
belongs_to :font
scope :front, -> {where(side:"front")}
scope :back, -> {where(side:"back")}
validates :font_id, presence: true
end
in badge_elements_controller.rb:
def destroy
badge = Badge.find(params[:badge_id])
badge_element = BadgeElement.find(params[:id])
badge_element.destroy
redirect_to badge_path(badge)
end
Does anyone know what's going on? Thanks.

I've just had this problem.
If your controller has a redirect_to and no sensitivity to the format of the request it will redirect to badge_path(badge) as instructed but the method will still be DELETE, thereby deleting the parent as well as the child.
I think you'll just need a respond_to block like so
def destroy
...
respond_to do |format|
format.html { redirect_to badge_path(badge) }
format.js { }
end
end

Related

Checking record before update

Goal: To check if a record has already been updated and either allow or not allow the record to be updated if it already has been.
This is in case a buyer is on a page that doesn't have updated information and attempts to cancel an order once it's already been completed.
I have the following code, which works but also doesn't work correctly:
private
def prevent_order_update
#order = Order.find(params[:id])
if ( #order.order_status[2] || #order.order_status[3] )
redirect_to #order, notice: "Your request status for Order:#{#order.id} has already been updated."
end
end
with:
before_action :prevent_order_update, :only => [:update]
This works, but also "works" if the :order_status is 1, which is shouldn't.
I only want a block in the update IF the order status is anything but 1.
The order status is from a model enum of 1,2,3.
I have also tried using:
if ( #order.order_status[2] || #order.order_status[3] ) && #order.order_status_previously_changed?
which completely blocks the prevent_order_update from working all together.
And:
( #order.order_status[2] || #order.order_status[3] ) != #order.order_status[1]
Which then blocks my update method all together and still gives me the prevent_order_update method notice when the order status is 1
#order.order_status is corresponding to string when it comes to rails enums.
In your case, say #order.order_status is charged. When you execute #order.order_status[2] it actually produces a which is the third item of charged string. In this case the comparison always returns true.
So try the following code:
def prevent_order_update
#order = Order.find(params[:id])
if ( #order.charged? || #order.canceled? )
redirect_to #order, notice: "Your request status for Order:#{#order.id} has already been updated."
end
end
You can use aasm gem, so that you no need any before_action. You can solve it in model level using aasm transitions
https://github.com/aasm/aasm

what var type to dynamically access Model's attribute from another controller? (Rails 4.2)

Goal: dynamically update another Model's properties (Tracker) from Controller (cards_controller.rb), when cards_controller is running the def update action.
Error I receive : NameError in CardsController#update, and it calls out the 2nd last line in the
def update_tracker(card_attribute) :
updated_array = #tracker.instance_variable_get("#{string_tracker_column}")[Time.zone.now, #card.(eval(card_attribute.to_s))]
Perceived problem: I have everything working except that I don't know the appropriate way to 'call' the attribute of Tracker correctly, when using dynamic attributes.
The attribute of the Tracker is an array (using PG as db works fine), I want to
figure out what property has been changed (works)
read the corresponding property array from Tracker's model, and make a local var from it. (works I think, )
push() a new array to the local var. This new array contains the datetime (of now) and, a string (with the value of the updated string of the Card) (works)
updated the Tracker with the correct attribute.
With the following code from the cards_controller.rb
it's the if #card.deck.tracked in the update method that makes the process start
cards_controller.rb
...
def update
#card = Card.find(params[:id])
if #card.deck.tracked
detect_changes
end
if #card.update_attributes(card_params)
if #card.deck.tracked
prop_changed?
end
flash[:success] = "Card info updated."
respond_to do |format|
format.html { render 'show' }
end
else
render 'edit'
end
end
...
private
def detect_changes
#changed = []
#changed << :front if #card.front != params[:card][:front]
#changed << :hint if #card.hint != params[:card][:hint]
#changed << :back if #card.back != params[:card][:back]
end
def prop_changed?
#changed.each do |check|
#changed.include? check
puts "Following property has been changed : #{check}"
update_tracker(check)
end
end
def update_tracker(card_attribute)
tracker_attribute = case card_attribute
when :front; :front_changed
when :back; :back_changed
when :hint; :hint_changed
end
string_tracker_column = tracker_attribute.to_s
#tracker ||= Tracker.find_by(card_id: #card.id)
updated_array = #tracker.instance_variable_get("#{string_tracker_column}")[Time.zone.now, #card.(eval(card_attribute.to_s))]
#tracker.update_attribute(tracker_attribute, updated_array)
end
Edit: For clarity here's the app/models/tracker.rb:
class Tracker < ActiveRecord::Base
belongs_to :card
end
Your use of instance_variable_get has been corrected, however this approach is destined to fail because ActiveRecord column values aren't stored as individual instance variables.
You can use
#tracker[string_column_changed]
#card[card_attribute]
To retrieve attribute values by name. If you want to get an association, use public_send. The latter is also useful if there is some accessor wrapping the column value (eg carrierwave)
From your error it seem your issue is this:
#tracker.instance_variable_get("#{string_tracker_column}")
evaluates to this after string interpolation:
#tracker.instance_variable_get("front_changed")
which is incorrect use of instance_variable_get. It needs an # prepended:
#tracker.instance_variable_get("#front_changed")
Seems like using instance_variable_get is unnecessary, though, if you set attr_reader :front_changed on the Tracker model.

Rails - Updating Boolean Attribute in a model on Create

I'm creating an app that lets users purchase items from an online store. I followed the RailsCasts episodes, and built my OrdersController like so.
def create
#order = current_cart.build_order(order_params)
#order.ip_address = request.remote_ip
if #order.save
if #order.purchase
Item.where(email: Order.last.email).last.purchased == true
PurchaseMailer.confirmation_email(Item.last.email).deliver
flash[:notice] = "Thanks for your purchase"
redirect_to root_path
else
flash[:danger] = "Something was wrong"
redirect_to :back
end
else
render :action => 'new'
end
end
I recently decided to add an attribute to my items, which says whether or not they've been purchased or not. Items in the cart have not yet been purchased. I created a migration, giving all items a purchased attribute, that is a boolean.
By default, items are not purchased, so the default value is false.
class AddPurchasedToItem < ActiveRecord::Migration
def change
add_column :items, :purchased, :boolean, :default => false
end
end
That's why I added this line of code to my Orders#Create action.
Item.where(email: Order.last.email).last.purchased == true
Here I was setting the value of purchased from false to true. However, when I load up rails console
Item.last.purchased
=> false
It looks like the value still isn't being stored
As another response points out, you're using the == to assign a value, which isn't right. You need = instead.
And you have to save an item after you assign a value to it.
An example:
conditions = {email: Order.last.email} # using your conditions
item = Item.find_by(conditions)
item.purchased = true
item.save # this is what you're missing
Item.find(item.id).purchased # will be true
Another way to update is the following:
item.update_attribute(:purchased, true)
Yet another way is to call update_all on the ActiveRecord::Relation object like so:
# update all items that match conditions:
Item.where(conditions).update_all(purchased: true)
Which method you choose may depend on the scenario as update_all doesn't run the callbacks you specify in the model.
In your case however, all you're missing is the item.save line.
Item.where(email: Order.last.email).last.purchased == true
You're using a == operator to try to assign a value. Try using = instead.

Ember remove record on invalid server validation

I am trying to create a record and on the server side I am using rails. The rails validations are failing and I am returning a 422 status code but when I delete it in the becameInvalid callback, it doesn't get removed from the template. It just shows a blank entry.
When it is waiting for the server to load it is just showing the name, which is expected.
Ember Model code
App.Job = DS.Model.extend({
name : DS.attr("string"),
user : DS.belongsTo("App.User", {embedded : "load"}),
plans : DS.hasMany("App.Plan", {embedded : "load"}),
shares : DS.hasMany("App.Share", {embedded : "load"}),
becameInvalid : function(data){
this.deleteRecord();
}
});
Ember controller call
PlanSource.Job.createRecord({"name" : name});
job.save();
Rails create method
def create
if can? :create, Job
#job = Job.new(name: params["job"]["name"], user_id: current_user.id)
if !#job.save
render :json => {job: #job}, status: :unprocessable_entity
return
end
if
render :json => {:job => #job}, include: [:plans, :user, :shares => {except: :token, include: [:user, :job]}]
else
render_no_permission
end
else
render_no_permission
end
end
My question is what is the best way to handle server side validation errors. I don't want to try to resubmit the record, I just want to delete it. I was looking for something to make Ember wait for server response but found nothing.
This method isn't working because it causes undefined errors down the model pipeline after deleting.
My question is what is the best way to handle server side validation errors.
Not sure there is one best way. Depends on what you want to happen in your UI. Typically you will want to let the user know that the record was not saved and present some information what went wrong.
I don't want to try to resubmit the record, I just want to delete it.
OK. If the record is new, delete does not really make sense but probably what you want to do is rollback the transaction? Try this.transaction.rollback() or this.rollback(). For example:
App.Job = DS.Model.extend({
name : DS.attr("string"),
user : DS.belongsTo("App.User", {embedded : "load"}),
plans : DS.hasMany("App.Plan", {embedded : "load"}),
shares : DS.hasMany("App.Share", {embedded : "load"}),
becameInvalid : function(data){
this.transaction.rollback();
}
});
See: How to deleteRecord when it was never committed on the backend?
I was looking for something to make Ember wait for server response but found nothing.
model.save() returns a promise. That means you can add success/failure handlers like this:
PlanSource.Job.createRecord({"name" : name});
var success = function(model) {
alert('ok');
};
var failure = function(model) {
alert('fail');
};
job.save().then(success, failure);

Intercepting creation of new object

I'm adding a categorization functionality to my app and struggling with it. Objects have many categories through categorizations. I'm trying to intercept the creation of a new categorization, check if theres a similar one, if so, increment it's count, if not, create a new object. Here's what I have so far.
validate :check_unique
protected
def check_unique
categorization = Categorization.where(:category_id => self.category_id, :categorizable_id => self.categorizable_id, :categorizable_type => self.categorizable_type)
if categorization.first
categorization.first.increment(:count)
end
end
This kind of logic should not exist in the controller. This is really business domain and should be in the model. Here's how you should go about it:
categorization = Categorization.find_or_create_by_category_id_and_categorizable_id_and_categorizable_type(self.category_id, self.categorizable_id, self.categorizable_type)
categorization.increment!(:count)
find_or_create will try to find the category in the DB, and if it doesn't exist, it'll create it. Now just make sure that count defaults to zero, and this code will do what you want. (when initially created the count would be 1, then later it'll increment)
PS: I'm not sure if find_or_create has changed in rails 3. But this is the main idea
I decided to move it out of the model object and put it into the controller method creating the categorization. It now works (Yay!) and here's the code if anyone is interested.
def add_tag
object = params[:controller].classify.constantize
#item = object.find(params[:id])
#categories = Category.find(params[:category_ids])
#categories.each do |c|
categorization = #item.categorizations.find(:first, :conditions => "category_id = #{c.id}")
if categorization
categorization.increment!(:count)
else
#item.categorizations.create(:category_id => c.id, :user_id => current_user.id)
end
end
if #item.save
current_user.update_attribute(:points, current_user.points + 15) unless #item.categorizations.exists?(:user_id => current_user.id)
flash[:notice] = "Categories added"
redirect_to #item
else
flash[:notice] = "Error"
redirect_to 'categorize'
end
end

Resources