Scope in rails to refine search - ruby-on-rails

I am trying to refine my article and giving my user flexibility to decide what they want to view.
Here the models with relationship
Article
has_many :tags, through: :articletags
ArticleTags
belongs_to :article
belongs_to :tags
Tags
has_many :article, through: articletags
Now the idea is the use would go in article and on the side see the tags.title which then give refresh the pages with Article where tags = "world". Now i am trying to do this with scope but i am not to sure how to do it. Here my scope in my model
scope :by_tags, where(title => ?, "world news")
Here how i call it
<%= link_to (tag.title), articles_path(:scope => "test") %>
But obviously it doesn't work how can i fix it?

View
<%= link_to (tag.title), articles_path(:scope => tag.title) %>
Model(Article)
def self.by_tags(tag)
joins(:tags).where('tags.title = ?', tag)
end
Controller
def index
#articles = Article.by_tags(params[:scope])
end

Related

Rails has_many relationship with prefilled views

I have a pretty basic Rails 4 app, and am using Cocoon's nested forms to manage the has_many... :through model association.
class Student < ActiveRecord::Base
has_many :evaluations
has_many :assessments, through: :evaluations
# ... etc
end
class Evaluation < ActiveRecord::Base
belongs_to :student
belongs_to :assessment
# ... etc
end
class Assessment < ActiveRecord::Base
has_many :evaluations
has_many :students, through: :evaluations
accepts_nested_attributes_for :evaluation, reject_if: :all_blank
# ... etc
end
When I use Cocoon in the View, I want to use the New Assessment view to pre-fill all the Student records in order to create a new Evaluation for each one. I don't want to have to do some hacky logic on the controller side to add some new records manually, so how would I structure the incoming request? With Cocoon I see that requests have some number in the space where the id would go (I've replaced these with ?? below).
{"utf8"=>"✓", "authenticity_token"=>"whatever", "assessment"=>{"description"=>"quiz 3", "date(3i)"=>"24", "date(2i)"=>"10", "date(1i)"=>"2015", "assessments_attributes"=>{"??"=>{"student_id"=>"2", "grade" => "A"}, "??"=>{"student_id"=>"1", "grade" => "B"}, "??"=>{"student_id"=>"3", "grade"=>"C"}}, }}, "commit"=>"Create Assessment"}
I see in the Coccoon source code that this is somehow generated but I can't figure out how it works with the Rails engine to make this into a new record without an ID.
What algorithm should I use (or rules should I follow) to fill in the id above to make a new record?
"??"
Never a good sign in your params.
With Cocoon I see that requests have some number in the space where the id would go
That ID is nothing more than the next ID in the fields_for array that Rails creates. It's not your record's id (more explained below).
From your setup, here's what I'd do:
#app/models/student.rb
class Student < ActiveRecord::Base
has_many :evaluations
has_many :assessments, through: :evaluations
end
#app/models/evaluation.rb
class Evaluation < ActiveRecord::Base
belongs_to :student
belongs_to :assessment
end
#app/models/assessment.rb
class Assessment < ActiveRecord::Base
has_many :evaluations
has_many :students, through: :evaluations
accepts_nested_attributes_for :evaluations, reject_if: :all_blank
end
This will allow you to do the following:
#app/controllers/assessments_controller.rb
class AssessmentsController < ApplicationController
def new
#assessment = Assessment.new
#students = Student.all
#students.each do
#assessment.evaluations.build
end
end
end
Allowing you:
#app/views/assessments/new.html.erb
<%= form_for #assessment do |f| %>
<%= f.fields_for :evaluations, #students do |e| %>
<%= e.hidden_field :student_id %>
<%= e.text_field :grade %>
<% end %>
<%= f.submit %>
<% end %>
As far as I can tell, this will provide the functionality you need.
Remember that each evaluation can connect with existing students, meaning that if you pull #students = Student.all, it will populate the fields_for accordingly.
If you wanted to add new students through your form, it's a slightly different ballgame.
Cocoon
You should also be clear about the role of Cocoon.
You seem like an experienced dev so I'll cut to the chase - Cocoon is front-end, what you're asking is back-end.
Specifically, Cocoon is meant to give you the ability to add a number of fields_for associated fields to a form. This was discussed in this Railscast...
Technically, Cocoon is just a way to create new fields_for records for a form. It's only required if you want to dynamically "add" fields (the RailsCast will tell you more).
Thus, if you wanted to just have a "static" array of associative data fields (which is I think what you're asking), you'll be able to use fields_for as submitted in both Max and my answers.
Thanks to #rich-peck I was able to figure out exactly what I wanted to do. I'm leaving his answer as accepted because it was basically how I got to my own. :)
assessments/new.html.haml (just raw, no fancy formatting)
= form_for #assessment do |f|
= f.fields_for :evaluations do |ff|
.meaningless-div
= ff.object.student.name
= ff.hidden_field :student_id, value: ff.object.student_id
= ff.label :comment
= ff.text_field :comment
%br/
assessments_controller.rb
def new
#assessment = Assessment.new
#students = Student.all
#students.each do |student|
#assessment.evaluations.build(student: student)
end
end

Rails - List separated by commas through a join table

I have three tables, one of which is a join table between the other two tables.
Jobs: id
Counties: id
Countyizations: job_ib, county_id
I want to create a list of counties a specific job has associations with. I'm trying to use something like:
<%= #counties.map { |county| county.id }.join(", ") %>
But this obviously is not using the countyizations table. How can I change the above code to accomplish what I need? Also, I'd like to list the counties alphabetically in ASC order.
P.S.
I suppose I should have added how I'm linking my tables in my models.
Jobs: has_many :countyizations & has_many :counties, :through => :countyizations
Counties: has_many :countyizations & has_many :jobs, :through => :countyizations
Countyizations: belongs_to :county & belongs_to :job
For a given job.id you can use this this return all the counties filtered by the given job.
<%= #counties.order('name asc').includes(:jobs).where('jobs.id = ?', job.id) %>
Replace job.id based on your requirement, you could set a #job instance variable in the controller and use in the view instead.
Or even better move this code to controller action:
# controller
def show
job_name = ...
#counties = ...
#county_jobs = #counties.order('name asc').includes(:jobs).where(jobs.name = ?', job_name)
end
Then in your view, to show all the counties that have the searched job:
<%= #counties.map(&:id).join.(',') %>
I am not sure if I understand you correctly. Is the following what you want?
class Job < ActiveRecord::Base
has_many :countries, :through => :countyizations
end
class County < ActiveRecord::Base
has_many :jobs, :through => :countyizations
end
<%= #job.counties.sort{|a, b| a.name <=> b.name}.map{ |county| county.name }.join(", ") %>
I think use "has_many_and_belongs_to" instead "of has_many" may work also.

Rails 3.2.8 & JQuery Tokeninput: Trying to add new records for has_many through relationship instead of replacing all current records in a form

I have a database of skills that relate to each other as prerequisites to each other. In an index of skills, I'd like to be able to search through other skills and add 1 or more as prerequisites. It's important to note that I ONLY want the user to be able to add prerequisites, not remove them, as that's taken care of through an up-down voting system. I'm using JQuery Tokeninput and actually have all of this working except for one thing: I can't figure out how to only add prerequisites, rather than replacing all the prerequisites for a particular skill on submit.
Models:
class Skill < ActiveRecord::Base
attr_accessible :skill_relationship_attributes, :prereq_tokens
has_many :skill_relationships
has_many :prereqs, :through => :skill_relationships
has_many :inverse_skill_relationships, :class_name => 'SkillRelationship', :foreign_key => "prereq_id"
has_many :inverse_prereqs, :through => :inverse_skill_relationships, :source => :skill
attr_reader :prereq_tokens
accepts_nested_attributes_for :skill_relationships, :allow_destroy => true
def prereq_tokens=(ids)
self.prereq_ids = ids.split(",")
end
end
class SkillRelationship < ActiveRecord::Base
attr_accessible :skill_id, :prereq_id, :skill_attributes, :prereq_attributes
belongs_to :skill
belongs_to :prereq, :class_name => 'Skill'
end
JQuery:
$('#skill_prereq_tokens').tokenInput('/skills.json',
{ theme:'facebook',
propertyToSearch:'title',
queryParam:'search',
preventDuplicates:'true'
});
View:
<%= simple_form_for skill do |f| %>
<%= f.input :prereq_tokens %>
<%= f.submit %>
<% end %>
I feel a bit silly for not getting this before, but I solved my problem by changing how prereq_tokens became prereq_ids in my Skill model.
I just changed this:
def prereq_tokens=(ids)
self.prereq_ids = ids.split(",")
end
to this:
def prereq_tokens=(ids)
self.prereq_ids += ids.split(",")
end
That's it. That little plus sign before the equals sign. I hope this helps anyone else who codes too long without a break!

Scope exception rules

I am trying to define an inventory for all my articles but I want to exclude the articles that are sent to me with a parameter.
Here is what the relationship looks like:
Article
has_many :tags, through: :articletags
ArticleTags
belongs_to :article
belongs_to :tags
Tags
has_many :article, through: articletags
Here's a method to define the one without the tags in my models:
def self.by_not_tags(tag)
joins(:tags).where('tags.title != ?', tag)
end
Here's how I call it in my view:
<%= link_to (tag.title), articles_path(:scope => tag.title) %>
Here's my controller:
def custom
if params[:scope].nil?
#articles = Article.all(:order => 'created_at DESC')
else
#articles = Article.by_tags(params[:scope])
#articles2 = Article.by_not_tags(params[:scope])
end
end
The goal is to see all the articles with a tag first, and then to show the other ones without that tag, so I don't have duplicates.
My issue is with the joins, but I am not sure how to find the article without tags. Maybe an except would work, but I am not sure what kind of query would work for it.
Assuming ArticleTag model needs to validate the presence of both article_id and tag_id,
Article.where('article_tag_id is null')
If I do not assume that above validation stated,
Article.where('not exists (select 1 from article_tags where article_id = articles.id)')

How can I elegantly construct a form for a model that has a polymorphic association?

Here are my models:
class Lesson < ActiveRecord::Base
belongs_to :topic, :polymorphic => true
validates_presence_of :topic_type, :topic_id
end
class Subject < ActiveRecord::Base
has_many :lessons, :as => :topic
end
class Category < ActiveRecord::Base
has_many :lessons, :as => :topic
end
Now, what I need is a form that will allow the user to create or update Lessons. The questions is, how can I provide a select menu that offers a mix of Subjects and Categories? (To the user, on this particular form, Subjects and Categories are interchangeable, but that's not the case elsewhere.)
Ideally, this would look something like this:
views/lessons/_form.html.haml
= simple_form_for(#lesson) do |f|
= f.input :title
= f.association :topic, :collection => (#subjects + #categories)
That won't work because we'd only be specifying the topic_id, and we need the topic_types as well. But how can we specify those values?
I guess the crux of the problem is that I really want a single select menu that specifies two values corresponding to two different attributes (topic_id and topic_type). Is there any elegant railsy way to do this?
A few notes:
a) Single table inheritance would make this issue go away, but I'd like to avoid this, as Categories and Subjects have their own relationship… I'll spare you the details.
b) I might could pull some javascript shenanigans, yes? But that sounds messy, and if there's a cleaner way to do it, some magic form helper or something, then that's certainly preferable.
c) Though I'm using simple_form, I'm not wedded to it, in case that's complicating matters.
Thanks
If you don't wish to use STI, you can do something similar: create a new model Topic(name:string) which will polymorphically reference Subject or Category.
class Lesson < ActiveRecord::Base
belongs_to :topic
validates_presence_of :topic_id
end
class Topic < ActiveRecord::Base
belongs_to :topicable, :polymorphic => true
end
class Subject < ActiveRecord::Base
has_one :topic, :as => :topicable
has_many :lessons, :through => :topic
accepts_nested_attributes_for :topic
end
class Category < ActiveRecord::Base
has_one :topic, :as => :topicable
has_many :lessons, :through => :topic
accepts_nested_attributes_for :topic
end
In the view where you create a new Subject/Category:
<%= form_for #subject do |subject_form| %>
<%= subject_form.fields_for :topic do |topic_fields| %>
<%= topic_fields.text_field :name %>
<% end %>
<% end %>
After thinking this through, the less dirty implementation IMO would be to hire the JS shenanigans (b):
= f.input_field :topic_type, as: :hidden, class: 'topic_type'
- (#subjects + #categories).each do |topic|
= f.radio_button :topic_id, topic.id, {:'data-type' => topic.class.name, class: 'topic_id'}
With a sprinkle of JS (your needs may vary):
$('input:radio.topic_id').change(function() {
$('input:hidden.topic_type').val($(this).attr('data-type'));
});
Notes:
I use a radio button to select a topic (category or subject) in a list
The class name of each of possible topic is stored in an attribute 'data-type'
When a radio button is selected, the class name is copied to the hidden input via JS
Using: HTML5, jQuery, haml, simple_form

Resources