I have 3 models source.rb belongs to category.rb and feed_entry.rb belongs to source.rb.
I need to display feed_entries in category
Category name
FeedEntry 1
FeedEntry 2
FeedEntry 3
Now it looks like this
class CategoriesController < ApplicationController
def show
#category = Category.find(params[:id])
#sources = #category.sources.all
end
end
show.html.erb
<%= #category.name %></h4>
<% #sources.each do |source| %>
<% source.feed_entries.each do |feed_entry| %>
<%= link_to feed_entry.name, feed_entry %>
<%= feed_entry.source.title %>
<% end %>
<% end %>
this is very slow
I use mongoid 4, rails 4
Models
class Category
include Mongoid::Document
field :name, type: String
has_many :sources, dependent: :destroy
end
class FeedEntry
include Mongoid::Document
field :name, type: String
belongs_to :source, touch: true
validates :source_id, presence: true
end
class Source
include Mongoid::Document
field :title, type: String
has_many :feed_entries, dependent: :destroy
belongs_to :category, touch: true
end
Some thinks to know :
Never use .all, unless you know size of result data. Always use pagination or limit.
When you have a loop like your each in view, this will call queries like this :
Give me a category
Give me its sources
Give me feed entries for source 1
Give me feed entries for source 2
....
You should eagler load your association like this :
#sources = #category.sources.limit(20).includes(:feed_entries)
It will to theses queries :
Give me a category
Give me its sources
Give me feed entries for theses sources
If you don't want any information about categories (like I think), you should add a relation to your model :
Class Category
has_many :sources
has_many :feed_entries, :through => :sources
end
Then call in your controller
#feed_entries = #category.feed_entries
This will do only ONE query :
Give me category
Give me the feed entries of the category
That's it !
I found a solution:
In Category.rb add feed_entries
class Category
def feed_entries
FeedEntry.in(source_id: sources.map(&:id))
end
end
and in show.html.erb
<% #category.feed_entries.includes(:source).each do |feed_entry| %>
<%= link_to feed_entry.name, feed_entry %>
<%= feed_entry.source.title %>
<% end %>
Related
I'm trying to create a Product form that has multiple sizes and prices for each of those sizes.
They way I have it modelled is a has_many :through relationship.
The associative table contains an extra field for price such that it will now hold the product_id, size_id, and price.
I'm not sure how I should go about creating my form or how Rails expects this to look. Any help would be much appreciated.
My Product is Cake :)
class Cake < ApplicationRecord
belongs_to :cake_type
has_many :cake_details
has_many :sizes, through: :cake_details
end
Size model
class Size < ApplicationRecord
has_many :cake_details
has_many :cakes, through: :cake_details
end
CakeDetail model
class CakeDetail < ApplicationRecord
belongs_to :cake
belongs_to :size
end
my migration
class CreateCakeDetails < ActiveRecord::Migration[5.1]
def change
create_table :cake_details do |t|
t.references :cake, foreign_key: true
t.references :size, foreign_key: true
t.decimal :price, :precision => 10, :scale => 2
t.timestamps
end
end
end
The only thing I'm stuck on is associating the form with the model.
i.e. for every size I want to have a text box with price associated with it.
This is currently how I'm approaching it but I have no idea how rails expects the id's of the text box to look or how I should structure this.
This is currently what I'm experimenting with in my form
<%= collection_check_boxes(:cake, :size_ids, Size.all, :id, :name) do |b| %>
<tr>
<td>
<%= b.label %>
</td>
<td>
<%= b.check_box %>
</td>
<td>
<%= form.text_field :cake_detail, id: b.label %>
</td>
</tr>
<% end %>
The way you define your business logic is normal
- A product has multiple sizes
- Each size has a price
The only thing I believe that it leads you to the problem is you are trying to create everything at the same time. Even Rails has nested_attributes which might solve your problem, but let's think once again.
Generally, Size records are fixed and was created beforehand. So that you don't have to create it at the same time with creating a Product.
Once you deal with this idea, your problem becomes much easier:
You had a list of Size: M, L, XL, XXL ... that was created beforehand
( You may create them via db/seeds.rb )
You want to create Product along ProductDetail with prices,
and link the ProductDetail with Size
Now you can use Rails's nested_attributes for the relation Product -> ProductDetail
Your model
# app/models/cake.rb
class Cake < ApplicationRecord
belongs_to :cake_type
has_many :cake_details
has_many :sizes, through: :cake_details
attr_accessor :is_enable
accepts_nested_attributes_for :cake_details, reject_if: :is_not_good_detail?
private
def is_not_good_detail?(attributed)
return true if attributed[:is_enable].to_i != 1
# Check if CakeDetail is good or not
end
end
Your controller
# app/controllers/cakes_controller.rb
class CakesController < ApplicationController
def new
#cake = Cake.new
# Build `cake_details`, so that you can render them at view
Size.each do |size|
#cake.cake_details.build(size_id: size.id, price: 0)
end
end
def create
# Create your Cake + CakeDetail
end
private
def cake_params
# permit your params here
end
end
Your view
# app/views/cakes/_form.html.erb
<%= form_for #cake do |f| %>
<%= f.fields_for :cakes_detail do |field| %>
<%= field.check_box :is_enable %>
<%= field.hidden_field :size_id %>
<%= field.text_field :price %>
<% end>
<% end %>
My code is completely not tested, and you still have a lot of things to do, but it should be the right way to solve your problem, tho.
You can consider the checklist to make it done:
Display name of size. Ex: XL, XXL
Permit the right params
Reject the invalid CakeDetail attribute set.
Avoid duplicate of size for a product when updating
<< Update >>>
Since the check_box only produces 0 and 1 value, so using it for size_id is incorrect. We can solve it by:
add an attr_accessor (ex: is_enable) for CakeDetail and use it for the check_box
size_id become a hidden field
Reject attributes if is_enable != 1
You can found here a working example yeuem1vannam/product-size
I have a model Person which can have many Cars and i want to create a nested form to create two records of car at once for which i am using accepts_nested_attributes_for. Allow to create two car records at once:
Hatchback
Sedan
A person with can leave the one or both of the car fields to be blank and i am using allow_blank to handle that.
Models:
#### Model: Car
class Car < ActiveRecord::Base
belongs_to :person
validates :registration_number, uniqueness: true, allow_blank: true,
format: {with: SOME_REGEX}
validate :both_cars_registration_cant_be_same
def both_cars_registration_cant_be_same
car1 = registration_number if type == 'hatchback'
car2 = registration_number if type == 'sedan'
if car1 == car2
errors.add(:registration_number, "Both number can't be same")
end
end
### Model : Person
class Person < ActiveRecord::Base
has_many :cars
accepts_nested_attributes_for :cars, allow_destroy: true,
reject_if: proc { |attr| attr['registration_number'].blank? }
Controller:
### Controller : Person
class PersonsController < ApplicationController
def new
#person = Person.new
2.times { #person.cars.build }
end
Below is the small snippet of form partial
...
...
### _form.html.erb ###
<%= f.fields_for :cars do |car|
<%= render 'car_form', c: car %>
<% end %>
...
...
### _car_form.html.erb ###
<% if c.index == 0 %>
<p>Type: Sedan</p>
<%= c.hidden_field :type, value: "sedan" %>
<% else %>
<p>Type: Hatchback</p>
<%= c.hidden_field :type, value: "hatchback" %>
<% end %>
<div>
<%= c.label :registration_number %>
<%= c.text_field :registration_number %>
</div>
I can use validate :both_cars_registration_cant_be_same with valid? from Cars model in rails console but how do i run this validation from the parent model (person) so that i get the error message when the form is submitted with same registration number for two cars?. I want to validate that the registration number fields entered for two record must be different and not same. Also let me know if my form helper where i am using index on the fields_for object is the correct way to do it?
P.S : Using rails 4
Move cars validation to Person model. This question could be helpful for you: Validate that an object has one or more associated objects
here is an example
belongs_to :project
validates_uniqueness_of :title, allow_blank: false, conditions: -> {where(parent_id: nil)}, scope: :project
use the scope validation option
http://guides.rubyonrails.org/active_record_validations.html
I'm trying to display the number of photos a category has in a list.
For it, I'm trying to do in my view:
<%= #photos.zone.name("Zone1").count%>
<%= #photos.zone.name("Zone2").count%>
<%= #photos.zone.name("Zone3").count%>
<%= #photos.zone.name("Zone4").count%>
But this doesn't work, and I don't know if this will make a million requests to my ddbb.
Which is the correct way to do this? Making a scope for each category?
Update
class Photo < ActiveRecord::Base
# validate :validate_minimum_image_size
has_many :tags , dependent: :destroy
belongs_to :user
belongs_to :category
belongs_to :zone
validates_presence_of :title, :description, :category, :zone
acts_as_votable
is_impressionable
before_destroy { |record| record.tags.destroy_all if record.tags.any? }
class User < ActiveRecord::Base
rolify
has_many :photos
has_many :tags, through: :photos
Thanks
This line here is wrong because you are calling it on a collection, and it doesn't represent your data model correctly.
<%= #photos.zone.name("Zone1").count%>
Let's say you wanted to get the zone of each photo, you would do something like this:
<% #photos.each do |photo| %>
<%= photo.zone.count %>
<% end %>
This still does not make sense because your association states that a photo belongs to a zone. So a photo can only have one zone per your data model.
class Photo
# ...
belongs_to :zone
end
Based on this information, I will assume that you want to display zones, and the number of photos per zone. In which case you would do something like this:
<%= #zone.photos.count %>
Or, if you wanted to show multiple zones on the same page:
<% #zones.each do |zone| %>
<%= zone.photos.count %>
<% end %>
How would you prepare the data? In your controller you would do something like this:
#zone = Zone.includes(:photos).find(params[:id]) # assuming a /zones/:id path
Or for multiple zones:
#zones = Zone.all.includes(:photos) # assuming a /zones/ path
It is also possible that you want to display photos grouped by zone, which is another story.
Extending #Mohamad's answer
The best bet is to use counter_cache.In your Photo model set counter_cahe =>true for zone
class Photo < ActiveRecord::Base
# validate :validate_minimum_image_size
has_many :tags , dependent: :destroy
belongs_to :user
belongs_to :category
belongs_to :zone,:counter_cache => true #here
validates_presence_of :title, :description, :category, :zone
acts_as_votable
is_impressionable
before_destroy { |record| record.tags.destroy_all if record.tags.any? }
end
Then add a column photo_counts to your zones table and use it like this
<% #zones.each do |zone| %>
<%= zone.photo_counts %>
<%end%>
This avoids multiple requests to the DB.Look into this Railscast for more info.
Hope it helps!
simply
<%= #photos.select{|photo| photo.zone.name == "Zone1"}.count%>
How do I search with associations and through with sunspot?
class StaticController < ApplicationController
def search
#search = Sunspot.search Business, Service do
fulltext params[:q]
paginate :per_page => 10
order_by_geodist(:location, *Geocoder.coordinates(params[:loc]))
end
#biz = #search.results
end
class Business < ActiveRecord::Base
attr_accessible :name
has_many :services, :through => :professionals
searchable do
text :name #name in business column
# how to do I get the services?
end
end
class Service < ActiveRecord::Base
attr_accessible :service
belongs_to :professional
end
class Professional < ActiveRecord::Base
belongs_to :business
has_many :services, as: :servicable
end
In the view, I have this (lots of looping)
<%= #biz.each do |b| %>
<%= b.name %>
<!-- looping through professionals model -->
<% b.professionals.each do |prof| %>
<!-- looping through services model -->
<% prof.services.each do |s| %>
<%= s.service %>
<% end %>
<% end %>
<% end %>
This works if I search for a name that is within the business model, but what if I'm searching through a term that's in the Service model? It won't display correctly because my view is only coming from the business side. How do I make it so the business name will pop up if I search through Service model?
Thanks
You will need to make additional indexes for the associated models in the calling model to make this happen. For example:
class Business < ActiveRecord::Base
attr_accessible :name
has_many :services, :through => :professionals
searchable do
text :name #name in business column
text :services do # this one for full text search
services.map(&:service).compact.join(" ")
end
string :services , :multiple => true do #this one for exact searches
services.map(&:service).compact
end
end
end
After that you can do queries like:
Bussines.search do
with(:services, "some_service")
end.execute.results
Now you no longer have to do join on mysql tables to fetch data. You can just fetch data from the solr. This is one of biggest advantages of solr.
I hope this makes it clear. Fell free to drop a comment if you need more details.
Seems I need to brush up on my associations in rails. At present I am trying to display all posts that have the department name as staff.
two models exist at present, posts and departments
class Post < ActiveRecord::Base
belongs_to :department
attr_accessible :title, :comments, :department_id
end
class Department < ActiveRecord::Base
has_many :posts
attr_accessible :name, :post_id
#Scopes
scope :staff_posts, where(:name => "Staff")
end
So i want to display all posts that have the department name staff
to do this i have put this in my controller
class PublicPagesController < ApplicationController
def staffnews
#staffpost = Department.staff_posts
end
end
In my view i am trying to display all these posts like so
<% #staffpost.each do |t| %>
<h2><%= t.title %>
<h2><%= t.comments %></h2>
<% end %>
Clearly going wrong somewhere as i get undefined method nil, even though i have 3 posts with the name 'Staff'
Can someone please explain where i am misunderstanding the association as would love to get this right
EDIT
Routes
scope :controller => :public_pages do
get "our_news"
match "our_news/staffnews" => "public_pages#staffnews"
In controller it returns department with name staff. And you are using title and comments on on department objects thats why its giving nil method error.
Use like this:
def staffnews
#dept_staff = Department.staff_posts
end
<% #dept_staff.each do |ds| %>
<% ds.posts.each do |p| %>
<h2><%= p.title %></h2>
<h2><%= p.comments %></h2>
<% end %>
<% end %>
or
In post model create named_scope
class Post < ActiveRecord::Base
belongs_to :department
attr_accessible :title, :comments, :department_id
scope :staff_posts, :include => :department, :conditions => {"departments.name" => "Staff"}
end
class Department < ActiveRecord::Base
has_many :posts
attr_accessible :name, :post_id
end
Controller:
def staffnews
#staffpost = Post.staff_posts
end
View: #No change
<% #staffpost.each do |t| %>
<h2><%= t.title %></h2>
<h2><%= t.comments %></h2>
<% end %>
Your staff_posts scope is only selecting the Departments with the name "Staff". Assuming you will have one and only one department named staff, you have a few ways to handle this.
This will find all departments with the name staff, and eager load the posts that go along with it:
#department = Department.where(name: "Staff").include(:posts).first
Since you are trying to scope Post, however, this belongs in Post. Here's an example using a method as scope:
class Post < ActiveRecord::Base
belongs_to :department
attr_accessible :title, :comments, :department_id
def self.staff
where(department_id: staff_department_id)
end
def staff_department_id
Department.find_by_name!("Staff").id
end
end
This way, you can use #staff_posts = Post.staff and iterate over that collection (Note: I don't recommend getting staff_department_id this way permanently. This could be set to a constant when the app boots up, or some other more robust solution).
You can find the all the posts that have the department name staff by following changes:
class PublicPagesController < ApplicationController
def staffnews
#get all the department which have name is staff
departments = Department.where("name=?","staff")
#get all the ids
department_ids = departments.map(&:id)
#retrieve post that department name is staff
#staffpost = Post.find_by_department_id(department_ids)
end
end