How to create configuration files based on ruby - ruby-on-rails

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

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.

Serialize relations in remote model

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.

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

How to set a default value in a text_field?

I know this question has been asked a zillion times, but I still feel I'm missing something obvious. Given a model Address with a field city which we want to be initialized with a default value, say Awesome City.
What I've tried so far:
1) Default value in the view:
# #file: /app/views/addresses/_form.html.erb
<dt><%= f.label :city %></dt>
<dd><%= f.text_field :city, :value => 'Awesome city' %></dd>
This works but doesn't feel right. Also the user cannot change it, the value will still be Awesome city.
2) Overwriting the city method:
# #file: app/models/address.rb
def city
self[:city] ||= 'Awesome city'
end
The field is rendered empty, so for some reason it doesn't work. If you try Address.new.city you get the default value though.
3) Assign the default value in the controller:
# address doesn't have a controller, we use nested forms
# belongs_to :parent
# #file: /app/controllers/parents_controller.rb
def new
#parent = Parent.new
#parent.build_address
#parent.address.city = 'Awesome city'
end
This actually works but I don't think it should be in the controller, rather in the model.
4) Adding an after_initialize call (yes, it works, you don't have to overwrite the callback):
# #file: app/models/address.rb
after_initialize :set_default_city
def set_default_city
self.city = 'Awesome city'
end
This works as well, but this will be called every time an object is instantiated as well, which we don't want.
5) JavaScript, this seems like an extreme measure, it should be easier than this.
Bottom line: what's the best way to add a default value? Everytime I have to give a field a default value it seems I'm trying to hard for such a simple thing. Are there other options which I'm missing?
Edit:: I'm using Rails 3.1.4 with Ruby 1.8.7/1.9.2
You can define it in your migration file:
class CreateAddress < ActiveRecord::Migration
def change
create_table :addresses do |t|
...
t.string :city, default: "Awesome City"
...
t.timestamps
end
end
end
Other option is to define it in the new action, in your controller, which is more flexible... So, when you create a new address it will be started with the default value....
def new
#address = Address.new
#address.city ||= "Awesome City"
...
end
EDIT - possible solution to define default value in the model:
before_save :set_default_city
def set_default_city
self.city ||= 'Awesome city'
end
My ruby is a little rusty but something like
Parent < ActiveRecord::Base
def self.custom_parent(options)
custom_parent = self.new
if options.empty?
custom_parent.update_attributes(default_options)
else
//Iterate over all options and assign them to custom_parent
end
return custom_parent
end
end
you have to create the "default_options" hash
I don't think this will run out of the box but I think you got my thought

Gender Validation in Multiple Rails Models

Hey all -- this question is specifically about a gender validation, but I'm interested in hearing how you've all handled similar situations with much larger collections (Country selection, for example.)
I'm working on a system that lets athletes register for various events, and am currently working on a good gender validation. My question is, what's the best, most DRY way to run the same validation on many different models?
Let's say I want to validate the gender property of Event and User. I can create a helper for validates_each that checks values for inclusion in the very short array of ["male", "female"] before updating the gender attribute. But what if I want to access this same gender array in a form_for block, say, as an input to collection_select?
I have it working for one model -- I declare a GENDERS constant in Event, and have a short class method
def self.genders
GENDERS
end
for access by forms. But where should I store the array if multiple models need access?
EDIT: One idea would be to use a class method in the application controller. Any thoughts on how appropriate this approach is would be great.
Here's my solution. I like to go with the standard plugin-style libraries. I'd put this in lib/acts_as_gendered:
module ActsAsGendered
GENDERS = ['male', 'female']
def self.included(base)
base.extend(ActsAsGenderedMethods)
end
module ActsAsGenderedMethods
def acts_as_gendered
extend ClassMethods
include InstanceMethods
validates_inclusion_of :gender, :in => GENDERS
end
end
module ClassMethods
def is_gendered?
true
end
end
module InstanceMethods
def is_male
gender = 'male'
end
def is_female
gender = 'female'
end
def is_male?
gender == 'male'
end
def is_female?
gender == 'female'
end
end
end
Yeah, it might be overkill for simple genders, but you can see where all the pieces go - the GENDERS constant, the acts_as_gendered ActiveRecord hook, which then includes the class and instance methods and the validation.
Then, in config/initializers/gender.rb:
require 'acts_as_gendered'
ActiveRecord::Base.send(:include, ActsAsGendered)
Then, for the grand finale, the model:
class User < ActiveRecord::Base
acts_as_gendered
end
This pattern may seem overly complicated, but that's how most libraries end up eventually :)
UPDATE: To answer your comment, this is how I'd modify the acts_as_gendered method to make validations optional on a per-model basis:
def acts_as_gendered options={}
config = {:allow_nil => false}
config.merge(options) if options.is_a?(Hash)
extend ClassMethods
include InstanceMethods
if config[:allow_nil]
validates_inclusion_of :gender, :in => (GENDERS + nil)
else
validates_inclusion_of :gender, :in => GENDERS
end
end
Now you can call it in the User model like this:
class User < ActiveRecord::Base
acts_as_gendered :allow_nil => true
end
I could have made it a simple parameter you pass in, but I like the clarity of passing in a hash. And it sets you up for adding other options down the road.
You've got the right idea by storing it in a constant. The only thing I would do differently is put it in an initializer file so that it's not tied to any particular model like it is in your example. If you're worried about potential name collisions at the top level, you could put it in a module in the lib directory and include the module only in the places you intend to use it.
I agree with putting this in a constant. I'd also put the strings themselves in constants because (1) they can change, and (2) when used in a conditional, the system will catch if you mistype them. E.g., in your environment.rb:
MALE = 'male'
FEMALE = 'female'
GENDERS = [MALE, FEMALE]
And then in your code, you only ever refer to these constants, e.g.:
def male?
return gender == MALE
end
I would write a custom validation plugin (say, validates_gender). Then you would call:
class Event < ActiveRecord::Base
validates_gender :gender
end
Grab a copy of my validates_as_email plugin and use that, replacing value =~ EMAIL_ADDRESS_RE with your own logic.

Resources