How do I deserialize a rails serialized object - ruby-on-rails

In my model I have:
class Team < ApplicationRecord
has_many :surveys
serialize :survey_status, Hash
If I do this puts statement I get:
puts "survey_status is: " + team.survey_status
..survey_status is: { intial_survey_complete: false, second_survey_complete: false, third_survey_complete: false, final_survey_complete: false }
I'm wanting to test against a single value, but when I try: puts "survey_status is: " + team.survey_status[:intial_survey_complete]
I get:
TypeError: no implicit conversion of Symbol into Integer
I'm guessing this is not a hash, but maybe a string? Regardless, the end goal is I'm wanting to see if survey_status[:intial_survey_complete] is true or false.
I want to be able to do something like:
if team.survey_status[:intial_survey_complete].false?
#do stuff

Related

Rails convert string data received from front-end to Boolean in the backend

I have a drop down menu from front end whose value are"true" and
"false" as a string respectively.
{label: 'No', value: 'false'},
{label: 'Yes', value: 'true'}
They will be insert into a column type with Boolean type.
As you can see, it may be problematic because the type mismatch.
The error i got in the console:
POST http://localhost:3000/api/my_forms.json 422 (Unprocessable Entity)
So is there a efficient way so that once the data is received,it will l be converted into Boolean
I am using Postgresql for my DB and React for my front-end, if that helps.
if it is effectively passed as a string, you could add a callback in your model to ensure the value is correctly transformed
For example :
class YourModel
before_validation :format_field,
if: proc { |model|
model.your_field.is_a?(String)
}
def format_field
your_field = (your_field == "true")
end
end
Or a before action on your controller :
before_action :format_problematic_field
def format_problematic_field
return unless params[:your_field].is_a?(String)
params[:your_field] = params[:your_field] == "true"
end

How to validate inclusion of array content in rails

Hi I have an array column in my model:
t.text :sphare, array: true, default: []
And I want to validate that it includes only the elements from the list ("Good", "Bad", "Neutral")
My first try was:
validates_inclusion_of :sphare, in: [ ["Good"], ["Bad"], ["Neutral"] ]
But when I wanted to create objects with more then one value in sphare ex(["Good", "Bad"] the validator cut it to just ["Good"].
My question is:
How to write a validation that will check only the values of the passed array, without comparing it to fix examples?
Edit added part of my FactoryGirl and test that failds:
Part of my FactoryGirl:
sphare ["Good", "Bad"]
and my rspec test:
it "is not valid with wrong sphare" do
expect(build(:skill, sphare: ["Alibaba"])).to_not be_valid
end
it "is valid with proper sphare" do
proper_sphare = ["Good", "Bad", "Neutral"]
expect(build(:skill, sphare: [proper_sphare.sample])).to be_valid
end
Do it this way:
validates :sphare, inclusion: { in: ["Good", "Bad", "Neutral"] }
or, you can be fancy by using the short form of creating the array of strings: %w(Good Bad Neutral):
validates :sphare, inclusion: { in: %w(Good Bad Neutral) }
See the Rails Documentation for more usage and example of inclusion.
Update
As the Rails built-in validator does not fit your requirement, you can add a custom validator in your model like following:
validate :correct_sphare_types
private
def correct_sphare_types
if self.sphare.blank?
errors.add(:sphare, "sphare is blank/invalid")
elsif self.sphare.detect { |s| !(%w(Good Bad Neutral).include? s) }
errors.add(:sphare, "sphare is invalid")
end
end
You can implement your own ArrayInclusionValidator:
# app/validators/array_inclusion_validator.rb
class ArrayInclusionValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
# your code here
record.errors.add(attribute, "#{attribute_name} is not included in the list")
end
end
In the model it looks like this:
# app/models/model.rb
class YourModel < ApplicationRecord
ALLOWED_TYPES = %w[one two three]
validates :type_of_anything, array_inclusion: { in: ALLOWED_TYPES }
end
Examples can be found here:
https://github.com/sciencehistory/kithe/blob/master/app/validators/array_inclusion_validator.rb
https://gist.github.com/bbugh/fadf8c65b7f4d3eaa55e64acfc563ab2

How do I use the value of an attribute within a model? Ruby on Rails

Basically, I have a model, Degree, and it has three attributes: degree_type, awarded_by, and date_awarded.
There are two arrays of values that should be valid for awarded_by. The two valid values for degree_type are "one" and "two", and the valid values for awarded_by depend on "one" and "two".
If degree_type is "one" (has a value of "one", that a user would put in), I want the valid values for awarded_by to be array_one. If degree_type has a value of "two", I want the valid values for awarded_by to be array_two.
Here is the code so far:
class Degree < ActiveRecord::Base
extend School
validates :degree_type, presence: true,
inclusion: { in: ["one",
"two"],
message: "is not a valid degree type"
}
validates :awarded_by, presence: true,
inclusion: { in: Degree.schools(awarded_by_type) }
end
Degree.schools outputs an array depending on the degree type, so Degree.schools("one") would return array_one, where
array_one = ['school01', 'school02'...]
My problem is, I don't know how to access the value of degree_type within the model.
What I tried below doesn't work:
validates :awarded_by, presence: true,
inclusion: { in: Degree.schools(:degree_type) }
I tried using before_type_cast but I was either using it incorrectly or there was another problem, as I couldn't get that to work either.
When I test this I get:
An object with the method #include? or a proc, lambda or symbol is required, and must be supplied as the :in (or :within) option of the configuration hash
Help me out? :) If any more info is needed, let me know.
EDIT: To add to this, I double checked it wasn't my Degree.schools method acting up - if I go into the rails console and try Degree.schools("one") or Degree.schools("two") I do get the array I should get. :)
EDIT again: When I tried #Jordan's answer, I got errors in the cases where the awarded_by was incorrect because in those cases, valid_awarded_by_values was nil and there is no include? method for a nil object. Therefore I added an if statement checking for whether valid_awarded_by_values was nil or not (so as to return if it was), and that solved the problem!
I put this inside the method, before the unless statement and after the valid_awarded_by_values declaration:
if valid_awarded_by_values.nil?
error_msg = "is not a valid awarded_by"
errors.add(:awarded_by, error_msg)
return
end
The easiest way will be to write a custom validation method, as described in the Active Record Validations Rails Guide.
In your case, it might look something like this:
class Degree < ActiveRecord::Base
validate :validate_awarded_by_inclusion_dependent_on_degree_type
# ...
def validate_awarded_by_inclusion_dependent_on_degree_type
valid_awarded_by_values = Degree.schools(degree_type)
unless valid_awarded_by_values.include?(awarded_by)
error_msg = "must be " << valid_awarded_by_values.to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')
errors.add(:awarded_by, error_msg)
end
end
end

Rails to_xml vs as_json

Trying to mirror my API responses with as little code duplication as possible and have this so far....
Really, this is a "There has to be a better 'Rails way' to accomplish this..." question.
class Quote < ActiveRecord::Base
belongs_to :author
has_many :votes
def as_json(options={})
hash = super(except)
hash[:author] = self.author.name
hash[:vote_count] = self.votes.count
hash
end
def to_xml(options={})
hash = super(except)
hash[:author] = self.author.name // <---- line 14
hash[:vote_count] = self.votes.count
hash
end
private
def except
{ :except => [ :id, :created_at, :updated_at, :author_id ] }
end
end
JSON response works like a champ, but the xml throws this error
can't convert Symbol into Integer
app/models/quote.rb:14:in `[]='
app/models/quote.rb:14:in `to_xml'
As a secondary question, is the the best way to customize the output like I am? I'd like to not duplicate this logic if I can avoid it.
hash[:author] = self.author.name
hash[:vote_count] = self.votes.count
hash
to_xml returns an XML string, not a hash. That's why it's surprised by a symbol in the brackets: it thinks you're trying to modify a particular character, e.g. name[0] = 'A'
If you're interested in changing bits of the XML output, maybe you should just build a new hash of the attributes you want and run to_xml on that.

Why does Model.new in Rails 3 do an implicit conversion?

I'm facing a weird behavior in Rails 3 model instantiation.
So, I have a simple model :
class MyModel < ActiveRecord::Base
validates_format_of :val, :with => /^\d+$/, :message => 'Must be an integer value.'
end
Then a simple controller :
def create
#mod = MyModel.new(params[:my_model])
if #mod.save
...
end
end
first, params[:my_model].inspect returns :
{:val => 'coucou :)'}
But after calling #mod = MyModel.new(params[:my_model]) ...
Now, if I call #mod.val.inspect I will get :
0
Why am I not getting the original string ?
At the end the validates succeed because val is indeed an integer.
Is this because val is defined as an integer in the database ?
How do I avoid this behavior and let the validation do his job ?
If val is defined as an integer in your schema then calling #my_model.val will always return an integer because AR does typecasting. That's not new to rails 3, it's always worked that way. If you want the original string value assigned in the controller, try #my_model.val_before_type_cast. Note that validates_format_of performs its validation on this pre-typecast value, so you don't need to specify that there.
EDIT
Sorry I was wrong about the "performs its validation on this pre-typecast value" part. Looking at the code of the validation, it calls .to_s on the post-typecast value which in your case returns "0" and therefore passes validation.
I'd suggest not bothering with this validation to be honest. If 0 is not a valid value for this column then validate that directly, otherwise just rely on the typecasting. If the user enters 123 foo you'll end up with 123 in the database which is usually just fine.
There is also better fitting validator for your case:
http://guides.rubyonrails.org/active_record_validations_callbacks.html#validates_numericality_of

Resources