I'm working on an app that downloads meta tags from websites and saves then. The downloading happens in a model called Site. I'd like to save off the downloaded robots meta tags into a model called robots_tag which is connected to sites via a join table called meta_tag_sites.
But the method that I've written in the sites model to do this isn't working. When I try to call the method in the console, I get the following error.
undefined method `robots_meta=' for []:ActiveRecord::Relation
Any idea what I'm doing wrong?
class Site < ActiveRecord::Base
attr_accessible :domain
belongs_to :user
has_many :meta_tag_sites
has_many :robots_tags, through: :meta_tag_sites
accepts_nested_attributes_for :robots_tags
# ...
def download_robots_meta_tags
robots_tags = Nokogiri::HTML(Net::HTTP.get(self.domain, "/")).xpath("//meta[#name='robots']")
robots_tags.each do |tag|
self.robots_tags.robots_meta = tag
end
end
# ...
end
class RobotsTag < ActiveRecord::Base
attr_accessible :robots_meta
has_many :meta_tag_sites
has_many :sites, through: :meta_tag_sites
end
class MetaTagSite < ActiveRecord::Base
attr_accessible :site_id, :meta_tag_id
belongs_to :site
belongs_to :robots_tag
end
(BTW, this post is related to an earlier post: Web-scraping Rails App Getting Over-Modelled?).
The problem is here:
self.robots_tags.robots_meta = tag
self.robots_tags is a collection of objects defined by has_many :robots_tags, and you're attempting to assign a specific attribute to that entire collection. You can't do this. If you want to assign to an attribute on a specific object, you have to either iterate over the collection, or select a specific object from the collection via first or last or any of the other Enumerable methods.
By inspection, the offending line appears to be:
self.robots_tags.robots_meta = tag
You should iterate over self.robots_tags instead, with something like:
self.robots_tags.each do |robot_tag|
robot_tag.robots_meta = tag
end
Related
I need some assistance with my Rails 4 associations. I have the following 4 models:
class User < ActiveRecord::Base
has_many :check_ins
has_many :weigh_ins, :through => :check_ins
has_many :repositionings, :through => :check_ins
end
class CheckIn < ActiveRecord::Base
belongs_to :user
has_one :weigh_in
has_one :repositioning
end
class Repositioning < ActiveRecord::Base
# belongs_to :user
belongs_to :check_in
end
class WeighIn < ActiveRecord::Base
# belongs_to :user
belongs_to :check_in
end
Question: If I am setup this way, how would I input repositionings and weigh_ins separately, but still have them linked through a single check in?
You would have to retain one of the other association's ID in order to make it work.
For example, let's say:
You have created a CheckIn.
You now add a Repositioning to that check in.
Store the ID of the repositioning object
When adding your WeighIn object, you would simply reference the correct CheckIn record: correct_checkin_record = CheckIn.where(repositioning: the_repositioning_id)
You can then add the WeighIn object to that particular record.
An alternative (and simpler) method would be to access the CheckIn directly through the User: correct_checkin_record = #user.checkin -- This would pull in the correct CheckIn every time.
I've included both options to help visualize exactly what is going on in the relation.
Do you want to have users input weigh_ins and repositionings on different pages?
Having weigh_ins and repositionings inputted separately but still be part of a single checkin is fine with that setup. Its just matter of getting the same check_in object and make the associations to that object, which can be done through the controller by passing in check_in ID params and do CheckIn.find(params[:id])
There is a cool feature for has_many in Rails. I can write
class Article < AR::Base
has_many :comments
has_one :another_association
and voila! method comment_ids= created, which I can use in strong parameters and mass assignment. Somethind like #article.comment_ids = [1,2,3]
I need something similar for has_one, like #article.another_association_id = 1. But I get NoMethodError exception. Is there any way to make this method works?
Has one has a different syntax.
#article.build_another_association
#article.create_another_association
See the guides for more info
Use attr_accessible
Specifies a white list of model attributes that can be set via mass-assignment
So you can add it like this assuming you created and ran the migration already:
class Article < AR::Base
has_many :comments
has_one :another_association
attr_accessible :another_association_id
But if it's Rails 4 you may need to handle it in the controller.
You have the direction of the association reversed.
With has_one, the other class should have an article_id to refer to a record in your articles table.
If you want articles.another_association_id, then you should specify belongs_to :another_association.
This method should only be used if the other class contains the foreign key. If the current class contains the foreign key, then you should use belongs_to instead.
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_one
If you want to simulate the way has_many works you could try this:
class Article < AR::Base
has_one :page
def page_id=(int_id)
self.page = Page.find(int_id)
end
end
#article.page_id = 3
Forgive me if this has already been asked (as I believe it has), but I couldn't find this exact issue (and it's very likely I'm not searching properly).
I have the following models:
class Company < ActiveRecord::Base
has_many :jobs
end
class Job < ActiveRecord::Base
belongs_to :company
has_and_belongs_to_many :tags
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :jobs
end
What I'm trying to accomplish is a list of tags by company, but I'm failing to figure out how (I should note that I'm pretty new to Ruby and Rails). I come from a .NET background, and with Linq I'd use something like Company.Jobs.SelectMany(j => j.Tags).
I tried to do Company.first.jobs.tags, which fails with NoMethodError: undefined method 'tags' for #<Job::ActiveRecord_Associations_CollectionProxy:0x892dca0>, but strangely enough, if I run Company.first.jobs.instance_methods on a rails console, there is a :tags method. And this is what I get when I use the console's autocomplete:
Any suggestions?
Thanks.
What you are looking for is called 'has many through'.
class Company < ActiveRecord::Base
has_many :jobs
has_many :tags, through: :jobs
end
This is how you can go through an association (in this case jobs) to get to that association's association.
This way you can run company.tags on an instance of a company.
The problem with your existing approach is that jobs is a collection, but you need to hit the method on individual instances. This would work with no adjustment:
Company.first.jobs.collect { |job| job.tags }
I have three models and they look like (simplified):
class Airline < ActiveRecord::Base
attr_accessible :name
has_many :airplanes
has_many :airplane_switches
end
class Airplane < ActiveRecord::Base
attr_accessible :airline_id, :register
belongs_to :airline
has_many :airplane_switches
end
class AirplaneSwitch < ActiveRecord::Base
attr_accessible :airline_id, :airplane_id
belongs_to :airplane
belongs_to :airline
end
Airplanes could have been in some Airlines, so I needed another model that indicates if an Airplane was in one or more Airlines.
I am building a form to let users upload some info about an Airplane, they just select the airplane register (callsign) and then they will get a list to choose in which Airline it was.
This will work over an AJAX request. But, I am trying to figure out how to show the Airline name from my controller, to avoid another AJAX call by fetching another JSON file just to get the name of the Airline based on the airline_id in AirplaneSwitch.
#airplane = Airplane.find_by_register(params[:register])
#airplane_switches = #airplane.airplane_switches # Here I need to join also each Airline.name
I think this way would be more efficient, but I have no idea if it's possible to do.
This should work:
#airplane.airplane_switches.select('*, airlines.name as airline_name').joins(:airline)
Let's say you have variable airplane_switch that contains AirlineSwitch instance fetched in that way. All you need to do to get your airline name is:
airplane_switch.airline_name
I have a simple has_many through relationship set up:
class Tag < ActiveRecord::Base
has_many :profile_tags
has_many :profiles, :through => :profile_tags
end
class ProfileTags < ActiveRecord::Base
belongs_to :profile
belongs_to :tag
end
class Profile < ActiveRecord::Base
has_many :profile_tags
has_many :tags, :through => :profile_tags
end
From my view I am accepting a set of tags (just strings), and am iterating over them in my controller and calling Tag.create( ... ) on each of them, and pushing them into an array. This all works fine.
So I get to a point where I have an Array of Tag objects (tags) which were each returned by the call to create, and variable #profile which was created by doing Profile.new
I would like to do: #profile.tags = tags
Doing this causes this error on the line where I try the assignment:
uninitialized constant Profile::ProfileTag
Rails is acting like I need to manually create and assign the join table association, even though here http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association it states that when you do an assignment like this, new associations will be created and if some are gone they will be deleted.
Any ideas what I could be doing wrong here?
Rails assumes that model classes are named with the singular form, i.e. the class ProfileTags should be called ProfileTag.
Depending on which Rails version you are using, probably the easiest way to fix this is to re-create the model using script/destroy and script/generate in Rails 2.x or rails destroy and rails generate in Rails 3.
Alternatively, specifying the class name manually by adding :class_name => 'ProfileTags' to the has_many declarations should work too.