Rails - displaying users with intersecting properties from 2 different join tables - ruby-on-rails

I'm a beginner so the title of my question probably isn't very clear. For that reason, it has been difficult to find any solutions to the problem I'm having. I'm working on a language learning app that allows users to connect with native speakers who want to learn their language i.e. a native English speaker who is learning Italian and Spanish will be able to connect with native Italian and Spanish speakers who want to learn English.
I have a Language table that has the language name as a string and 2 join tables: Native and Desired. These both take user_id and language_id.
My models are set up like so:
class User < ActiveRecord::Base
has_many :natives
has_many :native_languages, through: :natives, source: :language
has_many :desireds
has_many :desired_languages, through: :desireds, source: :language
end
class Language < ActiveRecord::Base
has_many :native_speakers, through: :native_languages, source: :natives
has_many :desired_speakers, through: :desired_languages, source: :desireds
end
class Native < ActiveRecord::Base
belongs_to :user
belongs_to :language
end
class Desired < ActiveRecord::Base
belongs_to :user
belongs_to :language
end
I want to display matched users on the User index, so I need help with the query that should go within the index method on the users controller.
This is my first post here so let me know if I missed any critical pieces. Thanks in advance for any help!

Using subquery.
Native Speakers I desired
user = User.find(params[:id])
desired_lanuages = user.desired_languages
User.where(id: Native.select(:user_id).where(language: desired_lanuages))
Who want me
native_language = user.native_languages
User.where(id: Desired.select(:user_id).where(language: native_language))
Match
User.where(id: Native.select(:user_id).where(language: desired_lanuages))
.where(id: Desired.select(:user_id).where(language: native_language))
I do not try running code. But I think that kind of code is possible

Related

Need help getting list of queries from Rails ActiveRecord

I currently have three models:
class Match < ApplicationRecord
has_many :rosters, dependent: :destroy
has_and_belongs_to_many :players
end
class Player < ApplicationRecord
has_and_belongs_to_many :matches
end
class Roster < ApplicationRecord
belongs_to :match
end
Let's say I have a specific player's id, say my_player_id. My current database contains several matches, each match has 2 rosters, and each roster has 3 players.
How can I get a list of rosters that belong to matches that belong to a player? (the rosters that have my_player_id and the roster that my_player_id is playing against)
I've tried looking into using joins and where methods but not sure where to go from there. Any answer and explanation of the code would be appreciated!
I think this should work
Roster.joins(match: :players).where('players.id = ?', my_player_id)
Update
If you do .to_sql you will get the equivalent sql query and what it simply does is use your associations to inner join match with rosters first using the foreign_key and then join players to matches and your data is ready. Now you just provide the where clause to fetch data belonging to particular player_id.
Amazingly, following should also work..
class Player < ApplicationRecord
has_and_belongs_to_many :matches
has_many :rosters, through: :matches
end
and now it is as easy as
Player.find_by_id(player_id).rosters
The above should work fine and efficient,
another solution is to break it down to into two queries
match_ids = #player.matct_ids
rosters = Roster.where(match_id: match_ids)
Notice! Rails provides a list of ids for child models
Notice
.where(some_attribute: [ARRAY])takes an array as input

How can I find a puzzle the user has not yet solved in my Rails 4 app

I'm building a simple puzzle game to learn Rails 4. I have two models in my Rails app, Users and Puzzles. I'm trying to understand how to structure the two models so that I can keep track of the puzzles a User has solved and be able to find an unsolved puzzle for him to play. The puzzles do NOT have to be solved in any order, so my app just needs to be able to find ANY puzzle the user has not yet solved.
One obvious way is to create a many-to-many relationship between Users and Puzzles and create an attribute on the User model that stores the IDs of puzzles he's already solved, then use a simple DB query to find a puzzle ID not in that list, but that feels inefficient...
#Untested code, apologies for typos/bugs
class User < ActiveRecord::Base
has_and_belongs_to_many :solved_puzzles, class_name: "Puzzle"
def unsolved_puzzle
solved_puzzle_ids = self.solved_puzzles.map {|p| p.id}
unsolved_puzzle = self.puzzles.where("id NOT IN ?", solved_puzzle_ids).first
end
end
class Puzzle < ActiveRecord::Base
has_and_belongs_to_many :solved_by, class_name: "User"
end
Are there any problems with this approach? Other thoughts?
Thanks in advance for your wisdom!
If you use has_and_belongs_to_many you can only store the link between user and puzzles. I would prefer has_many :through association where you can save the state of the puzzle for that particular user . The relationship between an user and a puzzle now have a separate class with solved state.
(Untested code)
class User
has_many :user_puzzles
has_many :puzzles, through: user_puzzles
def unsolved_puzzle
user_puzzles.where(solved: false).first
end
end
class UserPuzzle
belongs_to :user
belongs_to :puzzle
#has attribute solved
end
class Puzzle
has_many :user_puzzles
has_many :users, through: user_puzzles
end

Rails Multiple HABTM

I'm building a rails application for an art exhibition website. At the moment I have 4 models, Curator, Exhibition, Artist, and Artwork.
The application should work as follow, An Exhibition can be curated by many curators, an exhibition can display multiple artworks, an artwork can be displayed in many exhibitions, artist can own many artworks, and artworks belongs to one artist.
I'm a rail newb and I'm having difficulty building the relationship between the models. Can you tell me if I'm doing this right, or maybe there is a better way?
curator.rb
class Curator < ActiveRecord::Base
has_and_belongs_to_many :exhibitions
end
exhibition.rb
class Exhibition < ActiveRecord::Base
has_and_belongs_to_many :curators
has_and_belongs_to_many :artworks
end
artwork.rb
class Artwork < ActiveRecord::Base
has_and_belongs_to_many :exhibitions
belongs_to :artist
end
artist.rb
class Artist < ActiveRecord::Base
has_many :artworks
end
Thanks!
I would recommend using has_many :through if you want to have validations on your relationships, use callbacks or want to add extra attributes.
If you want to have more information regarding this matter, this rails guide is great:
http://guides.rubyonrails.org/association_basics.html#choosing-between-has_many-through-and-has_and_belongs_to_many
Your HABTM looks fine though. If you run into any problem, it would be advisable to also reference your joined tables.
It looks right to me.. Do create the join tables naming convention would be:
curators_exhibitions
artworks_exhibitions
Hope you get the idea.
Also you would want to check out has_many :through in case you would want to do validations on top of the relationships!

Rails - insert many random items on create with has_many_through relation

I want to create a random pack of 15 cards which should be invoked in the cardpacks_controller on create. I have the following models:
Card:
class Card < ActiveRecord::Base
# relations
has_many :cardpacks, through: :cardpackcards
belongs_to :cardset
end
Cardpack:
class Cardpack < ActiveRecord::Base
#relations
has_many :cards, through: :cardpackcards
belongs_to :cardset
# accept attributes
accepts_nested_attributes_for :cards
end
Cardpackcards:
class Cardpackcard < ActiveRecord::Base
#relations
belongs_to :card
belongs_to :cardpack
end
Cardsets:
class Cardset < ActiveRecord::Base
#relations
has_many :cards
has_many :cardsets
end
How can I create 15 Cardpackcards records with random card_id values and with the same cardpack_id (so they belong to the same pack)
I have watched the complex form series tutorial but it gives me no comprehension as how to tackle this problem.
I hope anyone can help me solve this problem and give me more insight in the rails language.
Thanks,
Erik
Depending on the database system you might be able to use an order random clause to find 15 random records. For example, in Postgres:
Model.order("RANDOM()").limit(15)
Given the random models, you can add a before_create method that will setup the associations.
If the Cardpackcard model doesn't do anything but provide a matching between cards and cardpacks, you could use a has_and_belongs_to_many association instead, which would simplify things a bit.
Without it, the controller code might look something like this:
cardset = Cardset.find(params[:cardset_id])
cardpack = Cardpack.create(:cardset => cardset)
15.times do
cardpack.cardpackcards.create(:card => Card.create(:cardset => cardset))
end

Rails: Display products based on multiple params

I'm learning rails and trying to set up a product library where the products will be displayed based on three elements: location, category and expiry date (products can have multiple locations and categories but just one expiry date). Products will be shown as long as their expiry date hasn't passed and location and category selection will be via dropdown menus.
I started writing this question while having difficulty with incorporating the location and category selection criteria which i found a solution to but any help on what could be done better is greatly appreciated.
I've used has_many through connections to create the connections between the products, location and categories.
Here's the models:
class Product < ActiveRecord::Base
has_many :categorizations
has_many :categories, :through => :categorizations
has_many :localizations
has_many :locations, :through => :localizations
end
class Categorization < ActiveRecord::Base
belongs_to :product
belongs_to :category
end
class Category < ActiveRecord::Base
has_many :categorizations
has_many :products, :through => :categorizations
end
class Localization < ActiveRecord::Base
belongs_to :product
belongs_to :location
end
class Location < ActiveRecord::Base
has_many :localizations
has_many :products, :through => :localizations
end
Here's my controller. Location & category ID's are passed as params and the expiry date of the products must be greater than the current time:
class LibraryController < ApplicationController
def index
#products = Product.find(:all, include => [ :locations, :categories ],
:conditions => ['expiry_date > ? AND locations.id = ? AND categories.id = ?',
Time.now, params[:location_id],params[:category_id]])
end
end
So by passing the location_id and category_id params in the URL I can list products by a combination of both.
Is there a better way of achieving what I'm trying to do?
This will also do what you want:
#products = Product.find_all_by_category_id_and_location_id(params[:category_id], params[:location_id])
You can also user Product.where which is supposedly better than find.
For more information, Google "dynamic finders".
Ok. No, I don't think there is a "better" way in this case. There certainly are "different" ways of doing what you want, but on the face of it, what you're doing is fine, and it doesn't scream out "this code is terrible!" or anything.
Questions of advice/style are tough to answer here, because ultimately the answer to them is, "search the web for what other people are doing in your situation, and evaluate/make the decision yourself if your solution seems conventional/logical," or these kinds of questions are answered via study of relevant books on the topic.
It's nearly impossible to answer a qualitative question like this, because:
There's several ways to solve every problem, many of which are neither "right" or "wrong"
There's always edge cases where people break the "rules", in which case even unconventional solutions can absolutely be the best way to do something
You're the developer, the one building the thing. To some extent you're expected to take a leadership role, and decide what's best
The reason I ask you to define "better" is primarily because of #1 - unless you give us a specific outcome you're trying to achieve, all you'll get are (a) answers that are full of opinion, and not directed toward a specific goal or (b) simply a different way of doing something which may or may not help you. Therefore, they aren't very useful in practical terms.
You could also improve upon your solution by using, "Product.where" (preferred over find in rails 3.1) and also turn them into named_scopes in Rails like and chain them as required.
scope :not_expired, where('expiry_date > ?', Time.now)

Resources