Many to many in Mongoid - ruby

Yes, I know how to create many to many relationships but nothing else. I did a google search and read official mongoid documentation but I didn't anything about how to work with it. By work I mean inserting, updating, deleting, finding, counting...
For example, suppose I have 2 models (from documentation):
class Band
include Mongoid::Document
has_and_belongs_to_many :tags
end
class Tag
include Mongoid::Document
field :name, type: String
has_and_belongs_to_many :bands
end
What should I do to make all the operations I mentioned above?
P.S. I use Sinatra and mongoid 3.

Inserting
From the documentation table immediately following your sample models (plus my comment).
# Create a tag for a band
band.tags.create(name: "electro")
Finding
Also from the same table.
# Find a tag that belongs to that band, whose name is "electro"
tag = band.tags.where(name: "electro")
Updating
From the doc on persistence, modified for the band/tags example.
# using variable tag from previous line.
tag.update_attributes(name: "dubstep")
Counting
From the doc on querying, which also contains information useful for the other items here (also modified).
band.tags.length
Deleting
This is from the persistence document also.
# we will delete the tag from earlier
tag.delete
Lastly
All the information needed to work Mongoid is in the docs, which are actually very thorough and useful. There is definitely a lot, but you can make it through it in a couple hours.

Related

Self-Documenting ActiveRecord Class Files in Rails 4 without attr_accessible

Question:
In a post-attr_accessible Rails 4 world, in what way do you recommend, if at all, annotating your ActiveRecord model class files to communicate its (database) attributes?
Further Thoughts
As part of a Rails 3 -> 4 upgrade, we are making a switch, and happily so, away from attr_accessible and to strong parameters in the controller. I understand and agree with the improvement in security via this switch. If you want to know more about this, the information is out there, and it's not hard to find.
But, I enjoyed, in my Rails 3 world, having those reminders of what attributes made up a class up there at the top of the model file. Especially since we're moving toward a world in which ActiveRecord classes are just DAOs, what else is the class but a collection of database attributes? I don't want to go to the schema.rb file just to remember them.
Am I thinking about this incorrectly? In a DAO world, should I be creating my ActiveRecord model class file and then never opening it again?
I know about the annotate_models gem and used it way back in the day. I was not a fan of having the attributes described in commented-out lines. (unreadable, hackish, fragile)
Thoughts? Opinions?
How about:
Person.column_names
If you are using an IDE or and editor that has a console feature this becomes an easy way to be reminded what attributes there are. I am no Ruby or Rails expert, still pretty new here, but I've been using Rails 4 almost exclusively and it just seems like you wouldn't need to see the attributes that often in the model. The params get whitelisted in the controller because that is where they will usually be used, no? If you don't want to use comments you could store an array of the attributes in the model:
my_attr = [:fname, :lname, :age, :height, :weight]
But is that really any more useful than a comment? Would there be a case of attributes that would have been in attr_accessible that wouldn't be in your whitelist in your controller? It would be trick if you put some code in a rake task that would run every time you ran
rake db:...
that would update the my_attr array in your model so you wouldn't have to remember to do it when you modified the model. I go into my models to add class methods and scopes, so I do see a value in it. But I work in RubyMine so I just click on the DB tab on the left side if I need to be reminded of columns that aren't in my whitelist.

do any mongodb ORMs allow you to alias fields?

I just watched this: http://blog.mongodb.org/post/38467892360/mongodb-schema-design-insights-and-tradeoffs-from
One suggestion that came out of the talk: in docs that will be replicated many times, try to make the field names as small as possible:
Reduce collection size by always using short field names as a
convention. This will help you save memory over time.
Choose "u" over "publicationUrl". Makes sense if you're talking about millions of rows. However, big readability problem there. It might be obvious that the value is a url, but what sort of url is it?
This might be solvable in the ORM though. Do any ORMs that interface with MongoDb allow you to say that 'u' in the db would map to 'publicationUrl' in the code? When you have things like a.u in code, that's pretty poor readability; article.u isn't much better.
(Ruby and node.js tags are there because those are the languages that I work with mongo in. Feel free to add tags.)
Per this discussion, Mongoose allows for virtual field names with getters and setters. Unfortunately virtuals can't be used in queries, and other server side operations such as map-reduce. The discussion also suggests this plugin for aliases as well which seems to address the query issue, but I suspect that it would also have trouble with more complex server side operations.
This is easy to with the Ruby ORM Mongoid. Here is an example straight from the docs:
class Band
include Mongoid::Document
field :n, as: :name, type: String
end
band = Band.new(name: "Placebo")
band.attributes #=> { "n" => "Placebo" }
criteria = Band.where(name: "Placebo")
criteria.selector #=> { "n" => "Placebo" }
I have used Mongoid on quite a few projects (albeit, all small ones) and really enjoy working with it. The docs are really great, and there is a section in the docs about performance as well.
Doctrine MongoDB ODM allows you to set an alias for your field while your object including getters and setters can remain readable f.e.:
/** #String(name="pUrl") */
private $publicationUrl;
Annotations Reference — Doctrine MongoDB ODM 1.0.0-BETA2 documentation — Field

Mongoid class with embedded_in and belongs_to entries

I am creating a Mongoid based application which will have a Class (called Question) whose Objects are stored in two different ways for different purposes. One group of those objects need to be stored in an N:N relationship with Class Page and another group of the same objects need to be stored as embedded (1:N) entries in a different Class (FilledPage).
I need to be able to copy a Question Object which has been referenced in a Page into a FilledPage and for the purposes of speed, I need that to be an embedded relationship.
I have tried creating a Superclass with the information and then two child classes, but I can't convert from one child class to the other without considerable work (and this same design needs to be used in a few other areas with much greater complexity).
Is there any way to support both embedding and references in the same class, or some other solution which will do similar.
Nothing block to have same class to be embedded or standalone. with reference. The limitation is about linking a master document to embedded document. It's not possible easily with mongodb, because your need get the master document and extract the embedded one.

What is the better way to cache fields of referenced document in Mongoid?

What is the better way to cache fields of referenced document in Mongoid?
Currently I use additional fields for that:
class Trip
include Mongoid::Document
belongs_to :driver
field :driver_phone
field :driver_name
end
class Driver
include Mongoid::Document
field :name
field :phone
end
May be it would be more clear to store cache as nested object so in mongo it would be stored as:
{ driver_cache: { name: "john", phone: 12345 } }
I thought about embedded document with 1-1 relation? Is that right choice?
Author of Mongoid (Durran Jordan) suggested folowing option
This gem looks handy for this type of thing:
https://github.com/logandk/mongoid_denormalize
Great question. I authored and maintain mongoid_alize, a gem for denormalizing mongoid relations. And so I struggled with this question.
As of 0.3.1 alize now stores data for a denormalized one-to-one in a Hash, very much like your second example above w/ driver_cache. Previous versions, however, stored data in separate fields (your first example).
The change was motivated by many factors, but largely to make the handling of one-to-one's and one-to-many's consistent (alize can denormalize one-to-one, one-to-many, and many-to-many). one-to-many's have always been handled by storing the data in a array of hashes. So storing one-to-one's as just a Hash becomes a much more symmetrical design.
It also solved several other problems. You can find a more detailed explanation here - https://github.com/dzello/mongoid_alize#release-030
Hope that helps!
Either way, you're fine.
First approach seems slightly better because it explicitly states what data you're caching. Also, it (probably) requires less work from Mongoid :-)
Alexey,
I would recommend thinking about how the data will be used. If you always use the driver information in the context of a trip object then embedding is probably the proper choice.
If, however, you will use that information in other contexts perhaps it would be better as it's own collection as you have created it.
Also, consider embedding the trips inside the driver objects. That may or may not make sense given what your app is trying to do but logically it would make sense to have a collection of drivers that each have a set of trips (embedded or not) instead of having trips that embed drivers. I can see that scenario (where a trip is always though of in the context of a driver) to be more common than the above.
-Tyler
Alternative hash storage of your cacheable data:
field :driver_cache, type: Hash
(Keep in mind, internally the keys will be converted to strings.)

Rails has_and_belongs_to_many & has_many :through scaffolding

Is there a way to generate a scaffold for Rails models that have either a has_and_belongs_to_many or has_many :through relationship? If not, is there a developmental reason why some basic form of this functionality has not been included? Rails requires developers to generate and edit a "custom" "join table migration." Is the necessary attention to detail a way of reminding Rails developers of some important fact or is this simply an example of how Rails is a 'work in progress?'
A quick Stackoverflow search for:
"has_and_belongs_to_many" yields 821 questions
"has_many :through" yields 933 questions
... and many of these start with "How do I..." So it seems the topic is not trivial and relevant to SO users. I Guess I am wondering why it has not received attention. Is the matter more complex than it seems? The feature set for Rails seems so extensive. I keep wondering why scaffolding for associations with join tables has been omitted.
Please focus your answer toward the assertions above or "include statement like, "A scaffolding would have to include..." or "A rails generate script would need..."
Thanks!
I like your question, I have wondered for myself why this is not included in Rails (and as I suspect not available as a Gem). Here are some of the reasons why that could be a problem. I will them explain at the typical example with assemblies and parts.
Scaffolding works by using generators to create files. If you migrate to a newer version, there is sometimes the option to update existing files by doing the following steps:
Copy existing ones to a new name.
Overwrite existing ones by new ones.
That implies that there is no notion of adding to files or changing files.
EDIT: This is not true, there are mechanisms to add to files and even change in files.
If you look at the generation of e.g. has_many :through (should be similar to has_and_belongs_to_many) you have to do the following things:
Add a migration that creates the join table.
==> Should be possible for scaffolding by doing: rails g scaffold_hmt Assembly Part
Add a model for that join model.
==> Should be possible for scaffolding by the previous scaffold.
Change existing models to include the lines:
assembly.rb: has_many 'assemblies_parts'; has_many :parts, :through => 'assemblies_parts'
part.rb: has_many 'assemblies_parts'; has_many :assemblies, :through => 'assemblies_parts'
==> So no scaffolding possible
What to do with views is wide open. There are examples at RailsCast how to do it, but it is not at all trivial, and there is no one single technique that works well in all circumstances. I do think that the different patterns could be implemented as templates for scaffolding (e.g. to use checkboxes or multi-select lists or text entry with completion). The problem is the same as in has_many views, however.
So as a summary, a scaffold could be worth a try (see the following paragraph). The RailsGuides to Creating and Customizing Rails Generators & Templates seems plausible at least. And a solution that generates parts of files, names them accordingly and helps in the output of the scaffold on the console to do the rest by hand could be worth a try.
I have tried yesterday to come up with a partial solution, you may have a look at it at GitHub. It works like that:
Copy the contents of the directory scaffold_hmt (stands for has_many :through) to your rails application directory lib/generators.
You may call then the generator with: rails g scaffold_hmt Assembly Part.
It will then generate:
Migration for the join table
Model for the join table
It fails to change the files:
Model assembly.rb
Model part.rb
The reason for that is that the finding of the right place is not trivial. As a workaround, it prints out what should have inserted into the files.
c:\apps\ruby\rails3\minds>rails generate scaffold_hmt Assembly Part
create db/migrate/20111011135944_create_assemblies_parts.rb
create app/models/assemblies_part.rb
Try to insert into file: app/models/assembly.rb the following statements:
has_many :assemblies_parts
has_many :parts, :through => :assemblies_parts
insert app/models/assembly.rb
Try to insert into file: app/models/part.rb the following statements:
has_many :assemblies_parts
has_many :assemblies, :through => :assemblies_parts
insert app/models/part.rb
Give it a try and see if that will help you.

Resources