How to pass parameters to a symbol method call - ruby-on-rails

I have a model concern which adds a method 't' allowing the models fields to be translated va I18n:
module Translatable
extend ActiveSupport::Concern
def t(field_name)
I18n.t("models.#{self.class.table_name}.#{translation_tag}.#{field_name}")
end
Model:
class Model < ActiveRecord::Base
include Translatable
This works fine almost everywhere using:
#model.t(:name)
However I have a select field which uses this code to map the entries:
Model.all.order(name: :asc), :id, :name
And I want :name to use the translatable method instead. The below works, but I'm getting a missing argument error (quite clear why):
Model.all.order(name: :asc), :id, :t
However this doesn't work:
Model.all.order(name: :asc), :id, :t(:name)
What is the correct way to pass variables to methods when they are called as symbols?

You can't in this context.
Either create a dedicated method in your model to handle the name layout or use map before the select.
def i18n_name
t(:name)
end
select Model.all.order(name: :asc), :id, :i18n_name
or
Model.all.map { |p| [ p.t(:name), p.id ] }
More information here
Note: I would not recommend placing i18n translations in your models, it should be in presenters. Have a look at Draper

Related

Associating a temporary (non-DB) model to a simple_form

I have a simple_form, that doesn't really apply to a normal model. If I have the object just set to :thing it seems to work.
However, I want it to have some validation. In other questions, I've found that this means that I NEED to have a model... I'm not sure what needs to be in that model, however. So, I made I model, but I can't figure out how to hook them up.
class ClientEmail
include ActiveModel::Validations
validate :cannot_be_present
attr_accessor :to_domain
def cannot_be_present
newDomClients = Client.where("email like :foo", {:foo => "%#{to_domain}%"})
errors.add(:base, "There cannot be any emails already in the database with the target domain" ) if newDomClients.length > 0
end
end
and the simple_form is:
= simple_form_for(#client_email, url: { action: "update" }, html: {class: "search_form", method: :put }) do |f|
.input-row
= f.input :newDomain, :label => "New domain name", :required => true
(etc)
Initially, it said that #client_email was nil, so I initialized it (which seems unlikely to be necessary given Rails...) with:
- #client_email = ClientEmail.new
but that tells me that ClientEmail doesn't have a to_key method, so I'm clearly missing a bunch of infrastructure somewhere.
If you create a form with an object that belongs to a model you can't create fields that doesn't belongs to that model. You need to create form like this in order to do that:
= simple_form_for(:client_email, url......)
if you create form with symbol like this you can create any field in this form and send to controller that you want. params hash won't change too much and you can call your special fields like this:
params[:client_email][:your_field_name]
since this field doesn't belongs to a model its better to validate it in controller.
Hope it helps.
It appears that I needed:
include Virtus.model
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
I'm not sure exactly what Virtus is. it's probably possible without it, but it's a library I already have...

How should I store a collection for an attribute in Rails?

I have MyModel which has attribute category. There are only two possible categories Category 1 and Category 2. The attributes are assigned using a form as follows:
<%= f.input :category, collection: category_options %>
What is considered "good practice" in Rails. Should I save the attributes as a string in the db, or should I create a new table / reference for the collection?
Storing the category as a string has the benefit that it keeps the db clean, but will have to store the collections seperately in the controller. Also, since I'm using i18n, I would expect that storing the category as a string will lead to translation issues.
If according to business logic you are planning to have some attributes, methods or other things for each of your categories, you should create another model for Category. It would be less hard to implement your future needs in terms of managing complexity. If you are 100% sure that there will be no more extras about categories, you should make it an attribute with addition of some validations in your code(inclusion in category1, category2, for example).
For simple cases, I usually use something like this. You can i18n the UI choices, using the internal string as the key.
# View Code:
# <%= form.select :role, MyModel.categories, prompt: '' -%>
class MyModel < ActiveRecord::Base
validates :category,
presence: true,
inclusion: {
in: :categories,
allow_blank: true }
class << self
def categories
%w[hot warm cold]
end
end
def categories
self.class.categories
end
end

How to pass a parameter inside a simple_form group_method?

I'm trying to display active projects per party in a drop down list. active_projects is a method within the Party model. The grouped_collection_select code below works however, when I attempt to convert my form into a simple_form, my active_projects method is no longer recognised.
Below are my two code extracts. The first working correctly while the other causes an error.
# rails default
<%= f.grouped_collection_select(:project_id,
Party.all,
:"active_projects(#{date.strftime("%Y%m%d")})",
:party_name,
:id, :project_name) %>
# simple form
<%= f.input :project_id,
collection: Party.all, as: :grouped_select,
group_method: :"active_projects(#{date})" %>
I know this one is a little old but I have a solution to this problem using simple_form. I am not sure if it is the best solution but it does work.
Basically, the issue comes down to passing in a value to the group_method. In my case I had a class that needed to get the current_users company that he/she belongs to. My model/database structure was like this:
Type -> Category
In my case the Type records were global and did not belong to a specific company. However, the category model records did belong to a specific company. The goal is to show a grouped select with global types and then company-specific categories underneath them. Here is what I did:
class Type < ActiveRecord::Base
has_many :categories
attr_accessor :company_id
# Basically returns all the 'type' records but before doing so sets the
# company_id attribute based on the value passed. This is possible because
# simple_form uses the same instance of the parent class to call the
# group_by method on.
def self.all_with_company(company_id)
Type.all.each do |item|
item.company_id = company_id
end
end
# Then for my group_by method I added a where clause that reuses the
# attribute set when I originally grabbed the records from the model.
def categories_for_company
self.categories.where(:company_id => self.company_id)
end
end
So the above is a definition of the type class. For reference here is my definition of the category class.
class Category < ActiveRecord::Base
belongs_to :company
belongs_to :type
end
Then on my simple_form control I did this:
<%= f.association :category, :label => 'Category', :as => :grouped_select, :collection => Type.all_with_company(company_id), :group_method => :categories_for_company, :label_method => :name %>
Basically instead of passing in the value we want to filter on in the :group_method property we pass it in on the :collection property. Even though it will not be used to get the parent collection it is just being stored for later use in the class instance. This way, when we call another method on that class it has the value we need to do our filtering on the child.

When overriding an ActiveRecord Model, and defining a new attr_accessible, how do I add without duplicating?

When I am using a Rails Engine, and desiring to override and add to its behavior, I have faced the following problem:
Say the Engine has a ActiveRecord model named Course
module MyEngine
class Course < ActiveRecord::Base
attr_accessible :name, :description, :price
end
end
And I want to create a migration in my main Rails app, to add a column to it, and I need to add that new column to the attr_accessible (so it can be mass assigned)
MyEngine::Course.class_eval do
attr_accessible :expiration_date
end
But then Rails complains that the first 3 attrs are not Mass-Assignable, so instead of just "adding" the new attribute to the override, I have to re-declare all the attributes in the overridden class, like:
MyEngine::Course.class_eval do
attr_accessible :name, :description, :price, :expiration_date
end
Is there a better way to not re-declare these attributes, and just add the new attribute?
By looking at the source code:
# File activemodel/lib/active_model/mass_assignment_security.rb, line 174
def attr_accessible(*args)
options = args.extract_options!
role = options[:as] || :default
self._accessible_attributes = accessible_attributes_configs.dup
Array.wrap(role).each do |name|
self._accessible_attributes[name] = self.accessible_attributes(name) + args
end
self._active_authorizer = self._accessible_attributes
end
You could try to use one of the internal data structures to recover the attributes that have already been defined, so you don´t duplicate code, or you can hack your way and create a new method that let´s you "append" values to attr_accessible. But there is no code baked for that yet.

Dynamically determining Rails model attributes

I have a model (simplified version below - the full one has a lot more fields) on which I want to do a search.
class Media < ActiveRecord::Base
attr_accessible :subject, :title, :ref_code
validates :title, :presence => true
validates :subject, :presence => true
def self.search(terms_hash)
return if terms_hash.blank?
composed_scope = self.scoped
terms_hash.each_pair do |key, value|
if self.respond_to? key and not value.is_blank?
value.split(' ').each do |term|
term = "%#{term}%"
composed_scope = composed_scope.where("#{key} LIKE ?", term)
end
end
composed_scope
end
end
Since the advanced search form is almost identical to the form used to create/update instances of the model, I want to create a method that dynamically looks through the params list of the request and matches form fields to model attributes via the name. So if the search request was,
/search?utf8=✓&ref_code=111&title=test&subject=code&commit=Search
then my controller could just call Media.search(params) and it would return a scope that would search the appropriate fields for the appropriate value(s). As you can see I tried using respond_to? but that is only defined for instances of the model rather than the actual model class and I need something that ignores any params not related to the model (eg. utf8 and commit in this case). If this all works well, I plan to refactor the code so that I can reuse the same method for multiple models.
Is there a class method similar to respond_to?? How would you go about the same task?
I know this is related to get model attribute dynamically in rails 3 but it doesn't really answer what I'm trying to do because I want to do it on the the model rather than an instance of the model.
There is a columns method on ActiveRecord model classes so you can easily get a list of attribute names:
names = Model.columns.map(&:name)
You could remove a few common ones easily enough:
names = Model.columns.map(&:name) - %w[id created_at updated_at]
The accessible_attributes class method might also be of interest, that will tell you what has been given to attr_accessible.

Resources