Hi I have two model Company and Feed
company.rb
class Company < ActiveRecord::Base
attr_accessible :rss_url, :name
has_many :feeds
end
feed.rb
class Feed < ActiveRecord::Base
attr_accessible :guid, :name, :published_at, :summary, :url
after_create { |feed| FeedEntry.update_from_feed(feed.feed_url) }
belongs_to :company
def self.update_from_feed(rss_url) ?????
feed = Feedzirra::Feed.fetch_and_parse(rss_url) ?????
add_entries(feed.entries)
end
end
How two take RSS_URL from company to feed ?
I don't know what add_entries does, but if you want to access the rss_url of Company inside Feed, you can use the association. Define this instance method on your Feed class.
def do_something
url = self.company.rss_url
puts "This feed belongs to a company with the following rss url: #{url}"
end
Related
I have 3 models: Employers, Partners and Collaborations.
As an Employer, I want to add a record to my Partner model and to my Collaboration model to be able to indicate a collaboration between a Partner and a Employer. I therefore have the following columns in my database/tabels.
Models
class Employer < ActiveRecord::Base
has_many :collaborations
has_many :partners, :through => :collaborations
end
class Partner < ActiveRecord::Base
has_many :collaborations
has_many :employers, :through => :collaborations
accepts_nested_attributes_for :collaborations
end
class Collaboration < ActiveRecord::Base
belongs_to :employer
belongs_to :partner
end
Tables
Collaborations
employer_id:integer
partner_id:integer
tarive:string
Partners
added_by:integer
name:string
Because I want to be able to add a Partner/Collaboration within 1 form, I use nested forms. So I can add a partner (name, etc) and a collaboration (tarive, etc) in one go.
My (simple_form) form looks like this (I have named_space resource).
Te reduce clutter, I removed as much HTML mark_up as I could, this is not the issue.
Form
/views/employer/partners/_form
= simple_form_for [:employer, #partner], html: { multipart: true } do |f|
Partner
= f.input :name, input_html: { class: 'form-control' }
= f.simple_fields_for :collaborations do |ff|
Tarive
= ff.input :tarive, input_html: { class: 'form-control' }
= f.button :submit, "Save"
My controller looks like
class Employer::PartnersController < ActionController::Base
def new
#partner = Partner.new
#partner.collaborations.build
end
def create
#partner = Partner.new(partner_params)
#partner.collaborations.build
#partner.added_by = current_employer.id
#partner.collaborations.employer_id = current_employer.employer_id
#partner.collaborations.partner_id = #partner.id
#partner.collaborations.added_by = current_employer.id
if #partner.save
redirect_to employer_partner_path(#partner), notice: "Succes!"
else
render 'new'
end
end
def partner_params
params.require(:partner).permit(:id, :name, collaborations_attributes: [:id, :employer_id, :partner_id, :tarive])
end
end
Problem
The problem/question I have is this. The attributes are assigned nicely and added in the model. But I want to add a employer_id as well, which I have in current_employer.employer.id (Devise). I do not want to work with hidden forms, just to avoid this issue.
I assigned 'parent' models always like #partner.added_by = current_employer.id and that works beautifully.
When I use:
#partner.collaborations.employer_id = current_employer.employer_id
I get an error, saying #partner.collaborations.employer_id is empty.
Question
How can I assign a variable to the nested_form (Collaboration) in my controller#create?
Or more specifically: how can I assign current_employer.employer_id to #partner.collaborations.employer_id?
There are several ways:
Merge the params
Deal with objects, not foreign keys
Personally, I feel your create method looks really inefficient. Indeed, you should know about fat model skinny controller - most of your associative logic should be kept in the model.
It could be improved using the following:
#app/controllers/employers/partners_controller.rb
class Employers::PartnersController < ApplicationController
def new
#partner = current_employer.partners.new #-> this *should* build the associated collaborations object
end
def create
#partner = current_employer.partners.new partner_params
#partner.save ? redirect_to(employer_partner_path(#partner), notice: "Succes!") : render('new')
end
private
def partner_params
params.require(:partner).permit(:id, :name, collaborations_attributes: [:tarive]) #when dealing with objects, foreign keys are set automatically
end
end
This would allow you to use:
#app/views/employers/partners/new.html.erb
= simple_form_for #partner do |f| #-> #partner is built off the current_employer object
= f.input :name
= f.simple_fields_for :collaborations do |ff|
= ff.input :tarive
= f.submit
... and the models:
#app/models/partner.rb
class Partner < ActiveRecord::Base
belongs_to :employer, foreign_key: :added_by
has_many :collaborations
has_many :employers, through: :collaborations
accepts_nested_attributes_for :collaborations
end
#app/models/collaboration.rb
class Collaboration < ActiveRecord::Base
belongs_to :employer
belongs_to :partner
belongs_to :creator, foreign_key: :added_by
before_create :set_creator
private
def set_creator
self.creator = self.employer_id #-> will probably need to change
end
end
#app/models/employer.rb
class Employer < ActiveRecord::Base
has_many :collaborations
has_many :employers, through: :collaborations
end
This may not give you the ability to set tarive, however if you cut down the manual declarations in your model, we should be able to look at getting that sorted.
The main thing you need to do is slim down your code in the controller. You're being very specific, and as a consequence, you're encountering problems like that which you mentioned.
I read this interesting article about Using Polymorphism to Make a Better Activity Feed in Rails.
We end up with something like
class Activity < ActiveRecord::Base
belongs_to :subject, polymorphic: true
end
Now, if two of those subjects are for example:
class Event < ActiveRecord::Base
has_many :guests
after_create :create_activities
has_one :activity, as: :subject, dependent: :destroy
end
class Image < ActiveRecord::Base
has_many :tags
after_create :create_activities
has_one :activity, as: :subject, dependent: :destroy
end
With create_activities defined as
def create_activities
Activity.create(subject: self)
end
And with guests and tags defined as:
class Guest < ActiveRecord::Base
belongs_to :event
end
class Tag < ActiveRecord::Base
belongs_to :image
end
If we query the last 20 activities logged, we can do:
Activity.order(created_at: :desc).limit(20)
We have a first N+1 query issue that we can solve with:
Activity.includes(:subject).order(created_at: :desc).limit(20)
But then, when we call guests or tags, we have another N+1 query problem.
What's the proper way to solve that in order to be able to use pagination ?
Edit 2: I'm now using rails 4.2 and eager loading polymorphism is now a feature :)
Edit: This seemed to work in the console, but for some reason, my suggestion of use with the partials below still generates N+1 Query Stack warnings with the bullet gem. I need to investigate...
Ok, I found the solution ([edit] or did I ?), but it assumes that you know all subjects types.
class Activity < ActiveRecord::Base
belongs_to :subject, polymorphic: true
belongs_to :event, -> { includes(:activities).where(activities: { subject_type: 'Event' }) }, foreign_key: :subject_id
belongs_to :image, -> { includes(:activities).where(activities: { subject_type: 'Image' }) }, foreign_key: :subject_id
end
And now you can do
Activity.includes(:part, event: :guests, image: :tags).order(created_at: :desc).limit(10)
But for eager loading to work, you must use for example
activity.event.guests.first
and not
activity.part.guests.first
So you can probably define a method to use instead of subject
def eager_loaded_subject
public_send(subject.class.to_s.underscore)
end
So now you can have a view with
render partial: :subject, collection: activity
A partial with
# _activity.html.erb
render :partial => 'activities/' + activity.subject_type.underscore, object: activity.eager_loaded_subject
And two (dummy) partials
# _event.html.erb
<p><%= event.guests.map(&:name).join(', ') %></p>
# _image.html.erb
<p><%= image.tags.first.map(&:name).join(', ') %></p>
This will hopefully be fixed in rails 5.0. There is already an issue and a pull request for it.
https://github.com/rails/rails/pull/17479
https://github.com/rails/rails/issues/8005
I have forked rails and applied the patch to 4.2-stable and it works for me. Feel free to use my fork, even though I cannot guarantee to sync with upstream on a regular basis.
https://github.com/ttosch/rails/tree/4-2-stable
You can use ActiveRecord::Associations::Preloader to preload guests and tags linked, respectively, to each of the event and image objects that are associated as a subject with the collection of activities.
class ActivitiesController < ApplicationController
def index
activities = current_user.activities.page(:page)
#activities = Activities::PreloadForIndex.new(activities).run
end
end
class Activities::PreloadForIndex
def initialize(activities)
#activities = activities
end
def run
preload_for event(activities), subject: :guests
preload_for image(activities), subject: :tags
activities
end
private
def preload_for(activities, associations)
ActiveRecord::Associations::Preloader.new.preload(activities, associations)
end
def event(activities)
activities.select &:event?
end
def image(activities)
activities.select &:image?
end
end
image_activities = Activity.where(:subject_type => 'Image').includes(:subject => :tags).order(created_at: :desc).limit(20)
event_activities = Activity.where(:subject_type => 'Event').includes(:subject => :guests).order(created_at: :desc).limit(20)
activities = (image_activities + event_activities).sort_by(&:created_at).reverse.first(20)
I would suggest adding the polymorphic association to your Event and Guest models.
polymorphic doc
class Event < ActiveRecord::Base
has_many :guests
has_many :subjects
after_create :create_activities
end
class Image < ActiveRecord::Base
has_many :tags
has_many :subjects
after_create :create_activities
end
and then try doing
Activity.includes(:subject => [:event, :guest]).order(created_at: :desc).limit(20)
Does this generate a valid SQL query or does it fail because events can't be JOINed with tags and images can't be JOINed with guests?
class Activity < ActiveRecord::Base
self.per_page = 10
def self.feed
includes(subject: [:guests, :tags]).order(created_at: :desc)
end
end
# in the controller
Activity.feed.paginate(page: params[:page])
This would use will_paginate.
Example:
I have the following:
class Person < ActiveRecord::Base
has_many :educations
end
class Education < ActiveRecord::Base
belongs_to :school
belongs_to :degree
belongs_to :major
end
class School < ActiveRecord::Base
has_many :educations
# has a :name
end
I want to be able to return all people who went to a specific school so in my PeopleController#index I have
#search = Person.search do
keywords params[:query]
end
#people = #search.results
How do I create the searchable method on the Person model to reach down into school? Do I do something like this:
searchable do
text :school_names do
educations.map { |e| e.school.name }
end
end
which I would eventually have to do with each attribute on education (degree etc) or can I make a searchable method on Education and somehow "call" that from Person.searchable?
Thanks
It would be best if you keep the declaration of all the indexed fields for an specific model in the same place.
Also, you were doing a good job indexing :school_names, just do the same thing for the rest of the associations fields' that you want to index.
I am trying to validate emails given from a CSV list of emails. So I have created the invite_list virtual attribute where when given a list of emails, it will loop and create a new record in the invited_only_emails model.
Now the thing is, this works fine, but how can I catch the validation error thrown by InvitedOnlyEmail while looping in Users model so I'll be able to use that error in the controller?
This is my main model:
class Users < ActiveRecord::Base
attr_accessor :invite_list
attr_accessible :invite_list
has_many :invited_only_emails
def invite_list=(list)
list.split(",").each do |address|
self.invited_only_emails.create! :email => address
end
end
def invite_list
self.invited_only_emails.map {|email| email.email}.join(',')
end
end
And this is the invited_only_emails model:
class InvitedOnlyEmail < ActiveRecord::Base
attr_accessible :email
belongs_to :users
validates_format_of :email, :with => /^([\w\.%\+\-]+)#([\w\-]+\.)+([\w]{2,})$/i
end
Thanks!
I think you could use validates_associated method:
class User < ActiveRecord::Base
validates_associated :invited_only_emails
# ...
end
http://apidock.com/rails/ActiveRecord/Validations/ClassMethods/validates_associated
Have you looked at validates_associated?
http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_associated
I'm starting with Ruby on Rails and get'm making an application that takes the likes of a facebook user and transforms them into products, creating a personalized gift list.
Well, I have three models: user, and like product. They are thus:
class Like < ActiveRecord::Base
belongs_to :user
has_one :product
attr_accessible :category, :created_time, :name
end
class Product < ActiveRecord::Base
belongs_to :user
has_one :like
attr_accessible :image_url, :price_max, :price_min, :product_name,
:url
end
class User < ActiveRecord::Base
has_many :likes, dependent: :destroy
has_many :products, through: :likes, dependent: :destroy
attr_accessible :email, :password, :password_confirmation, :remember_me,:first_name, :last_name, :gender, :facebook_username, :provider, :uid
end
I am also using two gems that help me catch the likes and turn them into products. I have two methods I use in 'rails console' for this:
#Facebook Client
client = FBGraph::Client.new(
client_id: 'client_id',
secret_id: 'secret_id',
token: 'token'
)
#Getting and Recording Likes
like = client.selection.me.likes.info!.data.data
like.sort_by{ |like| like.created_time.to_i }.reverse.each do |like|
case like.category
when 'Musician/band' then category = 'band'
...
else next
Like.create(category: category, created_time: like.created_time, name: like.name, user_id: current_user.id)
end
#Product API Client
buscape = Buscape.new(
app_id: 'app_id',
sandbox: true
)
#Getting product through likes, and recording
def find_for_products
likes.each do |like|
case like.category
when 'band' then
product = #buscape.products.where(keyword: URI.encode(like.name), categoryId: 2921, results: 1)['product']
...
else next
end
Product.create(
image_url:product['thumbnail']['url'],
price_max:product['priceMax'],
price_min:product['priceMin'],
product_name:product['productName'],
url:product['links']['link'][0]['url']
)
end
end
Using the rails console, I can manually register products and likes using Like.new and Product.new, and assign it to a user accessing and using 'user.products', but I have no idea how to implement it and in my view and in my comtroller to catch the likes of current_user and produce products at the moment the user clicks a button 'Find products for me'
Can anyone help me?
If you're using resources, you can define a new action on User in your routes.rb file, like this:
resources :user do
member do
post 'find_products'
end
end
Then implement this action in the controller class.
One option is to put the code you have for querying FB and finding products into a self method in the Product model, something like Product.find_and_create_products(user). Or alternatively into a separate module in a new file in lib directory in your rails app. Then call it from the controller.
If this operation takes a long time, you can later add some additonal code to run it as a delayed job, with something like delayed_job.