I have a controller that is:
def create
.
.
#project = Project.find(params[:project]
#Log = Logs.create(params[:action]).merge(:project_id => #project.id))
...
end
The issue hereis that sometimes when DEF CREATE, I'll have a project and I want to record that. Other times I won't and that's perfectly fine, I still want to create the #Log
What's the right way in Rails to handle this. I'll want to make sure:
The first line #project doesn't error.
Also that the #log doesn't error but inserts '' or NIL whatever is rails standard.
Thank you
Try this:
def create
#project = (project_id = params[:project_id]).blank? ? nil :
Project.find(project_id)
#Log = Logs.create(params[:action].merge(#project.nil? ? {} :
{:project_id => #project.id}))
end
If the input has a project_id, then above solution will throw an error if a project with the given id is not found. If you don't want this behavior use find_by_id instead of find.
Create a protected method near the bottom of your controller like so:
protected
def project_id
# return the cached value if we've already figured it out
return #project_id if defined?(#project_id)
# get the project by id in a failsafe way
project = params[:project_id] ? Project.find_by_id(params[:project_id]) : nil
# return nil if the project is nil, so we don't try to call "id" on it
return #project_id = nil if project.nil?
# cache and return the project id
#project_id = project.id
end
Notice I changed the parameter to :project_id instead of just project. This fits the rails convention better. Now in your create action, and all other actions, you can call it safely:
#Log = Logs.create(params[:action]).merge(:project_id => project_id))
I hope this helps!
Related
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.
Need a little help over here :-)
I'm trying to extend the Order class using a decorator, but I get an error back, even when I use the exactly same code from source. For example:
order_decorator.rb (the method is exactly like the source, I'm just using a decorator)
Spree::Order.class_eval do
def update_from_params(params, permitted_params, request_env = {})
success = false
#updating_params = params
run_callbacks :updating_from_params do
attributes = #updating_params[:order] ? #updating_params[:order].permit(permitted_params).delete_if { |k,v| v.nil? } : {}
# Set existing card after setting permitted parameters because
# rails would slice parameters containg ruby objects, apparently
existing_card_id = #updating_params[:order] ? #updating_params[:order][:existing_card] : nil
if existing_card_id.present?
credit_card = CreditCard.find existing_card_id
if credit_card.user_id != self.user_id || credit_card.user_id.blank?
raise Core::GatewayError.new Spree.t(:invalid_credit_card)
end
credit_card.verification_value = params[:cvc_confirm] if params[:cvc_confirm].present?
attributes[:payments_attributes].first[:source] = credit_card
attributes[:payments_attributes].first[:payment_method_id] = credit_card.payment_method_id
attributes[:payments_attributes].first.delete :source_attributes
end
if attributes[:payments_attributes]
attributes[:payments_attributes].first[:request_env] = request_env
end
success = self.update_attributes(attributes)
set_shipments_cost if self.shipments.any?
end
#updating_params = nil
success
end
end
When I run this code, spree never finds #updating_params[:order][:existing_card], even when I select an existing card. Because of that, I can never complete the transaction using a pre-existent card and bogus gateway(gives me empty blanks errors instead).
I tried to bind the method in order_decorator.rb using pry and noticed that the [:existing_card] is actuality at #updating_params' level and not at #updating_params[:order]'s level.
When I delete the decorator, the original code just works fine.
Could somebody explain to me what is wrong with my code?
Thanks,
The method you want to redefine is not really the method of the Order class. It is the method that are mixed by Checkout module within the Order class.
You can see it here: https://github.com/spree/spree/blob/master/core/app/models/spree/order/checkout.rb
Try to do what you want this way:
Create file app/models/spree/order/checkout.rb with code
Spree::Order::Checkout.class_eval do
def self.included(klass)
super
klass.class_eval do
def update_from_params(params, permitted_params, request_env = {})
...
...
...
end
end
end
end
I have 2 models. User and Want. A User has_many: Wants.
The Want model has a single property besides user_id, that's name.
I have written a custom validation in the Want model so that a user cannot submit to create 2 wants with the same name:
validate :existing_want
private
def existing_want
return unless errors.blank?
errors.add(:existing_want, "you already want that") if user.already_wants? name
end
The already_wants? method is in the User model:
def already_wants? want_name
does_want_already = false
self.wants.each { |w| does_want_already = true if w.name == want_name }
does_want_already
end
The validation specs pass in my model tests, but my feature tests fail when i try and submit a duplicate to the create action in the WantsController:
def create
#want = current_user.wants.build(params[:want])
if #want.save
flash[:success] = "success!"
redirect_to user_account_path current_user.username
else
flash[:validation] = #want.errors
redirect_to user_account_path current_user.username
end
end
The error I get: can't dump hash with default proc
No stack trace that leads to my code.
I have narrowed the issue down to this line:
self.wants.each { |w| does_want_already = true if w.name == want_name }
if I just return true regardless the error shows in my view as I would like.
I don't understand? What's wrong? and why is it so cryptic?
Thanks.
Without a stack trace (does it lead anywhere, or does it just not appear?) it is difficult to know what exactly is happening, but here's how you can reproduce this error in a clean environment:
# initialize a new hash using a block, so it has a default proc
h = Hash.new {|h,k| h[k] = k }
# attempt to serialize it:
Marshal.dump(h)
#=> TypeError: can't dump hash with default proc
Ruby can't serialize procs, so it wouldn't be able to properly reconstitute that serialized hash, hence the error.
If you're reasonably sure that line is the source of your trouble, try refactoring it to see if that solves the problem.
def already_wants? want_name
wants.any? {|want| want_name == want.name }
end
or
def already_wants? want_name
wants.where(name: want_name).count > 0
end
What i have: (Action in Controller)
def create
#test = Test.new(params[:test])
#test.save
devicefiles = params[:devicefiles]
if devicefiles != nil
devicefiles.each do |attrs|
devicenote = Testdevicenote.new(attrs, :test_id => #test.id)
devicenote.save
end
end
end
This controller action does not show any error message and is rendering the view, but :test_id is not being saved in the database. How can i solve this?
EDIT: Ok whoops, I see it now...
Models only take one hash on initialize, not 2.
Testobjectnote.new(attrs.merge(:test_id => #test.id))
In short no one here has any clue, because that's not enough information. We dont know how your models are setup.
But when debugging models that "won't save" it's often good to use the bang version save, save!. save returns true or false letting you know if it was able to save the record. But save! will raise exceptions when the model can't be saved, and the exception will tell you why.
That exception will likely tell you why the record is not being saved.
Also, its usually better to use the associations, rather than manage the ids yourself.
def create
#test = Test.new(params[:test])
if params[:devicefiles]
params[:devicefiles].each do |attrs|
#test.testdevicenotes << Testdevicenotes(attrs)
end
end
#test.save
end
It's hard to say because you didn't post your view with the form that is posting to the create action, but if it's a typical Rails form, it should probably look like:
def create
#test = Test.new(params[:test])
#test.save
devicefiles = params[:test][:devicefiles]
if devicefiles != nil
devicefiles.each do |attrs|
devicenote = Testdevicenote.new(attrs, :test_id => #test.id)
devicenote.save
end
end
objectfiles = params[:test][:objectfiles]
if objectfiles != nil
objectfiles.each do |attrs|
objectnote = Testobjectnote.new(attrs, :test_id => #test.id)
objectnote.save
end
end
end
This assumes that :devicefiles and :objectfiles are inside the form :test
I created a model for a thing called a Lesson that has :content and a :user_id. For early builds of this app, I want the content to be changing, based on my entry, and for the user_id to always = 1 so that it's clean in the DB and there's not a nil value.
How do I go about this?
In my lessons_controller.rb I have this:
def create
#lesson = Lesson.new(params[:lesson])
if #lesson.save
... do something
else
... do something else
I'm guessing this would be the best place to define that the user_id = 1 but how should I go about that?
You can just set #lesson.user_id = 1 in the line after you create it with new, and before you save it.
Another way to do it would be to set a hook in the lesson model -
before_validation :on => :create do |lesson|
lesson.user_id = 1
end