I am using an API and receiving a array of hashes. Lets say:
array = client.getObjects(123) //where 123 is some collection of object ID
I want to add some additional attributes to the array to use later in my view, like:
<%= array.getRequestor %> // return a string
What is the easiest way to do this? I was thinking about creating a new class that extends array but I wanted to know can I just add a string "requestor" attribute a lot easier?
Thanks
Extending a core class is not a good idea in general, especially when the additional responsibilities you want to add in are specific to your functional domain.
6 months down the line, somebody (perhaps yourself) will be trying to debug the code and wondering why does Array expose a random custom method.
It would be better to explicitly define your custom view object, perhaps by using a Struct, eg:
# my_view_object.rb
class MyViewObject < Struct.new(:hash)
def getRequestor
# manipulate / return specific hash data
end
end
# controller
#view_obj = MyViewObject.new(client.getObjects(123))
# view
#view_obj.hash # original hash
#view_obj.getRequestor # your custom attribute
Note that the intent of a Struct is to represent a custom data structure, not behaviour. If your custom method needs to do unrelated work, you might want to use a PORO (Plain Old Ruby Object) instead.
I'd say that extending Array sounds like a really bad idea. I would suggest you instead wrap the array in hash of your own. For example
my_hash = {getRequestor: ?, array: array}
and then use it like
<%= my_hash.getRequestor %>
as in your example code.
Related
I am writing a seed file that will make several API calls via HTTParty in order to populate the database. I am pulling the same information for several different models and I would like to be able to use a single method for all of them. However, I cannot figure out how to reference the model name through a variable. Specifically I am having difficulties because each of these must belong to another model. I have tried the following:
def create_assets(subject, model, geokit_hoods)
response = HTTParty.get("https://raw.githubusercontent.com/benbalter/dc-maps/master/maps/#{subject}.geojson")
parsed = JSON.parse(response)
collection = parsed["features"]
collection.each do |station|
coordinates = station["geometry"]["coordinates"].reverse
point = Geokit::LatLng.new(coordinates[0], coordinates[1])
geokit_hoods.each do |hood|
if hood[1].contains?(point)
hood[0][model].create(coordinates: coordinates, name: station["properties"]["NAME"], address: station["properties"]["ADDRESS"])
break
end
end
end
end
Which I called via the following:
create_assets("metro-stations-district", "metros", geokit_hoods)
hood[0] refers to an existing neighborhood model, and hood[1] is the polygon associated with that neighborhood. The code works when referring to hood[0].metros.create(...), but I am looking for a way to make this method useful across many models.
Any ideas would be appreciated!
For now I'm going to assume that what you have in the variable is a String that is the name of the class in table-name format. eg in your example you have metros in the variable... from that I assume you have a Metro class which you are trying to create.
If so... you first need to convert your lowercase table-name style variable ("metros") into a class name-style eg "Metro"
Note: this is title cased and singular (rather than plural).
Rails has a method to do this to strings for exactly the purpose you want: classify eg you could use it thus:
model_name = hood[0][model] # 'metros'
model_name.classify # 'Metro'
Note that it's still just a string, and you can't call create on a string.. so how do you make it the real class? constantize
Use this to turn the string into the actual model-class you're trying to find... which you can then call create on eg:
model_name = hood[0][model] # 'metros'
the_klass = model_name.classify.constantize # Metro
your_instance = the_klass.create(...)
ruby_on_rails rails 4 assignment non-screen data to insert record
Rather than using screen values (e.g. simple_form_for #user_evaluation_result) to populate the columns to insert a row I need to calculate some of the values in controller.
For example if I have these statements in the controller
….
# which if I had simple_form_for user_evaluation_result would get populated by the screen
#user_evaluation_result = UserEvaluationResult.new(user_evaluation_result_params)
….
# standard stuff I use for screen derived updates
def user_evaluation_result_params
params.require(:user_evaluation_result).
permit(:evaluation_assumption_id,
:company_listing_id,
:target_share_price_dollars )
end
How do I assign values to :user_assumption_id etc so that insert works. I have tried all sorts of statements. Alternatively do I use another format instead of calling "def user_evaluation_result_params".
Thanks in advance - Pierre
I'm hoping I've interpreted the question properly...
First, to make sure we're on the same page... The code inside of your user_evaluation_result_params method is using Strong Parameters to create an ActionController::Parameters object for the purpose of protecting your model from unpermitted mass-assignments. So, in general, when you're creating or updating an ActiveRecord object from a form in a view template, you want to use Strong Parameters so that users can't manipulate your form to set attributes that you're not expecting.
That said, if you want to set attributes on an object you don't have to use mass assignment. Here is an example of using one-at-a-time assignment (the opposite of mass-assignment):
obj = MyObject.new
obj.attr_one = "One"
obj.attr_two = "Two"
obj.save
There is nothing wrong with this approach other than that it's kind of a lot of work for the general case. So mass-assignment just saves us from having to do this all the time. But it sounds like this one-at-a-time assignment is what you're wanting in this case. So try something like this:
def create
#user_evaluation_result = UserEvaluationResult.new
# assuming you have a UserAssumption object instance in `my_user_assumption`
#user_evaluation_result.user_assumption = my_user_assumption
#user_evaluation_result.some_other_attr = "some value"
#user_evaluation_result.save
end
Note, instead of setting #user_evaluation_result.user_assumption_id directly, as you asked about, it is preferred to set the actual object association as I did above. Try to keep associations outside of mass-assignment and use object relationships to build up your object graphs.
Or, if you have some attributes coming from a form you can mix and match the two approaches:
def create
#user_evaluation_result = UserEvaluationResult.new(user_evaluation_result_params)
# assuming you have a UserAssumption object instance in `my_user_assumption`
#user_evaluation_result.user_assumption = my_user_assumption
#user_evaluation_result.some_other_attr = params[:user_evaluation_result][:some_other_attr]
#user_evaluation_result.save
end
private
def user_evaluation_result_params
params.require(:user_evaluation_result)
.permit(:evaluation_assumption_id,
:company_listing_id,
:target_share_price_dollars)
end
There is the following model for 'delivery_types' table:
class DeliveryType < ActiveRecord::Base
end
I want to determine a special delivery type, for example, "DELIVERY_BY_TIME", and I want that this const returns DeliveryType.first (I'll put info about this type in my table later). Is it possible? How can I do it? Thanks.
I don't think you can do this, as this is no "real const". What you could do though, is creating a class method, called "by_time", which returns your "by_time" object. I would also not rely on the fact that this is your "first" object. Rather I would use a "find_or_create_by_name("BY_TIME"), which always makes sure you deliver the right object. Combined, something like
def self.by_time
##by_time||= find_or_create_by_name!(name: 'BY_TIME')
end
def by_time?
self == DeliveryType.by_time
end
If you read "Rails anti-patterns", they discourage you from making separate classes for "status" fields. They recommend to just use a string for that in your parent object, with some validators that limit the list of values though...
My array contains a varying amount of objects. I need to iterate through the array and save each object id as a unique variable. Given the amount of objects within the array will vary, how should I do this?
"items"=>[{"id"=>"B00668BTCI"}, {"id"=>"B0041KJSL2"}]
I need to save the information to a new object that can support up to 16 IDs. #object.id_one, #object.id_two, etc...
The suitable way to save your data all depends upon how you want to reference it or access it later. Meta-programming is interesting and fun, but may be overkill depending upon your needs. You will need to determine that after looking at the choices. An alternative way is in an array:
array_of_ids = items.map(&:values).flatten
Or
array_of_ids = items.map { |item| item["id"] }
Then all of the IDs are in the array array_of_ids and becomes, in your example:
["B00668BTCI", "B0041KJSL2"]
Accessible by:
array_of_ids[0] # first id
array_of_ids[1] # second array
...
You need to do some meta-programming here...
Here is a post for you, it has an answer (by Chirantan) that shows how to create instance variables dynamically.
Hope this helps.
EDIT
Just in case you get interested to learn more, I have also found a good article about creating methods dynamically, check it out.
Dynamically adding class methods in Ruby by Ryan Angilly
I was wondering If I could modify objects using a class method.
For example, users = User.scoped # This will select all the objects
And, suppose, I want to assign a variable for each of the object there is. Let's say, I want them to share a single value. So, when I try to access, for example, users.first.my_variable it would produce the value, I want.
My naive implementation:
def self.set_my_variable(variable_value)
scoped.tap do |obj|
obj.my_variable = variable_value
end
end
So, ideally, when I wan't this variable to be set, I should call the class method like this: users.set_my_variable("hello, stackoverflow")
But, when I try accessing the variable through arbitrary object of the set, like this:
users.first.my_variable
I get nil. Comparing .object_id's in both: obj.object_id and users.first.object_id shows that they are different. Why so ? I thought that they share the same reference (pointing to the same objects)
Any way of fixing it (preferrably without passing a collection to this class method) ?
you need to save object to database:
obj.my_variable = variable_value
obj.save