Initializing a boolean on a form object that can be changed - ruby-on-rails

I have a form object that I want to have an enabled field on which can be true or false depending on other logic in my project, how do I initialize something like that in the form object class? I have something like this...
attr_accessor :enabled
def initialize
self.enabled = enabled
But I know this is incorrect.
And then in another spot in my code I want to do something like this
if something
form_object.enabled
elsif something_else
!form_object.enabled
end
Setting that enabled flag to the appropriate state so that I can use it for later
Edit: adding more info
The place where I want to put this enabled variable is in a helper Ruby Class I have
module Blah
module Type
module Mixins
module CallToActionHelper
attr_accessor :enabled
def initialize(cta_use: nil, cta: nil)
self.cta_type = cta_type
self.cta_token = create_cta_token
self.enabled = enabled
And then I have different ActiveRecord models for each type of call to action I have. They all also use the same controller, and the controller is the place where that enabled variable will be set depending on a hidden field in my params.
def populate_form_object(form_params, form_token)
cta_use = set_call_to_action_use(cta_use_id: form_params['cta_use_id'], cta_type_key: form_params['cta_type_key'], cta_id: form_params['cta_id'])
cta_type = CallToActionType.find_by_key(form_params['cta_type_key'])
form_object = cta_type.get_type_form(cta_use: cta_use)
form_object.cta_token = form_token
form_object.enabled(params["call_to_action_use_#{form_token}_enabled"])
# if params["call_to_action_use_#{form_token}_enabled"]
# form_object.enabled = true
# elsif params["call_to_action_use_#{form_token}_enabled"] == 'false'
# form_object.enabled = false
# else
# form_object.enabled = true
# end
return form_object
end
You can see the code I've commeneted out where I tried to set this enabled field.
The above method, populate_form_object gets called on a save.

Related

How to pass parameter when duplicating object in rails controller?

How can I provide .dup method with custom param so every time it is executed param is always True even when object who is being duplicated has this param at false?
Attribute I want to make true is called :original.
Here is my Modifications_controller create action:
def create
#modification = Modification.new(change_params.merge(user: current_user))
respond_to do |format|
if #modification.save
#modification.entity.boxes.each do |b|
#modification.boxes << b.dup #here I need to pass custom param
end
format.js {}
else
format.js {}
end
end
end
#dup doesn't know anything specific about your model logic. If you want to set some attributes to true, simply clone the object and then change the values.
box = b.dup
box.value = true
#modification.boxes << box
You can also consider to extract the feature in a custom method in the model, so that it's easier to write a test for it.
def duplicate
self.dup.tap do |i|
i.value = true
end
end
#modification.boxes << b.duplicate

RoR converting a virtual attribute into two database attributes

I'm currently having trouble finding a nice way to code the following situation:
There is a Model called TcpService, which has two attributes, port_from and port_to, both Integers. It also has a virtual attribute called portrange, which is a String. portrange is the String representation of the attributes port_from and port_to, so portrange = "80 90" should yield port_from = 80, port_to = 90. What I'm trying to do now is using the same Formtastic form for creating AND updating a TcpService-object. The form looks pretty standard (HAML code):
= semantic_form_for #tcp_service do |f|
= f.inputs do
= f.input :portrange, as: :string, label: "Portrange"
-# calls #tcp_service.portrange to determine the shown value
= f.actions do
= f.action :submit, label: "Save"
The thing is, I don't know of a non-messy way to make the values I want appear in the form. On new I want the field to be empty, if create failed I want it to show the faulty user input along with an error, else populate port_from and port_to using portrange. On edit I want the String representation of port_from and port_to to appear, if update failed I want it to show the faulty user input along with an error, else populate port_from and port_to using portrange.
The Model looks like this, which seems quite messy to me.
Is there a better way of making it achieve what I need?
class TcpService < ActiveRecord::Base
# port_from, port_to: integer
attr_accessor :portrange
validate :portrange_to_ports # populates `port_from` and `port_to`
# using `portrange` AND adds errors
# raises exception if conversion fails
def self.string_to_ports(string)
... # do stuff
return port_from, port_to
end
# returns string representation of ports without touching self
def ports_to_string
... # do stuff
return string_representation
end
# is called every time portrange is set, namely during 'create' and 'update'
def portrange=(val)
return if val.nil?
#portrange = val
begin
self.port_from, self.port_to = TcpService.string_to_ports(val)
# catches conversion errors and makes errors of them
rescue StandardError => e
self.errors.add(:portrange, e.to_s())
end
end
# is called every time the form is rendered
def portrange
# if record is freshly loaded from DB, this is true
if self.port_from && self.port_to && #portrange.nil?
self.ports_to_string()
else
#portrange
end
end
private
# calls 'portrange=(val)' in order to add errors during validation
def portrange_to_ports
self.portrange = self.portrange
end
end
Thanks for reading
In your model
def portrange
return "" if self.port_from.nil? || self.port_to.nil?
"#{self.port_from} #{self.port_to}"
end
def portrange=(str)
return false unless str.match /^[0-9]{1,5}\ [0-9]{1,5}/
self.port_from = str.split(" ").first
self.port_to = str.split(" ").last
self.portrange
end
Using this you should be able tu use the portrange setter and getter in your form.

Spree error when using decorator with the original code

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

Rails How can one query association definitions

I have a lot of dynamic code which keeps complex relations in a string.
ex:
"product.country.continent.planet.galaxy.name"
How can I check if these relations exist?
I want a method like the following:
raise "n00b" unless Product.has_associations?("product.country.planet.galaxy")
How could I implement this?
Try this:
def has_associations?(assoc_str)
klass = self.class
assoc_str.split(".").all? do |name|
(klass = klass.reflect_on_association(name.to_sym).try(:klass)).present?
end
end
If these are active record associations, here's how you can do it:
current_class = Product
has_associations = true
paths = "country.planet.galaxy".split('.')
paths.each |item|
association = current_class.reflect_on_association( item )
if association
current_class = association.klass
else
has_associations = false
end
end
puts has_association
And this will tell you if this specific path has all the associations.
If indeed you are storing the AR associations in a string like that, this code placed in an initializer should let you do what you want. For the life of me I can't quite figure out why you'd want to do this, but I trust you have your reasons.
class ActiveRecord::Base
def self.has_associations?(relation_string="")
klass = self
relation_string.split('.').each { |j|
# check to see if this is an association for this model
# and if so, save it so that we can get the class_name of
# the associated model to repeat this step
if assoc = klass.reflect_on_association(j.to_sym)
klass = Kernel.const_get(assoc.class_name)
# alternatively, check if this is a method on the model (e.g.: "name")
elsif klass.instance_method_already_implemented?(j)
true
else
raise "Association/Method #{klass.to_s}##{j} does not exist"
end
}
return true
end
end
With this you'll need to leave off the initial model name, so for your example it would be:
Product.has_associations?("country.planet.galaxy")

Rails - How to use find or create with a default value

I currently have the following:
conversation_participation = #user.conversation_participations.find_or_create_by_conversation_id(conversation.id)
This correctly creates a record, problem is the default value of conversation.read is false.
In this particular method I want the default value to be true when creating the record. Right now the only way I got this to work was by the following:
conversation_participation = #user.conversation_participations.find_or_create_by_conversation_id(conversation.id)
conversation_participation.read = true
conversation_participation.save
Problem is this hits the DB twice. How can I use find_or_create and set the default :read => true?
Thanks
You can try find_or_initialize_by...
Then set your read attribute as normal
conversation_participation = #user.conversation_participations.find_or_initialize_by_conversation_id(conversation.id)
conversation_participation.read = true
conversation_participation.save
Or
conversation_participation = #user.conversation_participations.find_or_initialize_by_conversation_id_and_read(conversation.id, true)
conversation_participation.save
With after_initialize (Oh.. you deleted your comment, but here is something for that just in case)
class Conversation < ActiveRecord::base
def after_initialize
self.read ||= true # true if not already set
end
end
Then you can do find_or_create|initialize_by... or which ever way you wish to proceed.
More on callbacks if you are interest.
From here:
Use the find_or_initialize_by_
finder if you want to return a new
record without saving it first.
So something like this:
conversation_participation = #user.conversation_participations.find_or_initialize_by_conversation_id(conversation.id)
conversation_participation.read = true
conversation_participation.save
This should just do an INSERT instead of an INSERT followed by an UPDATE.
Try this:
conversation_participation = #user.conversation_participations.find_or_create_by_conversation_id_and_read(conversation.id, true)

Resources