Using Variables for Model Names in Active Record - ruby-on-rails

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(...)

Related

Handling conditions at class level based on association

I'm not sure how to frame this question, I am looking to solve a design problem.
I'm using ActiveRecord.
An Agency can have multiple documents.
documents has a column additional_details of type jsonb. Contains hash details.
additional_details column has different set of key value pair based on agency.
Example:
doc1 = agency1.documents.first.additional_details => { xml_url: '', ... }
doc2 = agency2.documents.first.additional_details => { feed_url1: '', ... }
agency1 and agency2 are instance objects of Agency.
When I make a call to fetch the url like document.additional_details.get_url
I can write conditions like
def get_url
if agency1.name == 'Utah'
return additional_details[xml_url]
elsif
so on
elsif
so on
end
end
Which is not a good practice I feel.
I believe we can solve this at class level. Note I need to solve this on presentation layer, I'm using decorators .
Edit:
An particular agency will have same keys within additional_details column but values are certainly different.
I am assuming these are Active Record classes? Where is agency1 defined? You should almost never be hard coded based on specific instances, instead any such data should be part of the record and its instance.
It is especially unclear why you have different types of "URL" at all, and not just a simple url string column in the documents table. Why does agency1 use xml_url but agency2 uses feed_url1?
But as an example, if each agency has say a prefix defined (say the domain name / filestore, and the documents just the relative/local address), say:
agency1.document_root = "https://example.com/documents/"
agency1.documents.first.rel_url = "web/rails/rails_example.pdf"
Then in the Document class you might do:
def url
agency.document_root + rel_url
end
Which then gives you the:
agency1.documents.first.url
Add a field to your Agency model where you store the key to the url in the additional data hash. Let's say you name the field url_key. Then you can do
def get_url
additional_details[agency.url_key]
end
Note that I assume that get_url is a method on the Document model.

How does the model respond to the method :find?

I have been trying to get used to Rails and Ruby for the last two months and I`ve got a small question that stuck in my mind in order to be able to see the bigger picture more clearly.
Let`s assume I have a model named Playlist and I already have some entries in my database:
class Playlist < ActiveRecord::Base
end
As far as I understand we can reach a model instance which should correspond to a raw in database via the following:
p = Playlist.find(2)
First, the result "p" is an instance of the model Playlist, right? Second, I am trying to understand where the ":find" method is implemented or how the model responds to it. Is it a class method for my model or a class method for ActiveRecord::Base? Because I couldn`t list ":find" in any of the outputs below.
puts p.class.methods(false).sort
puts p.class.superclass.methods(false).sort
puts p.class.superclass.superclass.methods(false).sort
Thanks.
It's a class method of Playlist that it has inherited from ActiveRecord::Base.
Try:
ActiveRecord::Base.methods
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation/finder_methods.rb
Try this to get the Classes and/or Modules containing the :find method:
x = Playlist.ancestors
arr = []
m = :find
x.each{ |i| arr << i if i.method_defined?(m) }
puts "The following Classes and/or Modules contain the :#{m.to_s} method:"
puts arr
The first printed result is where the method m is located. The results below it list the ancestor Class and/or Modules (in order) containing the 'original' method before modification (although they may contain different methods entirely using the same method-name). This may come in handy if you want to investigate the relevant method further.
N.B. Only public and protected methods are matched using the method_defined? method.

How to update table row from values determined within controller

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

Ruby on Rails Class method scoped objects references

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

<< operator in ruby

I am quite new to ruby,
I came across the following code in rails, but I don't know how the "<<" operator works and what it does in the below code
def <<( rate )
r = Rating.new
r.rate = rate
r.rateable = proxy_owner
...
...
end
class << ActiveRecord::Base
...
...
end
Can anybody explain to me?
Edit: here is the code https://github.com/azabaj/acts_as_rateable/blob/master/lib/acts_as_rateable.rb
def <<( rating ):
In your example, this is used to add a rating to a rateable model. (E.g. in acts_as_rateable.rb:41), similar to appending something to a string (str << "abc"). As it is within a module, it will only be included for the models that you declare as rateable.
class << ClassName:
All the methods inside of this block will be static / class methods (see this blog entry). (In this case, all the models will have the methods Model.example_static_method.)
Nearly all operators in Ruby are actually instance methods called on the object preceding them.
There are many different uses for << depending on the object type you're calling it on. For example, in an array this works to push the given value onto the end of the array.
It looks like this is for a Rails model object, so in that case I would say that this is an auxiliary method called when you append a model object to model object collection. For example, in this case you might be appending a Rating to a Product.
If you showed the whole method definition and showed what class it's in, I could provide a more specific answer.

Resources