I try to update 2 model with following steps.
Article
id
current_version
status
ArticleHistory
id
article_id
title
content
version
These models have relationship with article_id and current_version = version.
First, we made one record like this.
article.id:1
article.current_version:1
article.status:public
article_history.id:1
article_history.title:"test title"
article_history.content:"test content"
article_history.version:1
And I'm going to update like this. Before that, I'd like to copy existing ArticleHistory record with new id. I mean, it's like renewing ArticleHistory.
article.id:1
article.current_version:2
article.status:public
(copied)article_history.id:2
(copied)article_history.title:"updated test title"
(copied)article_history.content:"updated test content"
(copied)article_history.version:2
But now, I can't figure out how to express by RoR ActiveRecord.
After this modification, Article has got multiple record.
Give me advise, please.
class Article
has_many :article_histories
should do the trick. If you need more, the doco for has_many is here:
http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_many
If this is not suitable - then please tell us why it doesn't work for you :)
To copy...
# first find the article_history with the highest version for this article
latest_history = article.article_histories.order(:version).last
# if there isn't one, create a new one
if latest_history.blank?
new_history = article.article_histories.new(params[:article_history])
new_history.version = 1
else
# Otherwise... merge params and the old attributes
combined_attributes = latest_history.attributes.merge(params[:article_history])
# and use that to create the newer article_history version
new_history = article.article_histories.build(combined_attributes)
new_history.version = latest_history.version + 1
end
new_history.save!
Note: this code is just to give you an idea of how it could be done.
You will need to bugfix it and make it actually work yourself.
Related
I'm trying to update a parent object (Client) and associated child objects (License). It is possible that either of these objects is new, that's why I have to check for existing objects to update and if there aren't any I have to create them.
My code currently looks like this:
return nil unless params.has_key?('client_uid')
client = Client.find_by_uid(params['client_uid'])
if client.nil?
client = Client.new(uid: params['client_uid'])
end
client.app = params['application']
client.app_version = params['application_version']
...
licenses = params['licenses']
licenses.each do |licenseInfo|
next unless licenseInfo.has_key?('application')
license = client.licenses.find_by_application(licenseInfo['application'])
if license.nil?
license = License.new
# add license without instantly saving
client.association(:licenses).add_to_target(license)
license.application = licenseInfo['application']
end
license.options = licenseInfo['options']
...
end
client.last_seen = Time.zone.now
client.save
This is able to create new clients and update existing ones from the values in the params, as well as adding new licenses for the client (I will implement deleting old licenses later). The problem is that changes to existing licenses aren't saved when I call client.save.
I'm not sure if I'm doing something wrong, or if ActiveRecord really isn't able to handle a situation like this. I already searched for this problem and tried to explicitly specify autosave: true for the License class' belongs_to as recommended here, but this didn't have any effect.
In conclusion, my question is: What do I have to change to be able to save my Client together with all changes (new, changed, deleted) to his licenses?
EDIT:
I tried manually saving all licenses and putting this together in a transaction:
Client.transaction do
client.save
client.licenses.each do |license|
license.save
end
end
But this didn't work, either. Now I'm confused....
I think you need to add
accepts_nested_attributes_for :licenses
in Client model. This would help you to save the child objects
as mentioned in comment. Please refer this link
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
So I have 2 fields in my Articles table.
- :vote_up
- :vote_down
I have methods in my app of updating the :vote_up and :vote_down fields that work fine. What I want to do is order my articles by total votes (:vote_up minus :vote_down).
What is the best way to do this. Can I do this directly in the controller with a certain method? Or must I create a :vote_total field that updates automatically according to the values of the other two fields (if so how do you do this).
Many thanks!
Don't do this in your controller. This is meant to be done in your model. Controllers should just use the model.
You can do this in 2 ways:
Solution 1
Try this in your console (rails c)
Article
.unscoped
.select(%q(articles.*, (articles.vote_up - articles.vote_down) AS vote_total))
.order(%q(vote_total DESC))
and the implement it as a scope in your Article class
scope :order_by_total_votes, -> {
select(%q(articles.*, (articles.vote_up - articles.vote_down) AS vote_total))
.order(%q(vote_total DESC))
}
Solution 2
Create a field vote_total for your Article and update it every time one of the vote fields gets updated (use a before_save callback). Then you can do the same as in solution 1, but without the select part.
Suggestion
I would go with solution 2, because amongst others it is faster in queries
Hope this helps.
Thanks #Hiasinho for your direction. I ended up just creating a :vote_total to my Article database and updated it on both my upvote and downvote methods like so
#article.update_attributes(vote_total: #article.vote_total + 1)
Obviously it was a -1 for downvotes.
I need to allow less-privileged users to propose changes to a record but have them sit somewhere until an administrator approves them. It should be similar to the way Stack Overflow allows users with lower reputation to propose an edit to a question or answer which must be reviewed by someone with higher reputation.
In papertrail terms, I'd like to allow users to create versions of a record without actually committing those changes to the record itself—future versions, rather than past versions. Then I'd like to allow another user to "revert" ("prevert"?) to the new version.
Is this something papertrail supports? Or is there another gem that can do this?
I know that this question is very old but let me explain how I managed to solve it:
Suppose that I have a model Post, two users: A and B, that A is authorized to update posts but he needs B approval before committing the changes, B is the monitor who can approve updates and can update posts as well.
I added a method to revert the record to a particular version of it so we can update it with whatever version we want:
def revert_to(version)
raise 'not version of this model' unless self == version.item
changes = version.changeset.select{ |k, v| not SKIP_FIELDS.include?(k) }.map{ |k,v| [k.to_sym, v[1]] }.to_h
self.update_attributes(changes)
end
I got that method from this Stackoverflow answer but I modified it a bit.
The trick is to not update the record itself if the current user A hasn't authorized to commit changing, rather than updating, a new Paper Trail version will be created, then the monitor B can accept the new changes by reverting the original record to that version.
For that I used a function from Paper Trail core paper_trail.record_update().
A:
p = Post.find(1)
p.title = "A new pending version"
p.paper_trail.record_update(nil)
B as a monitor:
p = Publication.find(1)
p.revert_to(p.versions.last)
B as an editor:
p = Publication.find(1)
p.title = "p will be updated to this version immediately"
p.paper_trail.record_update(nil)
p.save
I added has_paper_trail to Post model but I restricted it on create and destroy actions because as I said above I don't want a new version to be created after updating, I want it to be created before.
has_paper_trail :on => [:create, :destroy]
I'm facing the same problem right now.
No, it's not supported by paper_trail, but maybe we can achieve it adding an approved attribute to our Record. It should default to false so that, when the Record object is saved, a new paper_trail version is created with that attribute set to false.
Later an AdminUser can approve the record setting approved to true and paper_trail will create the new approved version.
It's not the cleanest solution but it should work. And we could also add other attributes to your Record model such as approved_by and approved_at, should you we them.
Mmm.. I'm thinking about it..
Please let me know if you found a better solution!
I was trying to refactoring and optimizing me code. In particular, I wanted to reduce the amount of queries going to the database. In my users controller it worked very well but in an other controller, where I tried the same, it didn't. I've searched for some time now for the answer why it didn't work but I can't really answer it.
I've got users, which can subscribe to courses through enrolments. They are connected through has_many :through etc. relationships. The following works:
#users_courses = current_user.courses
#courses = #users_courses.a_named_scope
But in my courses controller the following wont work:
#all_courses = Course.all
#specific_course = #all_courses.specific_course_scope
The scopes are defined in the respective models and work properly. They are not complicated, just "where ... true/false" definitions. Does someone know the problem here? Thanks!
I'm using rails version 3.2 and ruby version 2.
Until Rails 4 you should use scoped method if you want to have ActiveRecord::Relation instance (on which you can call other scopes) returned instead of Array:
#all_courses = Course.scoped
#specific_course = #all_courses.specific_course_scoped
This should work.
If you want to use includes(:courses), you just do it, for example with:
#specific_course = #all_courses.specific_course_scoped.includes(:courses)
If I manually create an image and assign a product. It works great in console:
p = Spree::Product.first
i = Spree::Image.create!(<create info>)
p.images << i
p.save!
When I try to run this in a program. It doesn't do anything. It runs as if the p.images << i never ran.
I tried searching, but didn't know how to search for << very well.
Update
If I add to the program:
p = Spree::Product.first
i = Spree::Image.create!(<create info>)
i = Spree::Image.find(i.id) #this line
p.images << i
p.save!
This works great, but i don't understand what is going on here.
Thanks in advanced!
Justin
So this is pretty common among ORMs.
The collection<< method adds one or more objects to the collection by
setting their foreign keys to the primary key of the calling model.
Per the rails tutorials:
http://guides.rubyonrails.org/association_basics.html
If you go to that page you can do ctrl+f
and search for: <<
That will give you a ton of information on this.
Also I'll recommend one more source. I like the documentation on this ORM a little better:
http://datamapper.org/docs/associations.html
It's NOT active record, but it's very similar. For me, working with datamapper helped me get a better grasp of active record.
Hope that helps.