I have been hearing a lot about Backbone and wanted to use it for my latest project to learn it. However, I am coming from Rails background, and my experiences do not seem to translate well for Backbone.
For example, I have four models which need to be displayed in a product view.
class Product < ActiveRecord::Base
belongs_to :category
belongs_to :child
has_many :actions
has_many :comments
end
class Child < ActiveRecord::Base
belongs_to :user
has_many :products
end
I am using Backbone-relational to define relationships in Backbone models. The following is product Backbone model.
class Ibabybox.Models.Product extends Backbone.RelationalModel
paramRoot: 'product'
urlRoot: '/products'
relations:
[
{
type: Backbone.HasMany
key: 'actions'
relatedModel: 'Ibabybox.Models.Action'
collectionType: 'Ibabybox.Collections.ActionsCollection'
reverseRelation:
key: 'product'
includeInJSON: 'id'
}
]
In the Backbone router, I do the following.
class Ibabybox.Routers.ProductsRouter extends Backbone.Router
routes:
"": "index"
":id": "show"
show: (id) ->
#product = new Ibabybox.Models.Product({id: id})
#product.fetch
success: (product) ->
actions = product.get('actions')
child = product.get('child')
#child_model = new Ibabybox.Models.Child({id: child.id})
user = #child_model.get('user')
#view = new Ibabybox.Views.Products.ShowView({model: product, actions: actions, child: child, user: user})
$("#products").html(#view.render().el)
And on Rails controller, I do the following.
class ProductsController < ApplicationController
def show
#product = Product.find(params[:id])
render json: #product.to_json(:include => [ :actions,
:child => { :include => {:user => {:methods => [:name] }}} ])
end
end
First, am I going about this in the right direction?
Second, it feels like a lot of things to define and write to display related things for a product and the reason for feeling I might be doing something wrong....
Any help/recommendation/correction would be much much appreciated!
Thanks!
According to backbone.js doc There's More Than One Way To Do It, but some ways become pain while your application grows. After developing 3 application using backbone.js & rails here is my experience.
backbone.js is not suited for every website. Specially if your website is content based. In this case using pjax give better result.
Resource based design is best approach. And hopefully both of rails and backbone.js rock. You can easily use default rails generator with minimal changes. For complicated objects i prefer using jbuilder since i can cache objects.
If you are using explicit $.ajax,$.post,$.get probably you are wrong.[same as 2]
If you are repeating yourself there is something wrong.
Don't miss backbone events.( In your case you can use events instead of passing success callback to fetch)
Related
In application user can enter new post which contain title, content of the post and category of post. So creating new post will be through some simple html form with few fields. Now i don't know where to put logic for creating new post for following reasons:
Post(or posts collection) is object which is constructed from different tables, for example.
#posts = User.joins(entries: [{storage: :vote}, :category])
.where("votes.count > ?", 0)
.select("users.username AS username,
storages.id AS storage_id,
storages.title AS title,
storages.content AS content,
votes.count AS votes,
categories.category_name AS category_name")
.order("votes.count DESC")
So when user create new post application must create new entries in different tables:
1.Create new entry in entries table. (id, user_id, category_id)
2. Create new entry in storages table.(id, title, content, entry_id)
3. Create new entry in vote table.(id, count, storage_id)
In situation where post is model i can use something like resources: posts then in posts controller through new and create i can create new post, but what in situation like this where i don't need posts controller nor post model? So, question is: which place is more appropriate to put logic for creating new post? Q1
My solution is to craete Storages controller with resource: storages, :only => [:new, :create] then through new and create of this controller to populate different tables in db? I'm forcing here only because i dont see any point of other CRUD actions here(like showing all or one storage), because i will not use storages individually but in conjunction with other tables. So from views/storages through new.html.erb and create.html.erb i can construct new post? Q2
Another solution is to create Post controller which doesn't have "corresponding" post model as i stated before. Here i'm guessing i can't use restful routes(CRUD) because there is not :id of post? I only can make manually non-restful routes like:
post 'posts/create/:title/:content/:category' => 'posts#create', :as => 'create_post' then from params[:title], params[:content] and params[:category] to populate other tables. Q3
Im new to rails so dont yell please :D
This sound like a call for nested forms, its covered in a screen cast
here.
You use the resources of the top model, in this case Entry.
and drill down to the 3rd model.
Simple sample of what to do is bellow.
Model should look like so,
entry.rb
class Entry < ActiveRecord::Base
has_many :storages, :dependent => :destroy
accepts_nested_attributes_for :storages, :allow_destroy => true
end
storage.rb
class Storage < ActiveRecord::Base
belongs_to :entry
has_many :votes, :dependent => :destroy
accepts_nested_attributes_for :votes, :allow_destroy => true
end
vote.rb
class Vote < ActiveRecord::Base
belongs_to :storage
end
and the form should look like so, in simple_form style
<%= simple_form_for #entry do |f| %>
<%= f.simple_fields_for :storages do |storage_fields| %>
<%= storage_fields_for :votes do |vote_fields| %>
<% end %>
<% end %>
<% end %>
and if you have all the models set up, you shouldn't have to do anything to the controller.
This approach is also nice because you can add multiple storages and votes ajax style(without reloading the page) if needed, which is always nice.
I'd use a form class instead of nested attributes any day, see http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ for an example of this pattern (Chapter "3. Extract Form Objects")
I've used the pattern often enough to gemify it https://github.com/bbozo/simple_form_class and it's used roughly in this way: https://gist.github.com/bbozo/5036937, if you're interested to use it I'll push some docs
EDIT: reason why I tend to go the form class route most of the time is because nested attributes never failed to bite me in the end, either because strong parameter handling got cumbersome, or validators get too complicated (if persisted? else ...), or persistence logic needs to be extended to support some little knack that resolves into callback hell or recursive saves in the model (before/after save spaghetti troubles)
If I just including nested model in query such this
#projects = current_user.projects.all(include: :reviews)
everything ok. But Review model has some scope, that I need implement in query above. I trying this
#projects = current_user.projects.all(include: :reviews.unreaded)
and gets error. What is the right way to do this?
One option would be to create an association based on the scope, roughly:
#projects = current_user.projects.all(include: :unread_reviews)
Then create an unread_reviews association, roughly:
class Project < ...
has_many :unread_reviews, :conditions => ['read=?', true], :class_name => "Review"
(Replace the above has_many with your association particulars, obviously.)
This technique is discussed in the association docs.
Using Mongoid 2.4.5 on Rails 3.2.1
I have a Model Book that has_many :pages.
class Book
include Mongoid::Document
has_many :pages
end
class Page
include Mongoid::Document
field :page_number
belongs_to :book
validates_uniqueness_of :page_number, scope: :book
end
I'm using nested resources so that I can get urls like /books/4f450e7a84b93e2b44000001/pages/4f4bba1384b93ea750000003/
What I would like to be able to do is use a url like /books/4f450e7a84b93e2b44000001/pages/3/ to get the third page in that book.
Now the crux of the question:
I want to find the page via a call like Book.find('4f450e7a84b93e2b44000001').pages.find('3') or like Book.find('4f450e7a84b93e2b44000001').pages.find('4f4bba1384b93ea750000003')
I know that I can override the find method in Page with something like
class << self
def find(*args)
where(:page_number => args.first).first || super(args)
end
end
But that doesn't seem to have any effect on the scoped query book.pages.find('3') as it seems the scoped search uses a different find method.
How do I specifically override the find method used by book.pages.find('3')?
Why just do a where criteria on your pages ?
Book.find('4f450e7a84b93e2b44000001').pages.where( :page_number => '3')
You can do a scope to in your Pages
class Page
scope :page_number, lambda{|num| where(:page_number => num) }
end
and use it like :
Book.find('4f450e7a84b93e2b44000001').pages.page_number('3')
Define a to_param method on your Page model that returns the page number. This way all Rails URL helpers use that when building URLs (automatically). Then you can just use something like
#book.pages.where(:page_number => params[:page_id]) # page_id is actually the result of page#to_param
Btw. I don't know how large your books are, but it might make more sense to embed your Pages in the Book from a document-oriented database point of view. The whole relationship business is not native to MongoDB.
The main idea is that I have several worker instances of a Rails app, and then a main aggregate
I want to do something like this with the following pseudo pseudo-code
posts = Post.all.to_json( :include => { :comments => { :include => :blah } })
# send data to another, identical, exactly the same Rails app
# ...
# Fast forward to the separate but identical Rails app:
# ...
# remote_posts is the posts results from the first Rails app
posts = JSON.parse(remote_posts)
posts.each do |post|
p = Post.new
p = post
p.save
end
I'm shying away from Active Resource because I have thousands of records to create, which would mean thousands of requests for each record. Unless there is a way to do it all in one request with Active Resource that is simple, I'd like to avoid it.
Format doesn't matter. Whatever makes it convenient.
The IDs don't need to be sent, because the other app will just be creating records and assigning new IDs in the "aggregate" system.
The hierarchy would need to be preserved (E.g. "Hey other Rails app, I have genres, and each genre has an artist, and each artist has an album, and each album has songs" etc.)
There are several options you could implement to get this to work:
Active Resource
As others have answered, you could make use of ActiveResource. After reading your comments, this seems like a solution you'd like to steer clear of due to the multiple-request aspect
Receiving Controller
You could have a controller in your second rails application that receives data and creates records out of it.
class RecordReceiver < ActiveRecord::Base
def create
params[:data][:posts].each do |p|
Post.create(p)
end
end
end
You could namespace this controller inside an "API" namespace, which is a rather clean solution if implemented properly.
Share the Database
You could share one database across two applications. This means you won't need to send the data from one model to another, it will already be there. This is the least amount of work for you as a developer, but may not be possible depending on the system architecture you have.
Two databases in each application
You could implement multiple database in each application, like so:
#Add to database.yml
other_development:
adapter: mysql
database: otherdb_development
username: root
password:
host: localhost
other_production:
adapter: mysql
database: otherdb_production
username: root
password:
host: localhost
Then, define your models like so:
class Post < ActiveRecord::Base
end
class PostClone < ActiveRecord::Base
establish_connection "other_#{RAILS_ENV}"
end
Now, your Clone model will point to the current database, and the PostClone model will point to the other database. With access to both, you can copy the data over whenever you need to with basic model methods.
Conclusion
Since you don't want to use ActiveResource, I would recommend that you simply share the database between the applications. If this isn't a possibility, then try having two models, each going to a different database. Finally, the receiving controller is a valid, albeit slower option (as it needs to do the HTTP request on top of the database requests)
Use active resource to directly create your posts in the remote app.
http://railscasts.com/tags/19
Not exactly an answer, but couple of ideas:
Instead of your to_json, you can call Marshal.dump with your posts.
You can create a controller which would receive such serialized data through HTTP on remote rails instance, Marshal.load and save them (probably with some code to solve all kinds of collisions).
I'm not sure how marshaling would handle included data and how much work would be needed on remote side to ensure clean importing (what about records which would break some uniqueness etc), but I'd experiment a bit and see.
BTW, Since you asked the question in the first place, I guess standard database replication solutions don't work for you?
I have a similar use case and I use ActiveResource. If you want to preserve the contained objects this is a good choice. ActiveResource gives you a choice of JSON or XML as the wire format.
You can pack all your records in one request. At the receiving end you can process the request in one transaction.
Source App
class PostsController < ApplicationController
def synch
#posts = Post.all(:conditions => {...})
#
# Multiple posts and their child objects uploaded in one HTTP call.
#
Remote::Post.upload(#posts)
end
end
# ActiveResource model for remote Post
module Remote
class Post < ActiveResource::Base
self.site = "http://host:3000/"
def self.upload posts
# pass :include option to to_xml to select
# hierarchy.
body = posts.to_xml(:root => 'posts', :except => [:id]
:include => { :genres =>{ :except => [:id],
:artists => { :except => [:id],
:albums => { :except => [:id],
:songs => {:except => [:id] }
}
}
}
}
)
post(:upload, {}, body)
end
end
end
Destination App
class PostsController < ApplicationController
def upload
#
# Multiple posts and their child objects are saved in one call.
#
Posts.create(params[:posts])
end
end
class Post < ActiveRecord::Base
has_many :genres
accepts_nested_attributes_for ::genres
end
class Genre < ActiveRecord::Base
has_many :artists
accepts_nested_attributes_for :artists
end
class Artist < ActiveRecord::Base
has_many :songs
accepts_nested_attributes_for :songs
end
class Album < ActiveRecord::Base
has_many :albums
accepts_nested_attributes_for :albums
end
class Songs < ActiveRecord::Base
end
Other options for faster processing at the destination is ARExtensions. This gem supports bulk inserts.
Destination Route
map.resources :posts, :collection => { :upload => :post }
I want to create a CMS like site where the user starts off with a some generic pages, i.e.
homepage
about
contact
etc
and from there can add child pages dynamically, for example
homepage
articles
article1
something
something-else
article2
about
contact
etc
To achieve this I'm planning on using some kind of self-referential association like
class Page < ActiveRecord::Base
belongs_to :parent, :class_name => 'Page'
has_many :children, :class_name => 'Page'
end
The one thing I'm struggling with is the route generation. Because pages can be added on the fly I need to dynamically generate routes for these pages and there is no way of knowing how many levels deep a page may be nested
So if I start off with the homepage:
/
and then start adding pages i.e.
/articles/article1/something/something-else/another-thing
How can something like that be achieved with the rails routing model?
Once you have some way to generate the URL string for your Page records (and I'll leave that part up to you), you can just map every page in config/routes.rb:
Page.all.each do |page|
map.connect page.url, :controller => 'pages', :action => 'show', :id => page
end
And have an observer hook the page model to reload routes when something changes:
class PageObserver < ActiveRecord::Observer
def reload_routes(page)
ActionController::Routing::Routes.reload!
end
alias_method :after_save, :reload_routes
alias_method :after_destroy, :reload_routes
end
Don't forget to edit config/environment.rb to load the observer:
# Activate observers that should always be running
config.active_record.observers = :page_observer
One solution to this prob is to dynamically load routes from hooks on your models. From example, a snippet from the Slug model on my site:
class Slug < ActiveRecord::Base
belongs_to :navigable
validates_presence_of :name, :navigable_id
validates_uniqueness_of :name
after_save :update_route
def add_route
new_route = ActionController::Routing::Routes.builder.build(name, route_options)
ActionController::Routing::Routes.routes.insert(0, new_route)
end
def remove_route
ActionController::Routing::Routes.routes.reject! { |r| r.instance_variable_get(:#requirements)[:slug_id] == id }
end
def update_route
remove_route
add_route
end
def route_options
#route_options ||= { :controller => navigable.controller,
:action => navigable.action,
:navigable_id => navigable_id,
:slug_id => id }
end
end
This inserts the route at top priority (0 in the routing array in memory) after it has been saved.
Also, it sounds like you should be using a tree management plugin and like awesome nested set or better nested set to manage the tree for your site.
You have to parse the route yourself
map.connect '*url', :controller => 'pages', :action => 'show'
Now you should have a params[:url] available in your action that is the request path as an array separated by the slashes. Once you have those strings its a simple matter to find the models you need from there.
That was from memory, and it's been a long while. Hope it works for you.
Look at RadiantCMS sources, they implement that functionality as far as i understand their self description.
I've implemented a similar functionality into a Rails gem, using self referential associations and a tree like js interface for reordering and nesting the "pages".
Templating language and authentication/authorization are left for the developer to implement.
https://github.com/maca/tiny_cms