Serialize relations in remote model - ruby-on-rails

In my rails app I have 2 "Remote models". Those models are not active_record models and are retrieved on an API using a gem provided by the API.
I send data in hash to the library, and the library give me the data in the form of hashes. My question is more on how to generate correct hashes
My question can be illustrated with the 2 following models ;
remote_user.rb
class RemoteUser
include ActiveModel::Model
include ActiveModel::Serialization
attr_accessor(
:Name
:Email
...)
end
def attributes{ 'Name'=> nil,'Email'=>nil .....}
attr_reader(:HeadquartersAdress)
def HeadquartersAddress=(data={})
if data.is_a? RemoteAdresse
#HeadquartersAddress=data
else
#HeadquartersAddress=RemoteAdresse.new(data)
end
end
remote_adresse.rb
class RemoteAdresse
include ActiveModel::Model
include ActiveModel::Serialization
attr_accessor(
:AddressLine1,
:AddressLine2,
:City,
:Region,
:PostalCode,
:Country
)
def attributes
{
'AddressLine1'=>nil,
'AddressLine2'=>nil,
'City'=>nil,
'Region'=>nil,
'PostalCode'=>nil,
'Country'=>nil
}
end
end
Test :
test = RemoteUser.new Name: 'Foo'
test.HeadquartersAddress=RemoteAddress.new City: 'singapour'
test.serializable_hash
>{"Name"=>"Foo","HeadquartersAddress"=>#<RemoteAdresse:0xa9c2ef8
#City="singapour"}
I would prefer to have : {"Name"=>"Foo","HeadquartersAddress"=>{
"City"="singapour"}}
The nested object (adresse) is not serialized. What can I do to make it serialize too?

If I'm not entirely mistaken, you need to include associations to the serializable_hash call like so:
test = RemoteUser.new Name: 'Foo'
test.HeadquartersAddress=RemoteAddress.new City: 'singapour'
test.serializable_hash(include: :HeadquarterAddress)
If that doesn't work, there's always the possibility to overwrite read_attribute_for_serialization and adapted it for the HeadquarterAddress attribute.

Related

How to get hash of aliased attributtes

I have following aliases on User model:
alias_attribute :firstName, :first_name
alias_attribute :lastName, :last_name
How can I get hash with aliases as the keys, for example:
user.alias_attributes
{
firstName: "Joe",
lastName: "Smith"
}
If you look at the ActiveModel::AttributeMethods source (where alias_attribute comes from), you'll see:
included do
class_attribute :attribute_aliases, :attribute_method_matchers, instance_writer: false
self.attribute_aliases = {}
#...
end
and then later:
def alias_attribute(new_name, old_name)
self.attribute_aliases = attribute_aliases.merge(new_name.to_s => old_name.to_s)
#...
end
so you could look at User.attribute_aliases to get the hash you're after.
Of course, this isn't part of the documented interface so it may or may not continue working; if you use this functionality in your app then you should include some tests for it in your test suite so that you'll at least know if it changes.

Rails: Validate input without need of models

Let's say that I have an input field with a value, and I want to validate it (on the server side) to make sure, for instance, that the field has at least 5 characters.
The problem is that it is not something that I want to save in the database, or build a model. I just want to check that the value validates.
In PHP with Laravel, validation is quite easy:
$validator = Validator::make($data, [
'email' => ['required', 'email'],
'message' => ['required']]);
if ($validator->fails()) { // Handle it... }
Is there anything similar in Rails, without need of ActiveRecord, or ActiveModel? Not every data sent from a form makes sense as a Model.
You can use ActiveModel::Validations like this
class MyClass
include ActiveModel::Validations
validates :email, presence: true
validates :message, presence: true
end
It will act as a normal model and you will be able to do my_object.valid? and my_object.errors.
Rails validations live in ActiveModel so doing it without ActiveModel seems kind of counter-productive. Now, if you can loosen that requirement a bit, it is definitely possible.
What I read you asking for, and as I read the PHP code doing, is a validator-object that can be configured on the fly.
We can for example build a validator class dynamically and use instance of that class to run our validations. I have opted for an API that looks similar to the PHP one here:
class DataValidator
def self.make(data, validations)
Class.new do
include ActiveModel::Validations
attr_reader(*validations.keys)
validations.each do |attribute, attribute_validations|
validates attribute, attribute_validations
end
def self.model_name
ActiveModel::Name.new(self, nil, "DataValidator::Validator")
end
def initialize(data)
data.each do |key, value|
self.instance_variable_set("##{key.to_sym}", value)
end
end
end.new(data)
end
end
Using DataValidator.make we can now build instances of classes with the specific validations that we need. For example in a controller:
validator = DataValidator.make(
params,
{
:email => {:presence => true},
:name => {:presence => true}
}
)
if validator.valid?
# Success
else
# Error
end

Ruby copying between active model objects

Simply trying to work out how to copy attributes from one Active Model to another without having to do it one by one.
I have two models one is RFM (ruby to filemaker) one is mongoid both mixin active model.
gem "ginjo-rfm"
the model
require 'rfm'
class StudentAdmin < Rfm::Base
config :layout => 'STUDENT_ADMIN_LAYOUT'
attr_accessor :name_first,:name_last
end
Mongoid model
class Student
include Mongoid::Document
field :first_name, type: String
field :last_name, type: String
end
Is there a quicky copy I can do? I found a sample between active record objects e.g.
student_admin = ... #load StudentAdmin
Student.new(student_admin.attributes.slice(Student.attribute_names))
but RFM doesn't provide a attributes method.
EDIT
Sorry what I am trying to achive is a better way than this
student_admins = #get student admins from external service
students = []
student_admins.each() do |sa|
students.push(Student.create!(first_name: sa.name_first, last_name: sa.name_last))
end
This example only shows 2 attributes, but in practice there is over 50 and was wondering if there is a way to do it without having to specify every attribute e.g. if the attribute names are the same on two objects copy them automatically.
Try this:
students = student_admins.map do |sa|
attrs = sa.methods.inject({}) do |hash, m|
next unless Student.column_names.include? m.to_s
hash[m] = sa.send m
end
Student.create(attrs)
end
Student would have to be a class that inherits from ActiveRecord::Base:
class Student < ActiveRecord::Base
...
end

how attr_accessor works in ActiveResource rails 3?

How does attr_accessor works in ActiveResource?
class User < ActiveResource::Base
attr_accessor :name
end
How its different from attr_accessor in ActiveRecord?
attr_accessor is built into Ruby, not rails. You may be confusing it with attr_accessible, which is part of ActiveRecord. Here's the difference:
attr_accessor
Take a class:
class Dog
attr_accessor :first_name, :last_name
def initialize(first_name, last_name)
self.first_name = first_name
self.last_name = last_name
end
end
attr_accessor creates a property and creates methods that allow it to be readable and writeable. Therefore, the above class would allow you to do this:
my_dog = Dog.new('Rex', 'Thomas')
puts my_dog.first_name #=> "Rex"
my_dog.first_name = "Baxter"
puts my_dog.first_name #=> "Baxter"
It creates two methods, one for setting the value and one for reading it. If you only want to read or write, then you can use attr_reader and attr_writer respectively.
attr_accessible
This is an ActiveRecord specific thing that looks similar to attr_accessor. However, it behaves very differently. It specifies which fields are allowed to be mass-assigned. For example:
class User
attr_accessible :name, :email
end
Mass assignment comes from passing the hash of POST parameters into the new or create action of a Rails controller. The values of the hash are then assigned to the user being created, e.g.:
def create
# params[:user] contains { name: "Example", email: "..."}
User.create(params[:user])
#...
end
For the sake of security, attr_accessible has to be used to specify which fields are allowed to be mass-assigned. Otherwise, if the user had an admin flag, someone could just post admin: true as data to your app, and make themselves an admin.
In summary
attr_accessor is a helper method for Ruby classes, whereas attr_accessible is an ActiveRecord thing for rails, to tighten up security.
You don't need to have attr_accessor to work with ActiveResource.
The base model (ActiveResource::Base) contains the #attributes hash in which you can 'dump' properties as you wish. (you should be careful though on what params you allow)
The way it does this, is by handling the method_missing? method.
You can take a look here
If you define attr_accessor, what ruby does is that it creates a setter and a getter method, so it will break the method_missing functionality since it will never get to execute that code.
If you still want to use attr_accessor, you should create a Concern something like this:
module Attributes
extend ActiveSupport::Concern
module ClassMethods
def attr_accessor(*attribs)
attribs.each do |a|
define_method(a) do
#attributes[a]
end
define_method("#{a}=") do |val|
#attributes[a] = val
end
end
end
end
end

How to override default deserialization of params to model object?

How to override default deserialization of params to model object?
In other words, how to make Rails understand camel case JSON with a snake case database?
Example: I receive params Foo object with a field fooBar and I want my Foo model to understand fooBar is in fact database field foo_bar.
"Foo": {
"fooBar": "hello" /* fooBar is database field foo_bar */
}
class Foo < ActiveRecord::Base
attr_accessible :foo_bar
end
class FoosController < ApplicationController
def new
#foo = Foo.new(params[:foo])
end
Foo.new(params[:foo]) assumes params[:foo] contains foo_bar. Instead params[:foo] contains fooBar (in my case params contains JSON data).
I would like a clean way to handle this case, the same way a model can override as_json:
class Foo < ActiveRecord::Base
attr_accessible :foo_bar, :another_field
def as_json(options = nil)
{
fooBar: foo_bar,
anotherField: another_field
}
end
end
There is a from_json method inside ActiveModel but it is not called when Foo.new(params[:foo]) is run.
I've read several times that overriding initialize from a model object is a terrible idea.
All that Foo.new does with the params hash you give it is iterate over the keys and values in that hash. If the key is foo_bar then it tries to call foo_bar= with the value.
If you define a fooBar= method that sets self.foo_bar then you'll be able to pass a hash with the key :fooBar to Foo.new.
Less manually, you can do
class Foo < ActiveRecord::Base
alias_attribute :fooBar, :foo_bar
end
which generates all the extra accessors for you.
I wouldn't say that overriding initialize is a terrible thing but it can be tricky to do right and there's almost always a simpler way or a way that makes your intentions clearer.
I've checked active_model_serializers, RABL and JBuilder. None of them allow to customize the JSON format that is received.
For that one must deal with wrap_parameters, see http://edgeapi.rubyonrails.org/classes/ActionController/ParamsWrapper.html
It works, still the code is ugly: I get JSON stuff inside my controller + the serializer/model instead of one place.
Example of use of wrap_parameters:
class EventsController < ApplicationController
wrap_parameters :event, include: [:title, :start, :end, :allDay, :description, :location, :color]
def create
respond_with Event.create(params[:event])
end
end
and then inside my model (Frederick Cheung is right on this part):
class Event < ActiveRecord::Base
attr_accessible :title, :start, :end, :allDay, :description, :location, :color
# JSON input allDay is all_day
alias_attribute :allDay, :all_day
# JSON input start is starts_at
# +datetime+:: UNIX time
def start=(datetime)
self.starts_at = Time.at(datetime)
end
# JSON input end is starts_at
# +datetime+:: UNIX time
def end=(datetime)
self.ends_at = Time.at(datetime)
end
# Override the JSON that is returned
def as_json(options = nil)
{
id: id,
title: title,
start: starts_at, # ISO 8601, ex: "2011-10-28T01:22:00Z"
end: ends_at,
allDay: all_day,
description: description, # Not rendered by FullCalendar
location: location,
color: color
}
end
end
For info ASP.NET MVC (with Json.NET) does it using C# decorator attributes which is pretty elegant:
class Post
{
[JsonPropertyAttribute("title")]
public string Title;
}
I have created a gist that shows how to implement serialization/deserialization: https://gist.github.com/3858908

Resources