Constructing a has-and-belongs-to-many query - sql

I have a rails app (running on version 2.2.2) that has a model called Product. Product is in a has-and-belongs-to-many relationship with Feature. The problem is that I need have search functionality for the products. So I need to be able to search for products that have a similar name, and some other attributes. The tricky part is that the search must also return products that have the exact set of features indicated in the search form (this is represented by a bunch of checkboxes). The following code works, but it strikes me as rather inefficient:
#products = Product.find(:all, :conditions=>["home=? AND name LIKE ? AND made_by LIKE ? AND supplier LIKE ? AND ins LIKE ?",hme,'%'+opts[0]+'%','%'+opts[1]+'%','%'+opts[3]+'%','%'+opts[4]+'%'])
#see if any of these products have the correct features
if !params[:feature_ids].nil?
f = params[:feature_ids].collect{|i| i.to_i}
#products.delete_if {|x| x.feature_ids!=f}
end
I'm sorry that my grasp of rails/sql is so weak, but does anyone have any suggestions about how to improve the above code? Thanks so much!

First, i would recommend you to manually write a FeatureProduct model (and not use the default 'has_and_belongs_to_many')
EG
class FeatureProduct
belongs_to :feature
belongs_to :product
end
class Product
has_many :feature_products
has_many :features, :through => :feature_products
end
class Feature
has_many :feature_products
has_many :products, :through => :feature_products
end
For the search: You may find the gem SearchLogic to be exactly what you need. It has support for 'LIKE' conditions (it means that you can write in a more 'Rails way' your query). It also has support for performing a search with conditions on a related model (on your Feature model, to be more precise).
The solution would be something like:
search = Product.search
search.name_like = opt[0]
search.made_by_like = opt[1]
...
search.feature_products_id_equals = your_feature_ids
..
#product_list = search.all
There is also an excellent screencast explaining the use of this gem.
Good luck :)

Related

Rails, gem with functionality in between enum and many to many

i have im my rails app model which has few options (no more than 10 i think).
Something like Product - Category, where product can be part of 1 or many categories.
But i think i have too few categories to engage fully fledged many-to-many construct.
Moreover the list of categroies is predefined and will almost never change.
I think from sql side this could look like string field categories with such content:"Fruits|Vegetables|..."
Maybe someone know preexisting gem for such functionality, or maybe it is no real advantage doing so and i should choose standart many-to-many ?
I checked acts-as-taggable-on plugin, but it is i think fits not very well for this task.
Enum gems like enumerize i think fit just best, but they are allow only single single value to be choosen.
Currently came out with following combination:
This gem:
https://github.com/pboling/flag_shih_tzu
In model:
class Product < ActiveRecord::Base
KINDS = { 1 => :fruit, 2 => :vegetable }
include FlagShihTzu
attr_accessible *KINDS.values
as_flags KINDS
Then in view (haml):
=form_for [#product] do |f|
-Product::KINDS.each do |k, v|
=f.check_box v
=f.label v
UPDATE:
Yet another gem adressing this problem: https://github.com/joelmoss/bitmask_attributes

Need help setting up relationships between models

Rails newbie here struggling with a small project. I am creating a simple ship building tool for a board game I like as an exercise and I am a bit lost.
What do I want to do?
-After creating my Ship model record I want to create the Traits model record that will be associated with the Ship model. After updating a Ship model record I want to update or create the Traits model that will be associated with the Ship model record.
What have I tried?
- Adding the traits to each Ship model record as column variables. I do not think that this is the most effecient way of storing the traits for each of my Ship models. I have a Traits model set up but I do not know how to navigate to it and associate it with my Ship models
What would I like to have when finished?
- An array that is stored in each Ship model record that will list the attributes for each ship with their corresponding values,
i.e. if
trait_list = [trait1 => t1, trait2 => t2, trait3 => t3, trait4 => t4]
ship_traits = [t1, t4].
In the end I would be able to call the traits on my ship diagram page without having to iterate through every single trait, just the ones pertinent to my current model.
I am lost on how I should set up the associations between the models. Any help or kind advice on directions I should be researching would be warmly welcomed. I apologize in advance for my vagueness, again I am a complete newbie.
Cheers,
Nick
I'm not 100% sure this would solve your problem, but you could do something like this:
class Ship < ActiveRecord::Base
has_many :traits
accepts_nested_attributes_for :traits
end
class Trait < ActiveRecord::Base
belongs_to :ship
end
# In your form
- form_for #ship do |f|
- f.fields_for :traits do |ff|
= ff.label :trait_name
= ff.text_field :trait_name
# this will return all the traits for model defined as #ship
#ship.traits
I know it's not an array within the Ship model, but I hear it's a little tricky to set a column in a model to be array. If you want the traits to be unique (as in many ships can have many traits and these traits can belong to many different ships), then you're going to have a has_many :through relationship. If that's the case, let me know and I'll answer again. Or you can take a look at this: http://guides.rubyonrails.org/association_basics.html

How to retrieve both "associated" records and "associated through" records in a performant way?

I am using Ruby on Rails 3.1 and I am trying to improve an SQL query in order to retrieve both "associated" records and "associated through" records (ActiveRecord::Associations) in a performant way so to avoid the "N + 1 query problem". That is, I have:
class Article < ActiveRecord::Base
has_many :category_relationships
has_many :categories,
:through => :category_relationships
end
class Category < ActiveRecord::Base
has_many :article_relationships
has_many :articles,
:through => :article_relationships
end
In a couple of SQL queries (that is, in a "performant way", maybe by using the Ruby on Rails includes() method) I would like to retrieve both categories and category_relationships, or both articles and article_relationships.
How can I make that?
P.S.: I am improve queries like the followings:
#category = Category.first
articles = #category.articles.where(:user_id => #current_user.id)
articles.each do |article|
# Note: In this example the 'inspect' method is just a method to "trigger" the
# "eager loading" functionalities
article.category_relationships.inspect
end
You can do
Article.includes(:category_relationships => :categories).find(1)
Which will reduce this to 3 queries (1 for each table). For performance, also make sure your foreign keys have an index.
But in general, I'm curious why the "category_relationships" entity exists at all, and why this isn't a has_and_belongs_to sort of situation?
Updated
As per your changed question, you can still do
Category.includes(:article_relationships => :articles).first
If you watch the console (or tail log/development) you'll see that when you call the associations, it'll hit the cached values and you're golden.
But I am still curious why you're not using a Has and Belongs To Many association.

BEGINNER: Correct seeds.rb in rails 3

I've just created two models and one "join table". Person, Adress (create_adresses_personss)
class Person < ActiveRecord::Base
has_and_belongs_to_many :streets
end
class Street < ActiveRecord::Base
has_and_belongs_to_many :persons
end
Now I want to add some data to these models in the db/seeds.rb file. The tutorial I follow just adds the objects:
person = Person.create :name => 'Dexter'
street.create[{:streetname => 'street1'},
{:streetname => 'street2'},
{:streetname => 'julianave'},
{:streetname => 'street3'}]
Question 1: Why is persons' data added differently than streets'? Is it just the tutorial that wants to show that there are many ways of adding data in the seeds.rb?
Question 2: The tutorial doesn't make the connections/joins in the seeds.rb. It does that in the rails console;
>>p1 = Person.find(1)
>>s1 = Street.find(1)
>>p1.streets << s1
Can't theese connections be made in the seeds.rb file?
Question 3: Would it be better to do this join with a "rich many_to_many-assocciation"?
Thanks for your time and patience with a beginner ;)
1) The first method is creating one object. The second method is creating multiple objects. However, for the second method you would need to do Street.create, not street.create.
2) Yes, you can do that in the seed file the same way.
3) The "Rich many-to-many" you're talking about is an association with a Join Model, I guess you're talking about. This is opposed to just a join table, which is what has_and_belongs_to_many does. To use a join model, you'll want to look up has_many :through. It's generally considered better to always use a proper join model, however I still use HABTM when I just need a quick, simple association. has_many :through allows for more options and more flexibility, but it is a little more complicated to setup (not that much, though). It's your decision.
One way that I like to create seed data for many-to-many associations is setting up one of the models, the adding a tap block that sets up the other models through the association.
Person.create!(:name => "Fubar").tap do |person|
3.times do |n|
person.streets.create!(:streetname => "street #{n}")
end
# OR
person.streets.create!([
{:streetname => "street 1"},
{:streetname => "street 2"},
... and so on
])
end
All tap is doing is executing the block with the object as it's only parameter. I find it convenient for seeds.
One other tip I would toss out there would be to have your model attribute names spaced on the words with underscores.
:street_name instead of :streetname
The difference is more profound when you start wanting to use some of the ActiveSupport helers that take model attributes and turn them into text strings for use in the UI.
e
:streetname.to_s.titleize # "Streetname"
:street_name.to_s.titleize # "Street Name"
And one last nitpick, you might want your join table to be addresses_people not addresses_persons since the rais inflector is going to pluralize person as people. The same would go for your controller on the Person model, PeopleController instead of PersonsController. Though maybe it will work with persons as well.
:person.to_s.pluralize # "people"
:people.to_s.singularize # "person"
:persons.to_s.singularize # "person"

Help with Rails find_by queries

Say if #news_writers is an array of records. I then want to use #news_writers to find all news items that are written by all the news writers contained in #news_writers.
So I want something like this (but this is syntactically incorrect):
#news = News.find_all_by_role_id(#news_writers.id)
Note that
class Role < ActiveRecord::Base
has_many :news
end
and
class News < ActiveRecord::Base
belongs_to :role
end
Like ennen, I'm unsure what relationships your models are supposed to have. But in general, you can find all models with a column value from a given set like this:
News.all(:conditions => {:role_id => #news_writers.map(&:id)})
This will create a SQL query with a where condition like:
WHERE role_id IN (1, 10, 13, ...)
where the integers are the ids of the #news_writers.
I'm not sure if I understand you - #news_writers is a collection of Role models? If that assumption is correct, your association appears to be backwards - if these represent authors of news items, shouldn't News belong_to Role (being the author)?
At any rate, I would assume the most direct approach would be to use an iterator over #news_writers, calling on the association for each news_writer (like news_writer.news) in turn and pushing it into a separate variable.
Edit: Daniel Lucraft's suggestion is a much more elegant solution than the above.