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.
Related
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.
I have this method
def create_billing_address(data)
address_data = data.permit(:first_name,
:last_name,
:phone,
:address_1,
:city,
:postcode,
:country)
service_customer.create_address(customer_id, address_data)
end
But now I want to check that all the keys are present. I tried to do this
address_data = data.require(:first_name,
:last_name,
:phone,
:address_1,
:city,
:postcode,
:country)
But require return an array instead of an hash.
How can I do to have the same behaviour of permit but with require ?
require is only intended to ensure that the params have the correct general structure.
For example if you have:
params.require(:foo).permit(:bar, :baz)
require lets us bail early if the :foo key is missing since we cant do anything with the request.
require is not intended to validate the presence of individual params - that is handled with model level validations in Rails.
If you really had to you could do:
def create_billing_address!(data)
keys = [:first_name, :last_name, :phone, :address_1, :city, :postcode, :country]
keys.each do |k|
raise ActionController::ParameterMissing and return unless data[k].present?
end
service_customer.create_address(customer_id, data.permit(*keys))
end
But thats just bad application design as you're letting the model level business logic creep into the controller.
permit and require are not interchangeable the way you think. If you establish that all the required keys are present, they still need to be permitted to be used in a mass assignment.
So you'd likely need to do...
def create_billing_address(data)
fields = %i(first_name last_name phone address_1 city postcode country)
address_data = data.permit(data.require(fields))
service_customer.create_address(customer_id, address_data)
end
The data.require will raise an ActionController::ParameterMissing exception if a key is missing, if not it will return the array of keys which can be used by permit.
Generally, what you want to do with require is more typically handled by model validation.
Documentation on require and permit is here...
http://api.rubyonrails.org/classes/ActionController/Parameters.html
How can I manipulate params in a more DSL way per country where each country has its own logic for province variable.
I would like to organise it better:
- create a configuration file per per each country.
- if files not exist than there will be a default file
- and each file is a ruby file that province parameter can be manipulated via ruby code which gives flexibility.
currently I do it in in the controller like this:
before_filter :modify_location_params, :only => [:create]
def location_params
params.require(:location).permit(
origin: [:name, :country, :city, :state, :postal_code, :address1, :address2,:province],
destination: [:name, :country, :city, :state, :postal_code, :address1, :address2,:province],
)
end
def modify_location_params
[:origin, :destination].each do |location|
unless (params[:location][location][:country].downcase =~ /(Sweden|sw)/).nil?
params[:location][location][:province] = 'SW'
end
unless (params[:location][location][:country].downcase == 'IL' && some_other_condition == true
params[:location][location][:city] = 'OM'
params[:location][location][:name] = 'some name'
end
end
end
Yes, I can do it in a switch/if statements but I think that since I have a lot of countries it would be a better way of doing a DSL like system for this manipulating. any ideas how implement such?
Not totally sure I understand what you're trying to do, but if you just want different implementations of a similar method for each country you could make a class for each one and have them inherit from a parent country class. Something like
class Country
def as_origin
#default code
end
end
class Sweden < Country
def as_origin
#override default code here
end
end
There's also a bunch of good gems to help with country information if you want to avoid doing it all by hand
If I understand correctly you want to add different key-value pairs to a hash depending on the :country value in that hash. If so then a YAML file should work.
Say you have the following in a YAML file
# country_details.yaml
sweden:
province: 'SW'
il:
city: 'OM'
name: 'some name'
Then you can define a method
def country_details(country)
parsed_yaml = YAML::load(File.open('path/to/file'))
details = parsed_yaml[country]
end
that you use like so
details = country_details('SW')
params[:location][location].merge! details
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
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