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.
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(...)
I've got a method in one of my models which returns data which will be fed into a charting gem.
class MyModel < ActiveRecord::Base
def ownership_data
format_data(item_ownerships.group(:owned).count)
end
end
I need to guarantee that the data return always has 2 values in the result. Something like this:
{ "yes" => 4, "no" => 2 }
In order to do this, I've written another method which is used in the first method:
def format_data(values)
values[false].nil? ? values = values.merge({ "no" => 0 }) : true
values[true].nil? ? values = values.merge({ "yes" => 0 }) : true
return values
end
My question is, where should this method go and how can I unit test it using rspec? I've currently got it in the model, however in trying to test it with rspec, my specs look like this:
let(:values) { { "yes" =>2 } }
it "it will return 2 values" do
result = MyModel.new.format_data(values)
expect(result.keys.count).to eq(2)
end
I'm not too happy about having to instantiate an instance of the model to test this. Any guidance is appreciated.
As AJ mentioned in the comment, we probably need more information to go on to give more specific advice. I'll give some anyway...
If you have a object that isn't necessarily depending on the model itself, you should consider moving it to a plain old ruby object or a service object. Those can live in a separate folder (lib/services) or in your models folder without inheriting from ActiveRecord::Base.
Your method could also be a class method def self.method_name(values, values_2) instead of a instance method (if you don't need specific values from the model).
When it comes to data manipulation for reporting for my projects, I've built specific folder of ruby service objects for those under 'lib/reports' and they take raw data (usually in init method) and return formatted data from a method call (allowing me to have multiple calls if the same data can be formatted in different output options). This makes them decoupled from the model. Also, this makes testing easy (pass in known values in Class.new expect specific values in method outputs.
Question: Is it possible to build a class method scope that can query objects based on values inside an array in a table? If yes, how can I do this?
In my example, I have a “wells” table that has an array field called “well_tags”. I want to build a query that returns all objects that have a specified value (such as “ceramic”) in the wells_tags array. The basic query would be something like this:
#well = Well.all
#query = #well.where(“well_tags contains ceramic”)
And then the class method scope would look something like this, with the “well_tag_search” param passed in from the controller:
class Well < ActiveRecord::Base
def self.well_tag_filter(well_tag_search)
if well_tag_search.present?
where(“well_tags contains ceramic")
else
Well.all
end
end
I found another post that asks a similar question (see link below), but I cannot get the answer to work for me...the result is always 'nil' when I know there should be at least 1 object. I am a beginner using sqlite (for now) as my database and rails 4.0.
Active Record Query where value in array field
Thanks!
UPDATE: some progress
I figured out how to create an array of all the objects I want using the ‘select’ method. But I still need to return the results as an Active Record object so I create a class method scope.
#well = Well.select
{ |well| if well.well_tags.present?
then well.well_tags.include? ‘ceramic' end }
#well.class #=> array
Not sure where Show is coming from.
Can you try doing Well.all instead of Show.all?
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...
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.