I'm attempting to figure out how to elegantly update a document's associations with existing documents with Mongoid.
If I have Users and Groups, and want to assign a User to an existing group, how could I do this via update_attributes ?
I want to be able to do something like this:
user.attributes = { groups: [{"_id":"existing group id here"}]}
user.save
When I try to do the above, Mongoid attempts to INSERT a new group, thereby causing a ID duplicate error.
I have tried doing the same via nested attributes:
user.groups_attributes = [{"_id":"existing group id here"}]
user.save
And the same error occurs. Is there anyway I can do this WITHOUT having to manually query the group id and push it into the array? The reason I'm asking is because lets say i have a model with many associations.. i dont want to have to have blocks of code to update each association manually
Assigning an an existing User to an existing Group with update_attributes is very simple (assuming you already added the Group/User relation).
user.update_attributes(:group_id => 'existing group id here')
Related
I'm attempting to query an object's associations, and only return objects where all of their associations are of a certain value. For example, if a User has a memberships association, and memberships has an active(boolean) field, I want to return a collection of all users that only have memberships with active: false.
The query I'm working with right now is:
User.includes(:memberships).where(memberships: {active: false})
However, this gives me all users that have inactive memberships, but also all users that have both active: false and active: true memberships. I tried doing an extra .where.not on this, but of course, that returned the same group.
I've considered mapping over the collection of users and creating a new array, kicking out the users with active memberships, but I need to keep the final value as an AR Collection as I continue to query it further in the controller.
I would prefer to stick to using ActiveRecord for this, but if it's not doable and I need to use SQL instead, I'm also open to that.
Any help would be greatly appreciated. Thank you!
Couple things here - you want a LEFT OUTTER joins, so make sure your SQL query is being constructed properly. Check with
User.includes(:memberships).where(memberships: {active: false}).explain
or
User.includes(:memberships).where(memberships: {active: false}).to_sql
Second, try:
User.joins(:memberships).where("memberships.active = ?", false)
or perhaps give .merge a try, which I like to use:
User.joins(:memberships).merge(Membership.inactive)
assuming you have a scope/class method .inactive on the Membership model
def self.inactive
where(active: false)
end
.includes should really only be used if you need the relationship loaded into memory.
EDIT:
Easiest way to exclude any user that has a single (of many) memberships would be to split this up into 2 queries.
active_user_ids = Membership.active.pluck(:user_id).uniq
User.where("id NOT IN (?)", active_user_ids.join(","))
To exclude Users with both a valid and invalid membership try--
User.includes(:memberships).where(memberships: {active: false}).where.not(memberships: {active: true})
Also--you probably want to use #joins instead of #includes. If you don't need access to the Memberships except for the query, there is no need to load that into memory (which #include does).
This article has a nice explanation--
http://tomdallimore.com/blog/includes-vs-joins-in-rails-when-and-where/
When a user updates an invoice form, i want to create a new invoice record with the updated attributes, but also change one or two fields of the old record and save it, too.
How would the outline of a controller action look like which could accomplish this?
Instead of a controller action i put the code in the model, using callbacks:
before_save do |rec|
if !rec.new_record?
attrb = rec.attributes.delete_if{|k, v| ["id"].include? k }
Book.create(attrb)
rec.restore_attributes
rec.year = rec.year + 2 # some custom change
true
end
end
I keep all attributes unless the 'id' (otherwise i get an error) for create a new record with the new attributes.
Then i restore the attributes of the existing record. I do some custom change before saving.
I am rather new with Rails but this seems pretty straightforward. As you mention the user is 'updating" an invoice, your controller view has probably been passed all the data available to the user for further change.
When submitting the form, your update action can easily update the current record data, as well as creating a new one on top of this
Though as it is automated, you need to make clear:
if a new invoice record is created each time an invoice record is
updated (thi can create a lot of copies of the same invoice)
how you make the old record an archive to avoid duplicates
can the 'additional" amendments be automated and easily processed through an algorithm...
Nested attributes made things a bit tricky. So in order to create new instances I had to use the dup method for both the resource and its nested items.
Generally, it is advisable to keep the controllers slim and make the models fat. Nevertheless, I have decided to include this code into my Invoices controller:
def revise_save
#contact = Contact.find(params[:contact_id])
#invoice = #contact.invoices.find(params[:invoice_id])
#invoice_old = #invoice.dup
#invoice.invoice_items.each do |item|
#invoice_old.invoice_items << item.dup
end
#invoice.datum = DateTime.now.to_date
# archive old invoice
# #invoice_old. ...
#invoice_old.save
# make old new invoice
#invoice.datum = Time.now
# ...
#invoice.update(invoice_params)
redirect_to invoices_path
end
Note that in this solution the currently edited (original) invoice becomes the new invoice, the old one is paradoxically created anew.
Thanks to #iwan-b for pointing me in the right direction.
I am new to django/python and working my way through my webapp. I need assistance in solving one of my problems.
In my app, I am planning to assign each user (from auth_user) to one of the group ( from auth_group). Each group can have multiple users. I have entry in auth_group, auth_user and auth_user_groups. Here is my question:
At time of login I want to check that logging user belongs to which group?
I want to keep that group info in session/cache so all pages I can show information about that group only.
If you have any sample code will be great.
Giving support to the very well #trinchet's answer with an example of context_processor code.
Puts inside your webapp a new file called context_processors.py and writes this lines on it:
def user_groups(request):
"""
Add `groups` var to the context with all the
groups the logged in user has, so you can access
in your templates to this var as: {{ groups }}
"""
groups = None
if request.user.is_authenticated():
groups = user.groups
return {'groups': groups}
Finally on your settings.py add 'webbapp.context_processors.user_groups'to TEMPLATE_CONTEXT_PROCESSOR:
TEMPLATE_CONTEXT_PROCESSORS = (
'webbapp.context_processors.user_groups',
)
1) Be user an instance of auth.models.User, you can get all groups the user belong to, through user.groups. If you want to ask at time of login then you should do this in your login view.
2) You can use session or cache approaches to deal with, this is irrelevant, but once you have the group you need to render the pages having this value, i mean, you need to provide the group to the template rendering, to do this I suggest to you using a custom context processor.
I'd like to use CFLDAP to retrieve all the users in a certain distribution group used by Exchange. If this is possible, what do I use for the 'filter' attribute of CFLDAP? Also, if all I have is the email address for the group (e.g. 'sales#example.com'), can I still get the user information, or do I need the name of the group that uses that email address?
For example, what would I put in the block below?
<cfldap server = "foo.example.com"
action = "query"
name = "ldap2"
start = "dc=foo,dc=example,dc=com"
attributes = "givenName,sn,sAMAccountName,mail,employeeID,dn"
filter="?????????????"
username="BAR\eterps"
password="12345" >
To get the Group name from the email address, I used Active Directory Explorer. I'm sure there is a way to query for it as well.
Once I had the group name, I created my filter for CFLDAP: (&(objectClass=user)(memberOf=cn=Sales,ou=Email Distribution Groups,dc=foo,dc=example,dc=com))
So the resulting CFLDAP query looks like:
<cfldap server = "foo.example.com"
action = "query"
name = "ldap2"
start = "dc=foo,dc=example,dc=com"
attributes = "givenName,sn,sAMAccountName,mail,employeeID,dn"
filter="(&(objectClass=user)(memberOf=cn=Sales,ou=Email Distribution Groups,dc=foo,dc=example,dc=com))"
username="BAR\eterps"
password="12345" >
a filter is not required when using cfldap in my experience. What happens when you run the query without a filter?
If I understand your question correctly, you can modify the start attribute with the specific Group dn, and not just the Root dn, it should only return the info from that group. If there is an attribute that points to users that are members of that group, make sure you include that in the attribute list.
If you dont modify the start attribute, your filter would be something like (cn=groupname) that points to the group you want.
I have a fairly complex relationship that I am trying to make work with the Django admin site. I have spent quite some time trying to get this right and it just seems like I am not getting the philosophy behind the Django models.
There is a list of Groups. Each Group has multiple departments. There are also Employees. Each Employee belongs to a single group, but some employees also belong to a single Department within a Group. (Some employees might belong to only a Group but no Department, but no Employee will belong only to a Department).
Here is a simplified version of what I currently have:
class Group:
name = models.CharField(max_length=128)
class Department
group = models.ForeignKey(Group)
class Employee
department = models.ForeignKey(Department)
group = models.ForeignKey(Group)
The problem with this is that the Department select box on the Employees page must display all Departments, because a group has not yet been set. I tried to rectify this by making an EmployeeInline for the GroupAdmin page, but it is not good to have 500+ employees on a non-paginated inline. I must be able to use the models.ModelAdmin page for Employees (unless there is a way to search, sort, collapse and perform actions on inlines).
If I make EmployeeInline an inline of DepartmentAdmin (instead of having a DepartmentInline in GroupAdmin), then things are even worse, because it is not possible to have an Employee that does not belong to a Group.
Given my description of the relationships, am I missing out on some part of the Django ORM that will allow me to structure this relationship the way it 'should be' instead of hacking around and trying to make things come together?
Thanks a lot.
It sounds like what you want is for the Department options to only be those that are ForeignKey'ed to Group? The standard answer is that the admin site is only for simple CRUD operations.
But doing what you're supposed to do is boring.
You could probably overcome this limitation with some ninja javascript and JSON.
So first of all, we need an API that can let us know which departments are available for each group.
def api_departments_from_group(request, group_id):
departments = Department.objects.filter(group__id=group_id)
return json(departments) # Note: serialize, however
Once the API is in place we can add some javascript to change the <option>'s on the department select...
$(function() {
// On page load...
if ($('#id_group')) {
// Trap when the group box is changed
$('#id_group').bind('blur', function() {
$.getJSON('/api/get-departments/' + $('#id_group').val() + '/', function(data) {
// Clear existing options
$('#id_department').children().remove();
// Parse JSON and turn into <option> tags
$.each(data, function(i, item) {
$('#id_department').append('<option>' + item.name + '</option>');
});
});
});
}
});
Save that to admin-ninja.js. Then you can include it on the admin model itself...
class EmployeeAdmin(models.ModelAdmin):
# ...
class Media:
js = ('/media/admin-ninja.js',)
Yeah, so I didn't test a drop of this, but you can get some ideas hopefully. Also, I didn't get fancy with anything, for example the javascript doesn't account for an option already already being selected (and then re-select it).