set instance variable equal to only specific model parameters - ruby-on-rails

def place
#place = Activity.find(params[:id])
end
How would I choose specific params? We have activity.address1,2, zip state etc and just need an instance variable we can use which is equal to just the address attributes.

You could do something like:
def place
#place ||= Activity.find(params[:id])
#address_attributes = #place.attributes.slice('address_1', 'address_2', 'zip', 'state')
end
In which case you'll have an instance variable that contains a hash of the specified attributes that you might use something like:
#address_attributes['address_1']
Personally, I find typing the '' around the key values annoying and prefer using symbols. In which case you might do something like:
def place
#place ||= Activity.find(params[:id])
#address_attributes = #place.attributes.with_indifferent_access.slice(:address_1, :address_2, :zip, :state)
end
Which you would use something like:
#address_attributes[:address_1]
But, even the brackets and symbols are a little annoying. So, you might do something like:
def place
#place ||= Activity.find(params[:id])
#address_attributes = OpenStruct.new(#place.attributes.with_indifferent_access.slice(:address_1, :address_2, :zip, :state))
end
Which you could then use something like:
#address_attributes.address_1
Now, personally, I don't like that long line in the place method, so I would be tempted to do something like:
def place
#place ||= Activity.find(params[:id])
#address_attributes = get_address_attributes
end
private
def get_address_attributes
OpenStruct.new(
#place.
attributes.
with_indifferent_access.
slice(
:address_1,
:address_2,
:zip,
:state
)
)
end
Now, if this is all in a controller, and you're setting the #address_attributes variable just so that you can use it in a view, then maybe that's good enough.
But, if you're using the #address_attributes variable elsewhere in the current instance, then you might consider doing something like:
attr_accessor *%w(
address_attributes
).freeze
delegate *%w(
address_1
address_2
zip
state
), to: :address_attributes, prefix: place
def place
#place ||= Activity.find(params[:id])
#address_attributes = get_address_attributes
end
private
def get_address_attributes
OpenStruct.new(
#place.
attributes.
with_indifferent_access.
slice(
:address_1,
:address_2,
:zip,
:state
)
)
end
In which case, you could make calls like:
place_address_1
Which seems a lot nicer than something like:
#address_attributes['address_1']

Related

Ruby metaprogramming to achieve dynamic methods?

Want to achieve the following code using metaprogramming.
#resource = {}
#voters = {}
#is_upvoted = {}
def resource(comment)
#resource[comment.id]
end
def voters(comment)
#voters[comment.id]
end
def is_upvoted(comment)
#is_upvoted[comment.id]
end
How can I create these methods using ruby metaprogramming and access the hash?
Can you tell me what is wrong in my code ?
['resource', 'voters', 'is_upvoted'].each do |attribute|
define_method("#{attribute}") do |comment|
instance_variable_set("##{attribute}", comment.id)
end
end
This bit seems redundant:
#resource = {}
#voters = {}
#is_upvoted = {}
Since you're already looping an array to do your metaprogramming.
You might try something like:
class Foo
%w(
resource
voters
is_upvoted
).each do |attr_sym|
define_method attr_sym do |comment|
instance_variable_set("##{attr_sym}", {}) unless instance_variable_get("##{attr_sym}")
instance_variable_get("##{attr_sym}")[comment.id]
end
end
end
Which I believe will give you methods roughly like:
class Foo
def resource(comment)
#resource ||= {}
#resource[comment.id]
end
end
Personally, it seems not great to me to have comment.id in your method. Because what if someday you want to use a different attribute (or something else altogether) as the key?
So, I think I would do:
class Foo
%w(
resource
voters
is_upvoted
).each do |attr_sym|
define_method attr_sym do |key|
instance_variable_set("##{attr_sym}", {}) unless instance_variable_get("##{attr_sym}")
instance_variable_get("##{attr_sym}")[key]
end
end
end
Now, it seems like you're going to want an easy way to set key-value pairs on your instance variable, so I guess I would try something like:
class Foo
%w(
resource
voters
is_upvoted
).each do |attr_sym|
define_method attr_sym do |key=nil|
instance_variable_set("##{attr_sym}", {}) unless instance_variable_get("##{attr_sym}")
hsh = instance_variable_get("##{attr_sym}")
return hsh[key] if key
hsh
end
end
end
In which case you should be able to do (assuming you have a #comment variable that responds to id):
#comment.id
=> 1
foo = Foo.new
=> #<Foo:0x000056536d7504b0>
foo.resource
=> {}
foo.resource[#comment.id] = :bar
=> :bar
foo.resource
=> {1=>:bar}
foo.resource[#comment.id]
=> :bar
Can you tell me what is wrong in my code ?
It's doing the equivalent of this:
def resource(comment)
#resource = comment.id
end
instance_variable_get would be a better choice.
This is how I used it and it works
['resource', 'voters', 'is_upvoted'].each do |attribute|
define_method("#{attribute}") do |comment|
instance_variable_get("##{attribute}")[comment.id]
end
end

Rails multiple params permit calls

I am trying to use multiple permits in a single method similar to the following (psuedocode)
def index
model.create(
params.permit(:b, :c)
)
params.permit(:a)
end
This is my actual code
def create
params.permit(:create_special_categories)
balance_sheet = ::BalanceSheet.create!(
balance_sheet_params.merge(date: Time.zone.now.to_date, entity: #entity)
)
balance_sheet.create_special_categories if params[:create_special_categories]
render json: balance_sheet, serializer: ::Api::V3::BalanceSheetSerializer
end
def balance_sheet_params
params.permit(
:id,
:entity,
:entity_id,
:date,
:name
)
end
However, I get the following error...
ActionController::UnpermittedParameters:
found unpermitted parameter: :create_special_categories
UPDATE
my solution was to avoid strong parameters all together.
def create
balance_sheet = ::BalanceSheet.new(
date: Time.zone.now.to_date, entity: #entity
)
balance_sheet.name = params[:name]
balance_sheet.save!
balance_sheet.create_special_categories if params[:create_special_categories]
render json: balance_sheet, serializer: ::Api::V3::BalanceSheetSerializer
end
This line doesn't have any effect, params.permit are not chained or added to a previous permit, you must use the result, that is why it's almost always used in a separate method.
params.permit(:create_special_categories)
What you must do is use what that returns for your following statements
permitted_params = params.permit(:create_special_categories)
Model.create(permitted_params)
...however you really should outsource this to a special method like you already have. You will have to tweak this to your use-case obviously.
def balance_sheet_params
if params[:create_special_categories]
params.permit(:id,
:entity,
:entity_id,
:date,
:name,
:create_special_categories)
else
params.permit(
:id,
:entity,
:entity_id,
:date,
:name)
end
end

Rails Controller: how do you account for optional and required parameters?

In the tutorials I have seen, create has been implemented like this:
def create
#note = Note.new(note_params)
#note.save
redirect_to #note
end
private
def note_params
params.require(:note).permit(:title, :type, :description, :dueDate)
end
I have read about the fetch method for optional parameters, but how do I mix the two methods to require some parameters and permit others? Would it be like this:
private
def note_params
params.require(:note).permit(:title, :type)
params.fetch(:note, {}).permit(:description, :dueDate)
end
In this case, could I expect to pass 0, 1, or 2 of the fetched params?
You're reading it wrong, I think. In this line
params.require(:note).permit(:title, :type, :description, :dueDate)
The required parameter is :note. And :title, :type and others are simply permitted/allowed to appear under :note. None of them are required by this syntax. If you really need :title to be there, that is best handled by presence validation on your Note model.
class Note
validates_presence_of :title
end
Now, if you don't pass params[:note][:title], #note.save will return false and you can render form with user-friendly errors (highlight missing fields, etc.). You wouldn't be able to do that (as easily) if note_params method raised an exception on missing title attribute. So that is how you handle required record attributes in rails.

Strong Parameters: How to permit parameters using conditions

I wan't to permit certain parameters depending on the current user's role.
E.g: only permit the role attribute if the user is an administrator.
Is this possible?
Yes, it's possible.
You can do something like this :
def user_params
# List of common params
list_params_allowed = [:email, :title, :last_name, :first_name, :phone]
# Add the params only for admin
list_params_allowed << :role if current_user.admin?
params.require(:user).permit(list_params_allowed)
end
This way, if later you have new params, you only have to add in one list (avoids error).
If you have more than one param to add for the admin, you can do this like this :
list_params_allowed << :role << other_param << another_param if current_user.admin?
Hope this help.
You can simply do the following:
def post_params
allowed = [:name, :age]
conditional = Some_Condition_Applies ? [:title, :description] : []
params_list = allowed + conditional
params.require(:post).permit(params_list)
end

Casting ActiveModel attribute on return

In my Rails 3.1.1 project I have an ActiveModel that talks to API (ripped from Paul Dix's book, shortened for readability):
class Job
include ActiveModel::Validations
include ActiveModel::Serializers::JSON
ATTRIBUTES = [ :id,
:title,
:description,
:company_id ]
attr_accessor *ATTRIBUTES
validates_presence_of :title, :description
validates_numericality_of :company_id, :id
def initialize(attributes = {})
self.attributes = attributes
end
def attributes
ATTRIBUTES.inject(
ActiveSupport::HashWithIndifferentAccess.new
) do |result, key|
result[key] = read_attribute_for_validation(key)
result
end
end
def attributes=(attrs)
attrs.each_pair {|k, v| send("#{k}=", v)}
end
def read_attribute_for_validation(key)
send(key)
end
# More method definitions...
end
I instantiate #job in my controller, new action (company_id is a segnment key in the route: /companies/:company_id/jobs/new) like this:
#job = Job.new(company_id: params[:company_id])
Then, using CanCan, I check user's permissions to create to create a job. Basically, CanCan checks if current_user's company_id attribute matches job's company_id. This check fails because #job.company_id is returned as String.
Certainly, I can use params[:company_id].to_i while instantiating the object, but this seems like a workaround that I would have to repeat later.
Question: is there a way to make my Job ActiveModel more "type-aware" and make it return int for #job.company_id call?
I googled around, checked activemodel source code, but doesn't seem to find an answer. Any help is greatly appreciated.
Update
I was thinking more of something like schema block for ActiveModel, just like the one in ActiveResource.
attr_accessor *ATTRIBUTES
create a method like this:
def company_id
#company_id
end
You can just override that with
def company_id
#company_id.to_i
end
Answering my own question....
mosch's answer suggested to override the getter for company_id in my ActiveModel. However, I would have to repeat this for all of _id attributes in the model. Therefore, I decided to cast all of the '_id' attributes to integers while initializing the object. As follows:
def attributes=(attrs)
attrs.each_pair do |k, v|
if "#{k}".include? "_id"
send("#{k}=", v.to_i)
else
send("#{k}=", v)
end
end
end
I'm assuming your Company has_many => :jobs? If so, you could try
def new
#company = Company.find(params[:company_id])
#job = #company.jobs.new
end

Resources