I have a few question about MongoID and embedded relations in Rails.
In my model I have:
class Board
include Mongoid::Document
attr_accessible :title
field :title, :type => String
#has_and_belongs_to_many :users
embeds_many :items
end
when I call
Board.all
it returns the whole collection, including also :items.
I've read in many articles/forums that using MongoDB embedded relations should be preferred over referenced ones but I have some questions:
What about performaces? each time i want to retrieve a board i'll also retrieve items inside it: it may be useful sometimes but in the case i want only board's information and not items inside it I should create a new method for not retrieving items.
When I want to update an item the DB will reload the whole document and not only the item I want to retrive, right?
Up to now I've noticed that the only advantage in using embedded document is for something like what in SQL are called "joins" but I also see a lot of performaces problem, there are important reason to use embedded relations over referenced relations?
EDIT
As pointed out by Adam C my thoughts are releated to situations like these:
as explained before I will have Boards each one with many Items inside it and using Rails scaffolding it generates methods that retrieve the whole Board document from the database but many times (for example when editing a Board) i want to load the document without the Items part.
Since I will be using mostly JSON calls my idea was to add an optional parameter to the url like "?get_items" to be set to TRUE in case I want also to get items, in other situations I would use Mongoid's:
Model.without
For example let's take the index action:
def index
#boards = Board.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #boards }
end
end
I'll need to get only fields specified in Board Model (in that case only :title) without items so I may use:
def index
#boards = Board.without :items
respond_to do |format|
format.html # index.html.erb
format.json { render json: #boards }
end
end
That my cause some problems?
If you need to retrieve items separately, then you should not embed them.
My rules of thumb:
Top-level domain objects (things that you work with one their own, that don't always appear in the context of their "parent") should get their own collections.
Embed when the related things
a. Don't grow unbounded. That is, in the 1-N relation, N is bounded.
b. Always (or nearly always) appear with their parent.
You can also embed if you can prove to yourself that the performance improvements to be gained by embedding outweigh the costs of the multiple queries required to obtain all objects.
Neither embedding nor relating should be preferred. They should be considered equally.
Related
I don't understand the Rails includes method as well as I'd like, and I ran into an issue that I'm hoping to clarify. I have a Board model that has_many :members and has_many :lists (and a list has_many :cards). In the following boards controller, the show method looks as follows:
def show
#board = Board.includes(:members, lists: :cards).find(params[:id])
...
end
Why is the includes method needed here? Why couldn't we just use #board = Board.find(params[:id]) and then access the members and lists via #board.members and #board.lists? I guess I'm not really seeing why we would need to prefetch. It'd be awesome if someone could detail why this is more effective in terms of SQL queries. Thanks!
Per the Rails docs:
Eager loading is the mechanism for loading the associated records of
the objects returned by Model.find using as few queries as possible.
When you simply load a record and later query its different relationships, you have to run a query each time. Take this example, also from the Rails docs:
clients = Client.limit(10)
clients.each do |client|
puts client.address.postcode
end
This code executes a database call 11 times to do something pretty trivial, because it has to do each lookup, one at a time.
Compare the above code to this example:
clients = Client.includes(:address).limit(10)
clients.each do |client|
puts client.address.postcode
end
This code executes a database call 2 times, because all of the necessary associations are included at the onset.
Here's a link to the pertinent section of the Rails docs.
Extra
A point to note as of recent: if you do a more complex query with an associated model like so:
Board.includes(:members, lists: :cards).where('members.color = ?', 'foo').references(:members)
You need to make sure to include the appended references(:used_eager_loaded_class) to complete the query.
I have two models: Patient and Facility.
Creating a new patient I have a modal that opens and does a search on the number of patients. Then, I have 4 fields that the usr can fill in and search for a specific patient. Then, when an existing patient is selected some values are passed to a main form. If a patient is not found, the data is passed also to the main form to create a new patient, based on those values...
I'm using Rails 3.2 and Ransack. Here's an example of the parameters returned on one basic search:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"gT8RPTlPiO6Wf3oLcU5+qSzVOZTjBZyX1Y0qNijT5oo=", "q"=>{"nid_cont"=>"2/14", "province_id_eq"=>"2", "district_id_eq"=>"2", "facility_id_eq"=>"146"}}
Then, on patient controller I have this code:
def patient_samples
#search_patients = Patient.includes(:gender, :province, :district, :facility).search(params[:q])
#patients = #search_patients.result
respond_to do |format|
format.html
format.js
format.json { render json: #patients }
end
end
This code is run when I open the modal and it returns all existing patients. Then, the user is also able to do the search that will only returns the respective rows.
When I select one of the existing patients, I can get all values from it and all the related models, for example:
patient.facility.printer_type
But when the search doesn't find a patient, I would also like to search the printer_type from the facility that I choose on the search parameters.
What I would like to do is to access one of the q: values, in this case
"facility_id_eq" => "146"
And then do something like:
#facility = Facility.find("146")
#facility.printer_type
But I can't seem to be able to access that value... I've read a lot of similar questions here in Stackoverflow, I've read the gem docs on github, I've tried several things, but haven't been able to do it...
I know it's a simple solution, but I'm blocked on it :(
Can you help me?
You can access that value with
facility_id = params[:q]['facility_id_eq']
#facility = Facility.find(facility_id)
I have the following show-view, where i display basic information about Product and display other User's Products.
<h1>Book <%= #product.name %></h1>
<% #products.each do |product| %>
<ul>
<%= product.name %>
<%= link_to "Make offer", {controller: "offers", :action => 'create', id: product.id } %>
</ul>
Controller
def show
#product = current_user.products.find(params[:id])
#products = Product.all
end
My goal is to make Offer between two Products.
I created Offer model and methods for making Offers:
class Offer < ActiveRecord::Base
belongs_to :product
belongs_to :exchanger, class_name: "Product", foreign_key: "exchanger_id"
validates :product_id, :exchanger_id, presence: true
def self.request(product, exchanger)
unless product == exchanger or Offer.exists?(product, exchanger)
transaction do
create(product: product, exchanger: exchanger, status: "oczekujace")
create(product: exchanger, exchanger: product, status: "oferta")
end
end
#other methods
end
Making offers is working, because I checked it in Console.
My problem is in OffersController:
class OffersController < ApplicationController
before_filter :setup_products
def create
Offer.request(#prod, #exchanger)
redirect_to root_path
end
private
def setup_products
#prod = current_user.products.find(1)
#exchanger = Product.find_by_id(params[:id])
end
end
Problem is with a following line (using link in show-page for products with different id's than 1 works):
#prod = current_user.products.find(1)
But I don't know how to find object in Db for actual product which my show-page shows. Not only for id = 1.
I don't know how to find this object in database.
I don't know the specific answer to your question, but perhaps if I explain what you need to look at, your solution will arise:
Find
Rails isn't magic - it uses ActiveRecord (which is an ORM - Object-Relation Mapper), which means every time you fire a query (through find), your ORM (ActiveRecord) will search the relevant database data for you
The problem you have is that although you're using the correct syntax for your lookup, you may not have a record with an id of 1 in your db.
current_user.products.find(1) tells ActiveRecord to scope the query around the current user, and their products. So you'll get something like like this:
SELECT * FROM 'products' WHERE user_id = '15' AND id = '1'
Objects
Further, you have to remember that Ruby (and Rails by virtue of being built on Ruby) is an object orientated language. This means that everything you load / interact with in the language should be based on an object
The problem you have is you're not associating your object to your Rails framework correctly. What I mean here is described below, but essentially, if you build your Rails framework correctly, it will give you the ability to associate your objects with each other, allowing you to call the various products you need from your offer
This is a simplistic way of looking at it, of course. You'll want to look at this diagram to see how it works:
Bottom line - try treating your application like a series of objects, rather than a logical flow. This will help you appreciate the various associations etc that you need to get it moving forward
Resources
You mention you can't show the product on your show page for an id other than one. I think the problem is really about how to get your show action to work.
If this is the case, let me explain...
Rails is resource-based, meaning that everything you do / create needs to be centred around a resource (object) of some sort. The problem is many people don't know this, and consequently complicate their controller structure for no reason:
Above is the typical "CRUD" routing structure for Rails-based resources. This should demonstrate the way that Rails will typically be constructed -- around resources
--
Further, Rails is built on the MVC programming pattern - meaning you need to use your controller to populate a series of data objects for use in your application.
To this end, if you load a resource, and want to populate it with resourceful information of another object - you need to make sure you have set up the data objects in a way to ensure you can look them up correctly, which either means passing the data through your routes or using a persistent data-type, such as cookies or sessions
The problem you have is you need to pass the product id to your controller somehow. How I'd do that is as follows (using nested resources):
#config/routes.rb
resources :offers do
resources :products #-> domain.com/offers/2/products
end
This will give you the ability to load the products controller with the variables params[:id] for the product, and params[:offer_id] for your Offer made available:
#app/controllers/products_controller.rb
Class ProductsController < ApplicationController
def show
#offer = Offer.find params[:offer_id]
#product = Product.find params[:id]
end
end
Right now my Posts model has_many :tags, :through => :tag_joins
When I add tags, while creating a post, the tag_join records are automatically created.
Now here is what I'm trying to accomplish: While viewing the show view of posts I want to be able to add a new tag.
I tried #post.tag = Tag.new didn't work (returns a "nomethoderror" for tag=)
So I'm trying to figure out how I can add tags and still create those joins automatically.
I am using accepts_nested_attributes etc.
UPDATE: I originally asked how to do this on the index view, but I have changed it to the show view - because I expect it to be a little easier.
You're not too far off with #posts.tags = Tag.new. Here's a couple of ways to do it;
#post.tags << Tag.create(params[:tag])
#post.tags.create params[:tag]
I see a couple of approaches to this problem.. One is to pass through the id of the post with the tag form using either a hidden_field or by using nested routes for tags. Then you can use that in the controller to retrieve the post and use a syntax similar to above.
While that would work, the problem is that it's a bit ugly.. It means your tag controller would be dealing with finding a post (which isn't necessarily wrong, but it shouldn't need to worry about posts. Unless tags can only be associated with posts, that is).
The more graceful way of dealing with it would be to make the form you're showing be a form for the post instance, not a tag. Then you could use nested attributes to create the tag as part of a post.
Take a look at the build_xxx or create_xxx methods that the association (belongs_to, has_many etc) add to the models. You need to create your tag through the post for rails to 'connect' it automatically.
The key observation here is the difference between .new and .create. For my Devour.space application, I was running into the same issue. If you create the object in memory using:
tag = #post.tags.new(tag_params)
tag.save
There will be no tag_joins entry saved to the database. #post.tags will not return your new tag. You must use .create at the moment of instantiation or the association will not be recorded in the JOIN table:
tag = #post.tags.create(tag_params)
#post.tags.last # tag
In my situation, this required a change in how my create action handled requests and errors:
has_many :deck_shares
has_many :decks, through: :deck_shares
....
deck = current_user.decks.new(deck_params)
if deck.save # Does not create entry in DeckShares table
render json: deck
else
render json: deck.errors, as: :unprocessable_entity
end
This became:
begin
deck = current_user.decks.create(deck_params) # creates DeckShare
rescue Exception => e
render json: e, as: :unprocessable_entity
end
render json: deck unless e
By 'view' here I mean different combinations of properties of the model, not the view of the traditional MVC. For example, I have the following model:
class Game < ActiveRecord::Base
has_many :players
belongs_to :status
has_one :deck
has_many :turns
has_one :current_turn, :class_name => 'Turn', :conditions => ['turn_num = ?', '#{self.turn_num}']
end
I've written a full_xml method for Game that I use for the 'normal' get operation, so that I can include certain properties of players and current_turn, and then I don't have to do GETs on every player all the time. I also don't want to include ALL the properties and children and children's properties of the Game model on every GET
Now, however, I want to GET a game history, which is all the turns (and their properties/children). Initially I thought of a new model w/out a corresponding table, and then realized that wasn't necessary because the data and relationships are already there in the game and turns models. I also thought about writing a new action, but I thought I read somewhere that in the RESTful world, you shouldn't be writing any actions other than the core 7.
BTW, I'm thinking here of returning xml, because I'm using a Flex front end instead of rails views.
You have a couple of options here - I would use "nested resources" so you end up with a /game/:game_id/turns route which calls 'index' on the Turns controller. The other option is to create a GameHistory controller, which might be useful if there is additional logic associated with your game history.
There is not a one-to-one correspondence between controllers and models; there is however a one-to-one correspondence between controllers and RESOURCES. A game history is a whole different resource from a Game, just like a user session resource is different from an actual user resource (this is commonly used to allow for RESTful logins as well as RESTful user management) Hopefully this helps :)
Yes, nested resources was the answer. This Railscast explains it nicely. I had briefly tried nested resources before, and couldn't get it to work. It was returning all the child resources, not only the nested resource of the parent resource. This was because I assumed Rails was automagically doing that for me, which it doesn't. The Railscast explains that you still have to make changes to the controller of the child resources, like so:
class TurnsController < ApplicationController
# GET /turns
# GET /turns.xml
def index
#game = Game.find(params[:game_id])
#turns = #game.turns
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #turns.to_xml( :except => [:created_at, :updated_at] ) }
end
end
... more methods
end
You also have to edit your routes.rb file. In this case, I want nested routes for both players and turns of the game, so I did this:
map.resources :games do |game|
game.resources :players
game.resources :turns
end