Mongoid, confused with embedded document with timestamps and versioning? - ruby-on-rails-3

I have been using Mongoid for about 3 months now, and I have managed to get done pretty much anything I need thanks to the great document and resources out there.
But going back to improve some stuff I have made a few backs, I am definitely struggling a lot on embedded documents.
In a nutshell what I am trying to do, is to maintain versioning and timestamps on embedded documents, but that I cannot manage to do.
Here is the relevant part of my model:
class Content
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Paranoia
embeds_many :localized_contents
accepts_nested_attributes_for :localized_contents
end
class LocalizedContent
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Paranoia
include Mongoid::Versioning
embedded_in :content, :inverse_of => :localized_contents
end
Nothing really complicated here, everything works fine regarding the behavior of the Content model, however the LocalizedContent model is not behaving the way I am expecting to, so my expectations either needs to get straighten up, or I need help fixing what I am doing wrong.
To create a new embedded document I do the following:
my_content = Content.find(params[:id])
my_content.localized_contents.build(params[:localized_content])
if parent.save
#redirect, etc.
end
This works in the sense that it successfully creates a new embedded document in the correct Content, however the timestamps fields I left a nil
Now, if I try to update that localized_content:
my_content = Content.find(params[:content_id])
localized_content = my_content.localized_contents.find(params[:id])
Now, if I do: localized_content.update_attributes(params[:localized_content]) I get the following error:
=> Mongoid::Errors::InvalidCollection: Access to the collection for LocalizedContent is not allowed since it is an embedded document, please access a collection from the root document.
Fair enough, then I update atomically the fields on the localized content and save the parent:
localized_content.fieldA = "value"
localized_content.fieldB = "value"
localized_content.fieldC = "value"
my_content.save
This works in updating the localized content properly but:
- timesteamps (udpated_at and created_at) are still nil
- versions does not receive the a copy of the current localized_content and version does not get incremented !
So as I read in many occasion in this groups and on some forums on the web, the call backs are not triggered on the embedded document for performance reason, since I am calling save on the parent. Again, faire enough, but as suggested in those places, I should call save on the embedded docs instead... but how !?!?! because every time I do I get the dreaded:
=> Mongoid::Errors::InvalidCollection: Access to the collection for LocalizedContent is not allowed since it is an embedded document, please access a collection from the root document.
Even more so, I tried to manually call the call back for versioning on my embedded: localized_content.revise, and again same error:
=> Mongoid::Errors::InvalidCollection: Access to the collection for LocalizedContent is not allowed since it is an embedded document, please access a collection from the root document.
I am going nuts here ! Please help. What I am doing wrong ? How should an embedded document be create and updated so I can call (even manually I don't care) the proper callbacks to update the time stamps and versioning ?
Thanks,
Alex
ps: I am using rails 3.0.3 and mongoid 2.0.1

Just in case this answer is still useful to anyone, Mongoid has added a tag which makes callbacks run on embedded child objects when the parent object is saved.
Your parent object should now look like this:
class Content
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Paranoia
embeds_many :localized_contents, cascade_callbacks: true
accepts_nested_attributes_for :localized_contents
end
That's it! Now, saving the parent object will run callbacks on the child objects (and Mongoid::Timestamps is smart enough to only run on the objects which were actually changed). This information is in the mongoid documentation, at the very bottom of the embedded documents page.

Try using create instead of build. EmbeddedDoc.build and EmbeddedDoc.new won't fire any save callbacks (because nothing's being saved yet), and saving the parent doc won't call the embedded children's callbacks (performance decision). EmbeddedDoc.create should fire the embedded docs callbacks though.
my_content = Content.find(params[:id])
puts my_content.localized_contents.create(params[:localized_content])

Related

Validations using simple_form: validated object lost after hitting create

We have a form created by several controllers's new actions, which we reuse via render :new in the create action to display validation error messages. I believe this is the way to go for simple_form and validations. Correct me, if I'm wrong here.
We also have a general language switching mechanic, that redirects to the current_url, with a different locale.
The problem:
After a failed validation and the second rendering of the new form, the language selection throws an error (which would be very misleading to post here). The problem is that the create action expects the validated object, which our language selection does not pass to the current url again.
How would you tackle this problem?
We could try to teach our language switcher about "create" and have it send another post request with the same params, but this seems awful. There would have to be a lot of logic in our little helper and where would we store the objects (at least one kind of them is not persisted at all)?
Someone mentioned (ab-)using a flash message to recreate the object, but it's a huge form with up to 50 validations and this get's uglier with size, I guess.
Storing the object in the session in these cases and have the helper post the object again, if it exists might work. I like this one the most, but it's far from feeling right as well.
We could try to have simple_form use the "new" action instead of just rendering "new", but this seems really bad.
We could disable language switching for create actions altogether, with an alert saying this one step has to be finished in the chosen language.
Do you have any opinions, other suggestions? I'd be very grateful.
Thanks,
Andy
So we changed the language helper to send the same post request again, if it is on a page created by a POST. It ended up looking like this. Not a lot of code added:
def language_link(language)
url_options = { locale: language }
if request.request_method == 'POST'
link_to(language, url_for(params.merge(url_options)), method: :post)
else
link_to(language, url_for(url_options))
end
end
We were carefully making sure we don't end up sending valid data a second time. Creating a second payment, or a second order would be quite bad here for example. We need to keep this in mind in the future as well, when we're creating new post routes accessible on a part of our application where language is changeable. That's the main problem here.
It does not consider PUT requests now because we don't have any edit/update functionality on the part of the app where language is selectable.
We can live with this version in our code. So I post this as an answer. But I'd still be happy to see a better (less dangerous) version, our any thoughts on this at all.
Cheers,
Andy

Rails 3, large multi-step form: 1 large controller or separated by resource?

I have a multi-step form where the user fills out info on several different pages. In conventional rails, you keep each resource separate in its own controller and you use the REST actions to manipulate the data.
In the conventional system I would have 3-5 different controllers (some steps are optional) for a single multi-step form. There's no real sense of "order" in the controllers if I do it the conventional way. A new developer coming on to the project has to learn what steps map to what steps and so forth.
On the other hand, I have thought about breaking convention and having a single controller that organizes the entire multi-step form. This controller would be full of methods like:
def personal_info
# code...
end
def person_info_update
# code...
end
def residence_info
# code...
end
def residence_info_update
# code...
end
# many more coupled methods like the above...
This single controller will get fairly long, but it's essentially a bunch of coupled methods: one for showing the step (form) and the other for updating and redirecting to the next step.
This would be breaking rails convention and I would have to setup my own routing.
But I'm curious how others have solved this problem? I know both CAN work, but I would like to know which is easier to maintain and code with in the long run.
A resource does not equal a page. I suspect that both ways would break a constraint on REST.
All of your interests have been with the View domain, which resides in your browser. If you want to display a single form in multiple parts you should do so using HTML, CSS etc.
Otherwise your just creating temporary storage on your servers for the forms progress.
I did something like this with https://github.com/pluginaweek/state_machine
The idea was to have one state per step of the form and simply render a different form partial depending on which state the actual resource has. The above gem let's you specify validations and callbacks for each states.
Like this, you can use the standard REST controller actions.

Rails - where do the model instance variables come from?

I come from an ASP.NET MVC background and am currently going through the following Rails tutorial: http://guides.rubyonrails.org/getting_started.html
I have created a "Post" model which contains some instance variables, but they do not seem to have been defined in the model. They must come from somewhere else. Where are they defined?
Googled "activerecord model" and this was in the top result:
Active Record objects don’t specify their attributes directly, but rather infer them from the table definition with which they’re linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
You can have virtual variables that don't correlate to fields in a table/model. A common example is the 'password' and 'password_confirmation' variables used in authentication. You have them exist temporarily until you encrypt it and save it to another field like 'encrypted_password'.
You can declare them but that's not required. You don't have to define or declare them anywhere... just start using them. Of course, they're not persistent though, so won't be saved.

namespaces and rails routes

I have a routing like that:
namespace :folio do
resources :portfolios do
resources :portfolio_items do
resources :images
end
end
end
Now please don´t flame me because of the deep stacking. This is a mongo db persisted tree like object and those levels are all persisted in the root object.
What puzzles me is the fact that the generated routings read something like
folio_portfolio_portfolio_item
But when I ask for a url from urlhelper
url_for [#portfolio, #portfolio_item]
I get a nice exception telling me
undefined method `hash_for_folio_portfolio_folio_portfolio_item_path' for #<Module:0x0000000492fc30>
See the second "folio" in there? Any idea how I can get rid of that? Providing an :url => is not an option, unfortunately, because that would triplicate my form views and before that I'd rather ditch the namespace altogether. But unwillingly so: this is a rails engine and I would want to avoid clashes.
So, in other words...
I want
= form_for [#portfolio, #portfolio_item] do |form|
to "just" work :). Is this too much to ask?
Observation
a routing like that brings me a bit forward:
resources :folio_portfolio_items, :controller=>Folio::PortfolioItemsController do
while ugly as hell it generates good urls. Problem is, when I want to to visit one of them I get
ActionController::RoutingError (uninitialized constant Folio::Folio):
Whatever this means...
As a sidenote I think it is very odd that this happens at all. I think my mapping controllers to domains is the expected one...
I created a sample app that illustrates this on https://github.com/janlimpens/testroutes

Need guidance in creating Rails 3 Engine/Plugin/Gem

I need some help figuring out the best way to proceed with creating a Rails 3 engine(or plugin, and/or gem).
Apologies for the length of this question...here's part 1:
My company uses an email service provider to send all of our outbound customer emails. They have created a SOAP web service and I have incorporated it into a sample Rails 3 app. The goal of creating an app first was so that I could then take that code and turn it into a gem.
Here's some of the background: The SOAP service has 23 actions in all and, in creating my sample app, I grouped similar actions together. Some of these actions involve uploading/downloading mailing lists and HTML content via the SOAP WS and, as a result, there is a MySQL database with a few tables to store HTML content and lists as a sort of "staging area".
All in all, I have 5 models to contain the SOAP actions (they do not inherit from ActiveRecord::Base) and 3 models that interact with the MySQL database.
I also have a corresponding controller for each model and a view for each SOAP action that I used to help me test the actions as I implemented them.
So...I'm not sure where to go from here. My code needs a lot of DRY-ing up. For example, the WS requires that the user authentication info be sent in the envelope body of each request. So, that means each method in the model has the same auth info hard coded into it which is extremely repetitive; obviously I'd like for that to be cleaner. I also look back now through the code and see that the requests themselves are repetitive and could probably be consolidated.
All of that I think I can figure out on my own, but here is something that seems obvious but I can't figure out. How can I create methods that can be used in all of my models (thinking specifically of the user auth part of the equation).
Here's part 2:
My intention from the beginning has been to extract my code and package it into a gem incase any of my ESP's other clients could use it (plus I'll be using it in several different apps). However, I'd like for it to be very configurable. There should be a default minimal configuration (i.e. just models that wrap the SOAP actions) created just by adding the gem to a Gemfile. However, I'd also like for there to be some tools available (like generators or Rake tasks) to get a user started. What I have in mind is options to create migration files, models, controllers, or views (or the whole nine yards if they want).
So, here's where I'm stuck on knowing whether I should pursue the plugin or engine route. I read Jordan West's series on creating an engine and I really like the thought of that, but I'm not sure if that is the right route for me.
So if you've read this far and I haven't confused the hell out of you, I could use some guidance :)
Thanks
Let's answer your question in parts.
Part One
Ruby's flexibility means you can share code across all of your models extremely easily. Are they extending any sort of class? If they are, simply add the methods to the parent object like so:
class SOAPModel
def request(action, params)
# Request code goes in here
end
end
Then it's simply a case of calling request in your respective models. Alternatively, you could access this method statically with SOAPModel.request. It's really up to you. Otherwise, if (for some bizarre reason) you can't touch a parent object, you could define the methods dynamically:
[User, Post, Message, Comment, File].each do |model|
model.send :define_method, :request, proc { |action, params|
# Request code goes in here
}
end
It's Ruby, so there are tons of ways of doing it.
Part Two
Gems are more than flexible to handle your problem; both Rails and Rake are pretty smart and will look inside your gem (as long as it's in your environment file and Gemfile). Create a generators directory and a /name/name_generator.rb where name is the name of your generator. The just run rails g name and you're there. Same goes for Rake (tasks).
I hope that helps!