rails mongoid undefined bson_type - ruby-on-rails

I defined my own custom field type Price for my application. I defined all the required methods (demongoize, mongoize, ..) and it works perfectly fine retrieving the data from mongodb, sending it to front end and getting data back and store them in mongo db. My Price class stores the data as an array [value, currency] in the DB and also my front end expect the price to be delivered as a an array. All ok so far.
In my model I now added a Hash which reflects a tree structure containing objects which have a price. I was re-using the price class for these prices when creating the tree structure.
But now I get an undefined bs_type error when I try to save this hash field to the database. Looks like rails/mongoid doesnt find a method to transform the price in the Hash into a suitable mongoid format.
I tried to define as_json, to_json, to_s, ... everything on my Price model but I always get the same message: undefined bson_type for Price whenever I want to save my tree field.
(If I store my values as [value,currency] in the hash - not using Price, obviously all works fine)
Any idea?
class Price
def initialize(value = 0, currency = '')
#value,#currency = value, currency
end
def mongoize
[#value,#currency.to_s]
end
class << self
def demongoize(object)
if object
cur = object[1] || ''
Price.new(object[0], cur)
else
Price.new()
end
end
def mongoize(object)
case object
when Price then object.mongoize
when Array then Price.new(object[0], object[1]).mongoize
else object
end
end
def evolve(object)
case object
when Price then object.mongoize
else object
end
end
end
I have another model:
class Financial
include Mongoid::Document
field: quote, type: Price
field: tree, type: Hash
end
In my application code setting the quote and saving it works.
Here is the problem:
revenue = Price.new(10,'USD')
record = Financial.new()
record.quote = revenue
record.save # All works so far
record.tree = { data: [10,'USD']}
record.save # this works too
record.tree = { data: revenue}
record.save # here I get the undefined bson_type for Price error.

Related

How to append an array in parameters after Rails form submit?

I have a form with checkboxes that get passed as an array "list_person_ids" on form submit. My models are "Occurance" which has an n:m relationship with "ListPerson" through the Model "Person". "list_person_ids" are saved in Person with the Occurance id and the ListPerson id.
I want to append one or more values to the array before this gets saved. The reason I need to do this is because the user can also add a new value in ListPerson using a textbox with the name "person_name".
def create
#occurance = Occurance.new(occurance_params)
add_person_id(#occurance)
...
# save
end
def add_person_id(object)
if params[:person_check] == '1'
object.list_person_ids.push( ListPerson.find_or_create_by(person: params[:person_name]).id )
end
end
def occurance_params
params.require(:occurance).permit(:person_check, :person_name, dim_person_ids: [])
end
find_or_create_by is successful, but nothing gets pushed to the array "list_person_ids". There is also no error message. Do I have to give permission somewhere to append this array? Please let me know if some information is missing.
on your model you can do something like below:
....
before_create :create_lists
private
def create_lists
ListPerson.find_or_create_by(list_person_ids: person_name.id)
end

How to save each value from an array to database in Rails?

I would like to store each value from an array.
For example the form sends me this data:
"attendance"=>{"event_id"=>"6", "member_id"=>["16", "28", "26"]}
I'd like the database to store the data as:
INSERT INTO "attendances" ("event_id", "member_id") VALUES ("6", "16")
INSERT INTO "attendances" ("event_id", "member_id") VALUES ("6", "28")
INSERT INTO "attendances" ("event_id", "member_id") VALUES ("6", "26")
I've tried to use the usual way of inserting data in Rails, but it failed because the member_ids didn't get passed (I've tried to print the member_ids after the Attendance.new(attendance_params)):
def create
#attendance = Attendance.new(attendance_params)
# puts #attendance[:event_id]
# puts #attendance[:member_id] -> Nothing showed up here.
if #attendance.save
flash[:success] = "Successfully created"
redirect_to new_attendance_path
else
#error_msg = #attendance.errors.full_messages
flash[:error] = #error_msg # Prints ["Member must exist"]
redirect_to new_attendance_path
end
end
I've also tried creating a new function in the model to change the Attendance.new but it'll return
NoMethodError - undefined method `new_each' for #<Class:0x000000000d1e7280>: app/controllers/attendances_controller.rb:17:in `create'
This is my current model:
class Attendance < ApplicationRecord
belongs_to :event
belongs_to :member
# def new_each(attendance)
# attendance_event = attenance[:event_id]
# attendance_members = attendance[:member_id]
# I tried to iterate and save each data here.
# end
end
So, how do I save each value from an array input (from the form) and save it to database?
Any answers and comment will be very appreciated.
You are trying to add attendance in bulk, For that you can do 2 things,
use gem bulk_insert, it's very easy to use here is the link https://github.com/jamis/bulk_insert
you need to iterate through all the members and events from params and create a record for each. though it's a very dirty way.
sample code would be like this. you can modify it as per your need.
errors = {}
attendance_params[:member_id].each do |member_id|
attendence = Attendence.new(event_id:attendance_params[:event_id], member_id: member_id)
errors[member_id] = "Could not save attendance, error =
attendance.errors.full_messages" unless
attendence.save
end

ActiveModel::MissingAttributeError in Controller can't write unknown attribute

For one of the views in my rails application, I have set up the controller as such. I want to get all students records from the db and append extra values to each student. This is giving me the error:
ActiveModel::MissingAttributeError in MemoMainTesterController#test_students
can't write unknown attribute current_target
class MemoMainTesterController < ApplicationController
def test_students
#all_students = Student.all
#all_students.each do |student|
current = current_target(student)
previous_test_info = last_pass(student)
student[:current_target] = current[0]
student[:current_level] = current[1]
student[:current_target_string] = "Level #{current[0]} - Target #{current[1]}"
student[:last_pass] = previous_test_info[0]
student[:attempts] = previous_test_info[1]
student[:last_pass_string] = previous_test_info[2]
end
end
.
.
.
end
It occurs specifically where student[:current_target] = current[0].
Am I not allowed to append extra values to this hash?
Is there a workaround for this?
EDIT: Although Student.all is a model instance, I want to turn it into a hash and append more key value pairs to it.
In your case, student is not a Hash but a Student model instance.
When you call student[:current_target] you are attempting to write Student's current_target attribute, which surely is not an actual attribute in the DB for students table. Hence the error.
To obtain a hash from your models containing the extra data, you may consider this refactor:
class MemoMainTesterController < ApplicationController
def test_students
#all_students = Student.all
#students_with_steroids = #all_students.map do |student|
current = current_target(student)
previous_test_info = last_pass(student)
student_attributes = student.attributes # <= this is a hash, that you store in student_attributes hash variable
student_attributes.merge(current_target: current[0],
current_level: current[1],
current_target_string: "Level #{current[0]} - Target #{current[1]}",
last_pass: previous_test_info[0],
attempts: previous_test_info[1],
last_pass_string: previous_test_info[2])
end
end

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.

Creating new class to put into an Array in Ruby

I am coming from a C# background and trying to learn Ruby and Ruby on Rails. I have the following Car class - note the build_xml method I need in order to build XML in that syntax and then pass to a WebService
class Car
##array = Array.new
#this will allow us to get list of all instances of cars created if needed
def self.all_instances
##array
end
def initialize(id, model_number, engine_size, no_doors)
# Instance variables
#id = id
#model_number = model_number
#engine_size = engine_size
#no_doors = no_doors
##array << self
end
def build_car_xml
car = { 'abc:Id'=> #id, 'abc:ModelNo' => #model_number, 'abc:ES' => #engine_size, 'abc:ND' => #no_doors}
cars = {'abc:Car' => [car] }
end
end
In another class then I was using this as below:
car1 = Car.new('1', 18, 3.0, 4)
request = car1.build_car_xml
This works as expected and the request is formatted how I need and the webservice returns the results. I now want to expand this however so I can pass in an array of cars and produce the request XML - however I am struggling to get this part working.
So far I have been trying the following (for now I am ok with just the Id changing as it is the only parameter required to be unique):
car_array = []
(1..10).each do |i|
car_array << Car.new(i.to_s, 18, 3.0, 4)
end
Am I correct in saying that I would need to define a new build_car_xml method on my Car class that can take an array of cars and then build the xml so my request call would be something like:
request = Car.build_car_xml(car_array)
What i am unsure of is 1) - is this the correct way of doing things in Ruby and 2) how to construct the method so that it is Building the XML in the correct format in the way it was when I call it on the single object - i.e - I need the namespaces added before the actual value.
def build_car_xml(car_array)
#here is where I am unsure how to contruct this method
end
Possible solution ('abc:Car' is a wrong name, should be Cars if you want it to hold an array):
class Car
...
def self.build_cars_xml(cars)
{ 'abc:Car' => cars.map(&:build_car_xml) }
end
def build_car_xml
{ 'abc:Id'=> #id, 'abc:ModelNo' => #model_number, 'abc:ES' => #engine_size, 'abc:ND' => #no_doors }
end
end
cars =
(1..10).map do |i|
Car.new(i.to_s, 18, 3.0, 4)
end
Car.build_cars_xml(cars)
It doesn't meet your requirements as instance build_car_xml doesn't generate Car namespace, but for me it's some inconsistency. Your XML is actually a collection, even if it has just one element, instance method should not be responsible for collection. Car.build_cars_xml([Car.new(...)] looks more logical to me.

Resources